/***************************************************************************
 *   crengine-ng                                                           *
 *   Copyright (C) 2008-2010,2012 Vadim Lopatin <coolreader.org@gmail.com> *
 *   Copyright (C) 2011 Konstantin Potapov <pkbo@users.sourceforge.net>    *
 *   Copyright (C) 2022 Aleksey Chernov <valexlin@gmail.com>               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License           *
 *   as published by the Free Software Foundation; either version 2        *
 *   of the License, or (at your option) any later version.                *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,            *
 *   MA 02110-1301, USA.                                                   *
 ***************************************************************************/

#ifndef CR_GUI_INCLUDED
#define CR_GUI_INCLUDED

#include <lvtypes.h>
#include <lvstring.h>
#include <lvptrvec.h>
#include <lvdrawbuf.h>
#include <lvdocview.h>
#include <crskin.h>

#if (COLOR_BACKBUFFER == 1)
#include <lvcolordrawbuf.h>
#else
#include <lvgraydrawbuf.h>
#endif

class CRGUIWindowManager;

#define KEY_FLAG_LONG_PRESS 1

/// Accelerator table entry: to convert keypress to command
class CRGUIAccelerator
{
public:
    int keyCode;
    int keyFlags;
    int commandId;
    int commandParam;
};

/// accelerator table
class CRGUIAcceleratorTable
{
protected:
    LVPtrVector<CRGUIAccelerator> _items;
    int indexOf(int keyCode, int keyFlags) {
        //CRLog::trace( "indexOf( %d, %d )", (int)keyCode, (int)keyFlags );
        for (int i = 0; i < _items.length(); i++) {
            //CRLog::trace( " compare( %d, %d )", (int)_items[i]->keyCode, (int)_items[i]->keyFlags );
            if (_items[i]->keyCode == keyCode && _items[i]->keyFlags == keyFlags) {
                //CRLog::trace(" found index = %d", i);
                return i;
            }
        }
        return -1;
    }
public:
    /// find key by command
    bool findCommandKey(int cmd, int param, int& keyCode, int& keyFlags) {
        //CRLog::trace( "indexOf( %d, %d )", (int)keyCode, (int)keyFlags );
        for (int i = 0; i < _items.length(); i++) {
            //CRLog::trace( " compare( %d, %d )", (int)_items[i]->keyCode, (int)_items[i]->keyFlags );
            if (_items[i]->commandId == cmd && _items[i]->commandParam == param) {
                //CRLog::trace(" found index = %d", i);
                keyCode = _items[i]->keyCode;
                keyFlags = _items[i]->keyFlags;
                return true;
            }
        }
        return false;
    }
    /// get item by index
    const CRGUIAccelerator* get(int index) const {
        return _items[index];
    }

    /// debug dump of table
    void dump() {
        if (CRLog::isTraceEnabled()) {
#if 0
            CRLog::trace("Accelerator table:");
            for ( int i=0; i<_items.length(); i++ ) {
                CRGUIAccelerator * p = _items[i];
                CRLog::trace("%d, %d => %d, %d\n", p->keyCode, p->keyFlags, p->commandId, p->commandParam);
            }
#endif
        }
    }
    /// returns number of entries
    int length() {
        return _items.length();
    }
    /// remove accelerator from table
    bool remove(int keyCode, int keyFlags) {
        int index = indexOf(keyCode, keyFlags);
        if (index >= 0) {
            _items.erase(index, 1);
            return true;
        }
        return false;
    }

    /// add accelerator to table or change existing
    bool add(int keyCode, int keyFlags, int commandId, int commandParam);

    /// add all items from another table
    void addAll(const CRGUIAcceleratorTable& v);

    /// translate keycode to command, returns true if translated
    bool translate(int keyCode, int keyFlags, int& commandId, int& commandParam) {
        int index = indexOf(keyCode, keyFlags);
        if (index < 0)
            return false;
        commandId = _items[index]->commandId;
        commandParam = _items[index]->commandParam;
        return true;
    }
    /// translate keycode to command, returns true if translated
    const CRGUIAccelerator* findKeyAccelerator(int keyCode, int keyFlags) {
        int index = indexOf(keyCode, keyFlags);
        if (index < 0)
            return NULL;
        return get(index);
    }
    /// empty table constructor
    CRGUIAcceleratorTable() { }
    /// copy constructor
    CRGUIAcceleratorTable(const CRGUIAcceleratorTable& v) {
        for (int i = 0; i < v._items.length(); i++) {
            _items.add(new CRGUIAccelerator(*v._items[i]));
        }
    }
    /// constructor from int array: 4 ints per entry (keyCode, keyFlags, commandId, commandParam), keyCode==0 indicates end of list
    CRGUIAcceleratorTable(const int* tableQuadsArray) {
        while (*tableQuadsArray) {
            CRGUIAccelerator* item = new CRGUIAccelerator();
            item->keyCode = *tableQuadsArray++;
            item->keyFlags = *tableQuadsArray++;
            item->commandId = *tableQuadsArray++;
            item->commandParam = *tableQuadsArray++;
            _items.add(item);
        }
    }
};

/// accelerator table reference
typedef LVRef<CRGUIAcceleratorTable> CRGUIAcceleratorTableRef;

