Clean up input handling in VulkanViewer

Sets up proper handling of characters and keycodes, and event modifiers.
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1865553005

Review URL: https://codereview.chromium.org/1865553005
diff --git a/tools/vulkan/VulkanTestContext.cpp b/tools/vulkan/VulkanTestContext.cpp
index 4a4c219..f9f037d 100644
--- a/tools/vulkan/VulkanTestContext.cpp
+++ b/tools/vulkan/VulkanTestContext.cpp
@@ -79,8 +79,7 @@
         return;
     }
 
-    // get this info from somewhere?
-    if (!this->createSwapchain(1024, 768)) {
+    if (!this->createSwapchain(-1, -1)) {
         this->destroyContext();
         return;
     }
@@ -152,7 +151,7 @@
         extent.height = height;
     }
 
-    // clamp width; to protect us from broken hints?
+    // clamp width; to protect us from broken hints
     if (extent.width < caps.minImageExtent.width) {
         extent.width = caps.minImageExtent.width;
     } else if (extent.width > caps.maxImageExtent.width) {
diff --git a/tools/vulkan/VulkanTestContext.h b/tools/vulkan/VulkanTestContext.h
index c1535b9..b300a57 100644
--- a/tools/vulkan/VulkanTestContext.h
+++ b/tools/vulkan/VulkanTestContext.h
@@ -42,8 +42,8 @@
 
     bool isValid() { return SkToBool(fBackendContext.get()); }
 
-    void resize() { 
-        this->createSwapchain(0, 0); 
+    void resize(uint32_t w, uint32_t h) {
+        this->createSwapchain(w, h); 
     }
 
     GrBackendContext getBackendContext() { return (GrBackendContext)fBackendContext.get(); }
diff --git a/tools/vulkan/Window.cpp b/tools/vulkan/Window.cpp
index d0a2a76..86f1814 100644
--- a/tools/vulkan/Window.cpp
+++ b/tools/vulkan/Window.cpp
@@ -11,17 +11,24 @@
 #include "SkCanvas.h"
 #include "VulkanTestContext.h"
 
-static bool default_key_func(int key, bool down, void* userData) {
+static bool default_char_func(SkUnichar c, uint32_t modifiers, void* userData) {
     return false;
 }
 
-static bool default_mouse_func(int x, int y, bool down, void* userData) {
+static bool default_key_func(Window::Key key, Window::InputState state, uint32_t modifiers, 
+                             void* userData) {
+    return false;
+}
+
+static bool default_mouse_func(int x, int y, Window::InputState state, uint32_t modifiers, 
+                               void* userData) {
     return false;
 }
 
 static void default_paint_func(SkCanvas*, void* userData) {}
 
-Window::Window() : fKeyFunc(default_key_func)
+Window::Window() : fCharFunc(default_char_func)
+                 , fKeyFunc(default_key_func)
                  , fMouseFunc(default_mouse_func)
                  , fPaintFunc(default_paint_func) {
 }
@@ -31,6 +38,18 @@
     fTestContext = nullptr;
 }
 
+bool Window::onChar(SkUnichar c, uint32_t modifiers) {
+    return fCharFunc(c, modifiers, fCharUserData);
+}
+
+bool Window::onKey(Key key, InputState state, uint32_t modifiers) {
+    return fKeyFunc(key, state, modifiers, fKeyUserData);
+}
+
+bool Window::onMouse(int x, int y, InputState state, uint32_t modifiers) {
+    return fMouseFunc(x, y, state, modifiers, fMouseUserData);
+}
+
 void Window::onPaint() {
     SkSurface* backbuffer = fTestContext->getBackbufferSurface();
     if (backbuffer) {
@@ -47,6 +66,6 @@
 }
 
 
-void Window::onSize() {
-    fTestContext->resize();
+void Window::onResize(uint32_t w, uint32_t h) {
+    fTestContext->resize(w, h);
 }
diff --git a/tools/vulkan/Window.h b/tools/vulkan/Window.h
index 2247ca1..dbfe4e1 100644
--- a/tools/vulkan/Window.h
+++ b/tools/vulkan/Window.h
@@ -8,6 +8,8 @@
 #ifndef Window_DEFINED
 #define Window_DEFINED
 
+#include "SkTypes.h"
+
 class SkCanvas;
 class VulkanTestContext;
 
@@ -34,10 +36,71 @@
     void detach();
 
     // input handling
-    typedef bool(*OnKeyFunc)(int key, bool down, void* userData);
-    typedef bool(*OnMouseFunc)(int x, int y, bool down, void* userData);
+    enum Key {
+        kNONE_Key,    //corresponds to android's UNKNOWN
+
+        kLeftSoftKey_Key,
+        kRightSoftKey_Key,
+
+        kHome_Key,    //!< the home key - added to match android
+        kBack_Key,    //!< (CLR)
+        kSend_Key,    //!< the green (talk) key
+        kEnd_Key,     //!< the red key
+
+        k0_Key,
+        k1_Key,
+        k2_Key,
+        k3_Key,
+        k4_Key,
+        k5_Key,
+        k6_Key,
+        k7_Key,
+        k8_Key,
+        k9_Key,
+        kStar_Key,    //!< the * key
+        kHash_Key,    //!< the # key
+
+        kUp_Key,
+        kDown_Key,
+        kLeft_Key,
+        kRight_Key,
+
+        kOK_Key,      //!< the center key
+
+        kVolUp_Key,   //!< volume up    - match android
+        kVolDown_Key, //!< volume down  - same
+        kPower_Key,   //!< power button - same
+        kCamera_Key,  //!< camera       - same
+
+        kLast_Key = kCamera_Key
+    };
+    static const int kKeyCount = kLast_Key + 1;
+
+    enum ModifierKeys {
+        kShift_ModifierKey = 1 << 0,
+        kControl_ModifierKey = 1 << 1,
+        kOption_ModifierKey = 1 << 2,   // same as ALT
+        kCommand_ModifierKey = 1 << 3,
+        kFirstPress_ModifierKey = 1 << 4,
+    };
+
+    enum InputState {
+        kDown_InputState,
+        kUp_InputState,
+        kMove_InputState   // only valid for mouse
+    };
+
+    // return value of 'true' means 'I have handled this event'
+    typedef bool(*OnCharFunc)(SkUnichar c, uint32_t modifiers, void* userData);
+    typedef bool(*OnKeyFunc)(Key key, InputState state, uint32_t modifiers, void* userData);
+    typedef bool(*OnMouseFunc)(int x, int y, InputState state, uint32_t modifiers, void* userData);
     typedef void(*OnPaintFunc)(SkCanvas*, void* userData);
 
+    void registerCharFunc(OnCharFunc func, void* userData) {
+        fCharFunc = func;
+        fCharUserData = userData;
+    }
+
     void registerKeyFunc(OnKeyFunc func, void* userData) {
         fKeyFunc = func;
         fKeyUserData = userData;
@@ -53,12 +116,17 @@
         fPaintUserData = userData;
     }
 
+    bool onChar(SkUnichar c, uint32_t modifiers);
+    bool onKey(Key key, InputState state, uint32_t modifiers);
+    bool onMouse(int x, int y, InputState state, uint32_t modifiers);
     void onPaint();
-    void onSize();
+    void onResize(uint32_t width, uint32_t height);
 
 protected:
     Window();
 
+    OnCharFunc   fCharFunc;
+    void*        fCharUserData;
     OnKeyFunc    fKeyFunc;
     void*        fKeyUserData;
     OnMouseFunc  fMouseFunc;
diff --git a/tools/vulkan/viewer/InputHandler.cpp b/tools/vulkan/viewer/InputHandler.cpp
deleted file mode 100644
index 0e78b94..0000000
--- a/tools/vulkan/viewer/InputHandler.cpp
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-* Copyright 2016 Google Inc.
-*
-* Use of this source code is governed by a BSD-style license that can be
-* found in the LICENSE file.
-*/
-
-#include "InputHandler.h"
-#include <ctype.h>
-
-InputHandler::InputHandler() : fMouseDown(false), fMousePressed(false), fMouseReleased(false)
-                             , fMouseX(0), fMouseY(0) {
-    // clear key states
-    memset(fKeys, 0, sizeof(bool) * 256);
-    memset(fKeyPressed, 0, sizeof(bool) * 256);
-    memset(fKeyReleased, 0, sizeof(bool) * 256);
-}
-
-void InputHandler::onKeyDown(unsigned char key) {
-    if (!fKeys[key]) {
-        fKeys[key] = true;
-        fKeyPressed[key] = true;
-    }
-} 
-
-void InputHandler::onKeyUp(unsigned char key) {
-    if (fKeys[key]) {
-        fKeys[key] = false;
-        fKeyReleased[key] = true;
-    }
-}
-
-void InputHandler::onMouseDown(unsigned int h, unsigned int v) {
-    if (!fMouseDown) {
-        fMouseDown = true;
-        fMousePressed = true;
-    }
-
-    fMouseX = h;
-    fMouseY = v;
-}
-
-void InputHandler::onMouseUp() {
-    if (fMouseDown)  {
-        fMouseDown = false;
-        fMouseReleased = true;
-    }
-}
diff --git a/tools/vulkan/viewer/InputHandler.h b/tools/vulkan/viewer/InputHandler.h
deleted file mode 100644
index 05d6942..0000000
--- a/tools/vulkan/viewer/InputHandler.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
-* Copyright 2016 Google Inc.
-*
-* Use of this source code is governed by a BSD-style license that can be
-* found in the LICENSE file.
-*/
-
-#ifndef InputHandler_DEFINED
-#define InputHandler_DEFINED
-
-#include <string.h>
-
-class InputHandler
-{
-public:
-    InputHandler();
-
-    void onKeyDown(unsigned char key);                  // Handle key down event
-    void onKeyUp(unsigned char key);                    // Handle key up event
-    void onMouseDown(unsigned int h, unsigned int v);   // Handle mouse down event
-    void onMouseUp();                                   // Handle mouse up event
-
-    bool isKeyDown(unsigned char key) const { 
-        return fKeys[key]; 
-    }
-    bool isKeyUp(unsigned char key) const { 
-        return !fKeys[key]; 
-    }
-    bool isKeyPressed(unsigned char key) const {
-        return fKeyPressed[key];
-    }
-
-    bool isKeyReleased(unsigned char key) const {
-        return fKeyReleased[key];
-    }
-
-    bool isMouseDown(unsigned int* h, unsigned int* v) const {
-        if (fMouseDown)
-        {
-            *h = fMouseX; 
-            *v = fMouseY;
-            return true;
-        }
-        return false;
-    }
-
-    bool isMousePressed(unsigned int* h, unsigned int* v) const {
-        if (fMousePressed)
-        {
-            *h = fMouseX; 
-            *v = fMouseY;
-            return true;
-        }
-        return false;
-    }
-
-    bool IsMouseReleased() const {
-        return fMouseReleased;
-    }
-
-    inline void Update() {
-        memset(fKeyPressed, 0, sizeof(bool) * 256);
-        memset(fKeyReleased, 0, sizeof(bool) * 256);
-        fMousePressed = false;
-        fMouseReleased = false;
-    }
-
-private:
-    // assumes ASCII keystrokes only
-    bool fKeys[256];            
-    bool fKeyPressed[256];
-    bool fKeyReleased[256];
-
-    bool fMouseDown;
-    bool fMousePressed;
-    bool fMouseReleased;
-    unsigned int fMouseX;
-    unsigned int fMouseY;
-
-    InputHandler(const InputHandler& other);
-    InputHandler& operator=(const InputHandler& other);
-};
-
-#endif
diff --git a/tools/vulkan/viewer/VulkanViewer.cpp b/tools/vulkan/viewer/VulkanViewer.cpp
index 0af423e..27e7b1c 100644
--- a/tools/vulkan/viewer/VulkanViewer.cpp
+++ b/tools/vulkan/viewer/VulkanViewer.cpp
@@ -18,16 +18,11 @@
     return new VulkanViewer(argc, argv, platformData);
 }
 
-static bool on_key_handler(int key, bool keyDown, void* userData) {
+static bool on_key_handler(Window::Key key, Window::InputState state, uint32_t modifiers,
+                           void* userData) {
     VulkanViewer* vv = reinterpret_cast<VulkanViewer*>(userData);
 
-    return vv->onKey(key, keyDown);
-}
-
-static bool on_mouse_handler(int x, int y, bool mouseDown, void* userData) {
-    VulkanViewer* vv = reinterpret_cast<VulkanViewer*>(userData);
-
-    return vv->onMouse(x, y, mouseDown);
+    return vv->onKey(key, state, modifiers);
 }
 
 static void on_paint_handler(SkCanvas* canvas, void* userData) {
@@ -44,7 +39,6 @@
 
     // register callbacks
     fWindow->registerKeyFunc(on_key_handler, this);
-    fWindow->registerMouseFunc(on_mouse_handler, this);
     fWindow->registerPaintFunc(on_paint_handler, this);
 
     fWindow->setTitle("VulkanViewer");
@@ -56,21 +50,12 @@
     delete fWindow;
 }
 
-bool VulkanViewer::onKey(int key, bool keyDown) {
-    if (keyDown) {
-        fInputHandler.onKeyDown(key);
-    } else {
-        fInputHandler.onKeyUp(key);
-    }
-    return true;
-}
+bool VulkanViewer::onKey(Window::Key key, Window::InputState state, uint32_t modifiers) {
+    if (Window::kDown_InputState == state && (modifiers & Window::kFirstPress_ModifierKey) &&
+        key == Window::kRight_Key) {
+        fGMs = fGMs->next();
+    } 
 
-bool VulkanViewer::onMouse(int x, int y, bool mouseDown) {
-    if (mouseDown) {
-        fInputHandler.onMouseDown(x, y);
-    } else {
-        fInputHandler.onMouseUp();
-    }
     return true;
 }
 
@@ -85,10 +70,5 @@
 }
 
 void VulkanViewer::onIdle(float dt) {
-    if (fInputHandler.isKeyPressed('l') || fInputHandler.isKeyPressed('L')) {
-        fGMs = fGMs->next();
-    }
     fWindow->onPaint();
-
-    fInputHandler.Update();
 }
diff --git a/tools/vulkan/viewer/VulkanViewer.h b/tools/vulkan/viewer/VulkanViewer.h
index 48ab7af..29cbbef 100644
--- a/tools/vulkan/viewer/VulkanViewer.h
+++ b/tools/vulkan/viewer/VulkanViewer.h
@@ -9,7 +9,6 @@
 #define VulkanViewer_DEFINED
 
 #include "../Application.h"
-#include "InputHandler.h"
 #include "../Window.h"
 #include "gm.h"
 
@@ -20,15 +19,13 @@
     VulkanViewer(int argc, char** argv, void* platformData);
     ~VulkanViewer() override;
 
-    bool onKey(int key, bool keyDown);
-    bool onMouse(int x, int y, bool mouseDown);
+    bool onKey(Window::Key key, Window::InputState state, uint32_t modifiers);
     void onPaint(SkCanvas* canvas);
 
     void onIdle(float dt) override;
 
 private:
     Window*      fWindow;
-    InputHandler fInputHandler;
 
     const skiagm::GMRegistry* fGMs;
 };
diff --git a/tools/vulkan/win/Window_win.cpp b/tools/vulkan/win/Window_win.cpp
index 20cf084..0c9e802 100644
--- a/tools/vulkan/win/Window_win.cpp
+++ b/tools/vulkan/win/Window_win.cpp
@@ -11,6 +11,7 @@
 #include <windows.h>
 #include <windowsx.h>
 
+#include "SkUtils.h"
 #include "VulkanTestContext_win.h"
 
 Window* Window::CreateNativeWindow(void* platformData) {
@@ -85,6 +86,71 @@
     return true;
 }
 
+static Window::Key get_key(WPARAM vk) {
+    static const struct {
+        WPARAM      fVK;
+        Window::Key fKey;
+    } gPair[] = {
+        { VK_BACK, Window::kBack_Key },
+        { VK_CLEAR, Window::kBack_Key },
+        { VK_RETURN, Window::kOK_Key },
+        { VK_UP, Window::kUp_Key },
+        { VK_DOWN, Window::kDown_Key },
+        { VK_LEFT, Window::kLeft_Key },
+        { VK_RIGHT, Window::kRight_Key }
+    };
+    for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
+        if (gPair[i].fVK == vk) {
+            return gPair[i].fKey;
+        }
+    }
+    return Window::kNONE_Key;
+}
+
+static uint32_t get_modifiers(UINT message, WPARAM wParam, LPARAM lParam) {
+    uint32_t modifiers = 0;
+
+    switch (message) {
+        case WM_UNICHAR:
+        case WM_CHAR:
+            if (0 == (lParam & (1 << 30))) {
+                modifiers |= Window::kFirstPress_ModifierKey;
+            }
+            if (lParam & (1 << 29)) {
+                modifiers |= Window::kOption_ModifierKey;
+            }
+            break;
+
+        case WM_KEYDOWN:
+        case WM_SYSKEYDOWN:
+            if (0 == (lParam & (1 << 30))) {
+                modifiers |= Window::kFirstPress_ModifierKey;
+            }
+            if (lParam & (1 << 29)) {
+                modifiers |= Window::kOption_ModifierKey;
+            }
+            break;
+
+        case WM_KEYUP:
+        case WM_SYSKEYUP:
+            if (lParam & (1 << 29)) {
+                modifiers |= Window::kOption_ModifierKey;
+            }
+            break;
+
+        case WM_LBUTTONDOWN:
+        case WM_LBUTTONUP:
+        case WM_MOUSEMOVE:
+            if (wParam & MK_CONTROL) {
+                modifiers |= Window::kControl_ModifierKey;
+            }
+            if (wParam & MK_SHIFT) {
+                modifiers |= Window::kShift_ModifierKey;
+            }
+    }
+
+    return modifiers;
+}
 
 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 {
@@ -93,66 +159,59 @@
 
     Window_win* window = (Window_win*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
 
-    switch (message)
-    {
-    case WM_PAINT:
-        hdc = BeginPaint(hWnd, &ps);
-        window->onPaint();
-        EndPaint(hWnd, &ps);
-        break;
+    bool eventHandled = false;
 
-    case WM_CLOSE:
-    case WM_DESTROY:
-        PostQuitMessage(0);
-        break;
+    switch (message) {
+        case WM_PAINT:
+            hdc = BeginPaint(hWnd, &ps);
+            window->onPaint();
+            EndPaint(hWnd, &ps);
+            eventHandled = true;
+            break;
 
-    case WM_ACTIVATE:
-        // disable/enable rendering here, depending on wParam != WA_INACTIVE
-        break;
+        case WM_CLOSE:
+        case WM_DESTROY:
+            PostQuitMessage(0);
+            eventHandled = true;
+            break;
 
-    case WM_SIZE:
-        window->onSize();
-        break;
+        case WM_ACTIVATE:
+            // disable/enable rendering here, depending on wParam != WA_INACTIVE
+            break;
 
-    case WM_KEYDOWN:
-    case WM_SYSKEYDOWN:
-        {
-            DWORD dwMask = (1 << 29);
-            bool bAltDown = ((lParam & dwMask) != 0);
-            UINT theChar = MapVirtualKey((UINT)wParam, 2);
-            // Handle Extended ASCII only
-            if (theChar < 256) {
-                return window->onKeyboard(theChar, true, bAltDown);
-            }
-        }
-        break;
+        case WM_SIZE:
+            window->onResize(LOWORD(lParam), HIWORD(lParam));
+            eventHandled = true;
+            break;
 
-    case WM_KEYUP:
-    case WM_SYSKEYUP:
-        {
-            DWORD dwMask = (1 << 29);
-            bool bAltDown = ((lParam & dwMask) != 0);
-            UINT theChar = MapVirtualKey((UINT)wParam, 2);
-            // Handle Extended ASCII only
-            if (theChar < 256) {
-                return window->onKeyboard(theChar, false, bAltDown);
-            }
-        }
-        break;
+        case WM_UNICHAR:
+            eventHandled = window->onChar((SkUnichar)wParam, 
+                                          get_modifiers(message, wParam, lParam));
+            break;
 
-    case WM_LBUTTONDOWN:
-    case WM_RBUTTONDOWN:
-    case WM_MBUTTONDOWN:
-    case WM_LBUTTONUP:
-    case WM_RBUTTONUP:
-    case WM_MBUTTONUP:
-        {
-            bool bLeftDown = ((wParam & MK_LBUTTON) != 0);
-            bool bRightDown = ((wParam & MK_RBUTTON) != 0);
-            bool bMiddleDown = ((wParam & MK_MBUTTON) != 0);
+        case WM_CHAR: {
+            const uint16_t* c = reinterpret_cast<uint16_t*>(&wParam);
+            eventHandled = window->onChar(SkUTF16_NextUnichar(&c), 
+                                          get_modifiers(message, wParam, lParam));
+        } break;
 
+        case WM_KEYDOWN:
+        case WM_SYSKEYDOWN:
+            eventHandled = window->onKey(get_key(wParam), Window::kDown_InputState, 
+                                         get_modifiers(message, wParam, lParam));
+            break;
+
+        case WM_KEYUP:
+        case WM_SYSKEYUP:
+            eventHandled = window->onKey(get_key(wParam), Window::kUp_InputState,
+                                         get_modifiers(message, wParam, lParam));
+            break;
+
+        case WM_LBUTTONDOWN:
+        case WM_LBUTTONUP: {
             int xPos = GET_X_LPARAM(lParam);
             int yPos = GET_Y_LPARAM(lParam);
+
             //if (!gIsFullscreen)
             //{
             //    RECT rc = { 0, 0, 640, 480 };
@@ -161,25 +220,37 @@
             //    yPos -= rc.top;
             //}
 
-            return window->onMouse(bLeftDown, bRightDown, bMiddleDown, false, false, 0, xPos, yPos);
-        }
-    break;
+            Window::InputState istate = ((wParam & MK_LBUTTON) != 0) ? Window::kDown_InputState
+                                                                     : Window::kUp_InputState;
 
-    default:
-        return DefWindowProc(hWnd, message, wParam, lParam);
+            eventHandled = window->onMouse(xPos, yPos, istate, 
+                                            get_modifiers(message, wParam, lParam));
+        } break;
+
+        case WM_MOUSEMOVE: 
+            // only track if left button is down
+            if ((wParam & MK_LBUTTON) != 0) {
+                int xPos = GET_X_LPARAM(lParam);
+                int yPos = GET_Y_LPARAM(lParam);
+
+                //if (!gIsFullscreen)
+                //{
+                //    RECT rc = { 0, 0, 640, 480 };
+                //    AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
+                //    xPos -= rc.left;
+                //    yPos -= rc.top;
+                //}
+
+                eventHandled = window->onMouse(xPos, yPos, Window::kMove_InputState,
+                                               get_modifiers(message, wParam, lParam));
+            }
+            break;
+
+        default:
+            return DefWindowProc(hWnd, message, wParam, lParam);
     }
 
-    return 0;
-}
-
-bool Window_win::onKeyboard(UINT nChar, bool bKeyDown, bool bAltDown) {
-    return fKeyFunc(nChar, bKeyDown, fKeyUserData);
-}
-
-bool Window_win::onMouse(bool bLeftButtonDown, bool bRightButtonDown, bool bMiddleButtonDown,
-                         bool bSideButton1Down, bool bSideButton2Down, int nMouseWheelDelta,
-                         int xPos, int yPos) {
-    return fMouseFunc(xPos, yPos, bLeftButtonDown, fMouseUserData);
+    return eventHandled ? 0 : 1;
 }
 
 void Window_win::setTitle(const char* title) {
diff --git a/tools/vulkan/win/Window_win.h b/tools/vulkan/win/Window_win.h
index 90d4191..4a5b7ff 100644
--- a/tools/vulkan/win/Window_win.h
+++ b/tools/vulkan/win/Window_win.h
@@ -21,12 +21,6 @@
     void setTitle(const char*) override;
     void show() override;
 
-    // event callbacks
-    bool onKeyboard(UINT nChar, bool bKeyDown, bool bAltDown);
-    bool onMouse(bool bLeftButtonDown, bool bRightButtonDown, bool bMiddleButtonDown,
-                 bool bSideButton1Down, bool bSideButton2Down, int nMouseWheelDelta,
-                 int xPos, int yPos);
-
     bool attach(BackEndTypes attachType, int msaaSampleCount, AttachmentInfo*) override;
 
 private: