[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kwin
Subject:    Re: [Kwin] Repost: Technical Review Request
From:       David Johnson <david () usermode ! org>
Date:       2003-05-20 1:20:57
[Download RAW message or body]

On Monday 19 May 2003 06:45 am, Lubos Lunak wrote:

>  When I asked you to send it here, I meant the article itself, not
> this mail ;).

Since it's part of a binary tarball (which includes example source code 
and images) I didn't want to send it to the list. I still can't send it 
to the list because it's larger than the 40K limit. But I'm attaching 
the bare article sans complete source, stylesheet and images...

-- 
David Johnson
___________________
http://www.usermode.org
["kwinstyle.html" (text/html)]

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>HOWTO: KWin Window Manager Themes</title>
    <link href="usermode.css" type="text/css" rel="stylesheet">
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
  </head>

  <body>
    <div id="body">
      <div id="standard">

        <h1>HOWTO: KWin Window Manager Themes</h1>

        <p>The default window manager for KDE is Kwin. Kwin has a very
        flexible theming mechanism, because all themes are ultimately
        plugins to the window manager. When you're subclassing a Qt
        based window manager, you can do a lot of things.</p>

        <p>This tutorial covers creating a native Kwin theme using C++
        code, as opposed to creating a theme for "pixmap engine", such
        as the IceWM plugin. Of course, you can still use this
        knowledge to create a new pixmap engine.</p>

        <p>The sample code for this tutorial, along with a copy of
        this documentation, can be found here: <a
        href="example.tar.gz">example.tar.gz</a> [256K]. The code
        snippets shown in this documentation are not documented, but
        the source code itself is.</p>

        <p>This tutorial is assuming that you have some experience
        with C++ and the Qt toolkit. Specifically, it assumes that you
        understand C++ inheritance, the Qt signal/slot paradigm, and
        are familiar with the common Qt classes. If you have never
        done any Qt programming, please stop right here and take some
        time out to familiarize yourself with this excellent
        toolkit. It comes with some of the best documentation for a
        development library that I have seen.</p>

        <h2>Step One: Proper Planning</h2>

        <p>Before you start working on a new theme, you should have
        some idea as to what the finished theme will look like. All
        usable window managers will have a title bar containing
        buttons and a frame which surround the application. There are
        two classic layouts for window manager decorations, with some
        minor variations. For lack of standard names, I'll call them
        the "motif" and "next" layouts.</p>

        <table summary="layout styles" width="100%" border="0"
          cellspacing="0" cellpadding="0">
          <tbody>
            <tr>
              <th align="center">The "motif" layout</th>
              <th align="center">The "next" layout</th>
            </tr>
            <tr>
              <td align="center"><img src="motiflayout.png" width="181"
                  height="121"></td>
              <td align="center"><img src="nextlayout.png" width="171"
                  height="116"></td>
            </tr>
          </tbody>
        </table>
    
        <p>The "motif" layout is the most popular. The title bar and
        window are surrounded by a thick frame. Sometimes the frame
        only wraps the left, right and bottom of the window, with the
        title bar being the top part of the frame.</p>

        <p>The "next" layout was first seen, to the best of my
        knowledge, in the NeXT desktop. It is used by the Windowmaker
        and Blackbox window managers, as well as several KDE
        themes. There is only a titlebar on top and a "handle" on the
        bottom.</p>

        <p>The example theme will use the motif layout because it is
        traditional. Since this is an example, I'm going to make
        everything look quite plain with simple lines delineating the
        parts of the decoration. Think of the example as a template
        from which to create your own, more aesthetic, themes.</p>

        <p>Drawing out the theme in a paint program like GIMP is very
        helpful. Sometimes what you think will look good won't, once
        you actually see it.</p>

        <h3>Usability</h3>

        <p>Always keep the usability of the theme in mind. No matter
        how gorgeous the look, no one will use it if it keeps getting
        in their way. Some simple rules will keep your theme usable by
        most people. If you must break any of the following rules,
        please have a good reason to do so.</p>

        <dl>
          <dt>Honor Button Positions
          <dd>People use custom button positions for a reason. Many
          people are used to having the close button on the left
          instead of on the right. Others never use the menu
          button. Those that don't use custom button positions will
          still expect to see the standard positions.

          <dt>Honor Standard Behavior
          <dd>There are certain standard behaviors that users will
          expect. Users do no want to change the way they interact
          with windows every time they change the theme. The maximize
          button should maximize the window. A double click on the
          title bar should shade the window. The frame or handle
          should be large enough to easily resize the window.
        </dl>

        <h2>Step Two: Infrastructure</h2>

        <p>To start out your project, you'll need a build
        infrastructure. This will be similar to most KDE
        applications. Surprisingly, it's an area a lot of people skimp
        on. The default infrastructure is based on GNU's
        automake/autoconf framework. There should be very little you
        need to change from the example code provided.</p>

        <p>There should be an <code>admin</code> directory that
        contains the standard KDE admin files. A lot of work has been
        done to make your life as a KDE developer easy, and most of it
        is in this directory. I have not included this directory in
        the example package, but you can obtain this from any KDE
        source package. The best place is to copy it from a current
        <code>kdesdk</code> package.</p>

        <p>Please include a <code>README</code> and
        <code>INSTALL</code> file. Describe what this theme is, and
        how to install it. And by all means, please state what license
        you code is under! There's nothing worse than wanting to
        borrow a piece of code, and not knowing if you're legally
        entitled to. The preferred location for this information is
        the <code>COPYING</code> file. The GPL is the most popular
        for KDE programs, but many Kwin themes are under the MIT
        license, since that's what Kwin is under.</p>

        <p>If you're using the example package as a template, you will
        need to modify the <code>configure.in.in</code>,
        <code>client/Makefile.am</code>,
        <code>client/config/Makefile.am</code>, and
        <code>example.desktop</code> files. Simply replace all
        occurances of "example" with the name of your theme. If you
        add additional source code files, you'll need to change these
        files as appropriate. Please see the <a
        href="http://developer.kde.org/documentation/other/developer-faq.html">
        KDE Developer FAQ</a> for more information on the standard KDE
        build infrastructure.</p>

        <h2>Step Three: Configuration</h2>

        <p>If there are any user configurable aspects to your theme,
        you should create a configuration dialog. The configuration
        dialog is actually a plugin to Control Center. Common
        configuration options for Kwin themes include title alignment,
        title bar height, drawing frames using the titlebar colors, and
        displaying an additional handle. For the example, we will be
        using only one option to specify the title alignment.</p>

        <p>I have chosen to use Qt Designer to handle the layout of
        the dialog. This makes it very easy to design the dialog. Add
        three QRadio buttons in a QButtonGroup box to let the user
        choose the alignment of the title text. Add some "What's This"
        help text. Designer will make all of the widgets public so
        that you can easily access them from your configuration
        plugin.</p>

        <p>The <code>exampleconfig.h</code> header file is quite
        short, and has only two member variables, one for the
        configuration, and one for the actual dialog:
<pre>...
private:
    KConfig *config_;
    ConfigDialog *dialog_;
</pre>

        <p>The <code>exampleconfig.cc</code> is almost as simple. Since
        most of the GUI work is being done by Qt Designer in our ui
        file, all we need to do is worry about is the configuration. The
        constructor creates a new configuration object and dialog, and
        connects with the dialog.

<pre>ExampleConfig::ExampleConfig(KConfig* config, QWidget* parent)
    : QObject(parent), config_(0), dialog_(0)
{
    config_ = new KConfig("kwinexamplerc");
    KGlobal::locale()-&gt;insertCatalogue("kwin_example_config");
    dialog_ = new ConfigDialog(parent);
    dialog_-&gt;show();
    load(config);
    connect(dialog_-&gt;titlealign, SIGNAL(clicked(int)),
            this, SLOT(selectionChanged(int)));
}
</pre>

        <p>Our work consists of loading and saving the configuration,
        and setting some sensible defaults. We simply set the dialog
        widgets to the existing configuration, and write them out
        again when the change.

<pre>void ExampleConfig::load(KConfig*)
{
    config_-&gt;setGroup("General");
    QString value = config_-&gt;readEntry("TitleAlignment", "AlignHCenter");
    QRadioButton *button = (QRadioButton*)dialog_-&gt;titlealign-&gt;child(value);
    if (button) button-&gt;setChecked(true);
}