/**
 * \brief Container for list of named accelerator tables read from files
 *
 * File format:
 * there are two files:
 *   1) definition file which has key code and command id definitions
 *   2) keymap file which has several key->command tables
 *
 * definition file format:
 *   # there may be comment lines started with # character
 *   other lines should be in format
 *   identifier=value
 *   where identifier is alphanumeric identifier to name value
 *   value is either decimal number, hex number with 0x prefix, or character in ''
 * example:
 *
 * # this is sample definition file
 * XK_Return=0xFF01
 * KEY_1='1'
 * KEY_SPACE=32
 * CMD_CLOSE=100
 *
 * keymap file format:
 *   # there may be comment lines started with # character
 *   file should be divided into sections using lines like
 *   [sectionname]
 *   other lines should be in format
 *   key[,keyflags]=cmd[,cmdparam]
 *   (keyflags and cmdparam are optional)
 *   key, keyflags, cmd, and cmdparam should be either
 *   identifier defined in definition file, decimal number, hex number with 0x prefix, or character in ''
 *   it's better to avoid constants in this file, instead just place them to definition file and use by identifiers
 *
 * example:
 *
 * # this is sample keymap file
 * [mainwindow]
 * #this is keymap table for main window, accessible by name "mainwindow"
 * XK_Return=CMD_SHOW_MENU, 0
 * XK_Return,LONG=CMD_SHOW_MENU_SETTINGS
 * XK_Down,LONG=CMD_MOVE_FORWARD, 10
 * '1'=CMD_GO_TO_PAGE
 *
 */
class CRGUIAcceleratorTableList
{
private:
    LVHashTable<lString32, CRGUIAcceleratorTableRef> _table;
public:
    /// add all tables
    void addAll(const CRGUIAcceleratorTableList& v);
    /// remove all tables
    void clear() {
        _table.clear();
    }
    /// add accelerator table definition from array
    void add(const char* name, const int* defs) {
        add(lString32(name), defs);
    }
    /// add accelerator table definition from array
    void add(const lString32& name, const int* defs) {
        _table.set(name, CRGUIAcceleratorTableRef(new CRGUIAcceleratorTable(defs)));
    }
    /// find accelerator table by name
    CRGUIAcceleratorTableRef get(const lString32& name) {
        return _table.get(name);
    }
    CRGUIAcceleratorTableRef get(const lString32& name, CRPropRef keyRemappingOptions);
    /// find accelerator table by name
    CRGUIAcceleratorTableRef get(const char* name) {
        return _table.get(lString32(name));
    }
    /// returns true if there are no no tables in list
    bool empty() {
        return _table.length() == 0;
    }
    /// constructs empty list, then it should be filled from file using openFromFile()
    CRGUIAcceleratorTableList()
            : _table(32) {
    }
    ~CRGUIAcceleratorTableList() { }
    /// reads definitions from files
    bool openFromFile(const char* defFile, const char* mapFile);
};

class CRKeyboardLayout
{
    lString32Collection _items;
public:
    CRKeyboardLayout() { }
    const lString32Collection& getItems() {
        return _items;
    }
    lString32 get(int i) {
        if (i < 0 || i >= (int)_items.length())
            return lString32::empty_str;
        return _items[i];
    }
    void set(int index, lString32 chars) {
        if (index < 0 || index > 20)
            return;
        while ((int)_items.length() <= index)
            _items.add(lString32::empty_str);
        _items[index] = chars;
    }
};

class CRKeyboardLayoutSet
{
public:
    lString32 name;
    LVRef<CRKeyboardLayout> vKeyboard;
    LVRef<CRKeyboardLayout> tXKeyboard;
    CRKeyboardLayoutSet()
            : vKeyboard(new CRKeyboardLayout())
            , tXKeyboard(new CRKeyboardLayout()) {
    }
    CRKeyboardLayoutSet(const CRKeyboardLayoutSet& v)
            : name(v.name)
            , vKeyboard(v.vKeyboard)
            , tXKeyboard(v.tXKeyboard) {
    }
    CRKeyboardLayoutSet& operator=(const CRKeyboardLayoutSet& v) {
        name = v.name;
        vKeyboard = v.vKeyboard;
        tXKeyboard = v.tXKeyboard;
        return *this;
    }
};

typedef LVRef<CRKeyboardLayoutSet> CRKeyboardLayoutRef;

class CRKeyboardLayoutList
{
    LVHashTable<lString32, CRKeyboardLayoutRef> _table;
    CRKeyboardLayoutRef _current;
public:
    // get currently set layout
    CRKeyboardLayoutRef getCurrentLayout();
    // get next layout
    CRKeyboardLayoutRef nextLayout();
    // get previous layout
    CRKeyboardLayoutRef prevLayout();

    CRKeyboardLayoutRef get(lString32 name) {
        return _table.get(name);
    }
    void set(lString32 name, CRKeyboardLayoutRef v) {
        _table.set(name, v);
    }
    CRKeyboardLayoutList()
            : _table(16) { }
    /// reads definitions from files
    bool openFromFile(const char* layoutFile);
};

/// i18n support interface
class CRGUIStringTranslator
{
public:
    /// translate string by key, return default value if not found
    virtual lString32 translateString(const char*, const char* defValue) {
        return Utf8ToUnicode(lString8(defValue));
    }
    virtual ~CRGUIStringTranslator() { }
};

enum CRGUIEventType
{
    CREV_WINDOW_EVENTS_START = 1,
    CREV_KEYDOWN = 1,
    CREV_KEYUP,
    CREV_COMMAND,

