[logo] JX Application Framework
   
Home     
* Cart

Introduction

Why use a framework?

Tutorial
    Class tree

    Open protocols

Download

GitHub

Vision
    API stability

    Code optimization

    Bibliography

Features
    Object messaging

    Extensibility

    Drag-and-Drop

    Networking

    3D Graphics

    Powerful tables

    Styled text editor

    Memory leak debugger

    Known bugs

    Future plans
    Projects

FAQ

Donate

Other software

What's new!

Log in  

   
     

Theme API

Note: This API is only a draft. It has not been implemented yet. Please refer to the list of unresolved issues at the bottom of this page.

Themes provide a way for the user to change the appearance of a program.

Goals

  1. Allow switching themes while the program is running.
  2. Allow any theme friendly widget's drawing code to be overridden by a theme.
  3. Allow application specific widgets to override any part of a theme by merely implementing the appropriate drawing function(s).
  4. Allow a sparse implementation: providing no function for a class implies that the base class' implementation will be used.

Unavoidable restrictions

  1. The theme has to know each widget's default action. If a widget overrides a drawing function, the theme needs to decide how to reimplement this.
  2. Because of (1), a theme can only be designed for a particular version of a library.

Design

JX themes are managed by a singleton class, JXThemeManager. This uses ACE to dynamically load a library of drawing functions. Internally, it maintains a hash table mapping class names to sets of these drawing functions. A NULL function pointer means that the base class' implementation should be used. No entry at all for the class name means that the default code should be called.

For each widget, the theme can provide the following functions:

Init
Set the border width, back color, focus color, etc.
Must be called at the end of the widget's constructor.

DrawBorder
This can draw the background, too, if it is a single pixmap, though it probably never should be, to allow scaling.

DrawBackground

Draw

The theme must also provide a global initialization function that creates the hash table. The theme must register every class name that it knows about, even ones where it provides a NULL implementation.

JXWidget includes a data member of type Time which stores a timestamp specifying when the theme specific initialization was last performed. If this timestamp is earlier than the time when the current theme was loaded, the widget needs to have ThemeReinit() called.

Since some themes (e.g. Microsoft Windows® clones) insist on providing the equivalent of a nervous twitch, themes need to be able to determine whether or not the mouse is currently over a particular widget. This is best done by calling JXWindow::GetMouseContainer() and comparing the result with this. JXWindow::SetMouseContainer() can check a flag in the widget (set by the theme) to decide whether or not to refresh it when the mouse container changes.

Modifications to existing widgets

Unfortunately, C++ isn't dynamic enough to allow overriding or changing the implementation of virtual functions at run time, unlike Python or Smalltalk. However, this can be simulated with very little extra effort via a few macros.

This allows a theme to provide a sparse implementation. If the background should simulate wood, then JXWidget::DrawBackground() can tile the pixmap and only JXTEBase has to override it to provide a plain background so the text is readable. As another example, JXButton::DrawBorder() can draw the same border for both JXTextButton and JXImageButton.

JXThemeManager provides the following macros:

JX_THEME_PREDECL(CLASS):

extern "C"
{
void CLASS##Init(JXContainer* obj);
void CLASS##DrawBorder(JXContainer* obj, JXWindowPainter& p, const JRect& frame);
void CLASS##DrawBackground(JXContainer* obj, JXWindowPainter& p, const JRect& frame);
void CLASS##Draw(JXContainer* obj, JXWindowPainter& p, const JRect& rect);
}


JX_THEME_DECL(CLASS):

friend void CLASS##Init(JXContainer* obj);
friend void CLASS##DrawBorder(JXContainer* obj, JXWindowPainter& p, const JRect& frame);
friend void CLASS##DrawBackground(JXContainer* obj, JXWindowPainter& p, const JRect& frame);
friend void CLASS##Draw(JXContainer* obj, JXWindowPainter& p, const JRect& rect);
public:
virtual void ThemeReinit();
protected:
virtual void DrawBorder(JXWindowPainter& p, const JRect& frame);
virtual void DrawBackground(JXWindowPainter& p, const JRect& frame);
virtual void Draw(JXWindowPainter& p, const JRect& rect);


JX_THEME_INIT(CLASS):

