/* This file is part of the KDE libraries
   Copyright (C) 1999 Daniel M. Duley <mosfet@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/
#ifndef __KTHEMEBASE_H
#define __KTHEMEBASE_H

#include <qdatetime.h>
#include <qtimer.h>
#include <kstyle.h>
#include <kpixmap.h>
#include <kconfig.h>
#include <qimage.h>
#include <qintcache.h>
#include <qstring.h>

#define WIDGETS 54

/**
 * This class adds simple time management to KPixmap for use in flushing
 * KThemeCache.
 *
 * @author Daniel M. Duley <mosfet@kde.org>
 */
class KThemePixmap : public KPixmap
{
public:
    enum BorderType{Top=0, Bottom, Left, Right, TopLeft, TopRight, BottomLeft,
    BottomRight};

    KThemePixmap(bool timer = true);
    KThemePixmap(const KThemePixmap &p);
    ~KThemePixmap();
    QPixmap* border(BorderType type);
    void setBorder(BorderType type, const QPixmap &p);
    void updateAccessed();
    bool isOld();
protected:
    QTime *t;
    QPixmap *b[8];
    
private:
    class KThemePixmapPrivate;
    KThemePixmapPrivate *d;
};

inline QPixmap* KThemePixmap::border(BorderType type)
{
    return(b[type]);
}

inline void KThemePixmap::setBorder(BorderType type, const QPixmap &p)
{
    if(b[type]){
        qWarning("KThemePixmap: Overwriting existing border!");
        delete(b[type]);
    }
    b[type] = new QPixmap(p);
}

inline void KThemePixmap::updateAccessed()
{
    if (t)
        t->start();
}

inline bool KThemePixmap::isOld()
{
    return(t ? t->elapsed() >= 300000 : false);
}

/**
 * A very simple pixmap cache for theme plugins. QPixmapCache is not used
 * since it uses QString keys which are not needed. All the information we
 * need can be encoded in a numeric key. Using QIntCache instead allows us to
 * skip the string operations.
 *
 * This class is mostly just inline methods that do bit operations on a key
 * composed of the widget ID, width and/or height, and then calls
 * QIntCache::find().
 *
 * One other thing to note is that full, horizontal, and vertically scaled
 * pixmaps are not used interchangeably. For example, if you insert a fully
 * scaled pixmap that is 32x32 then request a horizontally scaled pixmap with
 * a width of 32, they will not match. This is because a pixmap that has been
 * inserted into the cache has already been scaled at some point and it is
 * very likely the vertical height was not originally 32. Thus the pixmap
 * will be wrong when drawn, even though the horizontal width matches.
 *
 * @author Daniel M. Duley <mosfet@kde.org>
 *
 */
class KThemeCache : public QObject
{
    Q_OBJECT
public:
    /**
     * The scale hints supported by the cache. Note that Tiled is not here
     * since tiled pixmaps are kept only once in KThemeBase.
     */
    enum ScaleHint{FullScale, HorizontalScale, VerticalScale};
    /**
     * The constructor.
     *
     * @param maxSize The maximum size of the cache in kilobytes.
     */
    KThemeCache(int maxSize, QObject *parent=0, const char *name=0);
    /**
     * Inserts a new pixmap into the cache.
     *
     * @param pixmap The pixmap to insert.
     * @param scale The scaling type of the pixmap.
     * @param widgetID The widget ID of the pixmap, usually from KThemeBase's
     * WidgetType enum.
     *
     * @return True if the insert was successful, false otherwise.
     */
    bool insert(KThemePixmap *pixmap, ScaleHint scale, int widgetID,
               bool border=false, bool mask=false);
    /**
     * Returns a fully scaled pixmap.
     *
     * @param w The pixmap width to search for.
     * @param h The pixmap height to search for.
     * @param widgetID The widget ID to search for.
     *
     * @return True if a pixmap matching the width, height, and widget ID of
     * the pixmap exists, NULL otherwise.
     */
    KThemePixmap* pixmap(int w, int h, int widgetID, bool border=false,
                         bool mask=false);
    /**
     * Returns a horizontally scaled pixmap.
     *
     * @param w The pixmap width to search for.
     * @param widgetID The widget ID to search for.
     *
     * @return True if a pixmap matching the width and widget ID of
     * the pixmap exists, NULL otherwise.
     */
    KThemePixmap* horizontalPixmap(int w, int widgetID);
    /**
     * Returns a vertically scaled pixmap.
     *
     * @param h The pixmap height to search for.
     * @param widgetID The widget ID to search for.
     *
     * @return True if a pixmap matching the height and widget ID of
     * the pixmap exists, NULL otherwise.
     */
    KThemePixmap* verticalPixmap(int h, int widgetID);
protected slots:
    void flushTimeout();
protected:
    QIntCache<KThemePixmap> cache;
    QTimer flushTimer;
    
private:
    class KThemeCachePrivate;
    KThemeCachePrivate *d;
};