void ExampleConfig::save(KConfig*)
{
    config_-&gt;setGroup("General");
    QRadioButton *button = (QRadioButton*)dialog_-&gt;titlealign-&gt;selected();
    if (button) config_-&gt;writeEntry("TitleAlignment", QString(button-&gt;name()));
    config_-&gt;sync();
}
</pre>

        <h2>Step Four: The Handler</h2>

        <p>Each window is known as a "client", and is represented by
        the Client class. If you have five windows up on the screen,
        you will have five instances of Client. Managing the theme for
        multiple clients is the job of the <em>handler</em>. Not every
        theme needs a handler class, but it simplifies many things.</p>

        <p>We'll use the ExampleHandler to take care of the storing
        the configuration data. It could also be used to create and
        store the pixmaps used by the clients. Think of the handler as
        a global resource for your clients.</p>

<pre>void ExampleHandler::reset()
{
    readConfig();
    initialized_ = true;
    Workspace::self()-&gt;slotResetAllClientsDelayed();
}
</pre>

        <p>The constructor merely calls the <code>reset()</code>
        method, which reads in the configuration and resets all
        clients. We need to reset all the clients whenever the
        configuration has changed. Kwin does this for us via the
        <code>slotResetAllClientsDelayed()</code> call.</p>

<pre>void ExampleHandler::readConfig()
{
    KConfig config("kwinexamplerc");
    config.setGroup("General");

    QString value = config.readEntry("TitleAlignment", "AlignHCenter");
    if (value == "AlignLeft") titlealign_ = Qt::AlignLeft;
    else if (value == "AlignHCenter") titlealign_ = Qt::AlignHCenter;
    else if (value == "AlignRight") titlealign_ = Qt::AlignRight;
}
</pre>

        <p>Reading in the configuration is very similar to how we read
        it in the configuration dialog. By assigning the
        <code>titlealign_</code> member to the title alignment,
        clients can determine their title alignment without having to
        load the configuration. They will do this with ExampleHandler's
        <code>titleAlign()</code> method.</p>

        <h2>Step Five: The Buttons</h2>

        <p>The buttons on the titlebar are derived from the KWinButton
        class, which are ordinary QButtons. But we still need to
        determine their look and behavior.</p>

<pre>ExampleButton::ExampleButton(Client *parent, const char *name,
                             const QString& tip, ButtonType type,
                             const unsigned char *bitmap)
    : KWinButton(parent, name, tip), client_(parent), type_(type),
      deco_(0), lastmouse_(0)
{
    setBackgroundMode(NoBackground);
    setFixedSize(BUTTONSIZE, BUTTONSIZE);
    if (bitmap) setBitmap(bitmap);
}
</pre>

        <p>The constructor sets the size of the button then sets the
        bitmap decoration. The decoration is what visually
        distinguishes the buttons from each other. The close button
        will have an "x" decoration, while the minimize button will
        have a "v" decoration.</p>

<pre>void ExampleButton::setBitmap(const unsigned char *bitmap)
{
    if (deco_) delete deco_;
    if (!bitmap) return;

    deco_ = new QBitmap(DECOSIZE, DECOSIZE, bitmap, true);
    deco_-&gt;setMask(*deco_);
    repaint(false);
}
</pre>

        <p>A QBitmap has only two colors, foreground and
        background. If we wanted more colors for our decorations we
        could have used QPixmap instead. We repaint the button every
        time the bitmap is changed. </p>

        <p>For the menu button we <em>do</em> use a QPixmap. In this
        case we will use the application icon. We will access this
        pixmap from the Client when we draw the button.</p>

<pre>void ExampleButton::enterEvent(QEvent *e)
{
    KWinButton::enterEvent(e);
}

void ExampleButton::leaveEvent(QEvent *e)
{
    KWinButton::leaveEvent(e);
}
</pre>

        <p>We don't do anything with the enter and leave events except
        pass them on to the base class. If we wanted to implement
        "mouse over" highlighting of the buttons, however, this is
        where we would start.</p>

<pre>void ExampleButton::mousePressEvent(QMouseEvent* e)
{
    lastmouse_ = e-&gt;button();
    QMouseEvent me(e-&gt;type(), e-&gt;pos(), e-&gt;globalPos(), LeftButton, e-&gt;state());
    KWinButton::mousePressEvent(&me);
}

