//------------------------------------------------------------------------------
// emFileSelectionBox.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 emFileSelectionBox_h
#define emFileSelectionBox_h

#ifndef emCheckBox_h
#include <emCore/emCheckBox.h>
#endif

#ifndef emListBox_h
#include <emCore/emListBox.h>
#endif

#ifndef emSigModel_h
#include <emCore/emSigModel.h>
#endif

#ifndef emTextField_h
#include <emCore/emTextField.h>
#endif


//==============================================================================
//============================= emFileSelectionBox =============================
//==============================================================================

class emFileSelectionBox : public emBorder {

public:

        // Class for a file selection box. This allows the user to walk through
        // the file system, view the contents of a directory, and select one or
        // optionally more files in it. He may also enter a new file name. The
        // file selection box is an emBorder whith the following child panels:
        //  - An editable text field for the path of the directory.
        //  - A check box for switching the showing of hidden files on and off.
        //  - A list box which shows the contents of the directory. It has one
        //    item for each file or sub-directory, and the item ".." for the
        //    parent directory. Each item shows the name and an icon (if it's a
        //    directory) or preview (if it's a file).
        //  - An editable text field for the file name.
        //  - A list box for file type filters.

        emFileSelectionBox(
                ParentArg parent, const emString & name,
                const emString & caption=emString(),
                const emString & description=emString(),
                const emImage & icon=emImage()
        );
                // 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.

        virtual ~emFileSelectionBox();
                // Destructor.

        bool IsMultiSelectionEnabled() const;
                // Ask whether the user is allowed to select multiple entries.

        void SetMultiSelectionEnabled(bool enabled=true);
                // Set whether the user is allowed to select multiple entries.
                // The default is false.

        const emString & GetParentDirectory() const;
                // Get the absolute path of the parent directory.

        void SetParentDirectory(const emString & parentDirectory);
                // Set the parent directory.

        emString GetSelectedName() const;
                // Get the name of the selected entry. If nothing is selected,
                // an empty string is returned. If multiple entries are
                // selected, the first one is returned.

        const emArray<emString> & GetSelectedNames() const;
                // Get an array with the names of all the selected entries.

        void SetSelectedName(const emString & selectedName);
                // Select a single entry. An empty string means to clear the
                // selection.

        void SetSelectedNames(const emArray<emString> & selectedNames);
                // Select any number of entries.

        void ClearSelection();
                // Clear the selection.

        emString GetSelectedPath() const;
                // Get the absolute path of the selected entry. If nothing is
                // selected, the absolute path of the parent directory is
                // returned. If multiple entries are selected, the absolute path
                // of the first selected one is returned.

        void SetSelectedPath(const emString & selectedPath);
                // Change the parent directory and the selection in one call. If
                // the given path is a directory, the parent directory is set to
                // that directory, and the selection is cleared. Otherwise the
                // parent directory is set to the parent path of the given path,
                // and the selection is set to the final name in the given path.

        const emSignal & GetSelectionSignal() const;
                // Signaled when the selection or the parent directory has
                // changed.

        const emArray<emString> & GetFilters() const;
                // Get the file type filters. See SetFilters for more.

        void SetFilters(const emArray<emString> & filters);
                // Set the file type filters, from which the user can choose
                // one. The default is to have no filters, which means to show
                // all entries. If no filter is selected at the time of calling
                // SetFilters with a non-empty array, the first filter is
                // selected automatically. Each filter is a string of the form:
                //   <description>'('<pattern>[<separator><pattern>...]')'
                // With:
                //   <description> = Any text.
                //   <pattern>     = A pattern for the file name. The only supported
                //                   meta character is an asterisk ('*'). It
                //                   means to match any sequence (including the
                //                   empty sequence). Everything else is taken
                //                   literal, but case-insensitive.
                //   <separator>   = A space char, a semicolon, a comma, or a
                //                   vertical bar.
                // Examples:
                //   "All files (*)"
                //   "Targa files (*.tga)"
                //   "HTML files (*.htm *.html)"

        int GetSelectedFilterIndex() const;
                // Get the index of the selected filter, or -1 if none is
                // selected.

        void SetSelectedFilterIndex(int selectedFilterIndex);
                // Set the index of the selected filter. -1 means to select none.

        bool AreHiddenFilesShown() const;
                // Ask whether hidden files are shown.

        void SetHiddenFilesShown(bool hiddenFilesShown=true);
                // Set whether hidden files are shown. The default is false.

        const emSignal & GetFileTriggerSignal() const;
                // This signal is signaled when the user double-clicks an item
                // or presses enter in the name field - but only if it is not a
                // directory. (If it is a directory, the parent directory is
                // changed instead of sending the signal). The associated name
                // can be get with GetTriggeredFileName().

        const emString & GetTriggeredFileName() const;
                // Get the name of the file which was triggered by a
                // double-click or enter key press.

        void TriggerFile(const emString & name);
                // Trigger a file name programmatically.

        void EnterSubDir(const emString & name);
                // Enter a sub-directory programmatically. This is like
                // SetParentDirectory, but the given string is the name of a
                // sub-directory instead of a path, and some checks are
                // performed whether it is really a directory and whether it is
                // readable.

        bool IsParentDirFieldHidden() const;
                // Ask whether the text field for the parent directory is
                // hidden.

        void SetParentDirFieldHidden(bool parentDirFieldHidden=true);
                // Set whether to hide the text field for the parent directory.
                // The default is false.