(JXGetThemeManager())->Init(#CLASS, this);


JX_THEME_START(BASE, CLASS, FUNCTION, PAINTER, RECT):

const JXThemeManager::Result result_ =
	(JXGetThemeManager())->FUNCTION(#CLASS, this, PAINTER, RECT);
if (result_ == JXThemeManager::kCallInherited)
	{
	BASE::FUNCTION(PAINTER, RECT);
	}
else if (result_ == JXThemeManager::kNoTheme)
	{


JX_THEME_END:

	}


JX_THEME_NOOP(BASE, CLASS, FUNCTION, PAINTER, RECT):

JX_THEME_START(BASE, CLASS, FUNCTION, PAINTER, RECT)
BASE::FUNCTION(PAINTER, RECT);
JX_THEME_END


JX_THEME_BORDER_NOOP(BASE, CLASS):

void CLASS::DrawBorder(JXWindowPainter& p, const JRect& r)
{ JX_THEME_NOOP(BASE, CLASS, DrawBorder, p, r) }


JX_THEME_BACKGROUND_NOOP(BASE, CLASS):

void CLASS::DrawBackground(JXWindowPainter& p, const JRect& r)
{ JX_THEME_NOOP(BASE, CLASS, DrawBackground, p, r) }


JX_THEME_DRAW_NOOP(BASE, CLASS):

void CLASS::Draw(JXWindowPainter& p, const JRect& r)
{ JX_THEME_NOOP(BASE, CLASS, Draw, p, r) }
Each derived class of JXWidget only has to modify its code slightly:
  • Include JX_THEME_PREDECL before the class declaration to insure C linkage so the dynamically loaded function names are not subject to compiler dependent name mangling.

  • Include JX_THEME_DECL in the class declaration to declare the theme and drawing functions.

  • Call the initialization function at the end of the constructor.
  • Implement ThemeReinit().

  • Use JX_THEME_START and JX_THEME_END in all three drawing functions. Since all three functions must be implemented, the NOOP versions are provided for convenience.
As an example:
JX_THEME_PREDECL(JXButton)

class JXButton : public JXWidget
{
	existing class declaration minus drawing functions

	JX_THEME_DECL(JXButton)
}

JXButton::JXButton
	(
	no change to arguments
	)
{
	existing code minus code moved to JXButtonThemeReinit()

	JXButtonThemeReinit();
	JX_THEME_INIT(JXButton)
}

void
JXButton::ThemeReinit()
{
	JXButtonThemeReinit();
	JX_THEME_INIT(JXButton)
}

void
JXButton::JXButtonThemeReinit()
{
	
	code from original constructor that could be affected by theme
	(e.g. back color, focus color, border width, etc.)
	
}

void
JXButton::DrawBorder
	(
	JXWindowPainter& p,
	const JRect&     frame
	)
{
JX_THEME_START(JXWidget, JXButton, DrawBorder, p, frame)

	existing code

JX_THEME_END
}

JX_THEME_BACKGROUND_NOOP(JXWidget, JXButton)

JX_THEME_DRAW_NOOP(JXWidget, JXButton)

Unresolved issues

What does a theme do if it needs more space than is available for a particular widget? Good graphic design results in pleasing proportions. How does a theme preserve this? Does one have to fall back on the Springs & Struts model and let everything shuffle around to make space?

If a theme wants to draw white buttons (either always or only when the mouse is over it) and the controlling program chose white text on that particular button to make it stand out (on the assumption that the button was gray), what should the theme do about this? Should it force a change in the text color? What if the text color is gray90 or cyan or yellow? Should widgets that do not use the defaults ignore the theme? Can the theme know about color contrasts so it can change the text color to something readable? Or does having themes mean that one cannot use color to highlight certain widgets and must instead always accept the defaults? Should the theme provide information about which foreground colors are appropriate both as the default and for emphasis? Or has nobody even noticed or dealt with this issue before?

The theme cannot attach extra data to a widget because if the theme is changed while the program is running, there is no way to clean up this data without storing a list of { this, leaf_name } for every single widget in the entire program.

It ought to be possible to write a theme that can read at least GTK+/KDE pixmap themes.

Copyright © 2012 New Planet Software