void ExampleButton::mouseReleaseEvent(QMouseEvent* e)
{
    lastmouse_ = e-&gt;button();
    QMouseEvent me(e-&gt;type(), e-&gt;pos(), e-&gt;globalPos(), LeftButton, e-&gt;state());
    KWinButton::mouseReleaseEvent(&me);
}
</pre>

        <p>These two events tell us about mouse clicks. These are just
        the events, and not the signals, so we don't perform any
        actual windowing behaviors, such as minimize or close. But we
        do want to remember which mouse button did the
        clicking. That way if the maximize button was pressed, we will
        know whether to maximize horizontally, vertically or
        full.</p>

        <p>Notice that we pass on the event after setting the mouse
        button to "LeftButton". KWinButton only recognizes the left
        mouse button (since it is a QButton), but Kwin itself
        recognizes all mouse buttons. If we didn't change this, then a
        right mouse click on the maximize button would be passed on to
        KWin, which would then dutifully move our window to the
        back. That's not what we want!</p>

<pre>void ExampleButton::drawButton(QPainter *painter)
{
    if (!ExampleHandler::initialized()) return;

    QColorGroup group;
    int dx, dy;
    ...
</pre>

        <p>The last method to ExampleButton is to draw the button. We
        return if the handler has not been initialized. This should
        never happen, but it's better to be safe than to have several
        hundred bug reports concerning mysterious crashes.</p>

<pre>    ...
    group = options-&gt;colorGroup(Options::ButtonBg, client_-&gt;isActive());
    painter-&gt;fillRect(rect(), group.button());
    painter-&gt;setPen(group.dark());
    painter-&gt;drawRect(rect());
    ...
</pre>

        <p>There are an infinite number of ways to draw the
        button.  For the purposes of this example, we merely draw a
        blank button with a dark border around it.</p>

        <p>Notice the call to <code>options</code> object. Kwin keeps
        track of several configuration items which we can access
        through the global <code>Options</code> class. One of these
        items is a QColorGroup generated from the user defined button
        color.</p>

<pre>    ...
    if (type_ == ButtonMenu) {
        // we paint the mini icon (which is 16 pixels high)
        dx = (width() - 16) / 2;
        dy = (height() - 16) / 2;
        painter-&gt;drawPixmap(dx, dy, client_-&gt;miniIcon());
    } else {
        // otherwise we paint the deco
        dx = (width() - DECOSIZE) / 2;
        dy = (height() - DECOSIZE) / 2;
        if (isDown()) { dx++; dy++; }
        painter-&gt;setPen(group.dark());
        if (deco_) painter-&gt;drawPixmap(dx, dy, *deco_);
    }
}
</pre>

        <p>Finally we paint the button decoration. We do some minor
        calculations to center the decoration in the button. If this
        is the menu button, we draw the application icon. If it's any
        other button, we draw the bitmap decoration.</p>

        <table summary="sample buttons" width="50%" border="0"
          cellspacing="0" cellpadding="0">
          <tbody>
            <tr>
              <th align="center">Minimize Button</th>
              <th align="center">Menu Button</th>
            </tr>
            <tr>
              <td align="center"><img src="minimize.png" width="18"
                  height="18"></td>
              <td align="center"><img src="menu.png" width="18"
                  height="18"></td>
            </tr>
          </tbody>
        </table>

        <p>A common effect for buttons is to have make it look
        "pressed" when the mouse clicks on it, and it's in the "down"
        state. We do this by shifting the position of the decoration
        slightly.</p>

        <h2>Step Six: The Client</h2>

        <p>The ExampleClient class is the heart of the theme. Is
        provides most of the theming, and contains the application
        window and the title bar buttons.</p>

