/*
 *  Copyright (C)  2000-2001 Marc Wandschneider <mw@kiltdown.org>
 *
 *  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.
 *
 *  For more information look at the file COPYRIGHT in this package
 *
 */
#ifndef _KMAILLIST_H_
#define _KMAILLIST_H_

#include <sanity.h>

#include <qpixmap.h>
#include <qscrollbar.h>


#include <llist.h>


/**
 * this widget is similar to a QListView, but ends up requiring a few things
 * that the list view can't handle, such as pixmaps all over the place, and 
 * some items that have extra crap on the bottom (spanning all cells) and 
 * others that don't.
 *
 * Please note:  This widget is not intended to be terribly robust and handle
 * all unforseen cases -- I wrote it specifically for a given program, and 
 * as long as it does everything that program needs, that's cool.
 */

enum ColumnWidth {
    CW_FIXED = 0,
    CW_PERCENTAGE
};

enum ColumnAlignment {
    CA_LEFT = 0,
    CA_CENTRE,
    CA_RIGHT
};

enum ColumnSorting {
    CS_NONE = 0,
    CS_PRIMARY_UP = 1,
    CS_PRIMARY_DOWN = 2,
    CS_SECONDARY = 3
};

/** 
 * Forward decls
 */
class KMailList;
class KMailListItem;



/**
 * Pass an array of these to setColumns.  If the text is set, then that is 
 * used for the header, otherwise a pixmap is used.  If neither is set, the
 * column header is left blank.  The width of the column can be specified as
 * either fixed or a percentage.  If it's fixed, then when the control is 
 * resized, the column remains the same width.  However, percentage width
 * columns are resized as appropriate.  They use a percentage of all the
 * space that the fixed columns aren't using.
 */
struct KMailListHeader {

    const char *text;
    QPixmap *pixmap;
    int width;
    double pct;
    ColumnWidth type;
    ColumnSorting sort;
    unsigned int itemData;
    int (*sortfn)(int, unsigned int, KMailListItem *, KMailListItem *);
};


/**
 * You pass an array of these to addRow/updateRow.  It contains either the
 * text or the pixmap for the given column ...  The KMailList control will
 * free strings, but WILL NOT CLEAN UP PIXMAPS!!!!!!
 */
struct KMailListColumn {

    const char *text;
    QPixmap *pixmap;
    enum ColumnAlignment alignment;
};

/**
 * You pass in one of these to enter a row in the control.
 */
struct KMailListItem {
    struct KMailListColumn *columns;        // array of these, one per column.
    int ccolumns;
    bool boldEntry;                         // should all the text be bolded?
    unsigned int itemData;                  // whatever you want.
};

/**
 * This is for events that involve items.
 */
class KMailListEvent {

  public:
    /**
     * Returns the index of the item that triggered the event.  This is a zero
     * base index.
     */
    int index() { return itemIndex; }

    /**
     * Returns the number of items that were affected by this event.  This can
     * be greater than one when somebody goes and multi-selects a bunch of
     * objects, and then double clicks on one of them, or hits ENTER.
     */
    int itemCount() { return countOfItems; }

    /**
     * Returns an array of items that caused the event.  if itemCount() is 
     * greater than zero, and this is NULL, then E_OUTOFMEMORY;  CALLER frees
     * resulting array with localFree();
     */
    KMailListItem **items();



  private:

    /**
     * Only KMailList can create and destroy us!
     */
    friend class KMailList;
    KMailListEvent(int ind, int citems, KMailListItem **rgitems);
    ~KMailListEvent();

    int itemIndex;
    int countOfItems;
    KMailListItem **itemValues;
};

/**
 * This is the actual KMailList class ...
 */
class KMailList : public QWidget {
    
    /**
     * All MOC'able Qt Objects require this.
     */
    Q_OBJECT
    DEBUG_TEST_DECL;

  public:
    /**
     * you create a new one by specifying a parent and an optional name.
     */
    KMailList(QWidget *parent, const char *name = 0);

    /**
     * Adds the given row to the list.  The first two parameters specify the
     * number of columns being passed in and the data for those columns.  The
     * next two specify an optional item data and it's size.  This is merely
     * stored and nothing is done with it.
     *
     * The Maillist assumes control of the memory in the item (but not the
     * item itself).  The caller need not clean up any allocations made for
     * the columns.
     */
    ERRCODE addRow(KMailListItem *rowData);

    /** 
     * returns the item data for the selected rows in the database.  We don't
     * have need for a more general version that returns all the item data 
     * just yet.
     */
    ERRCODE getSelectedRows(LinkList<unsigned int> &);

    /**
     * Clears out the entire control, including the headers, contents,
     * etc.  control is left in empty state.
     */
    void nukeAll();

    /**
     * Removes the given rows from the mail list based on their item data. 
     * We don't currently have a need for another version based on any
     * other identifiers.
     */
    ERRCODE removeRows(int count, unsigned int *itemDataArray);

    /**
     * Sets the column headers for this control.  Duplicates of all the
     * data are maintained, so you should clean up whatever you have when
     * you're done with it.
     */
    ERRCODE setColumnHeaders(KMailListHeader *headers, int cheaders);

    /**
     * Given an identifier for a row, select it.
     */
    ERRCODE selectRowFromItemData(unsigned int);

    /**
     * Given some itemData to identify a row, go and find the row with that
     * itemData, and then go and update that row with the given information.
     * NOTE:  this assumes that itemData is unique.  I didn't design this
     * control to deal with the case where the itemData isn't something 
     * unique-like, such as a pointer, etc ...
     *
     * The Maillist assumes control of the memory in the item (but not the
     * item itself).  The caller need not clean up any allocations made for
     * the columns.
     */
    ERRCODE updateRowFromItemData(unsigned int, KMailListItem *rowData);