/**
 * This is a base class for KDE themed styles. It implements a cache,
 * configuration file parsing, pixmap scaling, gradients, and a lot
 * of inline methods for accessing user specified parameters.
 *
 * Note that this class *does not* actually implement any themes. It just
 * provides the groundwork for doing so. The only reason to use this class
 * directly is if you plan to reimplement all of the widgets. Otherwise,
 * refer to KThemeStyle for a fully themed style you can derive from.
 *
 * @author Daniel M. Duley <mosfet@kde.org>
 */
class KThemeBase: public KStyle
{
    Q_OBJECT
public:
    /**
     * Constructs a new KThemeBase object.
     */
    KThemeBase(const QString &configFile);
    ~KThemeBase();
    /**
     * Describes if a pixmap should be scaled fully, horizontally, vertically,
     * or not at all and tiled.
     */
    enum ScaleHint{FullScale, HorizontalScale, VerticalScale, TileScale};
    /**
     * The default arrow types.
     */
    enum ArrowStyle{MotifArrow, LargeArrow, SmallArrow};
    /**
     * The default frame shading styles.
     */
    enum ShadeStyle{Motif, Windows, Next, KDE};
    /**
     * The default scrollbar button layout. BottomLeft is like what Next
     * uses, BottomRight is like Platinum, and Opposite it like Windows and
     * Motif.
     */
     enum SButton{SBBottomLeft, SBBottomRight, SBOpposite};
    /**
     * The gradient types. Horizontal is left to right, Vertical is top to
     * bottom, and diagonal is upper-left to bottom-right.
     */
     enum Gradient{GrNone, GrHorizontal, GrVertical, GrDiagonal, GrPyramid,
     GrRectangle, GrElliptic, GrReverseBevel};
    /**
     * This provides a list of widget types that KThemeBase recognizes.
     */
    /* Internal note: The order here is important. Some widgets inherit
     * properties. This is usually for when you have two settings for the
     * same widget, ie: on(sunken), and off. The on settings will inherit
     * the properties of the off one when nothing is specified in the config.
     *
     * In order to be able to handle this while still having everything in
     * one group that is easy to loop from we have the following order:
     * unsunked(off) items, sunken(on)items, and then the ones that don't
     * matter. INHERIT_ITEMS define the number of widgets that have inheritence
     * so if 0 == PushButtonOff then INHERIT_ITEMS should == PushButtonOn
     * and so on. WIDGETS define the total number of widgets.
     */
     enum WidgetType{
         // Off (unsunken widgets)
         PushButton=0, ComboBox, HScrollBarSlider, VScrollBarSlider, Bevel,
         ToolButton, ScrollButton, HScrollDeco, VScrollDeco,
         ComboDeco, MenuItem, InactiveTab, ArrowUp, ArrowDown, ArrowLeft,
         ArrowRight,
         // On (sunken widgets)
         PushButtonDown, ComboBoxDown, HScrollBarSliderDown,
         VScrollBarSliderDown, BevelDown, ToolButtonDown, ScrollButtonDown,
         HScrollDecoDown, VScrollDecoDown, ComboDecoDown, MenuItemDown,
         ActiveTab, SunkenArrowUp, SunkenArrowDown, SunkenArrowLeft,
         SunkenArrowRight,
         // Everything else (indicators must have separate settings)
         HScrollGroove, VScrollGroove, Slider, SliderGroove, IndicatorOn,
         IndicatorOff, ExIndicatorOn, ExIndicatorOff, HBarHandle, VBarHandle,
         ToolBar, Splitter, CheckMark, MenuBar, DisArrowUp, DisArrowDown,
         DisArrowLeft, DisArrowRight, ProgressBar, ProgressBg, MenuBarItem,
         Background};

