diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..f830f51 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,7 @@ +Authors +------- +David Johnson + +Contributors +------------ +Lubos Lunak \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b8377a2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,73 @@ +################################################# +# +# (C) 2021 Emanoil Kotsev +# deloptes (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +cmake_minimum_required( VERSION 2.8.12 ) + + +##### general package setup ##################### + +project( twintheme ) +set( VERSION R14.1.0 ) + +##### include essential cmake modules ########### + +include( FindPkgConfig ) +include( CheckIncludeFile ) +include( CheckLibraryExists ) +include( CheckSymbolExists ) +include( CheckCSourceCompiles ) +include( CheckCXXSourceCompiles ) + + +##### include our cmake modules ################# + +set( CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules" ) +include( TDEMacros ) +include( TDESetupPaths ) + +##### setup install paths ####################### + +include( TDESetupPaths ) +tde_setup_paths( ) + +##### user requested modules #################### + +option( BUILD_ALL "Build all" OFF ) +#option( BUILD_DOC "Build doc" ${BUILD_ALL} ) +#option( BUILD_TRANSLATIONS "Build translations" ${BUILD_ALL} ) + + +##### configure checks ########################## + +include( ConfigureChecks.cmake ) + + +###### global compiler settings ################# + +#add_definitions( +# -DHAVE_CONFIG_H +#) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TQT_CXX_FLAGS}" ) +set( CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined" ) +set( CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined" ) + + +##### source directories ######################## + +add_subdirectory( client ) +#tde_conditional_add_subdirectory( BUILD_DOC doc ) +#tde_conditional_add_subdirectory( BUILD_TRANSLATIONS po ) + + +##### write configure files ##################### +# +#configure_file( config.h.cmake config.h @ONLY ) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake new file mode 100644 index 0000000..f3dfb85 --- /dev/null +++ b/ConfigureChecks.cmake @@ -0,0 +1,29 @@ +################################################# +# +# (C) 2010 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + + +# required stuff +find_package( TQt ) +find_package( TDE ) + +tde_setup_architecture_flags( ) + +include(TestBigEndian) +test_big_endian(WORDS_BIGENDIAN) + +tde_setup_largefiles( ) + + +##### check for gcc visibility support + +if( WITH_GCC_VISIBILITY ) + tde_setup_gcc_visibility( ) +endif( WITH_GCC_VISIBILITY ) diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..1702fca --- /dev/null +++ b/INSTALL @@ -0,0 +1,94 @@ +Example Installation +==================== + +The example themes requires the KDE 3.2 or greater libraries. This +can be found at . These are also included with +many Linux, BSD and Unix systems. + +You also need GNU make, and not your standard make. On many systems, +GNU make has been installed as "gmake" If this is your situation, +substitute "gmake" for every occurance of "make" in these +instructions. + +==================================================================== + +To save room in the distribution, the admin directory is not included +nor any of its included files. Before you can build the example, this +must be added. The simplest way is to copy one over from another KDE +package. + +==================================================================== + +The themes use the 'configure' script to attempt a reasonable and +correct configuration for compilation on your system. It creates an +appropriate 'Makefile' for each directory in this package, along with +one or more headerfiles containing system-dependent definitions. It +also creates 'config.status','config.cache' and 'config.log' files +that are useful for recreating or debugging the configuration. + +Sincerely, +David Johnson + +Example Installation in a Nutshell: +==================================== + +1) Type './configure' + +2) Type 'make' + +3) Type 'make install' + +Example Installation in More Detail: +===================================== + +0) Make sure that the prerequisite KDE 3.2 development libraries and +headers are installed first. These can be found at +. + +1) Unzip and untar the package into its own directory. Change into +this directory, and read the README file. (I'm assuming that +you've read this, the INSTALL file :-)) + +2) Type './configure --help", and peruse the configure options +available. It may be necessary to use one or more of these options +on your system. + +3) Type './configure' to configure the software for your system. If +you're using `csh' on an old version of System V, you might need +to type `sh ./configure' instead. + +4) Type 'make' to compile the package. + +5) If you are not logged in as root, type 'su' and enter the root +password to switch over to the root account. This is not necessary +if you configured the software to install to your home directory/ + +6) Type 'make install' to install the software. + +7) Activate the styles, decorations and color schemes through the +KDE Control Center or the Preferences menu. + +8) You can uninstall the software by typing 'make uninstall'. + +A Note on the KDE Library +======================== + +If you are having problems configuring the software, first make sure +that the required KDE 3.0 or greater libraries are installed. Several +systems have broken this into several packages. You will need both +the runtime and the development packages. + +If you still have problems, set the TDEDIR environment variable and +the --prefix configure option to the location where you installed +KDE. Also makesure that you are using GNU make and not the standard +make everyone else uses. + +Rebuilding the Configuration +============================ + +If you ever need to rebuild the configuration files, type the +following in thebase directory of the distribution: + make -f Makefile.cvs + +You will need the current automake/autoconf set of programs. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..13b33bd --- /dev/null +++ b/Makefile.am @@ -0,0 +1,20 @@ +SUBDIRS = $(TOPSUBDIRS) + +MAINTAINERCLEANFILES = subdirs configure.in acinclude.m4 configure.files + +EXTRA_DIST = Makefile.cvs admin + +AUTOMAKE_OPTIONS = foreign + +$(top_srcdir)/configure.in: configure.in.in $(top_srcdir)/subdirs + cd $(top_srcdir) && $(MAKE) -f admin/Makefile.common configure.in ; + +$(top_srcdir)/subdirs: + cd $(top_srcdir) && $(MAKE) -f admin/Makefile.common subdirs + +$(top_srcdir)/acinclude.m4: $(top_srcdir)/admin/acinclude.m4.in $(top_srcdir)/admin/libtool.m4.in + @cd $(top_srcdir) && cat admin/acinclude.m4.in admin/libtool.m4.in > acinclude.m4 + +dist-hook: + cd $(top_distdir) && perl admin/am_edit -padmin + cd $(top_distdir) && $(MAKE) -f admin/Makefile.common subdirs diff --git a/Makefile.cvs b/Makefile.cvs new file mode 100644 index 0000000..828868a --- /dev/null +++ b/Makefile.cvs @@ -0,0 +1,7 @@ +all: + $(MAKE) -f admin/Makefile.common cvs + +dist: + $(MAKE) -f admin/Makefile.common dist + +.SILENT: diff --git a/README.md b/README.md index 99f4dfb..7ca97a3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ # twintheme-example -This is an example on how to create TWin themes and was ported as such to TDE from www.usermode.org/docs/kwintheme.html \ No newline at end of file +This is an example on how to create TWin themes and was ported as such to TDE from www.usermode.org/docs/kwintheme.html + +Example 0.8 +=========== + +This is an example theme for KDE for use as a tutorial. Please see +the documentation under the docs directory for more information. + +As this is strictly example code, absolutely no support is available. +Bug reports are always welcome, but help in installation or +configuration will be politely ignored. + +Everybody is welcome to improve and update the example code and documentation. diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt new file mode 100644 index 0000000..3a5a581 --- /dev/null +++ b/client/CMakeLists.txt @@ -0,0 +1,32 @@ +################################################# +# +# (C) 2021 Emanoil Kotsev +# deloptes (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +add_subdirectory( config ) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + +##### twin_example ###################### + +tde_add_library( twin_example SHARED AUTOMOC + SOURCES exampleclient.cc + VERSION 1.0.0 + LINK tdecore-shared -L/opt/trinity/lib -ltdecorations + DESTINATION ${LIB_INSTALL_DIR} +) diff --git a/client/Makefile.am b/client/Makefile.am new file mode 100644 index 0000000..d2ec2ec --- /dev/null +++ b/client/Makefile.am @@ -0,0 +1,23 @@ +AUTOMAKE_OPTIONS = foreign + +SUBDIRS = config + +KDE_CXXFLAGS = -DQT_PLUGIN + +INCLUDES = $(all_includes) -I$(kde_includes)/twin + +twindir = $(kde_datadir)/twin/ +twin_DATA = example.desktop + +noinst_HEADERS = exampleclient.h + +kde_module_LTLIBRARIES = twin3_example.la +twin3_example_la_SOURCES = exampleclient.cc +twin3_example_la_LIBADD = $(kde_libraries)/libtdecorations.la +twin3_example_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -ltdecore -module +twin3_example_la_METASOURCES = AUTO + +DISTCLEANFILES = $(twin3_example_la_METASOURCES) + +messages: + $(XGETTEXT) *.cpp -o $(podir)/twin_example.po \ No newline at end of file diff --git a/client/config/CMakeLists.txt b/client/config/CMakeLists.txt new file mode 100644 index 0000000..efdc543 --- /dev/null +++ b/client/config/CMakeLists.txt @@ -0,0 +1,30 @@ +################################################# +# +# (C) 2021 Emanoil Kotsev +# deloptes (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + +##### twin_example_config ###################### + +tde_add_library( twin_example_config SHARED AUTOMOC + SOURCES exampleconfig.cc configdialog.ui + VERSION 1.0.0 + LINK tdeui-shared + DESTINATION ${LIB_INSTALL_DIR} +) diff --git a/client/config/Makefile.am b/client/config/Makefile.am new file mode 100644 index 0000000..bbc3d0a --- /dev/null +++ b/client/config/Makefile.am @@ -0,0 +1,18 @@ +AUTOMAKE_OPTIONS = foreign + +KDE_CXXFLAGS = -DQT_PLUGIN + +INCLUDES = $(all_includes) + +noinst_HEADERS = exampleconfig.h + +kde_module_LTLIBRARIES = twin_example_config.la +twin_example_config_la_SOURCES = exampleconfig.cc configdialog.ui +twin_example_config_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module +twin_example_config_la_LIBADD = $(LIB_TDEUI) +twin_example_config_la_METASOURCES = AUTO + +DISTCLEANFILES = $(twin_example_config_la_METASOURCES) + +messages: + $(XGETTEXT) *.cpp -o $(podir)/twin_example_config.pot diff --git a/client/config/configdialog.ui b/client/config/configdialog.ui new file mode 100644 index 0000000..0d4290f --- /dev/null +++ b/client/config/configdialog.ui @@ -0,0 +1,99 @@ + +ConfigDialog + + + ConfigDialog + + + + 0 + 0 + 235 + 108 + + + + Config Dialog + + + + unnamed + + + + generalbox + + + GroupBoxPanel + + + Sunken + + + Decoration Settings + + + + unnamed + + + + titlealign + + + Title &Alignment + + + + + + Use these buttons to set the alignment of the window title + + + + unnamed + + + + AlignLeft + + + Left + + + + + + + + AlignHCenter + + + Center + + + true + + + + + + + + AlignRight + + + Right + + + + + + + + + + + + + diff --git a/client/config/exampleconfig.cc b/client/config/exampleconfig.cc new file mode 100644 index 0000000..b865d41 --- /dev/null +++ b/client/config/exampleconfig.cc @@ -0,0 +1,118 @@ +////////////////////////////////////////////////////////////////////////////// +// exampleconfig.cc +// ------------------- +// Config module for Example window decoration +// ------------------- +// Copyright (c) 2003 David Johnson +// Please see the header file for copyright and license information. +////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include "exampleconfig.h" +#include "configdialog.h" + +////////////////////////////////////////////////////////////////////////////// +// ExampleConfig() +// ------------- +// Constructor + +ExampleConfig::ExampleConfig(TDEConfig* config, TQWidget* parent) + : TQObject(parent), config_(0), dialog_(0) +{ + // create the configuration object + config_ = new TDEConfig("twinexamplerc"); + TDEGlobal::locale()->insertCatalogue("twin_example_config"); + + // create and show the configuration dialog + dialog_ = new ConfigDialog(parent); + dialog_->show(); + + // load the configuration + load(config_); + + // setup the connections + connect(dialog_->titlealign, SIGNAL(clicked(int)), + this, SLOT(selectionChanged(int))); + +} + +////////////////////////////////////////////////////////////////////////////// +// ~ExampleConfig() +// -------------- +// Destructor + +ExampleConfig::~ExampleConfig() +{ + if (dialog_) delete dialog_; + if (config_) delete config_; +} + +////////////////////////////////////////////////////////////////////////////// +// selectionChanged() +// ------------------ +// Selection has changed + +void ExampleConfig::selectionChanged(int) +{ + emit changed(); +} + +////////////////////////////////////////////////////////////////////////////// +// load() +// ------ +// Load configuration data + +void ExampleConfig::load(TDEConfig*) +{ + config_->setGroup("General"); + + TQString value = config_->readEntry("TitleAlignment", "AlignHCenter"); + TQRadioButton *button = (TQRadioButton*)dialog_->titlealign->child(value.latin1()); + if (button) button->setChecked(true); +} + +////////////////////////////////////////////////////////////////////////////// +// save() +// ------ +// Save configuration data + +void ExampleConfig::save(TDEConfig*) +{ + config_->setGroup("General"); + + TQRadioButton *button = (TQRadioButton*)dialog_->titlealign->selected(); + if (button) config_->writeEntry("TitleAlignment", TQString(button->name())); + config_->sync(); +} + +////////////////////////////////////////////////////////////////////////////// +// defaults() +// ---------- +// Set configuration defaults + +void ExampleConfig::defaults() +{ + TQRadioButton *button = + (TQRadioButton*)dialog_->titlealign->child("AlignHCenter"); + if (button) button->setChecked(true); +} + +////////////////////////////////////////////////////////////////////////////// +// Plugin Stuff // +////////////////////////////////////////////////////////////////////////////// + +extern "C" +{ + TQObject* allocate_config(TDEConfig* config, TQWidget* parent) { + return (new ExampleConfig(config, parent)); + } +} + +#include "exampleconfig.moc" diff --git a/client/config/exampleconfig.h b/client/config/exampleconfig.h new file mode 100644 index 0000000..d804d52 --- /dev/null +++ b/client/config/exampleconfig.h @@ -0,0 +1,58 @@ +////////////////////////////////////////////////////////////////////////////// +// exampleconfig.h +// ------------------- +// Config module for Example window decoration +// ------------------- +// Copyright (c) 2003 David Johnson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#ifndef EXAMPLECONFIG_H +#define EXAMPLECONFIG_H + +#include + +class TDEConfig; +class ConfigDialog; + +class ExampleConfig : public TQObject +{ + TQ_OBJECT +public: + ExampleConfig(TDEConfig* config, TQWidget* parent); + ~ExampleConfig(); + +signals: + void changed(); + +public slots: + void load(TDEConfig*); + void save(TDEConfig*); + void defaults(); + +protected slots: + void selectionChanged(int); + +private: + TDEConfig *config_; + ConfigDialog *dialog_; +}; + +#endif // EXAMPLECONFIG_H diff --git a/client/example.desktop b/client/example.desktop new file mode 100644 index 0000000..d85a153 --- /dev/null +++ b/client/example.desktop @@ -0,0 +1,5 @@ +# KDE Desktop Entry +[Desktop Entry] +Encoding=UTF-8 +Name=Example +X-TDE-Library=twin3_example diff --git a/client/exampleclient.cc b/client/exampleclient.cc new file mode 100644 index 0000000..bb42510 --- /dev/null +++ b/client/exampleclient.cc @@ -0,0 +1,890 @@ +////////////////////////////////////////////////////////////////////////////// +// exampleclient.cc +// ------------------- +// Example window decoration for KDE +// ------------------- +// Copyright (c) 2003, 2004 David Johnson +// Please see the header file for copyright and license information. +////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "exampleclient.h" + +using namespace Example; + +// global constants + +static const int BUTTONSIZE = 18; +static const int DECOSIZE = 8; +static const int TITLESIZE = 18; +static const int FRAMESIZE = 4; + +// window button decorations + +static const unsigned char close_bits[] = { + 0x00, 0x66, 0x7e, 0x3c, 0x3c, 0x7e, 0x66, 0x00}; + +static const unsigned char help_bits[] = { + 0x7e, 0x7e, 0x60, 0x78, 0x78, 0x00, 0x18, 0x18}; + +static const unsigned char max_bits[] = { + 0x00, 0x18, 0x3c, 0x7e, 0xff, 0xff, 0x00, 0x00}; + +static const unsigned char min_bits[] = { + 0x00, 0x00, 0xff, 0xff, 0x7e, 0x3c, 0x18, 0x00}; + +static const unsigned char minmax_bits[] = { + 0x00, 0x02, 0x06, 0x0e, 0x1e, 0x3e, 0x7e, 0x00}; + +static const unsigned char stickydown_bits[] = { + 0x00, 0x18, 0x18, 0x7e, 0x7e, 0x18, 0x18, 0x00}; + +static const unsigned char sticky_bits[] = { + 0x00, 0x00, 0x00, 0x7e, 0x7e, 0x00, 0x00, 0x00}; + +static const unsigned char above_on_bits[] = { + 0x7e, 0x18, 0x7e, 0x3c, 0x18, 0x00, 0x00, 0x00 }; + +static const unsigned char above_off_bits[] = { + 0x18, 0x3c, 0x7e, 0x18, 0x7e, 0x00, 0x00, 0x00 }; + +static const unsigned char below_on_bits[] = { + 0x00, 0x00, 0x00, 0x18, 0x3c, 0x7e, 0x18, 0x7e }; + +static const unsigned char below_off_bits[] = { + 0x00, 0x00, 0x00, 0x7e, 0x18, 0x7e, 0x3c, 0x18 }; + +static const unsigned char shade_on_bits[] = { + 0xff, 0xff, 0x81, 0xa5, 0x81, 0xa5, 0x81, 0xff }; + +static const unsigned char shade_off_bits[] = { + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +////////////////////////////////////////////////////////////////////////////// +// ExampleFactory Class // +////////////////////////////////////////////////////////////////////////////// + +bool ExampleFactory::initialized_ = false; +TQt::AlignmentFlags ExampleFactory::titlealign_ = TQt::AlignHCenter; + +extern "C" KDecorationFactory* create_factory() +{ + return new Example::ExampleFactory(); +} + +////////////////////////////////////////////////////////////////////////////// +// ExampleFactory() +// ---------------- +// Constructor + +ExampleFactory::ExampleFactory() +{ + readConfig(); + initialized_ = true; +} + +////////////////////////////////////////////////////////////////////////////// +// ~ExampleFactory() +// ----------------- +// Destructor + +ExampleFactory::~ExampleFactory() { initialized_ = false; } + +////////////////////////////////////////////////////////////////////////////// +// createDecoration() +// ----------------- +// Create the decoration + +KDecoration* ExampleFactory::createDecoration(KDecorationBridge* b) +{ + return new ExampleClient(b, this); +} + +////////////////////////////////////////////////////////////////////////////// +// reset() +// ------- +// Reset the handler. Returns true if decorations need to be remade, false if +// only a repaint is necessary + +bool ExampleFactory::reset(unsigned long changed) +{ + // read in the configuration + initialized_ = false; + bool confchange = readConfig(); + initialized_ = true; + + if (confchange || + (changed & (SettingDecoration | SettingButtons | SettingBorder))) { + return true; + } else { + resetDecorations(changed); + return false; + } +} + +////////////////////////////////////////////////////////////////////////////// +// readConfig() +// ------------ +// Read in the configuration file + +bool ExampleFactory::readConfig() +{ + // create a config object + TDEConfig config("twinexamplerc"); + config.setGroup("General"); + + // grab settings + TQt::AlignmentFlags oldalign = titlealign_; + TQString value = config.readEntry("TitleAlignment", "AlignHCenter"); + if (value == "AlignLeft") titlealign_ = TQt::AlignLeft; + else if (value == "AlignHCenter") titlealign_ = TQt::AlignHCenter; + else if (value == "AlignRight") titlealign_ = TQt::AlignRight; + + if (oldalign == titlealign_) + return false; + else + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ExampleButton Class // +////////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////// +// ExampleButton() +// --------------- +// Constructor + +ExampleButton::ExampleButton(ExampleClient *parent, const char *name, + const QString& tip, ButtonType type, + const unsigned char *bitmap) + : TQButton(parent->widget(), name), client_(parent), type_(type), + deco_(0), lastmouse_(NoButton) +{ + setBackgroundMode(NoBackground); + setFixedSize(BUTTONSIZE, BUTTONSIZE); + setCursor(arrowCursor); + if (bitmap) setBitmap(bitmap); + TQToolTip::add(this, tip); +} + +ExampleButton::~ExampleButton() +{ + if (deco_) delete deco_; +} + +////////////////////////////////////////////////////////////////////////////// +// setBitmap() +// ----------- +// Set the button decoration + +void ExampleButton::setBitmap(const unsigned char *bitmap) +{ + if (!bitmap) return; // no bitmap, probably the menu button + + if (deco_) delete deco_; + deco_ = new TQBitmap(DECOSIZE, DECOSIZE, bitmap, true); + deco_->setMask(*deco_); + repaint(false); +} + +////////////////////////////////////////////////////////////////////////////// +// sizeHint() +// ---------- +// Return size hint + +TQSize ExampleButton::sizeHint() const +{ + return TQSize(BUTTONSIZE, BUTTONSIZE); +} + +////////////////////////////////////////////////////////////////////////////// +// enterEvent() +// ------------ +// Mouse has entered the button + +void ExampleButton::enterEvent(TQEvent *e) +{ + // if we wanted to do mouseovers, we would keep track of it here + TQButton::enterEvent(e); +} + +////////////////////////////////////////////////////////////////////////////// +// leaveEvent() +// ------------ +// Mouse has left the button + +void ExampleButton::leaveEvent(TQEvent *e) +{ + // if we wanted to do mouseovers, we would keep track of it here + TQButton::leaveEvent(e); +} + +////////////////////////////////////////////////////////////////////////////// +// mousePressEvent() +// ----------------- +// Button has been pressed + +void ExampleButton::mousePressEvent(TQMouseEvent* e) +{ + lastmouse_ = e->button(); + + // translate and pass on mouse event + int button = LeftButton; + if ((type_ != ButtonMax) && (e->button() != LeftButton)) { + button = NoButton; // middle & right buttons inappropriate + } + TQMouseEvent me(e->type(), e->pos(), e->globalPos(), + button, e->state()); + TQButton::mousePressEvent(&me); +} + +////////////////////////////////////////////////////////////////////////////// +// mouseReleaseEvent() +// ----------------- +// Button has been released + +void ExampleButton::mouseReleaseEvent(TQMouseEvent* e) +{ + lastmouse_ = e->button(); + + // translate and pass on mouse event + int button = LeftButton; + if ((type_ != ButtonMax) && (e->button() != LeftButton)) { + button = NoButton; // middle & right buttons inappropriate + } + TQMouseEvent me(e->type(), e->pos(), e->globalPos(), button, e->state()); + TQButton::mouseReleaseEvent(&me); +} + +////////////////////////////////////////////////////////////////////////////// +// drawButton() +// ------------ +// Draw the button + +void ExampleButton::drawButton(TQPainter *painter) +{ + if (!ExampleFactory::initialized()) return; + + TQColorGroup group; + int dx, dy; + + // paint a plain box with border + group = KDecoration::options()-> + colorGroup(KDecoration::ColorButtonBg, client_->isActive()); + painter->fillRect(rect(), group.button()); + painter->setPen(group.dark()); + painter->drawRect(rect()); + + if (type_ == ButtonMenu) { + // we paint the mini icon (which is 16 pixels high) + dx = (width() - 16) / 2; + dy = (height() - 16) / 2; + if (isDown()) { dx++; dy++; } + painter->drawPixmap(dx, dy, client_->icon().pixmap(TQIconSet::Small, + TQIconSet::Normal)); + } else if (deco_) { + // otherwise we paint the deco + dx = (width() - DECOSIZE) / 2; + dy = (height() - DECOSIZE) / 2; + if (isDown()) { dx++; dy++; } + painter->setPen(group.dark()); + painter->drawPixmap(dx, dy, *deco_); + } +} + +////////////////////////////////////////////////////////////////////////////// +// ExampleClient Class // +////////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////// +// ExampleClient() +// --------------- +// Constructor + +ExampleClient::ExampleClient(KDecorationBridge *b, KDecorationFactory *f) + : KDecoration(b, f) { ; } + +ExampleClient::~ExampleClient() +{ + for (int n=0; ninstallEventFilter(this); + + // for flicker-free redraws + widget()->setBackgroundMode(NoBackground); + + // setup layout + TQGridLayout *mainlayout = new TQGridLayout(widget(), 4, 3); // 4x3 grid + TQHBoxLayout *titlelayout = new TQHBoxLayout(); + titlebar_ = new TQSpacerItem(1, TITLESIZE, TQSizePolicy::Expanding, + TQSizePolicy::Fixed); + + mainlayout->setResizeMode(TQLayout::FreeResize); + mainlayout->addRowSpacing(0, FRAMESIZE); + mainlayout->addRowSpacing(3, FRAMESIZE*2); + mainlayout->addColSpacing(0, FRAMESIZE); + mainlayout->addColSpacing(2, FRAMESIZE); + + mainlayout->addLayout(titlelayout, 1, 1); + if (isPreview()) { + mainlayout->addWidget( + new TQLabel(i18n("
Example preview
"), + widget()), 2, 1); + } else { + mainlayout->addItem(new TQSpacerItem(0, 0), 2, 1); + } + + // the window should stretch + mainlayout->setRowStretch(2, 10); + mainlayout->setColStretch(1, 10); + + // setup titlebar buttons + for (int n=0; ntitleButtonsLeft()); + titlelayout->addItem(titlebar_); + addButtons(titlelayout, options()->titleButtonsRight()); + + // connections + connect(this, SIGNAL(keepAboveChanged(bool)), SLOT(keepAboveChange(bool))); + connect(this, SIGNAL(keepBelowChanged(bool)), SLOT(keepBelowChange(bool))); +} + +////////////////////////////////////////////////////////////////////////////// +// addButtons() +// ------------ +// Add buttons to title layout + +void ExampleClient::addButtons(TQBoxLayout *layout, const TQString& s) +{ + const unsigned char *bitmap; + TQString tip; + + if (s.length() > 0) { + for (unsigned n=0; n < 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->addWidget(button[ButtonMenu]); + } + break; + + case 'S': // Sticky button + if (!button[ButtonSticky]) { + if (isOnAllDesktops()) { + bitmap = stickydown_bits; + tip = i18n("Un-Sticky"); + } else { + bitmap = sticky_bits; + tip = i18n("Sticky"); + } + button[ButtonSticky] = + new ExampleButton(this, "sticky", tip, + ButtonSticky, bitmap); + connect(button[ButtonSticky], SIGNAL(clicked()), + this, SLOT(toggleOnAllDesktops())); + layout->addWidget(button[ButtonSticky]); + } + break; + + case 'H': // Help button + if ((!button[ButtonHelp]) && providesContextHelp()) { + button[ButtonHelp] = + new ExampleButton(this, "help", i18n("Help"), + ButtonHelp, help_bits); + connect(button[ButtonHelp], SIGNAL(clicked()), + this, SLOT(showContextHelp())); + layout->addWidget(button[ButtonHelp]); + } + break; + + case 'I': // Minimize button + if ((!button[ButtonMin]) && isMinimizable()) { + button[ButtonMin] = + new ExampleButton(this, "iconify", i18n("Minimize"), + ButtonMin, min_bits); + connect(button[ButtonMin], SIGNAL(clicked()), + this, SLOT(minimize())); + layout->addWidget(button[ButtonMin]); + } + break; + + case 'A': // Maximize button + if ((!button[ButtonMax]) && isMaximizable()) { + if (maximizeMode() == MaximizeFull) { + bitmap = minmax_bits; + tip = i18n("Restore"); + } else { + bitmap = max_bits; + tip = i18n("Maximize"); + } + button[ButtonMax] = + new ExampleButton(this, "maximize", tip, + ButtonMax, bitmap); + connect(button[ButtonMax], SIGNAL(clicked()), + this, SLOT(maxButtonPressed())); + layout->addWidget(button[ButtonMax]); + } + break; + + case 'X': // Close button + if ((!button[ButtonClose]) && isCloseable()) { + button[ButtonClose] = + new ExampleButton(this, "close", i18n("Close"), + ButtonClose, close_bits); + connect(button[ButtonClose], SIGNAL(clicked()), + this, SLOT(closeWindow())); + layout->addWidget(button[ButtonClose]); + } + break; + + case 'F': // Above button + if ((!button[ButtonAbove])) { + button[ButtonAbove] = + new ExampleButton(this, "above", + i18n("Keep Above Others"), ButtonAbove, + keepAbove() ? above_on_bits : above_off_bits); + connect(button[ButtonAbove], SIGNAL(clicked()), + this, SLOT(aboveButtonPressed())); + layout->addWidget(button[ButtonAbove]); + } + break; + + case 'B': // Below button + if ((!button[ButtonBelow])) { + button[ButtonBelow] = + new ExampleButton(this, "below", + i18n("Keep Below Others"), ButtonBelow, + keepBelow() ? below_on_bits : below_off_bits); + connect(button[ButtonBelow], SIGNAL(clicked()), + this, SLOT(belowButtonPressed())); + layout->addWidget(button[ButtonBelow]); + } + break; + + case 'L': // Shade button + if ((!button[ButtonShade && isShadeable()])) { + if ( isSetShade()) { + bitmap = shade_on_bits; + tip = i18n("Unshade"); + } else { + bitmap = shade_off_bits; + tip = i18n("Shade"); + } + button[ButtonShade] = + new ExampleButton(this, "shade", tip, + ButtonShade, bitmap); + connect(button[ButtonShade], SIGNAL(clicked()), + this, SLOT(shadeButtonPressed())); + layout->addWidget(button[ButtonShade]); + } + break; + + case '_': // Spacer item + layout->addSpacing(FRAMESIZE); + } + } + } +} + +////////////////////////////////////////////////////////////////////////////// +// activeChange() +// -------------- +// window active state has changed + +void ExampleClient::activeChange() +{ + for (int n=0; nreset(); + widget()->repaint(false); +} + +////////////////////////////////////////////////////////////////////////////// +// captionChange() +// --------------- +// The title has changed + +void ExampleClient::captionChange() +{ + widget()->repaint(titlebar_->geometry(), false); +} + +////////////////////////////////////////////////////////////////////////////// +// desktopChange() +// --------------- +// Called when desktop/sticky changes + +void ExampleClient::desktopChange() +{ + bool d = isOnAllDesktops(); + if (button[ButtonSticky]) { + button[ButtonSticky]->setBitmap(d ? stickydown_bits : sticky_bits); + TQToolTip::remove(button[ButtonSticky]); + TQToolTip::add(button[ButtonSticky], d ? i18n("Un-Sticky") : i18n("Sticky")); + } +} + +////////////////////////////////////////////////////////////////////////////// +// iconChange() +// ------------ +// The title has changed + +void ExampleClient::iconChange() +{ + if (button[ButtonMenu]) { + button[ButtonMenu]->setBitmap(0); + button[ButtonMenu]->repaint(false); + } +} + +////////////////////////////////////////////////////////////////////////////// +// maximizeChange() +// ---------------- +// Maximized state has changed + +void ExampleClient::maximizeChange() +{ + bool m = (maximizeMode() == MaximizeFull); + if (button[ButtonMax]) { + button[ButtonMax]->setBitmap(m ? minmax_bits : max_bits); + TQToolTip::remove(button[ButtonMax]); + TQToolTip::add(button[ButtonMax], m ? i18n("Restore") : i18n("Maximize")); + } +} + +////////////////////////////////////////////////////////////////////////////// +// shadeChange() +// ------------- +// Called when window shading changes + +void ExampleClient::shadeChange() +{ + bool s = isSetShade(); + if (button[ButtonShade]) { + button[ButtonShade]->setBitmap(s ? shade_on_bits : shade_off_bits); + TQToolTip::remove(button[ButtonShade]); + TQToolTip::add(button[ButtonShade], s ? i18n("Unshade") : i18n("Shade")); + } +} + +////////////////////////////////////////////////////////////////////////////// +// keepAboveChange() +// ------------ +// The above state has changed + +void ExampleClient::keepAboveChange(bool a) +{ + if (button[ButtonAbove]) { + button[ButtonAbove]->setBitmap(a ? above_on_bits : above_off_bits); + } +} + +////////////////////////////////////////////////////////////////////////////// +// keepBelowChange() +// ------------ +// The below state has changed + +void ExampleClient::keepBelowChange(bool b) +{ + if (button[ButtonBelow]) { + button[ButtonBelow]->setBitmap(b ? below_on_bits : below_off_bits); + } +} + +////////////////////////////////////////////////////////////////////////////// +// borders() +// ---------- +// Get the size of the borders + +void ExampleClient::borders(int &l, int &r, int &t, int &b) const +{ + l = r = FRAMESIZE; + t = TITLESIZE + FRAMESIZE; + b = FRAMESIZE * 2; +} + +////////////////////////////////////////////////////////////////////////////// +// resize() +// -------- +// Called to resize the window + +void ExampleClient::resize(const TQSize &size) +{ + widget()->resize(size); +} + +////////////////////////////////////////////////////////////////////////////// +// minimumSize() +// ------------- +// Return the minimum allowable size for this window + +TQSize ExampleClient::minimumSize() const +{ + return widget()->minimumSize(); +} + +////////////////////////////////////////////////////////////////////////////// +// mousePosition() +// --------------- +// Return logical mouse position + +KDecoration::Position ExampleClient::mousePosition(const TQPoint &point) const +{ + const int corner = 24; + Position pos; + + if (point.y() <= FRAMESIZE) { + // inside top frame + if (point.x() <= corner) pos = PositionTopLeft; + else if (point.x() >= (width()-corner)) pos = PositionTopRight; + else pos = PositionTop; + } else if (point.y() >= (height()-FRAMESIZE*2)) { + // inside handle + if (point.x() <= corner) pos = PositionBottomLeft; + else if (point.x() >= (width()-corner)) pos = PositionBottomRight; + else pos = PositionBottom; + } else if (point.x() <= FRAMESIZE) { + // on left frame + if (point.y() <= corner) pos = PositionTopLeft; + else if (point.y() >= (height()-corner)) pos = PositionBottomLeft; + else pos = PositionLeft; + } else if (point.x() >= width()-FRAMESIZE) { + // on right frame + if (point.y() <= corner) pos = PositionTopRight; + else if (point.y() >= (height()-corner)) pos = PositionBottomRight; + else pos = PositionRight; + } else { + // inside the frame + pos = PositionCenter; + } + return pos; +} + +////////////////////////////////////////////////////////////////////////////// +// eventFilter() +// ------------- +// Event filter + +bool ExampleClient::eventFilter(TQObject *obj, TQEvent *e) +{ + if (obj != widget()) return false; + + switch (e->type()) { + case TQEvent::MouseButtonDblClick: { + mouseDoubleClickEvent(static_cast(e)); + return true; + } + case TQEvent::MouseButtonPress: { + processMousePressEvent(static_cast(e)); + return true; + } + case TQEvent::Paint: { + paintEvent(static_cast(e)); + return true; + } + case TQEvent::Resize: { + resizeEvent(static_cast(e)); + return true; + } + case TQEvent::Show: { + showEvent(static_cast(e)); + return true; + } + default: { + return false; + } + } + + return false; +} + +////////////////////////////////////////////////////////////////////////////// +// mouseDoubleClickEvent() +// ----------------------- +// Doubleclick on title + +void ExampleClient::mouseDoubleClickEvent(TQMouseEvent *e) +{ + if (titlebar_->geometry().contains(e->pos())) titlebarDblClickOperation(); +} + +////////////////////////////////////////////////////////////////////////////// +// paintEvent() +// ------------ +// Repaint the window + +void ExampleClient::paintEvent(TQPaintEvent*) +{ + if (!ExampleFactory::initialized()) return; + + TQColorGroup group; + TQPainter painter(widget()); + + // draw the titlebar + TQRect title(titlebar_->geometry()); + group = options()->colorGroup(KDecoration::ColorTitleBar, isActive()); + painter.fillRect(title, group.background()); + painter.setPen(group.dark()); + painter.drawRect(title); + + // draw title text + painter.setFont(options()->font(isActive(), false)); + painter.setPen(options()->color(KDecoration::ColorFont, isActive())); + painter.drawText(title.x() + FRAMESIZE, title.y(), + title.width() - FRAMESIZE * 2, title.height(), + ExampleFactory::titleAlign() | AlignVCenter, + caption()); + + // draw frame + group = options()->colorGroup(KDecoration::ColorFrame, isActive()); + + TQRect 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()); + + // outline the frame + painter.setPen(group.dark()); + frame = widget()->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); +} + +////////////////////////////////////////////////////////////////////////////// +// resizeEvent() +// ------------- +// Window is being resized + +void ExampleClient::resizeEvent(TQResizeEvent *) +{ + if (widget()->isShown()) { + TQRegion region = widget()->rect(); + region = region.subtract(titlebar_->geometry()); + widget()->erase(region); + } +} + +////////////////////////////////////////////////////////////////////////////// +// showEvent() +// ----------- +// Window is being shown + +void ExampleClient::showEvent(TQShowEvent *) +{ + widget()->repaint(); +} + +////////////////////////////////////////////////////////////////////////////// +// maxButtonPressed() +// ----------------- +// Max button was pressed + +void ExampleClient::maxButtonPressed() +{ + if (button[ButtonMax]) { +#if KDE_IS_VERSION(3, 3, 0) + maximize(button[ButtonMax]->lastMousePress()); +#else + switch (button[ButtonMax]->lastMousePress()) { + case MidButton: + maximize(maximizeMode() ^ MaximizeVertical); + break; + case RightButton: + maximize(maximizeMode() ^ MaximizeHorizontal); + break; + default: + (maximizeMode() == MaximizeFull) ? maximize(MaximizeRestore) + : maximize(MaximizeFull); + } +#endif + } +} + +////////////////////////////////////////////////////////////////////////////// +// shadeButtonPressed() +// ----------------- +// Shade button was pressed + +void ExampleClient::shadeButtonPressed() +{ + if (button[ButtonShade]) { + setShade( !isSetShade()); + } +} + +////////////////////////////////////////////////////////////////////////////// +// aboveButtonPressed() +// ----------------- +// Above button was pressed + +void ExampleClient::aboveButtonPressed() +{ + if (button[ButtonAbove]) { + setKeepAbove( !keepAbove()); + } +} + +////////////////////////////////////////////////////////////////////////////// +// belowButtonPressed() +// ----------------- +// Below button was pressed + +void ExampleClient::belowButtonPressed() +{ + if (button[ButtonBelow]) { + setKeepBelow( !keepBelow()); + } +} + +////////////////////////////////////////////////////////////////////////////// +// menuButtonPressed() +// ------------------- +// Menu button was pressed (popup the menu) + +void ExampleClient::menuButtonPressed() +{ + if (button[ButtonMenu]) { + TQPoint p(button[ButtonMenu]->rect().bottomLeft().x(), + button[ButtonMenu]->rect().bottomLeft().y()); + KDecorationFactory* f = factory(); + showWindowMenu(button[ButtonMenu]->mapToGlobal(p)); + if (!f->exists(this)) return; // decoration was destroyed + button[ButtonMenu]->setDown(false); + } +} + +#include "exampleclient.moc" diff --git a/client/exampleclient.h b/client/exampleclient.h new file mode 100644 index 0000000..33b52ec --- /dev/null +++ b/client/exampleclient.h @@ -0,0 +1,165 @@ +////////////////////////////////////////////////////////////////////////////// +// exampleclient.h +// ------------------- +// Example window decoration for KDE +// ------------------- +// Copyright (c) 2003, 2004 David Johnson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#ifndef EXAMPLECLIENT_H +#define EXAMPLECLIENT_H + +#include +#include +#include + +class QSpacerItem; +class QPoint; + +namespace Example { + +class ExampleClient; + +enum ButtonType { + ButtonHelp=0, + ButtonMax, + ButtonMin, + ButtonClose, + ButtonMenu, + ButtonSticky, + ButtonAbove, + ButtonBelow, + ButtonShade, + ButtonTypeCount +}; + +// ExampleFactory ///////////////////////////////////////////////////////////// + +class ExampleFactory: public KDecorationFactory +{ +public: + ExampleFactory(); + virtual ~ExampleFactory(); + virtual KDecoration *createDecoration(KDecorationBridge *b); + virtual bool reset(unsigned long changed); + + static bool initialized(); + static TQt::AlignmentFlags titleAlign(); + +private: + bool readConfig(); + +private: + static bool initialized_; + static TQt::AlignmentFlags titlealign_; +}; + +inline bool ExampleFactory::initialized() + { return initialized_; } + +inline TQt::AlignmentFlags ExampleFactory::titleAlign() + { return titlealign_; } + +// ExampleButton ////////////////////////////////////////////////////////////// + +class ExampleButton : public TQButton +{ +public: + ExampleButton(ExampleClient *parent=0, const char *name=0, + const TQString &tip=NULL, + ButtonType type=ButtonHelp, + const unsigned char *bitmap=0); + ~ExampleButton(); + + void setBitmap(const unsigned char *bitmap); + TQSize sizeHint() const; + ButtonState lastMousePress() const; + void reset(); + +private: + void enterEvent(TQEvent *e); + void leaveEvent(TQEvent *e); + void mousePressEvent(TQMouseEvent *e); + void mouseReleaseEvent(TQMouseEvent *e); + void drawButton(TQPainter *painter); + +private: + ExampleClient *client_; + ButtonType type_; + TQBitmap *deco_; + ButtonState lastmouse_; +}; + +inline TQt::ButtonState ExampleButton::lastMousePress() const + { return lastmouse_; } + +inline void ExampleButton::reset() + { repaint(false); } + +// ExampleClient ////////////////////////////////////////////////////////////// + +class ExampleClient : public KDecoration +{ + Q_OBJECT +public: + ExampleClient(KDecorationBridge *b, KDecorationFactory *f); + virtual ~ExampleClient(); + + virtual void init(); + + virtual void activeChange(); + virtual void desktopChange(); + virtual void captionChange(); + virtual void iconChange(); + virtual void maximizeChange(); + virtual void shadeChange(); + + virtual void borders(int &l, int &r, int &t, int &b) const; + virtual void resize(const TQSize &size); + virtual TQSize minimumSize() const; + virtual Position mousePosition(const TQPoint &point) const; + +private: + void addButtons(TQBoxLayout* layout, const TQString& buttons); + + bool eventFilter(TQObject *obj, TQEvent *e); + void mouseDoubleClickEvent(TQMouseEvent *e); + void paintEvent(TQPaintEvent *e); + void resizeEvent(TQResizeEvent *); + void showEvent(TQShowEvent *); + +private slots: + void maxButtonPressed(); + void menuButtonPressed(); + void aboveButtonPressed(); + void belowButtonPressed(); + void shadeButtonPressed(); + void keepAboveChange(bool); + void keepBelowChange(bool); + +private: + ExampleButton *button[ButtonTypeCount]; + TQSpacerItem *titlebar_; +}; + +} // namespace Example + +#endif // EXAMPLECLIENT_H diff --git a/configure.in.in b/configure.in.in new file mode 100644 index 0000000..bd8d1bc --- /dev/null +++ b/configure.in.in @@ -0,0 +1,3 @@ +#MIN_CONFIG + +AM_INIT_AUTOMAKE(example, 0.8) diff --git a/docs/images/menu.png b/docs/images/menu.png new file mode 100644 index 0000000..543f67b Binary files /dev/null and b/docs/images/menu.png differ diff --git a/docs/images/minimize.png b/docs/images/minimize.png new file mode 100644 index 0000000..21b3785 Binary files /dev/null and b/docs/images/minimize.png differ diff --git a/docs/images/motiflayout.png b/docs/images/motiflayout.png new file mode 100644 index 0000000..5154c82 Binary files /dev/null and b/docs/images/motiflayout.png differ diff --git a/docs/images/nextlayout.png b/docs/images/nextlayout.png new file mode 100644 index 0000000..7680d92 Binary files /dev/null and b/docs/images/nextlayout.png differ diff --git a/docs/images/screenshot.png b/docs/images/screenshot.png new file mode 100644 index 0000000..2d3b8e5 Binary files /dev/null and b/docs/images/screenshot.png differ diff --git a/docs/images/titlebar.png b/docs/images/titlebar.png new file mode 100644 index 0000000..63e2877 Binary files /dev/null and b/docs/images/titlebar.png differ diff --git a/docs/kwintheme.html b/docs/kwintheme.html new file mode 100644 index 0000000..d8b75e6 --- /dev/null +++ b/docs/kwintheme.html @@ -0,0 +1,1101 @@ + + + + + + + HOWTO: TWin Window Manager Decorations + + + + + +
+

HOWTO: TWin Window Manager Decorations

+ +

The default window manager for TDE is TWin. TWin has a + very flexible theming mechanism, because all decorations are + ultimately plugins to the window manager. This allows you to + change both the look (appearance) and feel (behavior) of the + window manager.

+ +

This tutorial covers creating a native TWin decoration + using C++ code, as opposed to creating a decoration for + "pixmap engine", such as the IceWM plugin. The material + presented here may be of use in creating a new pixmap engine + however.

+ +

The sample code for this tutorial, along with a copy of + this documentation, can be found here: twintheme.tar.gz [34K]. The code + snippets shown in this documentation are not documented, but + the source code itself is.

+ +

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

+ +

Note: This tutorial and accompanying + example code are based on TDE 14.x. It is not applicable to + earlier versions of TDE.

+ +

Step One: Proper Planning

+ +

Before you start working on a new decoration, you should + have some idea as to what the finished decoration 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.

+ + + + + + + + + + + + + + + +
The "motif" layoutThe "next" layout
motif layoutnext layout
+ +

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.

+ +

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 TDE + decorations. There is only a titlebar on top and a "handle" + on the bottom.

+ +

The example decoration 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, + decorations.

+ +

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

+ +

Education is also a part of planning. Read the + kdecoration.h and kdecorationfactory.h + header files. They are located in the + $TDEBASE/include directory, and are very well + documented. These should be your primary documentation for the + TWin API. Read the source code for other TDE decorations.