    /**
     * These two methods are used to iterate over items in the mail list.
     * you pass in some item Data, and the next/previous item is returned, or
     * NULL if the item is no longer in the list, or at the end/beginning.
     */
    KMailListItem *nextItem(unsigned int);
    KMailListItem *previousItem(unsigned int);

    /**
     * Little selection helpers that the folders might want to use sometime
     */
    void selectFirst();
    void selectLast();

    ~KMailList();



  signals:
    /**
     * Emitted whenever a user has changed the size or location of a header.
     */
    void columnHeadersChanged(int, KMailListHeader *);

    /**
     * The user has hit enter or double clicked on the given items(s).
     */
    void itemLaunched(KMailListEvent *);

    /**
     * The user has selected the given item.  It currently has the focus
     * selection rectangle on it.
     */
    void itemSelected(KMailListEvent *);

    /**
     * The user has pressed a key -- if we don't use it, then we'll pass it
     * on to whomever is listening.
     */
    void keyPressed(QKeyEvent *);

    /**
     * The user has right clicked somewhere in the control.
     */
    void rightButtonPressed(QMouseEvent *);

    /**
     * The user is about to start dragging something.  fweee!
     */
    void dragItemStarted();



  protected:
    /**
     * Event routines.
     */
    virtual void paintEvent(QPaintEvent *);
    virtual void wheelEvent(QWheelEvent *);
    virtual void resizeEvent(QResizeEvent *);
    virtual void focusInEvent(QFocusEvent *);
    virtual void keyPressEvent(QKeyEvent * e);
    virtual void focusOutEvent(QFocusEvent *);
    virtual void mouseMoveEvent(QMouseEvent *);
    virtual void mousePressEvent(QMouseEvent *);
    virtual void mouseReleaseEvent(QMouseEvent *);
    virtual void mouseDoubleClickEvent(QMouseEvent *);



  private slots:
    void scrollValueChanged(int);



  private:

    enum KMouseMode {
        MMNormal = 0,
        MMColumnResize,
        MMColumnDrag
    };

    /**
     * invalidates our buffer and forces an update.
     */
    void forceUpdate() {
        mustUpdateBuffer = true;
        this->repaint();
    }

    /**
     * We need to keep a little extra information about the column around.
     */
    struct KInternalHeader : public KMailListHeader {
        int widthCache;
        int stringCache;
        int pressedX;
        bool pressed;
    };

    /**
     * Our internal list will be a doubly linked list of these fellows.
     * This will make things a bit complicated, but more efficient than
     * using an array, unfortunately.
     */
    struct KInternalItem : public KMailListItem {
        bool selected;
        KInternalItem *next;
        KInternalItem *prev;
    };

    /**
     * Headers.
     */
    KInternalHeader *headers;
    int cheaders;

    /**
     * Rows.
     */
    KInternalItem *front, *end;
    int viewableRows;
    int rowHeight;
    int crowTop;
    int crows;
    int secondarySort;
    int primarySort;
    bool primaryUp;

    /**
     * Mouse control and tracking.
     */
    enum KMouseMode mouseMode;
    int activeColumn, resizeX;

    /**
     * Selection management.
     */
    int focusSelect;
    int shiftAnchor;
    bool rememberPress;

    /**
     * these are for drawing and updating.
     */
    bool mustUpdateBuffer;
    QPixmap *_doubleBuffer;
    QBrush *_backgroundBrush;

    /**
     * This is for handling our scrollbar.
     */
    QScrollBar *scrollBar;


    /**
     +=---------------------------------------------------------------------=
     * Private routines.
     +=---------------------------------------------------------------------=
     */

    /**
     * signals
     */
    ERRCODE internalGenerateEventInfo(KMailListItem ** &, int &);
    void emitRightButtonPressed(QMouseEvent *);
    void emitKeyPressed(QKeyEvent *);
    void emitDragItemStarted();
    void emitColumnsChanged();
    void emitItemLaunched();
    void emitItemSelected();

    /**
     * Drawing
     */
    void drawHeadersInBuffer(QPainter &, QColorGroup &);
    void drawRowsInBuffer(QPainter &, QColorGroup &);
    void drawContentsInBuffer();
    int  drawableWidth();

    /**
     * Mouse input
     */
    void rightButtonPress(QMouseEvent *);
    void leftButtonPress(QMouseEvent *);

    /**
     * Sizing/resizing
     */
    void adjustScrollBar(int newHeight = -1);
    void resizeColumnHeader(int, int);
    int  getMinRightWidth(int);
    int  getFixedWidth();
    int  getRowHeight();
    int  getColumnLeft(int);

    /**
     * Item creation
     */
    ERRCODE copyColumnInfo(KMailListColumn *, KMailListColumn **);
    ERRCODE addRowInternal(KInternalItem *item);

    /**
     * Selection management.
     */
    KInternalItem *itemAtIndex(int);
    void previousElement(ButtonState);
    void nextElement(ButtonState);
    void columnSortingChanged();
    int  getSelectedCount();

    /**
     * keyboard input
     */
    void pageDown(ButtonState);
    void homeKey(ButtonState);
    void pageUp(ButtonState);
    void endKey(ButtonState);

    /**
     * Scroll Bar goo
     */
    QScrollBar *getScrollBar();
    void setScrollValue(int);


    /**
     * Clean up
     */
    void clearColumnInfo(KMailListColumn *);
    void cleanUpHeaders();
    void cleanUpRows();



};


#endif // _KMAILLIST_H_




syntax highlighted by Code2HTML, v. 0.9.1