    /**
     * The scaling type specified by the KConfig file.
     *
     * @param widget A Widgets enum value.
     *
     * @return A ScaleHint enum value.
     */
    ScaleHint scaleHint(WidgetType widget) const;
    /**
     * The gradient type specified by the KConfig file.
     *
     * @param widget A Widgets enum value.
     *
     * @return A Gradient enum value.
     */
    Gradient gradientHint(WidgetType widget) const;
    /**
     * The color group specified for a given widget.
     * If a color group is set in the theme configuration
     * that is used, otherwise defaultColor is returned.
     *
     * @param defaultColor The colorGroup to set if one is available.
     *
     * @param widget The widget whose color group to retrieve.
     *
     */
    const QColorGroup* colorGroup(const QColorGroup &defaultGroup,
                            WidgetType widget) const;

    QBrush pixmapBrush(const QColorGroup &group, QColorGroup::ColorRole role,
                       int w, int h, WidgetType widget);
    /**
     * True if the widget has a pixmap or gradient specified.
     */
    bool isPixmap(WidgetType widget) const;
    /**
     * True if the widget has a color group specified.
     */
    bool isColor(WidgetType widget) const;
    /**
     * True if the user specified a 3D focus rectangle
     */
    bool is3DFocus() const;
    /**
     * If the user specified a 3D focus rectangle, they may also specify an
     * offset from the default rectangle to use when drawing it. This returns
     * the specified offset.
     */
    int focusOffset() const;
    /**
     * The border width of the specified widget.
     */
    int borderWidth(WidgetType widget) const;
    /**
     * Pixmap border width of the specified widget.
     */
    int pixBorderWidth(WidgetType widget) const;
    /**
     * Returns the border pixmap if enabled for the specified widget. This
     * will contain the originial pixmap, plus the edges separated in
     * KThemePixmap::border() if valid. If invalid it will return NULL.
     */
    KThemePixmap* borderPixmap(WidgetType widget);
    /**
     * The highlight width of the specified widget.
     */
    int highlightWidth(WidgetType widget) const;
    /**
     * The border plus highlight width of the widget.
     */
    int decoWidth(WidgetType widget) const;
    /**
     * The extent (width for vertical, height for horizontal) requested
     * for the scrollbars.
     */
    int getSBExtent() const;
    /**
     * The scrollbar button layout.
     */
    SButton scrollBarLayout() const;
    /**
     * The arrow type.
     */
    ArrowStyle arrowType() const;
    /**
     * The shading type.
     */
    ShadeStyle shade() const;
    /**
     * The frame width.
     */
    int frameWidth() const;
    /**
     * The splitter width.
     */
    int splitWidth() const;
    /**
     * The contrast for some bevel effects such as reverse gradient.
     */
    int bevelContrast(WidgetType widget) const;
    /**
     * The button text X shift.
     */
    int buttonXShift() const;
    /**
     * The button text Y shift.
     */
    int buttonYShift() const;
    /**
     * Returns either the slider length of the slider pixmap if available,
     * otherwise the length specified in the config file.
     */
    int sliderButtonLength() const;
    /**
     * True if rounded buttons are requested.
     */
    bool roundButton() const;
    /**
     * True if rounded comboboxes are requested.
     */
    bool roundComboBox() const;
    /**
     * True if rounded slider grooves are requested.
     */
    bool roundSlider() const;
    /**
     * True if a line should be drawn on the bottom of active tabs.
     */
    bool activeTabLine() const;
    /**
     * True if a line should be drawn on the bottom of inactive tabs.
     */
    bool inactiveTabLine() const;
    /**
     * Returns the current uncached pixmap for the given widget. This will
     * usually be either the last scaled or gradient pixmap if those have
     * been specified in the config file, the original pixmap if not, or NULL
     * if no pixmap has been specified.
     */
    KThemePixmap* uncached(WidgetType widget) const;
    /**
     * Returns the pixmap for the given widget at the specified width and
     * height. This will return NULL if no pixmap or gradient is specified.
     * It may also return a different sized pixmap if the scaling
     * is set to Tiled. When using this method, you should call it using
     * the needed width and height then use QPainter::drawTiledPixmap to
     * paint it. Doing this, if the pixmap is scaled it will be the proper
     * size, otherwise it will be tiled.
     *
     * @param w Requested width.
     * @param h Requested height.
     * @param widget Widget type.
     * @return The pixmap or NULL if one is not specified.
     */
    virtual KThemePixmap *scalePixmap(int w, int h, WidgetType widget);
    /**
     * This method reads a configuration file and applies it to the user's
     * kstylerc file. It does not signal applications to reload via the
     * KDEChangeGeneral atom, if you want to do this you must do so yourself.
     * See kcmdisplay's general.cpp for an example.
     *
     * @param file The configuration file to apply.
     */
    static void applyConfigFile(const QString &file);
protected:
    /**
     * Returns a QImage for the given widget if the widget is scaled, NULL
     * otherwise. QImages of the original pixmap are stored for scaled
     * widgets in order to facilitate fast and accurate smooth-scaling. This
     * also saves us a conversion from a pixmap to an image then back again.
     */
    QImage* image(WidgetType widget) const;
    /**
     * Returns the gradient high color if one is specified, NULL otherwise.
     */
    QColor* gradientHigh(WidgetType widget) const;
    /**
     * Returns the gradient low color if one is specified, NULL otherwise.
     */
    QColor* gradientLow(WidgetType widget) const;
    /**
     * Reads in all the configuration file entries supported.
     *
     * @param colorStyle The style for the color groups. In KDE, colors were
     * calculated a little differently for Motif vs Windows styles. This
     * is obsolete.
     */
    void readConfig(Qt::GUIStyle colorStyle = Qt::WindowsStyle);
    void readWidgetConfig(int i, KConfig *config, QString *pixnames,
                          QString *brdnames, bool *loadArray);
    void copyWidgetConfig(int sourceID, int destID, QString *pixnames,
                         QString *brdnames);
    /**
     * Makes a full color group based on the given foreground and background
     * colors. This is the same code used by KDE (kapp.cpp) in previous
     * versions.
     */
    QColorGroup* makeColorGroup(QColor &fg, QColor &bg,
                                Qt::GUIStyle style = Qt::WindowsStyle);
    KThemePixmap* scale(int w, int h, WidgetType widget);
    KThemePixmap* scaleBorder(int w, int h, WidgetType type);
    KThemePixmap* gradient(int w, int h, WidgetType widget);
    KThemePixmap* blend(WidgetType widget);
    void generateBorderPix(int i);
    void applyResourceGroup(KConfig *config, int i);
    void applyMiscResourceGroup(KConfig *config);
    void readResourceGroup(int i, QString *pixnames, QString *brdnames,
                          bool *loadArray);
    void readMiscResourceGroup();
    /**
     * Attempts to load a pixmap from the default KThemeBase locations.
     */
    KThemePixmap* loadPixmap(QString &name);
    /**
     * Attempts to load a image from the default KThemeBase locations.
     */
    QImage* loadImage(QString &name);
private:
    SButton sbPlacement;
    ArrowStyle arrowStyle;
    ShadeStyle shading;
    int defaultFrame;
    int btnXShift, btnYShift;
    int sliderLen;
    int splitterWidth;
    int focus3DOffset;
    int sbExtent;
    bool smallGroove;
    bool roundedButton, roundedCombo, roundedSlider;
    bool aTabLine, iTabLine;
    bool focus3D;
    KThemeCache *cache;
    int cacheSize;
    QString configFileName;