+ +

If you are porting an earlier TWin decoration to the new TWin + API for TDE 14, please use the PORTING document + created for this purpose. It is located in the TDE source tree + at tdebase/twin/clients/PORTING. It will help you + find the places in your decoration that need changing.

+ +

Usability

+ +

Always keep the usability of the decoration 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 + decoration usable by most people. If you must break any of + the following rules, please have a good reason to do so.

+ +
+
Honor Standard Behavior
+ +
There are certain standard behaviors that users will + expect. Users do not want to change the way they interact + with windows every time they change the decoration. The + title bar should be on the top. The maximize button should + maximize the window. The frame or handle should be large + enough to easily resize the window.
+ +
Honor User Preferences
+ +
If users do not like the default behaviors, they will + change them. Do not presume to know what is best for the + user. For example, people who use custom button positions + do so for a reason, and it will only annoy them to + disregard their preference. Those that don't will still + expect to see the standard button positions.
+
+ +

Step Two: Infrastructure

+ +

To start out your project, you'll need a build + infrastructure. This will be similar to most TDE + 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.

+ +

There should be an admin directory that + contains the standard TDE admin files. A lot of work has been + done to make your life as a TDE 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 + tdesdk package.

+ +

Please include a README and + INSTALL file. Describe what this decoration is, + and how to install it. And by all means, please state what + license your 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 COPYING file. The GPL is the + most popular for TDE programs, but many decorations are under + the MIT license, since that's what TWin itself is under.