    CREV_WM_EVENTS_START = 100,
    CREV_UPDATE = 100,
    CREV_RESIZE

};

class CRGUIWindow;
class CRGUIWindowManager;

class CRGUIEvent
{
protected:
    int _type;
    CRGUIWindow* _targetWindow;
    int _param1;
    int _param2;
public:
    virtual bool isForVisibleOnly() {
        return false;
    }
    virtual bool isForModalOnly() {
        return false;
    }
    virtual bool isWindowEvent() {
        return _type < CREV_WM_EVENTS_START;
    }
    virtual bool isWMEvent() {
        return _type >= CREV_WM_EVENTS_START;
    }
    int getType() {
        return _type;
    }
    virtual bool handle(CRGUIWindow* window) {
        return false;
    }
    virtual bool handle(CRGUIWindowManager* wm) {
        return false;
    }
    CRGUIEvent& setParam1(int v) {
        _param1 = v;
        return *this;
    }
    CRGUIEvent& setParam2(int v) {
        _param2 = v;
        return *this;
    }
    CRGUIEvent& setTargetWindow(CRGUIWindow* targetWindow) {
        _targetWindow = targetWindow;
        return *this;
    }
    int getParam1() {
        return _param1;
    }
    int getParam2() {
        return _param2;
    }
    CRGUIEvent(int type)
            : _type(type)
            , _targetWindow(NULL) {
    }
    virtual ~CRGUIEvent() { }
};

/// Screen object - provides canvas and interface to device screen
class CRGUIScreen
{
public:
    // for turbo updates
    enum UpdateMode
    {
        NormalMode,
        PrepareMode
    };
    virtual void setTurboUpdateEnabled(bool flg) { }
    virtual bool getTurboUpdateEnabled() {
        return false;
    }
    virtual bool getTurboUpdateSupported() {
        return false;
    }
    virtual void setTurboUpdateMode(UpdateMode mode) { }
    /// fast update feature parameter setting
    virtual void setFullUpdateInterval(int pagesBeforeFullupdate = 1) = 0;
    /// creates compatible canvas of specified size
    virtual LVDrawBuf* createCanvas(int dx, int dy) = 0;
    /// sets new screen size, returns true if size is changed
    virtual bool setSize(int dx, int dy) = 0;
    /// returns screen width
    virtual int getWidth() = 0;
    /// returns screen height
    virtual int getHeight() = 0;
    /// returns screen dimension
    virtual lvRect getRect() {
        return lvRect(0, 0, getWidth(), getHeight());
    }
    /// return pointer to screen canvas
    virtual LVDrawBufRef getCanvas() = 0;
    /// draw image on screen canvas
    virtual void draw(LVDrawBuf* img, int x = 0, int y = 0) = 0;
    /// transfers contents of buffer to device, if full==true, redraws whole screen, otherwise only changed area
    virtual void flush(bool full) = 0;
    /// invalidates rectangle: add it to bounding box of next partial update
    virtual void invalidateRect(const lvRect& rc) { }
    virtual ~CRGUIScreen() { }
};

/// window configure flag, on screen size change
#define CRGUI_CONFIGURE_FLAG_SCREEN_SIZE 1
/// window configure flag, on screen orientation change
#define CRGUI_CONFIGURE_FLAG_SCREEN_ORIENTATION 2

/// Window interface
class CRGUIWindow
{
public:
    /// override to handle
    virtual bool handleEvent(CRGUIEvent* event) {
        if (!event->isWindowEvent())
            return false;
        // by default, allow event to do something with window
        return event->handle(this);
    }
    /// sets scroll label (e.g. "Page $1 of $2" or "$1 / $2")
    virtual void setScrollLabelTemplate(lString32 text) = 0;
    /// returns scroll label (e.g. "$1 of $2")
    virtual lString32 getScrollLabelTemplate() = 0;
    /// sets skin name for window
    virtual void setSkinName(const lString32& skin) = 0;
    /// returns skin name for window
    virtual lString32 getSkinName() = 0;
    /// set accelerator table for window
    virtual void setAccelerators(CRGUIAcceleratorTableRef) { }
    /// get window accelerator table
    virtual CRGUIAcceleratorTableRef getAccelerators() {
        return CRGUIAcceleratorTableRef();
    }
    /// returns true if key is processed
    virtual bool onKeyPressed(int key, int flags = 0) = 0;
    /// returns true if command is processed
    virtual bool onCommand(int command, int params = 0) = 0;
    /// returns true if window is visible
    virtual bool isVisible() const = 0;
    /// returns true if window is fullscreen
    virtual bool isFullscreen() = 0;
    /// returns true if window is changed but now drawn
    virtual bool isDirty() = 0;
    /// sets dirty flag
    virtual void setDirty() = 0;
    /// shows or hides window
    virtual void setVisible(bool visible) = 0;
    /// called on system configuration change: screen size and orientation
    virtual void reconfigure(int flags) = 0;
    /// returns window rectangle
    virtual const lvRect& getRect() = 0;
    /// sets window rectangle
    virtual void setRect(const lvRect& rc) = 0;
    /// draws content of window to screen
    virtual void flush() = 0;
    /// called if window gets focus
    virtual void activated() {
        setDirty();
    }
    virtual void reactivated() { }
    /// called if window loss focus
    virtual void covered() { }
    /// called if window is being closed
    virtual void closing() { }
    /// returns window manager
    virtual CRGUIWindowManager* getWindowManager() = 0;
    /// destroys window
    virtual ~CRGUIWindow() { }
};