    /**
     * The theme pixmaps. Many of these may be NULL if no pixmap is specified.
     * There may also be duplicate pixmap pointers if more than one widget
     * uses the same tiled pixmap. If a pixmap is tiled, it is kept here and
     * this acts as a cache. Otherwise this will hold whatever the last scaled
     * pixmap was.
     */
    KThemePixmap *pixmaps[WIDGETS];
    /**
     * The theme images. These are for scaled images and are kept in order
     * to maintain fast smoothscaling.
     */
    QImage *images[WIDGETS];
    /**
     * The border widths
     */
    unsigned char borders[WIDGETS];
    /**
     * The highlight widths
     */
    unsigned char highlights[WIDGETS];
    /**
     * The scale hints for pixmaps and gradients.
     */
    ScaleHint scaleHints[WIDGETS];
    /**
     * All the color groups.
     */
    QColorGroup *colors[WIDGETS];
    /**
     * Gradient low colors (or blend background).
     */
    QColor *grLowColors[WIDGETS];
    /**
     * Gradient high colors.
     */
    QColor *grHighColors[WIDGETS];
    /**
     * Gradient types.
     */
    Gradient gradients[WIDGETS];
    /**
     * Blend intensity factors
     */
    float blends[WIDGETS];
    /**
     * Bevel contrasts
     */
    unsigned char bContrasts[WIDGETS];
    /**
     * Duplicate pixmap entries (used during destruction).
     */
    bool duplicate[WIDGETS];
    /**
     * Pixmapped border widths
     */
    int pbWidth[WIDGETS];
    /**
     * Pixmapped borders
     */
    KThemePixmap *pbPixmaps[WIDGETS];
    /**
     * Duplicate border pixmapped border entries
     */
    bool pbDuplicate[WIDGETS];

private:
    class KThemeBasePrivate;
    KThemeBasePrivate *d;
    
};

