//------------------------------------------------------------------------------
// emListBox.h
//
// Copyright (C) 2015-2016,2021 Oliver Hamann.
//
// Homepage: http://eaglemode.sourceforge.net/
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License version 3 as published by the
// Free Software Foundation.
//
// 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 version 3 for
// more details.
//
// You should have received a copy of the GNU General Public License version 3
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------

#ifndef emListBox_h
#define emListBox_h

#ifndef emAnything_h
#include <emCore/emAnything.h>
#endif

#ifndef emRasterGroup_h
#include <emCore/emRasterGroup.h>
#endif


//==============================================================================
//================================= emListBox ==================================
//==============================================================================

class emListBox : public emRasterGroup {

public:

        // Class for list box. It is an emRasterGroup which shows a group of
        // text items, from which the user can select one item. Optionally,
        // multi-selection can be enabled. Each item just consists of a text,
        // but it is possible to derive from emListBox in order to create custom
        // item panels, which show more than just a text.

        enum SelectionType {
                // Type of selection a user can make in an emListBox.
                READ_ONLY_SELECTION,
                SINGLE_SELECTION,
                MULTI_SELECTION,
                TOGGLE_SELECTION
        };

        emListBox(
                ParentArg parent, const emString & name,
                const emString & caption=emString(),
                const emString & description=emString(),
                const emImage & icon=emImage(),
                SelectionType selType=SINGLE_SELECTION
        );
                // Constructor.
                // Arguments:
                //   parent      - Parent for this panel (emPanel or emView).
                //   name        - The name for this panel.
                //   caption     - The label's caption, or empty.
                //   description - The label's description, or empty.
                //   icon        - The label's icon, or empty.
                //   selType     - Type of selection.

        virtual ~emListBox();
                // Destructor.

        SelectionType GetSelectionType() const;
        void SetSelectionType(SelectionType selType);
                // Get or set the type of selection, which the user can make in
                // this list box.

        int GetItemCount() const;
                // Get number of Items.

        void AddItem(
                const emString & name, const emString & text,
                const emAnything & data = emAnything()
        );
                // Add an item to the end of the list.
                // Arguments:
                //   name - The name for the item. It is any user-defined
                //          identification. There must not be any other item
                //          with the same name. This is used as the name
                //          for the item panel.
                //   text - The text to be shown in the item.
                //   data - Any custom data to be stored in the item.

        void InsertItem(
                int index, const emString & name, const emString & text,
                const emAnything & data = emAnything()
        );
                // Add an item at any position in the list.
                // Arguments:
                //   index - Index of the new item.
                //   name  - The name for the item. It is any user-defined
                //           identification. There must not be any other item
                //           with the same name. This is used as the name
                //           for the item panel.
                //   text  - The text to be shown in the item.
                //   data  - Any custom data to be stored in the item.

        void MoveItem(int fromIndex, int toIndex);
                // Move an item to another position by changing its index.
                // Arguments:
                //   fromIndex - Original index of the item.
                //   toIndex   - Desired new index of the item.

        bool SortItems(
                int(*compare)(
                        const emString & item1name, const emString & item1text,
                        const emAnything & item1data,
                        const emString & item2name, const emString & item2text,
                        const emAnything & item2data,
                        void * context
                ),
                void * context=NULL
        );
                // Sort all items.
                // Arguments:
                //   compare - A function for comparing two items. The
                //             result is:
                //               < 0   -  item1 is "less" than item2
                //               > 0   -  item1 is "greater" than item2
                //               == 0  -  item1 is "equal" to item2 (no
                //                        change in the order)
                //   context - Any pointer to be forwarded to the compare
                //             function.
                // Returns: Whether there was a change.

        void RemoveItem(int index);
                // Remove an item from the list.
                // Arguments:
                //   index - Index of the item to be removed.

        void ClearItems();
                // Remove all items.

        const emString & GetItemName(int index) const;
                // Get the name of an item.
                // Arguments:
                //   index - Index of the item.
                // Returns: The name of the item, or an empty string if the
                // index is out of range.

        int GetItemIndex(const char * name) const;
                // Get the index of an item by name.
                // Arguments:
                //   name - Name of the item.
                // Returns: The index of the item, or -1 if not found.