/// Window manager
class CRGUIWindowManager: public CRGUIStringTranslator
{
protected:
    LVPtrVector<CRGUIWindow, true> _windows;
    LVPtrVector<CRGUIEvent, true> _events;
    CRGUIScreen* _screen;
    /// if true, we should delete screen in destructor
    bool _ownScreen;
    LVRef<CRGUIStringTranslator> _i18n;
    int _postedCommand;
    int _postedCommandParam;
    time_t _lastProgressUpdate;
    int _lastProgressPercent;
    CRSkinRef _skin;
    CRGUIAcceleratorTableList _accTables;
    CRKeyboardLayoutList _kbLayouts;
    cr_rotate_angle_t _orientation;
    LVRefVec<LVImageSource> m_batteryIcons;
    bool _stopFlag;
public:
    /// forward events from system queue to application queue
    virtual void forwardSystemEvents(bool waitForEvent) { }
    /// post application event to message queue
    virtual void postEvent(CRGUIEvent* event);
    /// peeks head of application message queue, w/o removing from queue (returns NULL if no events in queue)
    virtual CRGUIEvent* peekEvent() {
        forwardSystemEvents(false);
        return _events.peekHead();
    }
    /// returns head of application message queue, removing from queue (returns NULL if no events in queue)
    virtual CRGUIEvent* getEvent() {
        forwardSystemEvents(false);
        return _events.popHead();
    }
    /// handle all events from queue
    virtual bool handleAllEvents(bool waitForEvent);
    /// override to handle
    virtual bool handleEvent(CRGUIEvent* event);
    /// called when message queue is empty and application is going to wait for event
    virtual void idle() { }

    /// returns list of battery icons
    virtual LVRefVec<LVImageSource>& getBatteryIcons() {
        return m_batteryIcons;
    }
    /// set list of battery icons to display battery state
    virtual void setBatteryIcons(LVRefVec<LVImageSource> icons) {
        m_batteryIcons.clear();
        m_batteryIcons.add(icons);
    }
    /// draw battery state to specified rectangle of screen
    virtual void drawBattery(LVDrawBuf& buf, const lvRect& rc);
    /// sets screen orientation value, to be red by corresponding getter. Doesn't rotate screen actually.
    virtual void setScreenOrientation(cr_rotate_angle_t angle) {
        _orientation = angle;
    }
    /// returns current screen orientation
    virtual cr_rotate_angle_t getScreenOrientation() {
        return _orientation;
    }
    /// draws icon at center of screen, with optional progress gauge
    virtual void showWaitIcon(lString32 filename, int progressPercent = -1);
    /// draws icon with gauge at center of screen, skipping too frequent updates
    virtual void showProgress(lString32 filename, int progressPercent);
    /// loads skin from file
    virtual bool loadSkin(lString32 pathname);
    /// returns keyboard layouts
    virtual CRKeyboardLayoutList& getKeyboardLayouts() {
        return _kbLayouts;
    }
    /// returns accelerator table list
    virtual CRGUIAcceleratorTableList& getAccTables() {
        return _accTables;
    }
    /// return battery status
    virtual bool getBatteryStatus(int& percent, bool& charging) {
        // stub
        percent = 0;
        charging = false;
        return false;
    }
    /// set skin
    virtual void setSkin(CRSkinRef skin) {
        _skin = skin;
    }
    /// returns currently selected skin
    virtual CRSkinRef getSkin() {
        return _skin;
    }
    /// sets another i18n translator
    virtual void setTranslator(LVRef<CRGUIStringTranslator> i18n) {
        _i18n = i18n;
    }
    /// translate string by key, return default value if not found
    virtual lString32 translateString(const char* key, const char* defValue) {
        if (_i18n.isNull())
            return Utf8ToUnicode(lString8(defValue));
        return _i18n->translateString(key, defValue);
    }
    /// returns count of windows
    virtual int getWindowCount() {
        return _windows.length();
    }
    /// changes screen size and orientation
    virtual void reconfigure(int dx, int dy, cr_rotate_angle_t orientation);
    /// adds command to message queue
    virtual void postCommand(int command, int params = 0);
    /// runs posted events (commands)
    virtual bool processPostedEvents() {
        return handleAllEvents(false);
    }
    /// returns true if command is processed
    virtual bool onCommand(int command, int params = 0) {
        for (int i = _windows.length() - 1; i >= 0; i--) {
            if (_windows[i]->isVisible() && _windows[i]->onCommand(command, params))
                return true;
        }
        return false;
    }
    /// returns true if key is processed
    virtual bool onKeyPressed(int key, int flags = 0);
    /// returns top visible window
    CRGUIWindow* getTopVisibleWindow() {
        for (int i = _windows.length() - 1; i >= 0; i--) {
            if (!_windows[i]->isVisible())
                continue;
            return _windows[i];
        }
        return NULL;
    }
    /// shows or hides window
    void showWindow(CRGUIWindow* window, bool visible) {
        int index = _windows.indexOf(window);
        if (index >= 0) { //&& window->isVisible()!=visible
            window->setVisible(visible);
            if (!visible) {
                window->covered();
                CRGUIWindow* wnd = getTopVisibleWindow();
                if (wnd)
                    activateWindow(wnd);
            } else
                activateWindow(window);
        }
    }
    /// activates window, brings it on top; add to stack if not added
    void activateWindow(CRGUIWindow* window);
    /// closes window, removes from stack, destroys object
    void closeWindow(CRGUIWindow* window);
    /// redraw one window
    virtual void updateWindow(CRGUIWindow* window);
    /// full redraw of all windows
    void update(bool fullScreenUpdate, bool forceFlushScreen = true);
    /// returns screen associated with window manager
    virtual CRGUIScreen* getScreen() {
        return _screen;
    }
    /// runs event loop
    virtual int runEventLoop();
    /// constructor
    CRGUIWindowManager(CRGUIScreen* screen)
            : _screen(screen)
            , _ownScreen(false)
            , _postedCommand(0)
            , _postedCommandParam(0)
            , _lastProgressUpdate(0)
            , _lastProgressPercent(-1)
            , _orientation(CR_ROTATE_ANGLE_0)
            , _stopFlag(false) {
    }
    virtual void closeAllWindows() {
        for (int i = _windows.length() - 1; i >= 0; i--) {
            closeWindow(_windows[i]);
        }
    }
    /// destroy all windows on close
    virtual ~CRGUIWindowManager() {
        closeAllWindows();
        if (_ownScreen)
            delete _screen;
    }
};

