/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-6 by Raw Material Software ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified 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.

   JUCE 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 JUCE; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
   Boston, MA 02111-1307 USA

  ------------------------------------------------------------------------------

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

  ==============================================================================
*/

#ifndef __JUCE_LISTBOX_JUCEHEADER__
#define __JUCE_LISTBOX_JUCEHEADER__

#include "../layout/juce_Viewport.h"
#include "../../../../juce_core/containers/juce_SparseSet.h"
class ListViewport;


//==============================================================================
/**
    A list of items that can be scrolled vertically.

    Listbox subclasses must override a set of methods to create and update the
    subcomponents that represent each row of the list.

    For most lists it's easier to use the SimpleListBox class instead, which
    allows the class to draw items in the list without having to define component
    classes to represent the items, but if each row requires subcomponents or more
    complex user interaction, the ListBox class lets you do this.

    @see SimpleListBox, ComboBox
*/
class JUCE_API  ListBox  : public Component
{
public:
    //==============================================================================
    /** Creates a ListBox.
    */
    ListBox (const String& componentName);

    /** Destructor. */
    ~ListBox();

    //==============================================================================
    /** Causes the list to refresh its content.

        Call this when the number of rows in the list changes, or if you want it
        to call updateRowComponent() on all the row components.

        Be careful not to call it from a different thread, though, as it's not
        thread-safe.
    */
    void updateContent();

    //==============================================================================
    /** Turns on multiple-selection of rows.

        By default this is disabled.

        When your row component gets clicked you'll need to call the
        selectRowsBasedOnModifierKeys() method to tell the list that it's been
        clicked and to get it to do the appropriate selection based on whether
        the ctrl/shift keys are held down.
    */
    void setMultipleSelectionEnabled (bool shouldBeEnabled);

    /** Makes the list react to mouse moves by selecting the row that the mouse if over.

        This function is here primarily for the ComboBox class to use, but might be
        useful for some other purpose too.
    */
    void setMouseMoveSelectsRows (bool shouldSelect);

    //==============================================================================
    /** Selects a row.

        If the row is already selected, this won't do anything.

        @param rowNumber                the row to select
        @param dontScrollToShowThisRow  if true, the list's position won't change; if false and
                                        the selected row is off-screen, it'll scroll to make
                                        sure that row is on-screen
        @param deselectOthersFirst      if true and there are multiple selections, these will
                                        first be deselected before this item is selected
        @see isRowSelected, selectRowsBasedOnModifierKeys, flipRowSelection, deselectRow,
             deselectAllRows, selectRangeOfRows
    */
    void selectRow (const int rowNumber,
                    bool dontScrollToShowThisRow = false,
                    bool deselectOthersFirst = true);

    /** Selects a set of rows.

        This will add these rows to the current selection, so you might need to
        clear the current selection first with deselectAllRows()

        @param firstRow     the first row to select (inclusive)
        @param lastRow      the last row to select (inclusive)
    */
    void selectRangeOfRows (int firstRow,
                            int lastRow);

    /** Deselects a row.

        If it's not currently selected, this will do nothing.

        @see selectRow, deselectAllRows
    */
    void deselectRow (const int rowNumber);

    /** Deselects any currently selected rows.

        @see deselectRow
    */
    void deselectAllRows();

    /** Selects or deselects a row.

        If the row's currently selected, this deselects it, and vice-versa.
    */
    void flipRowSelection (const int rowNumber);

    /** Returns a sparse set indicating the rows that are currently selected.

        @see setSelectedRows
    */
    const SparseSet<int> getSelectedRows() const;

    /** Sets the rows that should be selected, based on an explicit set of ranges.

        @see getSelectedRows
    */
    void setSelectedRows (const SparseSet<int>& setOfRowsToBeSelected);

    /** Checks whether a row is selected.
    */
    bool isRowSelected (const int rowNumber) const;

    /** Returns the number of rows that are currently selected.

        @see getSelectedRow, isRowSelected, getLastRowSelected
    */
    int getNumSelectedRows() const;

    /** Returns the row number of a selected row.

        This will return the row number of the Nth selected row. The row numbers returned will
        be sorted in order from low to high.

        @param index    the index of the selected row to return, (from 0 to getNumSelectedRows() - 1)
        @returns        the row number, or -1 if the index was out of range or if there aren't any rows
                        selected
        @see getNumSelectedRows, isRowSelected, getLastRowSelected
    */
    int getSelectedRow (const int index = 0) const;