        const emString & GetItemText(int index) const;
                // Get the text of an item.
                // Arguments:
                //   index - Index of the item.
                // Returns: The text of the item, or an empty string if the
                // index is out of range.

        void SetItemText(int index, const emString & text);
                // Set the text of an item.
                // Arguments:
                //   index - Index of the item.
                //   text  - The text to be shown in the item.

        emAnything GetItemData(int index) const;
                // Get the data of an item.
                // Arguments:
                //   index - Index of the item.
                // Returns: The data of the item, or an invalid emAnything if
                // the index is out of range.

        void SetItemData(int index, const emAnything & data);
                // Set the data of an item.
                // Arguments:
                //   index - Index of the item.
                //   data  - Any custom data to be stored in the item.

        int GetSelectionCount() const;
                // Get number of selected items.

        const emArray<int> & GetSelectedIndices() const;
                // Get the indices of the selected items. The returned array is
                // always sorted.

        void SetSelectedIndices(const emArray<int> & itemIndices);
                // Set the indices of the selected items.

        int GetSelectedIndex() const;
                // Get the index of the first (or solely) selected item. If no
                // item is selected, -1 is returned.

        void SetSelectedIndex(int index);
                // Select a certain item solely.
                // Arguments:
                //   index - Index of the item.

        bool IsSelected(int index) const;
                // Ask whether a certain item is selected.
                // Arguments:
                //   index - Index of the item.

        void Select(int index, bool solely=false);
                // Select an item.
                // Arguments:
                //   index  - Index of the item.
                //   solely - Whether all other items should be deselected.

        void Deselect(int index);
                // Deselect an item.
                // Arguments:
                //   index - Index of the item.

        void ToggleSelection(int index);
                // Invert the selection of an item.
                // Arguments:
                //   index - Index of the item.

        void SelectAll();
                // Select all items.

        void ClearSelection();
                // Deselect all items.

        const emSignal & GetSelectionSignal() const;
                // This signal is signaled after each change of the selection.

        const emSignal & GetItemTriggerSignal() const;
                // This signal is signaled when an item is triggered by a double
                // click or by pressing the enter key. The triggered item index
                // can be get with GetTriggeredItemIndex().

        int GetTriggeredItemIndex() const;
                // Get the index of the item which was triggered by a double
                // click or enter key press.

        void TriggerItem(int index);
                // Trigger an item programmatically.

        class ItemPanelInterface {

        public:

                // Class for an interface to an item panel. This must be derived
                // by custom item panel classes. Also see
                // emListBox::CreateItemPanel(...).

                ItemPanelInterface(emListBox & listBox, int itemIndex);
                        // Constructor.
                        // Arguments:
                        //   listBox   - The list box.
                        //   itemIndex - The index of the item.

                virtual ~ItemPanelInterface();
                        // Destructor.

                emListBox & GetListBox() const;
                        // Get the list box.

                int GetItemIndex() const;
                        // Get the index of the item.

                const emString & GetItemName() const;
                        // Get the name of the item.

                const emString & GetItemText() const;
                        // Get the text of the item.

                const emAnything & GetItemData() const;
                        // Get the data of the item.

                bool IsItemSelected() const;
                        // Whether the item is selected.

        protected:

                void ProcessItemInput(
                        emPanel * panel, emInputEvent & event,
                        const emInputState & state
                );
                        // Process mouse an keyboard events which select,
                        // deselect, or trigger an item. This method must be
                        // called from the Input method of the item panel.

                virtual void ItemTextChanged() = 0;
                        // Called when the text of the item has changed.

                virtual void ItemDataChanged() = 0;
                        // Called when the data of the item has changed.

                virtual void ItemSelectionChanged() = 0;
                        // Called when the selection of the item has changed.

        private:
                friend class emListBox;
                emListBox & ListBox;
                void * Item;
        };

        class DefaultItemPanel : public emPanel, public ItemPanelInterface {

        public:

                // Default class for an item panel.

                DefaultItemPanel(emListBox & listBox, const emString & name,
                                 int itemIndex);

                virtual ~DefaultItemPanel();

        protected:

                virtual void Input(emInputEvent & event,
                                   const emInputState & state,
                                   double mx, double my);