/// Window base implementation
class CRGUIWindowBase: public CRGUIWindow
{
protected:
    CRGUIWindowManager* _wm;
    lvRect _rect;
    bool _visible;
    bool _fullscreen;
    bool _dirty;
    bool _passKeysToParent;
    bool _passCommandsToParent;
    int _page;
    int _pages;
    CRGUIAcceleratorTableRef _acceleratorTable;
    lString32 _skinName;
    lString32 _scrollLabel;
    lString32 _caption;
    lString32 _statusText;
    lString32 _inputText;
    LVImageSourceRef _icon; // window title icon
    // draws frame, title, status and client
    virtual void draw();

    /// use to override status text
    virtual lString32 getStatusText() {
        return _statusText;
    }

    /// draw status bar using current skin, with optional status text and scroll/tab/page indicator
    virtual void drawStatusBar();
    /// draw status text
    virtual void drawStatusText(LVDrawBuf& buf, const lvRect& rc, CRRectSkinRef skin);
    /// draw title bar using current skin, with optional scroll/tab/page indicator
    virtual void drawTitleBar();
    /// draw input box, if any
    virtual void drawInputBox();
    /// draw title bar using current skin, with optional scroll/tab/page indicator
    virtual void drawClient();

    /// calculates title rectangle for window rectangle
    virtual bool getTitleRect(lvRect& rc);
    /// calculates status rectangle for window rectangle
    virtual bool getStatusRect(lvRect& rc);
    /// calculates client rectangle for window rectangle
    virtual bool getClientRect(lvRect& rc);
    /// calculates input box rectangle for window rectangle
    virtual bool getInputRect(lvRect& rc);
    /// calculates scroll rectangle for window rectangle
    virtual bool getScrollRect(lvRect& rc);
public:
    /// use to override status text
    virtual void setStatusText(lString32 s) {
        _statusText = s;
    }
    /// formats scroll label (like "1 of 2")
    virtual lString32 getScrollLabel(int page, int pages);
    /// calculates minimum scroll size
    virtual lvPoint getMinScrollSize(int page, int pages);
    /// sets scroll label (e.g. "Page $1 of $2" or "$1 / $2")
    virtual void setScrollLabelTemplate(lString32 text) {
        _scrollLabel = text;
    }
    /// returns scroll label (e.g. "$1 of $2")
    virtual lString32 getScrollLabelTemplate() {
        return _scrollLabel;
    }
    /// called on system configuration change: screen size and orientation
    virtual void reconfigure(int flags);
    /// sets skin name for window
    virtual void setSkinName(const lString32& skin) {
        _skinName = skin;
    }
    /// returns skin name for window
    virtual lString32 getSkinName() {
        return _skinName;
    }
    /// returns true if command is processed
    virtual bool onCommand(int command, int params = 0) {
        return !_passCommandsToParent;
    }
    /// returns true if key is processed (by default, let's translate key to command using accelerator table)
    virtual bool onKeyPressed(int key, int flags = 0);
    /// set accelerator table for window
    virtual void setAccelerators(CRGUIAcceleratorTableRef table) {
        _acceleratorTable = table;
    }
    /// get window accelerator table
    virtual CRGUIAcceleratorTableRef getAccelerators() {
        return _acceleratorTable;
    }
    /// returns window width
    inline int getWidth() {
        return getRect().width();
    }
    /// returns window height
    inline int getHeight() {
        return getRect().height();
    }
    /// sets dirty flag
    virtual void setDirty() {
        _dirty = true;
    }
    /// returns true if window is changed but now drawn
    virtual bool isDirty() {
        return _dirty;
    }
    /// shows or hides window
    virtual void setVisible(bool visible) {
        _visible = visible;
        setDirty();
    }
    virtual bool isVisible() const {
        return _visible;
    }
    virtual const lvRect& getRect() {
        return _rect;
    }
    virtual void setRect(const lvRect& rc) {
        _rect = rc;
        setDirty();
    }
    virtual void flush() {
        draw();
        _dirty = false;
    }
    /// returns true if window is fullscreen
    virtual bool isFullscreen() {
        return _fullscreen;
    }
    /// set fullscreen state for window
    virtual void setFullscreen(bool fullscreen) {
        _fullscreen = fullscreen;
    }
    virtual CRGUIWindowManager* getWindowManager() {
        return _wm;
    }
    CRGUIWindowBase(CRGUIWindowManager* wm)
            : _wm(wm)
            , _visible(true)
            , _fullscreen(true)
            , _dirty(true)
            , _passKeysToParent(false)
            , _passCommandsToParent(false)
            , _page(0)
            , _pages(0) {
        // fullscreen visible by default
        _rect = _wm->getScreen()->getRect();
        //_statusText = "Sample status text";
    }
    virtual ~CRGUIWindowBase() { }
};