    /** Returns the last row that the user selected.

        This isn't the same as the highest row number that is currently selected - if the user
        had multiply-selected rows 10, 5 and then 6 in that order, this would return 6.

        If nothing is selected, it will return -1.
    */
    int getLastRowSelected() const;

    /** Multiply-selects rows based on the modifier keys.

        If no modifier keys are down, this will select the given row and
        deselect any others.

        If the ctrl (or command on the Mac) key is down, it'll flip the
        state of the selected row.

        If the shift key is down, it'll select up to the given row from the
        last row selected.

        @see selectRow
    */
    void selectRowsBasedOnModifierKeys (const int rowThatWasClickedOn,
                                        const ModifierKeys& modifiers);

    //==============================================================================
    /** Scrolls the list to a particular position.

        The proportion is between 0 and 1.0, so 0 scrolls to the top of the list,
        1.0 scrolls to the bottom.

        If the total number of rows all fit onto the screen at once, then this
        method won't do anything.
    */
    void setVerticalPosition (double proportion);

    /** Scrolls if necessary to make sure that a particular row is visible.
    */
    void scrollToEnsureRowIsOnscreen (const int row);

    /** Returns a pointer to the scrollbar.

        (Unlikely to be useful for most people).
    */
    ScrollBar* getVerticalScrollBar() const;

    /** Finds the row index that contains a given x,y position.

        The position is relative to the ListBox's top-left.

        If no row exists at this position, the method will return -1.

        @see getComponentForRowNumber
    */
    int getRowContainingPosition (const int x, const int y);

    /** Finds the row component for a given row in the list.

        The component returned will have been created using createRowComponent().

        If the component for this row is off-screen or if the row is out-of-range,
        this will return 0.

        @see getRowContainingPosition
    */
    Component* getComponentForRowNumber (const int rowNumber) const;

    /** Returns the width of a row (which may be less than the width of this component
        if there's a scrollbar).
    */
    int getVisibleRowWidth() const;

    //==============================================================================
    /** Sets the height of each row in the list.

        The default height is 22 pixels.

        @see getRowHeight
    */
    void setRowHeight (const int newHeight);

    /** Returns the height of a row in the list.

        @see setRowHeight
    */
    int getRowHeight() const throw()   { return rowHeight; }

    /** Returns the number of rows actually visible.

        This is the number of whole rows which will fit on-screen, so the value might
        be more than the actual number of rows in the list.
    */
    int getNumRowsOnScreen() const throw();

    /** Changes the background colour to paint the listbox with.

        You can set this to a transparent colour if you need to.
    */
    void setBackgroundColour (const Colour& newBackgroundColour);

    /** Sets a colour and thickness to use to draw an outline around the box.

        By default no outline is drawn, but you can change this if you like.

        @see getOutlineThickness
    */
    void setOutlineColour (const Colour& outlineColour,
                           const int outlineThickness);

    /** Returns the thickness of outline that will be drawn around the listbox.

        @see setOutlineColour
    */
    int getOutlineThickness() const throw()    { return outlineThickness; }

    /** Sets a component that the list should use as a header.

        This will position the given component at the top of the list, maintaining the
        height of the component passed-in, but rescaling it horizontally to match the
        width of the items in the listbox.

        The component will be deleted when setHeaderComponent() is called with a
        different component, or when the listbox is deleted.
    */
    void setHeaderComponent (Component* const newHeaderComponent);

    /** Changes the width of the rows in the list.

        This can be used to make the list's row components wider than the list itself - the
        width of the rows will be either the width of the list or this value, whichever is
        greater, and if the rows become wider than the list, a horizontal scrollbar will
        appear.

        The default value for this is 0, which means that the rows will always
        be the same width as the list.
    */
    void setMinimumContentWidth (const int newMinimumWidth);


    /** This fairly obscure method creates an image that just shows the currently
        selected row components.

        It's a handy method for doing drag-and-drop, as it can be passed to the
        DragAndDropContainer for use as the drag image.

        Note that it will make the row components temporarily invisible, so if you're
        using custom components this could affect them if they're sensitive to that
        sort of thing.

        @see Component::createComponentSnapshot
    */
    Image* createSnapshotOfSelectedRows();