+ +

If you're using the example package as a template, you + will need to modify the configure.in.in, + client/Makefile.am, + client/config/Makefile.am, and + example.desktop files. Simply replace all + occurances of "example" with the name of your decoration. If + you add additional source code files, you'll need to change + these files as appropriate.
Please see the + TDE Developer FAQ for more information on the standard + TDE build infrastructure.

+ +

Step Three: Configuration

+ +

If there are any user configurable aspects to your + decoration, you should create a configuration dialog. The + configuration dialog is actually a plugin to Control Center. + Common configuration options for TWin decorations include + title alignment, title bar height, and displaying an additional + handle. For the example, we will be using only one option to + specify the title alignment.

+ +

I have chosen to use TQt Designer to handle the layout of + the dialog. This makes it very easy to arrange the dialog. + Add three TQRadio buttons in a TQButtonGroup 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.

+ +

The exampleconfig.h header file is quite + short, and has only two member variables, one for the + configuration, and one for the actual dialog:

+
+...
+private:
+    TDEConfig *config_;
+    ConfigDialog *dialog_;
+
+ +

The exampleconfig.cc is almost as simple. + Since most of the GUI work is being done by TQt 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.

+
+ExampleConfig::ExampleConfig(TDEConfig* config, TQWidget* parent)
+    : TQObject(parent), config_(0), dialog_(0)
+{
+    config_ = new TDEConfig("twinexamplerc");
+    TDEGlobal::locale()->insertCatalogue("twin_example_config");
+
+    dialog_ = new ConfigDialog(parent);
+    dialog_->show();
+
+    load(config_);
+
+    connect(dialog_->titlealign, SIGNAL(clicked(int)),
+            this, SLOT(selectionChanged(int)));
+}
+
+ +

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 they change.