                virtual bool IsOpaque() const;

                virtual void Paint(const emPainter & painter,
                                   emColor canvasColor) const;

                virtual void ItemTextChanged();

                virtual void ItemDataChanged();

                virtual void ItemSelectionChanged();
        };

        emPanel * GetItemPanel(int index) const;
                // Get the emPanel for an item. Returns NULL if index out of
                // range or if panel tree not expanded.

        ItemPanelInterface * GetItemPanelInterface(int index) const;
                // Get the ItemPanelInterface for an item. Returns NULL if index
                // out of range or if panel tree not expanded.

protected:

        virtual void CreateItemPanel(const emString & name, int itemIndex);
                // Create the panel for an item. This can be overloaded in order
                // to have custom item panels. The default implementation
                // creates an instance of emListBox::DefaultItemPanel. A derived
                // class may create a panel of an other class. It just has to be
                // a derivative of emPanel and mListBox::ItemPanelInterface.

        virtual void Notice(NoticeFlags flags);

        virtual void Input(emInputEvent & event, const emInputState & state,
                           double mx, double my);

        virtual void AutoExpand();
        virtual void AutoShrink();

        virtual bool HasHowTo() const;
        virtual emString GetHowTo() const;

private:

        friend class ItemPanelInterface;

        struct Item {
                emAvlNode AvlNode;
                int Index;
                emString Name;
                emString Text;
                emAnything Data;
                ItemPanelInterface * Interface;
                bool Selected;
        };

        struct CompareContext {
                int(*origCompare)(
                        const emString & item1name, const emString & item1text,
                        const emAnything & item1data,
                        const emString & item2name, const emString & item2text,
                        const emAnything & item2data,
                        void * context
                );
                void * origContext;
        };

        void ProcessItemInput(
                int itemIndex, emPanel * panel, emInputEvent & event,
                const emInputState & state
        );

        void SelectByInput(int itemIndex, bool shift, bool ctrl, bool trigger);

        void KeyWalk(emInputEvent & event, const emInputState & state);

        static int CompareItems(Item * const * item1, Item * const * item2,
                                void * context);

        SelectionType SelType;
        emArray<Item*> Items;
        emAvlTree AvlTree;
        emArray<int> SelectedItemIndices;
        Item * TriggeredItem;
        Item * PrevInputItem;
        emSignal SelectionSignal;
        emSignal ItemTriggerSignal;
        emString KeyWalkChars;
        emUInt64 KeyWalkClock;

        static const char * const HowToListBox;
        static const char * const HowToReadOnlySelection;
        static const char * const HowToSingleSelection;
        static const char * const HowToMultiSelection;
        static const char * const HowToToggleSelection;
};

inline emListBox::SelectionType emListBox::GetSelectionType() const
{
        return SelType;
}

inline int emListBox::GetItemCount() const
{
        return Items.GetCount();
}

inline int emListBox::GetSelectionCount() const
{
        return SelectedItemIndices.GetCount();
}

inline const emArray<int> & emListBox::GetSelectedIndices() const
{
        return SelectedItemIndices;
}

inline const emSignal & emListBox::GetSelectionSignal() const
{
        return SelectionSignal;
}

inline const emSignal & emListBox::GetItemTriggerSignal() const
{
        return ItemTriggerSignal;
}

inline int emListBox::GetTriggeredItemIndex() const
{
        return TriggeredItem ? TriggeredItem->Index : -1;
}

inline emListBox & emListBox::ItemPanelInterface::GetListBox() const
{
        return ListBox;
}

inline int emListBox::ItemPanelInterface::GetItemIndex() const
{
        return ((const emListBox::Item*)Item)->Index;
}

inline const emString & emListBox::ItemPanelInterface::GetItemName() const
{
        return ((const emListBox::Item*)Item)->Name;
}

inline const emString & emListBox::ItemPanelInterface::GetItemText() const
{
        return ((const emListBox::Item*)Item)->Text;
}

inline const emAnything & emListBox::ItemPanelInterface::GetItemData() const
{
        return ((const emListBox::Item*)Item)->Data;
}

inline bool emListBox::ItemPanelInterface::IsItemSelected() const
{
        return ((const emListBox::Item*)Item)->Selected;
}


#endif