        bool IsHiddenCheckBoxHidden() const;
                // Ask whether the "show hidden files" check box is hidden.

        void SetHiddenCheckBoxHidden(bool hiddenCheckBoxHidden=true);
                // Set whether to hide the "show hidden files" check box. The
                // default is false.

        bool IsNameFieldHidden() const;
                // Ask whether the text field for the name is hidden.

        void SetNameFieldHidden(bool nameFieldHidden=true);
                // Set whether to hide the text field for the name. The default
                // is false.

        bool IsFilterHidden() const;
                // Ask whether the list box for the filters is hidden.

        void SetFilterHidden(bool filterHidden=true);
                // Set whether to hide the list box for the filters. The default
                // is false.

protected:

        virtual bool Cycle();
        virtual void Input(emInputEvent & event, const emInputState & state,
                           double mx, double my);
        virtual void AutoExpand();
        virtual void AutoShrink();
        virtual void LayoutChildren();

private:

        void InvalidateListing();
        void ReloadListing();
        void SelectionToListBox();
        void SelectionFromListBox();

        static int CompareNames(
                const emString * name1, const emString * name2, void * context
        );

        static bool MatchFileNameFilter(const char * fileName, const char * filter);
        static bool MatchFileNamePattern(const char * fileName, const char * pattern,
                                         const char * patternEnd);

        class FilesListBox;
        class FileItemPanel;
        class FileOverlayPanel;
        friend class FilesListBox;
        friend class FileItemPanel;

        class FilesListBox : public emListBox {
        public:
                FilesListBox(emFileSelectionBox & parent, const emString & name);
                virtual ~FilesListBox();
                emFileSelectionBox & GetFileSelectionBox() const;
                const emImage & GetDirImage() const;
                const emImage & GetDirUpImage() const;
        protected:
                virtual void CreateItemPanel(const emString & name, int itemIndex);
        };

        class FileItemPanel : public emPanel, public emListBox::ItemPanelInterface {
        public:
                FileItemPanel(FilesListBox & listBox, const emString & name, int itemIndex);
                virtual ~FileItemPanel();
        protected:
                virtual void Notice(NoticeFlags flags);
                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 AutoExpand();
                virtual void AutoShrink();
                virtual void LayoutChildren();
                virtual void ItemTextChanged();
                virtual void ItemDataChanged();
                virtual void ItemSelectionChanged();
        private:
                emColor GetBgColor() const;
                emColor GetFgColor() const;
                friend class FileOverlayPanel;
                FilesListBox & ListBox;
                emPanel * FilePanel;
                emPanel * OverlayPanel;
        };

        class FileOverlayPanel : public emPanel {
        public:
                FileOverlayPanel(FileItemPanel & parent, const emString & name);
                virtual ~FileOverlayPanel();
        protected:
                virtual void Input(emInputEvent & event, const emInputState & state,
                                   double mx, double my);
        };

        struct FileItemData {
                bool IsDirectory;
                bool IsReadable;
                bool IsHidden;
        };

        emRef<emSigModel> FileModelsUpdateSignalModel;

        bool MultiSelectionEnabled;
        emString ParentDir;
        emArray<emString> SelectedNames;
        emSignal SelectionSignal;
        emArray<emString> Filters;
        int SelectedFilterIndex;
        bool HiddenFilesShown;

        emSignal FileTriggerSignal;
        emString TriggeredFileName;

        bool ParentDirFieldHidden;
        bool HiddenCheckBoxHidden;
        bool NameFieldHidden;
        bool FilterHidden;

        emTextField * ParentDirField;
        emCheckBox * HiddenCheckBox;
        FilesListBox * FilesLB;
        emTextField * NameField;
        emListBox * FiltersLB;

        bool ListingInvalid;
};

inline bool emFileSelectionBox::IsMultiSelectionEnabled() const
{
        return MultiSelectionEnabled;
}

inline const emString & emFileSelectionBox::GetParentDirectory() const
{
        return ParentDir;
}

inline const emArray<emString> & emFileSelectionBox::GetSelectedNames() const
{
        return SelectedNames;
}

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

inline const emArray<emString> & emFileSelectionBox::GetFilters() const
{
        return Filters;
}

inline int emFileSelectionBox::GetSelectedFilterIndex() const
{
        return SelectedFilterIndex;
}

inline bool emFileSelectionBox::AreHiddenFilesShown() const
{
        return HiddenFilesShown;
}

inline const emSignal & emFileSelectionBox::GetFileTriggerSignal() const
{
        return FileTriggerSignal;
}

inline const emString & emFileSelectionBox::GetTriggeredFileName() const
{
        return TriggeredFileName;
}

inline bool emFileSelectionBox::IsParentDirFieldHidden() const
{
        return ParentDirFieldHidden;
}

inline bool emFileSelectionBox::IsHiddenCheckBoxHidden() const
{
        return HiddenCheckBoxHidden;
}

inline bool emFileSelectionBox::IsNameFieldHidden() const
{
        return NameFieldHidden;
}

inline bool emFileSelectionBox::IsFilterHidden() const
{
        return FilterHidden;
}

inline emFileSelectionBox & emFileSelectionBox::FilesListBox::GetFileSelectionBox() const
{
        return *((emFileSelectionBox*)GetParent());
}

inline const emImage & emFileSelectionBox::FilesListBox::GetDirImage() const
{
        return GetTkResources().ImgDir;
}

inline const emImage & emFileSelectionBox::FilesListBox::GetDirUpImage() const
{
        return GetTkResources().ImgDirUp;
}


#endif