/// Base Screen class implementation
class CRGUIScreenBase: public CRGUIScreen
{
protected:
    int _width;
    int _height;
    lvRect _updateRect;
    LVDrawBufRef _canvas;
    LVDrawBufRef _front;
    int _fullUpdateInterval;
    int _fullUpdateCounter;
    /// override in ancessor to transfer image to device
    virtual void update(const lvRect& rc, bool full) = 0;
public:
    /// fast update feature parameter setting
    virtual void setFullUpdateInterval(int pagesBeforeFullupdate = 1) {
        _fullUpdateInterval = pagesBeforeFullupdate;
        if (_fullUpdateInterval > 0)
            _fullUpdateCounter = _fullUpdateInterval;
    }
    virtual bool checkFullUpdateCounter() {
        if (_fullUpdateInterval <= 0)
            return false; // always partial update
        if (_fullUpdateInterval == 1)
            return true; // always full update
        _fullUpdateCounter--;
        if (_fullUpdateCounter <= 0) {
            _fullUpdateCounter = _fullUpdateInterval;
            return true; // full update
        }
        return false; // partial update
    }

    /// creates compatible canvas of specified size
    virtual LVDrawBuf* createCanvas(int dx, int dy) {
#if (COLOR_BACKBUFFER == 1)
        LVDrawBuf* buf = new LVColorDrawBuf(dx, dy);
#else
        LVDrawBuf* buf = new LVGrayDrawBuf(dx, dy, GRAY_BACKBUFFER_BITS);
#endif
        return buf;
    }
    /// sets new screen size
    virtual bool setSize(int dx, int dy) {
        if (_width != dx || _height != dy) {
            _width = dx;
            _height = dy;
            _canvas = LVDrawBufRef(createCanvas(dx, dy));
            if (!_front.isNull())
                _front = LVDrawBufRef(createCanvas(dx, dy));
            return true;
        }
        return false;
    }

    /// returns screen width
    virtual int getWidth() {
        return _width;
    }
    /// returns screen height
    virtual int getHeight() {
        return _height;
    }
    /// return pointer to screen canvas
    virtual LVDrawBufRef getCanvas() {
        return _canvas;
    }
    /// draw image on screen canvas
    virtual void draw(LVDrawBuf* img, int x = 0, int y = 0) {
        img->DrawTo(_canvas.get(), x, y, 0, NULL);
    }
    /// transfers contents of buffer to device, if full==true, redraws whole screen, otherwise only changed area
    virtual void flush(bool full);
    /// invalidates rectangle: add it to bounding box of next partial update
    virtual void invalidateRect(const lvRect& rc) {
        _updateRect.extend(rc);
    }
    CRGUIScreenBase(int width, int height, bool doublebuffer)
            : _width(width)
            , _height(height)
            , _canvas(NULL)
            , _front(NULL)
            , _fullUpdateInterval(1)
            , _fullUpdateCounter(1) {
        if (width && height) {
            _canvas = LVDrawBufRef(createCanvas(width, height));
            if (doublebuffer)
                _front = LVDrawBufRef(createCanvas(width, height));
        }
    }
    virtual ~CRGUIScreenBase() {
    }
};

/// Window to show LVDocView contents
class CRDocViewWindow: public CRGUIWindowBase
{
protected:
    LVDocView* _docview;
    CRWindowSkinRef _skin;
    virtual void draw();
public:
    LVDocView* getDocView() {
        return _docview;
    }
    CRDocViewWindow(CRGUIWindowManager* wm)
            : CRGUIWindowBase(wm) {
        CRLog::trace("CRDocViewWindow()");
        _docview = new LVDocView(wm->getScreen()->getCanvas()->GetBitsPerPixel());
        CRLog::trace("resizing...");
        _docview->Resize(getWidth(), getHeight());
        _docview->setPageMargins(lvRect(10, 10, 10, 10));
        CRLog::trace("CRDocViewWindow() finished");
    }
    virtual ~CRDocViewWindow() {
        delete _docview;
        _docview = NULL;
    }

    virtual void setRect(const lvRect& rc);

    /// returns true if command is processed
    virtual bool onCommand(int command, int params);

    /// returns true if window is changed but now drawn
    virtual bool isDirty() {
        return _dirty || !_docview->isPageImageReady(0);
    }
};

//===========================================================================================
// MENU SUPPORT