    //==============================================================================
    /** Returns the number of items in the list.

        This method must return the total number of rows that the list contains - if the
        value that is returned here changes (when whatever data the list represents changes),
        then you should call the updateContent() method to refresh the list.
    */
    virtual int getNumRows() = 0;

protected:
    /** Creates a component suitable to represent one of the rows in the list.

        A subclass must implement this method to return a component that can be
        added to the list to represent one of the rows.

        After being created, the listbox will use the updateRowComponent() method
        to tell it which row it represents, and will resize and reposition it appropriately.
        Note that these components will be reused and may be assigned to many different
        row numbers during their lifetime.

        When no longer needed, the component returned will be deleted by the listbox.

        This method mustn't return a null pointer!
    */
    virtual Component* createRowComponent() = 0;

    /** Updates the state of one of the row components.

        Each row in the list is represented by a component created by the createRowComponent()
        method. When this component needs to be reassigned to a different row number or
        when it is selected/deselected, this method is used to update it.

        The subclass's implementation will depend on the type of component that it's using
        for the rows - a typical routine might look like this: @code
        void updateRowComponent (Component* rowComponent,
                                 int rowNumber,
                                 bool isSelected)
        {
            MyRowComponentType* myRowComp = (MyRowComponentType*) rowComponent;

            myRowComp->setCurrentRowNumber (rowNumber);
            myRowComp->setHighlighted (isSelected);
            myRowComp->repaint();
        }
        @endcode

        @param rowComponent     the component, as previously created by createRowComponent. This
                                pointer will not be null
        @param rowNumber        the new row number that this component now represents
        @param isSelected       whether or not the row is now selected
    */
    virtual void updateRowComponent (Component* rowComponent,
                                     int rowNumber,
                                     bool isSelected) = 0;

    /** Overridden by a subclass to receive notification of changes to the selected row or rows.

        This will be called whenever a row is selected or deselected. If a range of
        rows is selected all at once, this will just be called once for that event.

        @param lastRowSelected      the last row that the user selected. If no
                                    rows are currently selected, this may be -1.
    */
    virtual void selectedRowsChanged (int lastRowSelected);

    /** Overridden by a subclass to be told when the user presses the delete or backspace keys.

        If no rows are selected when they press the key, this won't be called.

        @param currentSelectedRow   the last row that had been selected when they pressed the
                                    key - if there are multiple selections, this might not be
                                    very useful
    */
    virtual void deleteKeyPressed (int currentSelectedRow);

    /** Overridden by a subclass to be told when the user presses the return key.

        If no rows are selected when they press the key, this won't be called.

        @param currentSelectedRow   the last row that had been selected when they pressed the
                                    key - if there are multiple selections, this might not be
                                    very useful
    */
    virtual void returnKeyPressed (int currentSelectedRow);

    /** Overridden by a subclass to be told when the user clicks on a bit of the list's background.

        This is called if the user clicks somewhere on the list that doesn't contain a row.
    */
    virtual void backgroundClicked();

    /** Allows subclasses to find out when the list is scrolled up or down.

        This might be caused by the user moving the scrollbar, or by programmatic changes
        to the list position.
    */
    virtual void listWasScrolled();

public:
    //==============================================================================
    /** @internal */
    void keyPressed (const KeyPress& key);
    /** @internal */
    void keyStateChanged();
    /** @internal */
    void paint (Graphics& g);
    /** @internal */
    void resized();
    /** @internal */
    void mouseWheelMove (const MouseEvent& e, float wheelIncrement);
    /** @internal */
    void mouseMove (const MouseEvent& e);
    /** @internal */
    void mouseExit (const MouseEvent& e);

    juce_UseDebuggingNewOperator

private:
    //==============================================================================
    friend class ListViewport;
    ListViewport* viewport;
    Component* headerComponent;
    int totalItems, rowHeight, minimumRowWidth;
    int scrollBarSize, outlineThickness;
    int lastMouseX, lastMouseY, lastRowSelected;
    bool mouseMoveSelects, multipleSelection, hasDoneInitialUpdate;
    SparseSet <int> selected;
    Colour backgroundColour, outlineColour;

    void selectRowInternal (const int rowNumber,
                            bool dontScrollToShowThisRow,
                            bool deselectOthersFirst,
                            bool isMouseClick);

    ListBox (const ListBox&);
    const ListBox& operator= (const ListBox&);
};


#endif   // __JUCE_LISTBOX_JUCEHEADER__