<pre>ExampleClient::ExampleClient(Workspace *ws, WId w, QWidget *parent,
                             const char *name)
    : Client(ws, w, parent, name, WResizeNoErase | WRepaintNoErase),
      titlebar_(0)
{
    setBackgroundMode(NoBackground);
    ...
</pre>

        <p>In the constructor we pass a couple of WFlags to the base
        class. This helps eliminate some flicker when resizing and
        repainting windows. We also set a <code>NoBackground</code>
        mode for the same reason.</p>

<pre>    ...
    QGridLayout *mainlayout = new QGridLayout(this, 4, 3); // 4x3 grid
    QHBoxLayout *titlelayout = new QHBoxLayout();
    titlebar_ = new QSpacerItem(1, TITLESIZE, QSizePolicy::Expanding, QSizePolicy::Fixed);
    mainlayout-&gt;setResizeMode(QLayout::FreeResize);
    mainlayout-&gt;addRowSpacing(0, FRAMESIZE);
    mainlayout-&gt;addRowSpacing(3, FRAMESIZE*2);
    mainlayout-&gt;addColSpacing(0, FRAMESIZE);
    mainlayout-&gt;addColSpacing(2, FRAMESIZE);

    mainlayout-&gt;addLayout(titlelayout, 1, 1);
    mainlayout-&gt;addWidget(windowWrapper(), 2, 1);
    ...
</pre>

        <p>Qt's layout classes are very flexible and powerful, so we
        use them to layout our theme. We create a four row by three
        column grid. See the "motif" diagram above for a visual
        explanation. The central window is the only widget that will
        go into this grid.</p>

        <p>The only objects we actually add to the main layout is the
        <code>titlelayout</code> and window. The outer rows and
        columns around them are empty spacing. We will later use this
        spacing to draw the window frame.

<pre>     ...
    mainlayout-&gt;setRowStretch(2, 10);
    mainlayout-&gt;setColStretch(1, 10);
    ...
</pre>

        <p>It is important to ensure that only the central window
        changes size when the window is resized. We do this by
        setting the stretch for the central row and column. Try
        removing these two lines to see what happens without it. It's
        not pretty.</p>

<pre>    ...
    for (int n=0; n&lt;ButtonTypeCount; n++) button[n] = 0;
    addButtons(titlelayout, options-&gt;titleButtonsLeft());
    titlelayout-&gt;addItem(titlebar_);
    addButtons(titlelayout, options-&gt;titleButtonsRight());
}
</pre>

        <p>To finish up the contructor we layout the titlebar. The
        global <code>options</code> object will tell us the button
        layout that the user has chosen. Between the left and the
        right buttons is the <code>titlebar_</code> spacer we
        created. This spacer will stretch horizontally as needed, but
        keep a fixed vertical height.</p>

        <h3>Adding Buttons</h3>

        <p>I have created an <code>addButton()</code> method to ease
        the creation and layout of the title bar buttons. Most themes
        that honor custom button layouts use a similar method. The
        function is lengthy, but the concept is simple.</p>

<pre>void ExampleClient::addButtons(QBoxLayout *layout, const QString& s)
{
    if (s.length() > 0) {
        for (unsigned n=0; n &lt; s.length(); n++) {
            switch (s[n]) {
              case 'M': // Menu button
                  if (!button[ButtonMenu]) {
                      button[ButtonMenu]
                          = new ExampleButton(this, "menu", i18n("Menu"), ButtonMenu, 0);
                      connect(button[ButtonMenu], SIGNAL(pressed()),
                              this, SLOT(menuButtonPressed()));
                      layout-&gt;addWidget(button[ButtonMenu]);
                  }
                  break;
    ...
</pre>

        <p>A string is passed in representing the button positions,
        and the layout the buttons are to be added to. We go through
        the string one character at a time. For each character we
        construct a button. In the first case shown above, the
        character 'M' constructs a menu button, connects the pressed
        signal to the <code>menuButtonPressed()</code> method, and
        adds it to the layout.</p>

        <p>Notice that we are keeping our buttons points in an
        array. This makes it easy to access all buttons at once, as we
        did when we initialized all the pointers to null in the
        constructor. An enum is used to conveniently access them
        individually.</p>

        <p>Only the menu button is shown here. The other buttons are
        similar.</p>

<pre>    ...
              case '_': // Spacer item
                  layout-&gt;addSpacing(FRAMESIZE);
            }
	}
    }
}
</pre>

        <p>A spacing in the button layout is a special case. In the
        example theme we merely add a small bit of spacing to the
        layout.</p>

        <p>The finished title bar layout will look like this:</p>

        <img src="titlebar.png" width="331" height="18">

        <h3>Painting the Window</h3>