inline bool KThemeBase::isPixmap( WidgetType widget) const
{
    return(pixmaps[widget] != NULL || gradients[widget] != GrNone);
}

inline bool KThemeBase::isColor(WidgetType widget) const
{
    return(colors[widget] != NULL);
}

inline bool KThemeBase::is3DFocus() const
{
    return(focus3D);
}

inline int KThemeBase::focusOffset() const
{
    return(focus3DOffset);
}

inline int KThemeBase::bevelContrast(WidgetType widget) const
{
    return(bContrasts[widget]);
}

inline KThemeBase::ScaleHint KThemeBase::scaleHint(WidgetType widget) const
{
    return((widget < WIDGETS) ? scaleHints[widget] : TileScale);
}

inline KThemeBase::Gradient KThemeBase::gradientHint(WidgetType widget) const
{
    return((widget < WIDGETS) ? gradients[widget] : GrNone);
}

inline KThemePixmap* KThemeBase::uncached(WidgetType widget) const
{
    return(pixmaps[widget]);
}

inline QBrush KThemeBase::pixmapBrush(const QColorGroup &group,
                                      QColorGroup::ColorRole role,
                                      int w, int h, WidgetType widget)
{
    if(pixmaps[widget] || images[widget])
        return(QBrush(group.color(role), *scalePixmap(w, h, widget)));
    else
        return(group.color(role));
}

inline const QColorGroup* KThemeBase::colorGroup(const QColorGroup &defaultGroup,
                                                 WidgetType widget) const
{
    return((colors[widget]) ? colors[widget] : &defaultGroup);
}

inline int KThemeBase::borderWidth(WidgetType widget) const
{
    return(pbWidth[widget] ? pbWidth[widget] : borders[widget]);
}

inline int KThemeBase::pixBorderWidth(WidgetType widget) const
{
    return(pbWidth[widget]);
}

inline int KThemeBase::highlightWidth(WidgetType widget) const
{
    return(pbWidth[widget] ? 0 : highlights[widget]);
}

inline int KThemeBase::decoWidth(WidgetType widget) const
{
    return(pbWidth[widget] ? pbWidth[widget] : borders[widget]+highlights[widget]);
}

inline QColor* KThemeBase::gradientHigh(WidgetType widget) const
{
    return(grHighColors[widget]);
}

inline QColor* KThemeBase::gradientLow(WidgetType widget) const
{
    return(grLowColors[widget]);
}

inline QImage* KThemeBase::image(WidgetType widget) const
{
    return(images[widget]);
}

inline KThemeBase::SButton KThemeBase::scrollBarLayout() const
{
    return(sbPlacement);
}

inline KThemeBase::ArrowStyle KThemeBase::arrowType() const
{
    return(arrowStyle);
}

inline KThemeBase::ShadeStyle KThemeBase::shade() const
{
    return(shading);
}

inline int KThemeBase::frameWidth() const
{
    return(defaultFrame);
}

inline int KThemeBase::buttonXShift() const
{
    return(btnXShift);
}

inline int KThemeBase::splitWidth() const
{
    return(splitterWidth);
}

inline int KThemeBase::buttonYShift() const
{
    return(btnYShift);
}

inline int KThemeBase::sliderButtonLength() const
{
    if(isPixmap(Slider))
        return(uncached(Slider)->width());
    else
        return(sliderLen);
}

inline bool KThemeBase::roundButton() const
{
    return(roundedButton);
}

inline bool KThemeBase::roundComboBox() const
{
    return(roundedCombo);
}

inline bool KThemeBase::roundSlider() const
{
    return(roundedSlider);
}

inline bool KThemeBase::activeTabLine() const
{
    return(aTabLine);
}

inline bool KThemeBase::inactiveTabLine() const
{
    return(iTabLine);
}

inline int KThemeBase::getSBExtent() const
{
    return(sbExtent);
}

inline KThemePixmap* KThemeBase::borderPixmap(WidgetType widget)
{
    return(pbPixmaps[widget]);
}

#endif