enum CRMenuControlCmd
{
    MCMD_CANCEL = 500,
    MCMD_OK,
    MCMD_SCROLL_FORWARD,
    MCMD_SCROLL_BACK,
    MCMD_SELECT_0,
    MCMD_SELECT_1,
    MCMD_SELECT_2,
    MCMD_SELECT_3,
    MCMD_SELECT_4,
    MCMD_SELECT_5,
    MCMD_SELECT_6,
    MCMD_SELECT_7,
    MCMD_SELECT_8,
    MCMD_SELECT_9,
    MCMD_SELECT_0_LONG,
    MCMD_SELECT_1_LONG,
    MCMD_SELECT_2_LONG,
    MCMD_SELECT_3_LONG,
    MCMD_SELECT_4_LONG,
    MCMD_SELECT_5_LONG,
    MCMD_SELECT_6_LONG,
    MCMD_SELECT_7_LONG,
    MCMD_SELECT_8_LONG,
    MCMD_SELECT_9_LONG,
    MCMD_SCROLL_FORWARD_LONG,
    MCMD_SCROLL_BACK_LONG,
    MCMD_CLEAR,
    MCMD_NEXT_ITEM,
    MCMD_PREV_ITEM,
    MCMD_NEXT_PAGE,
    MCMD_PREV_PAGE,
    MCMD_SELECT,
    MCMD_SELECT_LONG
};

enum CRGUICmd
{
    GCMD_PASS_TO_PARENT = 550
};

class CRMenu;

/// CRGUI menu item base class
class CRMenuItem
{
protected:
    CRMenu* _menu;
    int _id;
    lString32 _label;
    LVImageSourceRef _image;
    LVFontRef _defFont;
    lString32 _propValue;
    bool _itemDirty;
public:
    /// id of item
    int getId() {
        return _id;
    }
    /// set id of item
    void setId(int id) {
        _id = id;
    }
    /// item label
    lString32 getLabel() {
        return _label;
    }
    void setLabel(lString32 label) {
        _label = label;
        setItemDirty();
    }
    /// item icon
    LVImageSourceRef getImage() {
        return _image;
    }
    /// item label font
    virtual LVFontRef getFont() {
        return _defFont;
    }
    /// constructor
    CRMenuItem(CRMenu* menu, int id, lString32 label, LVImageSourceRef image, LVFontRef defFont, const lChar32* propValue = NULL)
            : _menu(menu)
            , _id(id)
            , _label(label)
            , _image(image)
            , _defFont(defFont)
            , _propValue(propValue) { }
    /// constructor
    CRMenuItem(CRMenu* menu, int id, const char* label, LVImageSourceRef image, LVFontRef defFont, const lChar32* propValue = NULL)
            : _menu(menu)
            , _id(id)
            , _label(label)
            , _image(image)
            , _defFont(defFont)
            , _propValue(propValue) { }
    /// measures item size
    virtual lvPoint getItemSize(CRRectSkinRef skin);
    /// draws item
    virtual void Draw(LVDrawBuf& buf, lvRect& rc, CRRectSkinRef skin, CRRectSkinRef valueSkin, bool selected);
    /// returns true if submenu
    virtual bool isSubmenu() const {
        return false;
    }
    /// called on item selection
    virtual int onSelect() {
        return 0;
    }
    virtual ~CRMenuItem() { }
    /// submenu for options dialog support
    virtual lString32 getSubmenuValue() {
        return lString32::empty_str;
    }
    /// property value, for options editor support
    virtual lString32 getPropValue() {
        return _propValue;
    }
    virtual bool isItemDirty() {
        return _itemDirty;
    }
    virtual void setItemDirty() {
        _itemDirty = true;
    }
    virtual void onLeave() {
        CRLog::trace("Menu item %d leave", _id);
        setItemDirty();
    }
    virtual void onEnter() {
        CRLog::trace("Menu item %d enter", _id);
        setItemDirty();
    }
};