+
+void ExampleConfig::load(TDEConfig*)
+{
+    config_->setGroup("General");
+    TQString value = config_->readEntry("TitleAlignment", "AlignHCenter");
+    TQRadioButton *button = (TQRadioButton*)dialog_->titlealign->child(value);
+    if (button) button->setChecked(true);
+}
+
+void ExampleConfig::save(TDEConfig*)
+{
+    config_->setGroup("General");
+    TQRadioButton *button = (TQRadioButton*)dialog_->titlealign->selected();
+    if (button) config_->writeEntry("TitleAlignment", TQString(button->name()));
+    config_->sync();
+}
+
+ +

Step Four: The Factory

+ +

Each window is known as a "client", and is represented by + the ExampleClient class. If you have five windows up on the + screen, you will have five instances of ExampleClient. + Initializing and managing the decoration for multiple clients + is the job of the factory. Every decoration will + need a factory class, since this is what TWin uses to create + the decoration.

+ +

The decoration factory is often used to store global data + needed by the clients. We'll use the ExampleFactory to take + care of storing the configuration data. It could also be used + to create and store the pixmaps used by the clients.

+
+ExampleFactory::ExampleFactory()
+{
+    readConfig();
+    initialized_ = true;
+}
+
+KDecoration* ExampleFactory::createDecoration(KDecorationBridge* b)
+{
+    return new ExampleClient(b, this);
+}
+
+ +