<pre>void ExampleClient::paintEvent(QPaintEvent*)
{
    if (!ExampleHandler::initialized()) return;

    QPainter painter(this);
    QColorGroup group;
    ...
</pre>

        <p>Painting the parts of the window is simply a matter of
        determining the coordinates of the various parts, then using
        the standard QPainter methods to draw. The only parts we need
        to worry about are the titlebar and the outside frame. The
        buttons and the window will draw themselves.</p>

<pre>    ...
    QRect title(titlebar_-&gt;geometry());
    group = options-&gt;colorGroup(Options::TitleBar, isActive());
    painter.fillRect(title, group.background());
    painter.setPen(group.dark());
    painter.drawRect(title);
    ...
</pre>

        <p>To draw the titlebar we get its coordinates, set the color
        group, fill it with the background color, then outline it with
        a dark color.</p>

        <p>For the example this is good enough. Other possibilities
        are to draw a bevel effect around the title, using a gradient
        or custom pixmap, or even fancier techniques.</p>

<pre>    ...
    painter.setFont(options-&gt;font(isActive(), false));
    painter.setPen(options-&gt;color(Options::Font, isActive()));
    painter.drawText(title.x() + FRAMESIZE, title.y(),
                     title.width() - FRAMESIZE * 2, title.height(),
                     ExampleHandler::titleAlign() | AlignVCenter,
                     caption());
    ...
</pre>

        <p>The <code>options</code> object also gives us the font
        and font color. The <code>false</code> parameter when
        retrieving the font indicates that we want the normal font. If
        we had set it to <code>true</code> we would have gotten a
        smaller font suitable for use in tool window titlebars.</p>

        <p>A small space is left on the left and right sides, so that
        the title text doesn't run up against the buttons.</p>

<pre>    ...
    group = options-&gt;colorGroup(Options::Frame, isActive());

    QRect frame(0, 0, width(), FRAMESIZE);
    painter.fillRect(frame, group.background());
    frame.setRect(0, 0, FRAMESIZE, height());
    painter.fillRect(frame, group.background());
    frame.setRect(0, height() - FRAMESIZE*2, width(), FRAMESIZE*2);
    painter.fillRect(frame, group.background());
    frame.setRect(width()-FRAMESIZE, 0, FRAMESIZE, height());
    painter.fillRect(frame, group.background());

    painter.setPen(group.dark());
    frame = rect();
    painter.drawRect(frame);
    frame.setRect(frame.x() + FRAMESIZE-1, frame.y() + FRAMESIZE-1,
                  frame.width() - FRAMESIZE*2 +2, 
                  frame.height() - FRAMESIZE*3 +2);
    painter.drawRect(frame);
}
</pre>

        <p>Drawing the outer frame involves a more complex
        geometry. We simplify this by separating it into four
        parts. The corners of the frame will each be drawn twice, due
        to the overlap, but for simple fills, this does not result in
        any performance loss. Notice that the frame geometry matches
        the spacing we put in the main layout.</p>

        <h3>Event Handling</h3>

        <p>We've finished all of our drawing code. But we're still far
        from done with the client class. There are events we have to
        take care of. Some of these will be X11 or Qt events. Others
        will be Kwin state changes or  user actions.</p>

<pre>void ExampleClient::mouseDoubleClickEvent(QMouseEvent *e)
{
    if (titlebar_-&gt;geometry().contains(e-&gt;pos()))
        workspace()-&gt;performWindowOperation(this,
            options-&gt;operationTitlebarDblClick());
}
</pre>

        <p>When the user double clicks in our window, we need to
        decide what to do. The only place where a double click does
        anything is in the title bar, so we first check to see if
        that's where the event happened. If so, we tell Kwin to
        perform the <code>operationTitlebarDblClick()</code>
        action. Usually this shades or unshades the window, but the
        user can redefine this behavior.</p>

<pre>void ExampleClient::activeChange(bool)
{
    for (int n=0; n&lt;ButtonTypeCount; n++)
        if (button[n]) button[n]-&gt;repaint(false);
    repaint(false);
}
</pre>

        <p>A window is only active when it has the focus. The
        typical Kwin behavior is to use a different color scheme for
        active and inactive windows. When the focus state changes, we
        need to redraw the window because it will have a different
        color scheme. This is a simple matter of redrawing all the
        buttons, and then the client.</p>