/// CRGUI menu base class
class CRMenu: public CRGUIWindowBase, public CRMenuItem
{
private:
    void doCloseMenu(int command, bool highlight = false, int param = 0);
protected:
    LVPtrVector<CRMenuItem> _items;
    CRPropRef _props;
    lString32 _propName;
    LVFontRef _valueFont;
    int _topItem;
    int _pageItems;
    int _helpHeight;
    int _cmdToHighlight;
    int _selectedItem;
    bool _pageUpdate;
    CRMenuSkinRef _skin; // = _wm->getSkin()->getMenuSkin( path.c_str() );
    // override for CRGUIWindow method
    virtual void draw();
    virtual void Draw(LVDrawBuf& buf, lvRect& rc, CRRectSkinRef skin, CRRectSkinRef valueSkin, bool selected);
    //virtual void Draw( LVDrawBuf & buf, int x, int y );
    virtual void highlightCommandItem(int cmd);
    virtual bool onItemSelect(int command, int params = 0);
    int getLastOnPage();
public:
    /// returns index of selected item, -1 if no item selected
    virtual int getSelectedItemIndex();
    virtual int getDefaultSelectionIndex() {
#ifdef CR_POCKETBOOK
        if (getProps().isNull())
            return 0;
#endif
        return -1;
    }
    virtual void activated();
    virtual void drawClient();
    virtual int getScrollHeight();
    CRMenuSkinRef getSkin();
    CRMenu(CRGUIWindowManager* wm, CRMenu* parentMenu, int id, lString32 label, LVImageSourceRef image, LVFontRef defFont, LVFontRef valueFont, CRPropRef props = CRPropRef(), const char* propName = NULL, int pageItems = 8)
            : CRGUIWindowBase(wm)
            , CRMenuItem(parentMenu, id, label, image, defFont)
            , _props(props)
            , _propName(Utf8ToUnicode(lString8(propName)))
            , _valueFont(valueFont)
            , _topItem(0)
            , _pageItems(pageItems)
            , _cmdToHighlight(-1)
            , _selectedItem(-1)
            , _pageUpdate(true) {
        _fullscreen = false;
        _helpHeight = 0;
    }
    CRMenu(CRGUIWindowManager* wm, CRMenu* parentMenu, int id, const char* label, LVImageSourceRef image, LVFontRef defFont, LVFontRef valueFont, CRPropRef props = CRPropRef(), const char* propName = NULL, int pageItems = 8)
            : CRGUIWindowBase(wm)
            , CRMenuItem(parentMenu, id, label, image, defFont)
            , _props(props)
            , _propName(Utf8ToUnicode(lString8(propName)))
            , _valueFont(valueFont)
            , _topItem(0)
            , _pageItems(pageItems)
            , _cmdToHighlight(-1) {
        _fullscreen = false;
        _helpHeight = 0;
    }
    virtual bool isSubmenu() const {
        return true;
    }
    LVPtrVector<CRMenuItem>& getItems() {
        return _items;
    }
    CRPropRef getProps() const {
        return _props;
    }
    lString32 getPropName() const {
        return _propName;
    }
    LVFontRef getValueFont() const {
        return _valueFont;
    }
    void setValueFont(LVFontRef font) {
        _valueFont = font;
    }
    void addItem(CRMenuItem* item) {
        _items.add(item);
    }
    CRMenuItem* findItem(int id) {
        for (int i = 0; i < _items.length(); i++)
            if (_items[i]->getId() == id)
                return _items[i];
        return NULL;
    }
    CRMenu* findSubmenu(int id) {
        for (int i = 0; i < _items.length(); i++)
            if (_items[i]->getId() == id && _items[i]->isSubmenu())
                return (CRMenu*)_items[i];
        return NULL;
    }
    /// called on system configuration change: screen size and orientation
    virtual void reconfigure(int flags);
    virtual int getPageCount();
    /// returns true if current page is changed
    virtual bool setCurPage(int nPage);
    virtual void setCurItem(int nItem);
    virtual int getCurPage();
    virtual int getTopItem();
    virtual lString32 getSubmenuValue();
    virtual void toggleSubmenuValue();
    virtual int getItemHeight();
    virtual lvPoint getMaxItemSize();
    virtual lvPoint getItemSize();
    virtual lvPoint getSize();
    virtual ~CRMenu() { }
    // CRGUIWindow
    virtual const lvRect& getRect();
    /// overriden to disable passing key to parent windows
    virtual bool onKeyPressed(int key, int flags);
    /// returns true if command is processed
    virtual bool onCommand(int command, int params = 0);
    /// closes menu and its submenus, posts command
    virtual void closeMenu(int command, int params = 0);
    /// closes top level menu and its submenus, posts command
    virtual void closeAllMenu(int command, int params = 0);
    /// closes menu and its submenus
    virtual void destroyMenu();
    virtual void covered() {
        _pageUpdate = true;
    }
};

class CRGUIUpdateEvent: public CRGUIEvent
{
public:
    CRGUIUpdateEvent(bool fullScreen = false)
            : CRGUIEvent(CREV_UPDATE) {
        _param1 = fullScreen ? 1 : 0;
    }
    virtual bool handle(CRGUIWindow* window) {
        return false;
    }
    virtual bool handle(CRGUIWindowManager* wm) {
        wm->update(_param1 != 0, false);
        return true;
    }
};

class CRGUIResizeEvent: public CRGUIEvent
{
    cr_rotate_angle_t _angle;
public:
    CRGUIResizeEvent(int dx, int dy, cr_rotate_angle_t angle)
            : CRGUIEvent(CREV_RESIZE) {
        _param1 = dx;
        _param2 = dy;
        _angle = angle;
    }
    virtual bool handle(CRGUIWindow* window) {
        return false;
    }
    virtual bool handle(CRGUIWindowManager* wm) {
        wm->reconfigure(_param1, _param2, _angle);
        return true;
    }
};

class CRGUIKeyDownEvent: public CRGUIEvent
{
public:
    virtual bool isForVisibleOnly() {
        return true;
    }
    virtual bool isForModalOnly() {
        return true;
    }
    CRGUIKeyDownEvent(int key, int params)
            : CRGUIEvent(CREV_KEYDOWN) {
        _param1 = key;
        _param2 = params;
    }
    virtual bool handle(CRGUIWindow* window);
    virtual bool handle(CRGUIWindowManager* wm) {
        return false;
    }
};

class CRGUICommandEvent: public CRGUIEvent
{
public:
    CRGUICommandEvent(int cmd, int params)
            : CRGUIEvent(CREV_COMMAND) {
        _param1 = cmd;
        _param2 = params;
    }
    virtual bool handle(CRGUIWindow* window);
    virtual bool handle(CRGUIWindowManager* wm) {
        return false;
    }
    virtual bool isForModalOnly() {
        return false;
    }
    virtual bool isForVisibleOnly() {
        return true;
    }
};

#endif // CR_GUI_INCLUDED