The factory constructor reads in the configuration and + performs any other global initialization for the decoration. + TWin will call the createDecoration() method to + create the decoration for each client window.

+
+bool ExampleFactory::reset(unsigned long changed)
+{
+    initialized_ = false;
+    bool confchange = readConfig();
+    initialized_ = true;
+
+    if (confchange ||
+        (changed & (SettingDecoration | SettingButtons | SettingBorder))) {
+        return true;
+    } else {
+        resetDecorations(changed);
+        return false;
+    }
+}
+
+ +

We need to reset all the clients whenever the + configuration or settings have changed. We first read in our + decoration configuration, noting if anything has changed. + Then we used the changed parameter to find out + if any TWin settings have changed. changed is a + bit field that specifies values for + SettingDecoration, SettingColors, + SettingFont, SettingButtons, + SettingTooltips, or + SettingBorder.

+ +

If the reset() method returns true if all of + the decorations need to be remade, or false if only a repaint + is necessary. We do not remake the decorations if only the + color, font or tooltip status has changed.

+
+bool ExampleFactory::readConfig()
+{
+    TDEConfig config("twinexamplerc");
+    config.setGroup("General");
+
+    TQt::AlignmentFlags oldalign = titlealign_;
+    TQString value = config.readEntry("TitleAlignment", "AlignHCenter");
+    if (value == "AlignLeft") titlealign_ = TQt::AlignLeft;
+    else if (value == "AlignHCenter") titlealign_ = TQt::AlignHCenter;
+    else if (value == "AlignRight") titlealign_ = TQt::AlignRight;
+
+    if (oldalign == titlealign_) return false;
+    else return true;
+}
+
+ +

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

+ +

Note that we used the oldalign variable to + detect any configuration changes. We do not want to + unnecessarily remake the decorations if the configuration has + not changed.

+ +

Step Five: The Buttons

+ +

The buttons on the titlebar are derived from the QButton + class, but we still need to determine their look and + behavior.

+
+ExampleButton::ExampleButton(ExampleClient *parent, const char *name,
+                             const TQString& tip, ButtonType type,
+                             const unsigned char *bitmap)
+    : TQButton(parent->widget(), name), client_(parent), type_(type),
+      deco_(0), lastmouse_(NoButton)
+{
+    setBackgroundMode(NoBackground);
+    setFixedSize(BUTTONSIZE, BUTTONSIZE);
+    setCursor(arrowCursor);
+    if (bitmap) setBitmap(bitmap);
+    TQtoolTip::add(this, tip);
+}
+
+ +

The constructor sets the size of the button, the cursor + that will be shown when the mouse is over the button, and 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.

+
+void ExampleButton::setBitmap(const unsigned char *bitmap)
+{
+    if (!bitmap) return;
+
+    if (deco_) delete deco_;
+    deco_ = new TQBitmap(DECOSIZE, DECOSIZE, bitmap, true);
+    deco_->setMask(*deco_);
+    repaint(false);
+}
+
+ +

A TQBitmap has only two colors, foreground and background. + If we wanted more colors for our decorations we could have + used TQPixmap instead. We repaint the button every time the + bitmap is changed.

+ +

For the menu button we do use a TQPixmap. In this + case we will use the application icon. We will access this + pixmap from the ExampleClient when we draw the button.

+
+void ExampleButton::enterEvent(TQEvent *e)
+{
+    TQButton::enterEvent(e);
+}
+
+void ExampleButton::leaveEvent(TQEvent *e)
+{
+    TQButton::leaveEvent(e);
+}
+
+ +

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.

+
+void ExampleButton::mousePressEvent(TQMouseEvent* e)
+{
+    lastmouse_ = e->button();
+    int button = LeftButton;
+    if ((type_ != ButtonMax) && (e->button() != LeftButton)) {
+        button = NoButton;
+    }
+    TQMouseEvent me(e->type(), e->pos(), e->globalPos(),
+                   button, e->state());
+    TQButton::mousePressEvent(&me);
+}
+
+void ExampleButton::mouseReleaseEvent(TQMouseEvent* e)
+{
+    lastmouse_ = e->button();
+    int button = LeftButton;
+    if ((type_ != ButtonMax) && (e->button() != LeftButton)) {
+        button = NoButton;
+    }
+    TQMouseEvent me(e->type(), e->pos(), e->globalPos(),
+                   button, e->state());
+    TQButton::mouseReleaseEvent(&me);
+}
+
+ +

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.

+ +

Unless it is the maximize button, it is a good idea for the + buttons to react to left mouse presses only. Only respond to + middle or right clicks if your button makes use of them. In our + example, we translate middle and right clicks to "NoButton" if + the button is not the maximize button, and pass it on to the base + class.

+ +

Notice that we translate all other clicks to "LeftButton". + This is because the base TQButton class only responds to left + mouse button clicks. We're saving the actual button pressed + since our maximize button will have different behaviors + depending on which button was pressed.

+ +
+void ExampleButton::drawButton(TQPainter *painter)
+{
+    if (!ExampleFactory::initialized()) return;
+
+    TQColorGroup group;
+    int dx, dy;
+    ...
+
+ +

The last method to ExampleButton is to draw the button. We + return if the factory 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.

+
+    ...
+    group = KDecoration::options()->colorGroup(KDecoration::ColorButtonBg, client_->isActive());
+    painter->fillRect(rect(), group.button());
+    painter->setPen(group.dark());
+    painter->drawRect(rect());
+    ...
+
+ +

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.

+ +

Notice the call to the KDecoration::options + object. TWin keeps track of several configuration items which + we can access through the the + KDecoration::options class. One of these items + is a TQColorGroup generated from the user defined button + color.

+
+    ...
+    if (type_ == ButtonMenu) {
+        dx = (width() - 16) / 2;
+        dy = (height() - 16) / 2;
+        if (isDown()) { dx++; dy++; }
+        painter->drawPixmap(dx, dy,
+            client_->icon().pixmap(TQIconSet::Small, TQIconSet::Normal));
+    } else if (deco_) {
+        dx = (width() - DECOSIZE) / 2;
+        dy = (height() - DECOSIZE) / 2;
+        if (isDown()) { dx++; dy++; }
+        painter->setPen(group.dark());
+        painter->drawPixmap(dx, dy, *deco_);
+    }
+}
+
+ +

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. Notice that + we get this pixmap from the client. If it's any other button, + we draw the bitmap decoration.

+ + + + + + + + + + + + + + + +
Minimize ButtonMenu Button
minimize buttonmenu button
+ +

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.

+ +

Step Six: The Client

+ +

The ExampleClient class is the heart of the decoration. Is + provides most of the theming, and contains the application + window and the title bar buttons.

+
+ExampleClient::ExampleClient(KDecorationBridge *b, KDecorationFactory *f)
+    : KDecoration(b, f) { ; }
+
+ +

The constructor doesn't do anything. All of the + initialization is done in the init() method, + which is called by TWin.

+
+void ExampleClient::init()
+{
+    createMainWidget(WResizeNoErase | WRepaintNoErase);
+    widget()->installEventFilter(this);
+    widget()->setBackgroundMode(NoBackground);
+    ...
+
+ +

In the initializer we create the main widget of the + decoration, passing in a couple of flags to help eliminate + some flicker when resizing and repainting windows. This + should be the first method called in init(). We + also set a NoBackground mode for the same reason. An event + filter is set so that we receive important events. More + information on this event filter is given below.

+
+    ...
+    TQGridLayout *mainlayout = new TQGridLayout(widget(), 4, 3); // 4x3 grid
+    TQHBoxLayout *titlelayout = new TQHBoxLayout();
+    titlebar_ = new TQSpacerItem(1, TITLESIZE, TQSizePolicy::Expanding,
+                                TQSizePolicy::Fixed);
+
+    mainlayout->setResizeMode(TQLayout::FreeResize);
+    mainlayout->addRowSpacing(0, FRAMESIZE);
+    mainlayout->addRowSpacing(3, FRAMESIZE*2);
+    mainlayout->addColSpacing(0, FRAMESIZE);
+    mainlayout->addColSpacing(2, FRAMESIZE);
+    mainlayout->addLayout(titlelayout, 1, 1);
+    ...
+
+ +

TQt's layout classes are very flexible and powerful, so we + use them to layout our decoration. We create a four row by + three column grid. See the "motif" diagram above for a visual + explanation.

+ +

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

+
+    if (isPreview()) {
+        mainlayout->addWidget(
+        new TQLabel(i18n("<b><center>Example preview</center></b>"),
+        widget()), 2, 1);
+    } else {
+        mainlayout->addItem(new TQSpacerItem(0, 0), 2, 1);
+    }
+
+ +

Normally the center of the layout is a spacer item. The + actual client widnow is drawn by TWin on top of the spacer. + But when the decoration is previewed in the Control Center, + we need to set our own widget for display purposes. In this + case we simply make it a label with the name of the + decoration.

+ +
+     ...
+    mainlayout->setRowStretch(2, 10);
+    mainlayout->setColStretch(1, 10);
+    ...
+
+ +

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.

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

To finish up initialization we layout the titlebar. The + options object will tell us the button layout + that the user has chosen. This options object is + the same as the KDecoration::options object we + used earlier in the button class. Both the local and the + global static objects are identical. Between the left and the + right buttons is the titlebar_ spacer we + created. This spacer will stretch horizontally as needed, but + keep a fixed vertical height.

+ +

Adding Buttons

+ +

I have created an addButton() method to ease + the creation and layout of the title bar buttons. Most + decorations that honor custom button layouts use a similar + method. The method is lengthy, but the concept is simple.

+
+void ExampleClient::addButtons(TQBoxLayout *layout, const TQString& s)
+{
+    unsigned char *bitmap;
+    TQString tip;
+
+    if (s.length() > 0) {
+        for (unsigned n=0; n < 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->addWidget(button[ButtonMenu]);
+                  }
+                  break;
+    ...
+
+ +

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 menuButtonPressed() method, and + adds it to the layout.

+ +

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 + initializer. An enum is used to conveniently access them

+ +
+    ...
+              case 'S': // Sticky button
+                  if (!button[ButtonSticky]) {
+		      if (isOnAllDesktops()) {
+			  bitmap = stickydown_bits;
+			  tip = i18n("Un-Sticky");
+		      } else {
+			  bitmap = sticky_bits;
+			  tip = i18n("Sticky");
+		      }
+                      button[ButtonSticky] =
+                          new ExampleButton(this, "sticky", tip,
+                                            ButtonSticky, bitmap);
+                      connect(button[ButtonSticky], SIGNAL(clicked()),
+                              this, SLOT(toggleOnAllDesktops()));
+                      layout->addWidget(button[ButtonSticky]);
+                  }
+                  break;
+    ...
+
+ +

The sticky button is similar. The difference is that we must + check the state of the window. We set the appropriate bitmap and + tooltip text depending on the "sticky" state of the window. A + similar state check also needs to be made when creating the + maximize button. See the example source code for the details.

+
+    ...
+              case '_': // Spacer item
+                  layout->addSpacing(FRAMESIZE);
+            }
+        }
+    }
+}
+
+ +

A spacing in the button layout is a special case. In the + example decoration we merely add a small bit of spacing to + the layout.

+ +

The finished title bar layout will look like this:

+ + + +

Painting the Window

+
+void ExampleClient::paintEvent(TQPaintEvent*)
+{
+    if (!ExampleFactory::initialized()) return;
+    TQColorGroup group;
+    TQPainter painter(widget());
+    ...
+
+ +

Painting the the window is simply a matter of determining + the coordinates of the various parts, then using the standard + TQPainter 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.

+
+    ...
+    TQRect title(titlebar_->geometry());
+    group = options()->colorGroup(KDecoration::ColorTitleBar, isActive());
+    painter.fillRect(title, group.background());
+    painter.setPen(group.dark());
+    painter.drawRect(title);
+    ...
+
+ +

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.

+ +

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.

+
+    ...
+    painter.setFont(options()->font(isActive(), false));
+    painter.setPen(options()->color(KDecoration::ColorFont, isActive()));
+    painter.drawText(title.x() + FRAMESIZE, title.y(),
+                     title.width() - FRAMESIZE * 2, title.height(),
+                     ExampleFactory::titleAlign() | AlignVCenter,
+                     caption());
+    ...
+
+ +

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

+ +

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

+
+    ...
+    group = options()->colorGroup(KDecoration::ColorFrame, isActive());
+
+    TQRect 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());
+
+    // outline the frame
+    painter.setPen(group.dark());
+    frame = widget()->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);
+}
+
+ +

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.

+ +

Event Handling

+ +

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. As you may have noticed, our decoration is not + a widget. Instead it has a TQWidget as a member. This widget is + placed behind the application window when it is drawn. The event + filter is so that our decoration class can handle the events + meant for the widget member.

+
+bool ExampleClient::eventFilter(TQObject *obj, TQEvent *e)
+{
+    if (obj != widget()) return false;
+
+    switch (e->type()) {
+      case TQEvent::MouseButtonDblClick: {
+          mouseDoubleClickEvent(static_cast<TQMouseEvent *>(e));
+          return true;
+      }
+      case TQEvent::MouseButtonPress: {
+          processMousePressEvent(static_cast<TQMouseEvent *>(e));
+          return true;
+      }
+      case TQEvent::Paint: {
+          paintEvent(static_cast<TQPaintEvent *>(e));
+          return true;
+      }
+      case TQEvent::Resize: {
+          resizeEvent(static_cast<TQResizeEvent *>(e));
+          return true;
+      }
+      case TQEvent::Show: {
+          showEvent(static_cast<TQShowEvent *>(e));
+          return true;
+      }
+      default: {
+          return false;
+      }
+    }
+    return false;
+}
+
+ +

Our event filter is simple. We merely pass on the events + to the appropriate method. If we do not handle an event, we + return false so that it will be handled + elsewhere.

+
+void ExampleClient::mouseDoubleClickEvent(TQMouseEvent *e)
+{
+    if (titlebar_->geometry().contains(e->pos()))
+        titlebarDblClickOperation();
+}
+
+ +

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 TWin to + perform the titlebarDblClickOperation() action. + Usually this shades or unshades the window, but the user can + redefine this behavior.

+ +

Other events that our decoration must handle are + resizeEvent() and showEvent(). + These are not shown here, but are included in the example + code. We already covered the paintEvent() method + earlier.

+ +

State Changes and Information

+ +

There are a lot of state changes that occur in a TWin + decoration. When a change occurs, TWin will call the + appropriate method in our class. We also need methods to + provide TWin with information about our decoration.

+
+void ExampleClient::activeChange()
+{
+    for (int n=0; n<ButtonTypeCount; n++)
+        if (button[n]) button[n]->reset(false);
+    widget()->repaint(false);
+}
+
+ +

A window is only active when it has the focus. The typical + TWin 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. If you wish to know if the window is active + or inactive, use the isActive() method.

+
+void ExampleClient::maximizeChange()
+{
+    bool m = (maximizeMode() == MaximizeFull);
+    if (button[ButtonMax]) {
+        button[ButtonMax]->setBitmap(m ? minmax_bits : max_bits);
+    TQtoolTip::remove(button[ButtonMax]);
+    TQtoolTip::add(button[ButtonMax], m ? i18n("Restore") : i18n("Maximize"));
+    }
+}
+
+ +

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

+
+void ExampleClient::borders(int &l, int &r, int &t, int &b) const
+{
+    l = r = FRAMESIZE;
+    t = TITLESIZE + FRAMESIZE;
+    b = FRAMESIZE * 2;
+}
+
+ +

The borders() method returns the sizes of the + decoration borders. These sizes might change according to the + window maximize of shaded state. They could also change if + border width is a user configurable parameter. For the + example decoration we will keep the border sizes the same for + all states.

+ +

It is important that this method return the real size of the + borders. TWin doesn't pay attention to the spacing we added to + the main widget above. All it cares about are the values we + return here. If they do not match the "reality", the the + decorations will not be drawn correctly. +

+KDecoration::Position ExampleClient::mousePosition(const TQPoint &point) const
+{
+    const int corner = 24;
+    Position pos;
+
+    if (point.y() <= FRAMESIZE) { // inside top frame
+        if (point.x() <= corner)                 pos = PositionTopLeft;
+        else if (point.x() >= (width()-corner))  pos = PositionTopRight;
+        else                                     pos = PositionTop;
+    } else if (point.y() >= (height()-FRAMESIZE*2)) { // inside handle
+        if (point.x() <= corner)                 pos = PositionBottomLeft;
+        else if (point.x() >= (width()-corner))  pos = PositionBottomRight;
+        else                                     pos = PositionBottom;
+    } else if (point.x() <= FRAMESIZE) { // on left frame
+        if (point.y() <= corner)                 pos = PositionTopLeft;
+        else if (point.y() >= (height()-corner)) pos = PositionBottomLeft;
+        else                                     pos = PositionLeft;
+    } else if (point.x() >= width()-FRAMESIZE) { // on right frame
+        if (point.y() <= corner)                 pos = PositionTopRight;
+        else if (point.y() >= (height()-corner)) pos = PositionBottomRight;
+        else                                     pos = PositionRight;
+    } else { // inside the frame
+        pos = PositionCenter;
+    }
+    return pos;
+}
+
+ +

TWin also needs to know where the mouse cursor is + logically oriented in the window. We use the mouse position + to compute the logical positions. These will be used to + change the mouse cursor and direct any resizing actions.

+ +

Actions

+ +

Finally we need to handle user actions such as mouse + clicks. These will be the slots for our earlier decoration + buttons.

+
+void ExampleClient::maxButtonPressed()
+{
+    if (button[ButtonMax]) {
+        switch (button[ButtonMax]->lastMousePress()) {
+          case MidButton:
+              maximize(maximizeMode() ^ MaximizeVertical);
+              break;
+          case RightButton:
+              maximize(maximizeMode() ^ MaximizeHorizontal);
+              break;
+          default:
+              (maximizeMode() == MaximizeFull) ? maximize(MaximizeRestore)
+                  : maximize(MaximizeFull);
+        }
+    }
+}
+
+ +

The expected behavior for TWin 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.

+ +

The maximize mode of a window can be + MaximizeRestore, MaximizeVertical, + or MaximizeHorizontal. There is also a + MaximizeFull mode, which is identical to + (MaximizeVertical | MaximizeHorizontal). We can + use this information to easily change the maximization state + of the window.

+
+void ExampleClient::menuButtonPressed()
+{
+    if (button[ButtonMenu]) {
+        TQPoint p(button[ButtonMenu]->rect().bottomLeft().x(),
+                 button[ButtonMenu]->rect().bottomLeft().y());
+        KDecorationFactory* f = factory();
+        showWindowMenu(button[ButtonMenu]->mapToGlobal(p));
+        if (!f->exists(this)) return; // 'this' was destroyed
+        button[ButtonMenu]->setDown(false);
+    }
+}
+
+ +

Clicking in the menu button brings up the window menu. We + want this menu positioned just below our button, so we pass + this point to TWin's showWindowMenu() + method.

+ +

For some decorations, a double click in the menu button + will close the window. There is a small controversy over + whether this is desirable behavior. I have decided not to + implement "double-click to close" functionality for the + example.

+ +

Notice the call the exists(). If the user + selects the "close" option from the menu, then the window + decoration will be destroyed. This will invalidate the current + decoration object, "this".

+ +

Wrapping Things Up

+ +

I haven't shown all of the example source code here, but + it's all included in the twintheme.tar.gz package. Besides + reading though the example, also look at the + kdecoration.h header file located in + $TDEDIR/include/twin/lib. This header file is + well documented. Also look at other TWin decorations.

+ +

The finished product isn't pretty, but it's fully + functional. Think of it as your starting point for your own + TWin decoration.

+ finished decoration + +

Tips and Tricks

+ +

Developing a new TWin decoration can be sometimes + frustrating. It seems that if you want to test a minor code + change you must kill the existing TWin window manager and then + restart it. If you know some tricks, however, it isn't + difficult.

+ +

If all you're interested in seeing are changes to the look of + the decoration, you can see them in the KControl window decoration + module preview. If you're interested in seeing the behavior of the + decoration, then you must restart TWin. The simplest and safest + way to do this is to use the --replace option to + TWin. This "replaces" the current window manager with TWin, even + if the current manager is already TWin.

+ +
twin --replace &
+ +

Also remember that if you're porting an earlier TWin + decoration to the new API, use the PORTING document + to help you out.

+ +

Excercises

+ +

Here's a list of simple exercises to get you some hands on + knowledge of TWin decorations

+ +
    +
  • When we shade a window, we still draw the handle and + borders. Fix this so that when a window is shaded only the + titlebar is drawn.
  • + +
  • 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.
  • + +
  • The simple dark lines around the window parts is very + plain. It's almost, but not quite, a presentable + decoration. 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 decorations.
  • +
+
+ +

©David Johnson 2003, 2004

+ + +