<pre>void ExampleClient::maximizeChange(bool m)
{
    if (button[ButtonMax]) {
        button[ButtonMax]-&gt;setBitmap(m ? minmax_bits : max_bits);
        button[ButtonMax]-&gt;setTipText(m ? i18n("Restore") : i18n("Maximize"));
    }
}
</pre>

        <p>When the window has changed its maximize state, we need to
        change the button decoration for the maximize button. We also
        need to change the "tooltip" text</p>

<pre>void ExampleClient::maxButtonPressed()
{
    if (button[ButtonMax]) {
        switch (button[ButtonMax]-&gt;lastMousePress()) {
          case MidButton:   maximize(MaximizeVertical);   break;
          case RightButton: maximize(MaximizeHorizontal); break;
          default:          maximize();
        }
    }
}
</pre>

        <p>The expected behavior for Kwin maximize buttons is to
        maximize horizontally if the right button is pressed,
        maximize vertically if the middle button is pressed, and
        maximize fully if the left button was pressed. We know which
        mouse button was pressed because we had the button save that
        information for us earlier.</p>

<pre>void ExampleClient::menuButtonPressed()
{
    static QTimer timer;

    if (timer.isActive()) closeWindow(); // doubleclick
    
    timer.start(QApplication::doubleClickInterval(), true);

    if (button[ButtonMenu]) {
        QPoint pt(button[ButtonMenu]-&gt;rect().bottomLeft().x(),
                     button[ButtonMenu]-&gt;rect().bottomLeft().y());
        workspace()-&gt;showWindowMenu(button[ButtonMenu]-&gt;mapToGlobal(pt), this);
        button[ButtonMenu]-&gt;setDown(false);
    }
}
</pre>

        <p>Clicking in the menu button is more complicated. Not only
        does this bring up the window menu, it also typically closes
        the window if there is a double click.</p>

        <p>We use a timer to determine if there has been a mouse
        double click. If it is not running, we start it with a
        duration equal to the double click interval, then move on. If
        we enter the method and find that it is running, then we know
        that this is the second click of a double click, and we close
        the window.</p>

        <p>Then we pop up the window menu. We want this menu
        positioned just below our button, so we pass this point to
        Kwin's <code>showWindowMenu()</code> method. We also set the
        menu button to the "down" state.</p>

        <h2>Wrapping Things Up</h2>

        <p>I haven't shown all of the example source code here. But
        it's all included in the <a
        href="example.tar.gz">example.tar.gz</a> package. Besides
        reading though the example, also look at the
        <code>client.h</code> and <code>options.h</code> header files
        located in <code>$KDEDIR/include/kwin</code>. The former is
        your theme's base class, and the latter provides you with
        access to a lot of configuration information.</p>

        <p>The finished product isn't pretty, but it's fully
        functional. Think of it as your starting point for your own
        Kwin theme.</p>

        <img src="screenshot.png" width="339" height="137">

        <h3>Excercises</h3>

        <p>Here's a list of simple exercises to get you some hands on
        knowledge of Kwin themes</p>

        <ul>
          <li>If the custom button layout includes spacing between the
          buttons, we add a spacer item to the title layout. But we
          don't draw those spacers. This results in an ugly
          transparency. Draw those spacers in the title bar color.

          <li>One quirk of Kwin is that if the window is shaded, there
          is still a one pixel high region for the client window. We
          don't do anything about this, and it results in another ugly
          transparency, one pixel high. Fill in this gap with the
          frame background color.

          <li>The simple dark lines around the window parts is very
          plain. It's almost, but not quite, a presentable theme. Fix
          up the little details to make something you might want to
          actually use on your desktop. With just a little bit of
          work, you could make this a clone of the CDE or KDE1 themes.
        </ul>

      </div>
    </div>

    <div class="footer">
      <table summary="navigation table" width="100%" border="0"
      cellspacing="0" cellpadding="0">
        <tbody>
          <tr>
            <td align="left" width="33%"><a href=
            "../freenix.html">FreeNIX</a></td>

            <td align="center" width="33%"><a href=
            "../index.html">Home</a></td>

            <td align="right" width="33%"><a href=
            "mailto:david@usermode.org">&copy;David Johnson 2003</a></td>
          </tr>
        </tbody>
      </table>
    </div>

  </body>
</html>


_______________________________________________
Kwin mailing list
Kwin@mail.kde.org
http://mail.kde.org/mailman/listinfo/kwin


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic