[zt] The GTK+ Theme Architecture, version 2

http://www.gtk.org/~otaylor/gtk/2.0/theme-engines.html

The GTK+ Theme Architecture, version 2

Theming, the ability for the user to choose different appearances for their applications, was added to GTK+ several years ago. The theming ability in GTK+ differs somewhat from that in other toolkits in that a GTK+ theme defines not the appearance for entire widgets, but instead defines ways of drawing a number of different graphical elements, such as beveled boxes, frames, and check button indicators. This approach allows entirely new widgets that are composed with these same elements to draw correctly with the new theme.

A GTK+ theme consists of three things: a theme engine, which is a shared object including code to draw the graphical elements, a configuration file to configure the operation of the theme engine and the core parts of GTK+, and extra data files for the theme engine such as images. Some variation is possible in the use of a theme engine. A theme can include its own theme engine, it can use an existing theme engine, such as the Pixbuf theme which draws all elements in terms of images, or it can use GTK+'s built in drawing code.

One interesting thing about the way themes work in GTK+ is that the choice of theme engine is not global; rather it, and all other aspects of the theme are configured on a per-widget basis. Each widget contains a pointer to a GtkStyle object that defines the theme engine and theme options for that particular widget. So, in fact, a theme can apply different theme engines to different widgets. While using two separate external theme engines is not something that makes a lot of sense, it is pretty common to have a theme that uses a fancy external theme engine for some widgets, and the default built-in GTK+ theme engine for other widgets that require less intricate treatment.

Styles and RC styles

The RC file is the basic part of a theme. Declarations in the RC file determine the style that is applied to each widget. The file, however, does not actually define styles directly. Instead, what it defines are known as RC styles, which can be thought of partial styles. It defines RC styles and includes additional declarations to bind the RC styles to classes of widgets. To form the final style for a particular widget, all the RC styles that apply to the widget are merged together.

The full RC file syntax will not be described here. Rather, we'll just give a few examples. For a full description, see the GTK+ reference documentation. A simple definition of a a RC style in a RC file looks like:

       style "my-button" {
          xthickness = 3
          ythickness = 3
          engine "pixmap" {
             image  {
	         function = BOX
	         file     = "button1.png"
	         border   = { 3, 3, 3, 3 }
                 stretch  = TRUE
             }
        }
    

This defines a style called my-button. First we set some standard GTK+ options - the space reserved for the button's bevel in the X and Y directions.Then, with the engine declaration, we say that the theme engine to use is the pixmap engine. If the engine is not already loaded, GTK+ will look for a shared object called libpixmap.so in the standard engine directory and load it. Then it will call the theme engines parse function to parse the text inside the following braces. This defines options for the theme engine. In this case, the text describes the image file to use to draw boxes.

Once we've declared an RC style, we bind it to a class of widgets with a declaration like:

      class "GtkButton" style "my-button"
    

This declares that the my-button RC style should apply to all widgets in the GtkButton class and in classes deriving from GtkButton. Some other examples of binding statements are:

      
	# Bind to all label widgets within button widgets
	widget_class "*.GtkButton.GtkLabel"style "my-label"

	# Bind to all widgets with the name "gtk-tooltips" set
	# via gtk_widget_set_name()
	widget "*.gtk-tooltips" style "my-tooltips"
      
    

The way that multiple RC styles bound to the same widget are prioritized is that RC styles bound with widget take precedence over RC styles bound with widget_class, which, in turn, take precedence over RC styles bound with class. For multiple declarations of the same type, later declarations take precedence over earlier ones.

Theme engines

Now we've seen the basics of how a theme writer defines a theme, let's look at some details in implementing a theme engine. As mentioned above, a theme engine is a shared object. The shared object must export three entry points:

      void        theme_init            (GtkThemeEngine *engine);
      void        theme_exit            (GtkThemeEngine *engine);
      GtkRcStyle *theme_create_rc_style (GtkThemeEngine *engine)
    

The GtkThemeEngine argument that is passed to each of these functions is an object that GTK+ creates for each theme engine module that it loads. It serves as a handle for that particular module.

The theme_init() and theme_exit() functions are called immediately after the theme engine is loaded and just before it is unloaded. They take care of any initialization and cleanup that the theme engine needs. The theme_create_rc_style() function is the central function of the theme engine. When GTK+ parses an engine declaration in an RC file, it calls the theme engine's create_rc_style() function to create the GtkRcStyle object for the RC style that is being defined. This will typically not actually be an object of the GtkRcStyle class, but instead a subclass of the GtkRcStyle class that the theme engine defines. Further interaction with the theme engine is done by calling functions in the RC styles class structure. (Virtual function table.)

The GtkRcStyle class has the following virtual functions:

GtkRcStyle *(*clone) (GtkRcStyle *rc_style)

Create an empty style of the same type as this style. The default implementation, which does g_object_new (G_TYPE_FROM_INSTANCE (style)); should work in most cases.

guint (*parse) (GtkRcStyle *rc_style, GScanner *scanner)

Fill in engine specific parts of GtkRcStyle by parsing contents of brackets. Returns G_TOKEN_NONE if successful, otherwise returns the token it expected but didn't get.

void (*merge) (GtkRcStyle *dest, GtkRcStyle *src)

Combine RC style data from src into dest. This will be called under two circumstances. First, it will be called when the RC style is first created in response to a engine declaration in an RC file. In this case, it is called to copy the previously parsed information in the style declaration into the newly created RC style. Second, it is called when merging all RC files together prior to creating a style for a particular widget. If this function is overridden, the implementation in the subclass should chain to the parent implementation.

GtkStyle *(*create_style) (GtkRcStyle *rc_style)

Create an empty GtkStyle suitable to this RC style. The init_from_rc function in the GtkStyleClass structure will be called to initialize the style from the RC style.

Most theme engines can rely on the default implementation of clone(). Theme engines that do not have any special options can rely on the default implementation of parse() and merge(). However, all theme engines will have their own implementation of create_style(). The create_style() function creates an instance of a subclass of GtkStyle specific to the theme. The virtual functions in GtkStyleClass include both a number of functions to handle various administrative operations for GtkStyle and a large number of functions that take care of drawing the various different graphical elements.

The administrative functions in GtkStyleClass are:

void (*realize) (GtkStyle *style)

Initialize for a particular colormap/depth combination. style->colormap/style->depth will have been set at this point. Will typically chain to parent.

void (*unrealize) (GtkStyle *style)

Clean up for a particular colormap/depth combination. Will typically chain to parent.

void (*copy) (GtkStyle *style, GtkStyle *src)

Make style an exact duplicate of src.

GtkStyle *(*clone) (GtkStyle *style);

Create an empty style of the same type as this style. The default implementation, which does g_object_new (G_TYPE_FROM_INSTANCE (style)); should work in most cases.

void (*init_from_rc) (GtkStyle *style, GtkRcStyle *rc_style);

Initialize the GtkStyle with the values in the GtkRcStyle. should chain to the parent implementation.

The number of functions here may look intimidating, but actually, the typical theme engine can rely on the default implementations of all of these functions. The reason for this is that the rc_style field of the GtkStyle structure contains a pointer to the RC style from which the GtkStyle was created. If no additional state is needed for drawing, then the default implementations of the above functions will suffice. The realize() and unrealize() function are the most likely functions to need to be overridden. These functions would be overridden if the theme engine needed to allocate particular GDK resources for the style, such as graphics contexts, or additional colors beyond those found in the base GtkStyle structure.

The bulk of the functions in GtkStyleClass structure are the drawing functions. There are 21 of these -- too many to describe in detail here. (The For a full description, see the GTK+ reference documentation describes, or will eventually describe, each function in detail.) A typical one would be:

  void (*draw_box) (GtkStyle		*style,
		    GdkWindow		*window,
		    GtkStateType	 state_type,
		    GtkShadowType	 shadow_type,
		    GdkRectangle	*area,
	            GtkWidget		*widget,
		    const gchar		*detail,
		    gint		 x,
		    gint		 y,
		    gint		 width,
		    gint		 height);
    

The various parameters here specify extra information about what sort of box to draw, they specify how the box should be positioned, and they indicate which GDK drawable to draw onto.

style

The style parameter specifies the GtkStyle being used to draw this graphics element. It will be a member of the style subclass for the particular theme engine, so if the theme has stored additional parameters in the instance structure, it can cast the style to the theme's subclass and retrieve them.

window

This parameter specifies the GDK drawable onto which to draw the result. Despite the name, this drawable is not necessarily a GtkWindow. Instead, it might be a GdkPixmap.

state_type

The state parameter indicates the state for the element. It will be one of GTK_STATE_NORMAL, GTK_STATE_ACTIVE, GTK_STATE_PRELIGHT, GTK_STATE_SELECTED, or GTK_STATE_INSENSITIVE. The state here will usually be derived from the state of the widget -- during mouse-over a button will have the state GTK_STATE_PRELIGHT, when the button is depressed, it has the state GTK_STATE_ACTIVE, and so forth. However, sometimes a different state is used than the state of the widget to indicate ``draw this element as for a widget in the specified state.''

area

The area parameter gives a rectangle to which drawing should be clipped; it may be NULL indicating that no clipping should be done. As a special case, if the area parameter is NULL and the window parameter actual is a window (you can check using GDK_IS_WINDOW()), then in addition to simply drawing on the window, the theme engine is permitted to set the background pixmap and shape of the window.

widget

This parameter indicates the widget on which drawing is being done. It may be NULL if the drawing is not associated with a particular widget, so you should not rely on it being set. If it is set, some themes may find it useful for fine-tuning the drawing behavior.

detail

Like the widget parameter, the detail parameter is used for fine-tuning drawing. It may be NULL. If not NULL, it will be a string such as "entry_bg" or "handle" given by the caller which specifies additional information about the graphical element being drawn

As an example of the usage of the detail field, the default theme engine, when drawing a flat box checks to see if the detail field is "entry_bg", and if so, draws the background using style->base_gc instead of style->bg_gc. (style->base is the color used for the background of entries and text fields. It is, by default white instead of gray.) This is not the only way that this effect could be achieved. A more generic way for a theme engine to do this would have a boolean parameter defined in it's engine section, use_base_for_bg, and then, in the theme's RC file, have declaration like:

style "my-entry" {
  engine "my-engine" {
    use_base_for_bg = true	  
  }	  
}

class "GtkEntry" style "my-entry"
	

Hard-coding the behavior using the detail field is just a simpler way to achieve the same effect. About the only time the detail field would need to be used, is if single widget drew two elements of the same type which the theme engine needed to distinguish. This situation does not currently occur in GTK+.

x, y, width, height

These parameters specify how the box is positioned within the drawable given by window.

Finally GtkStyleClass contains a couple of other functions for miscellaneous operations relating to the appearance of widgets. These are:

  • void (*set_background) (GtkStyle      *style,
                            GdkWindow     *window,
                            GtkStateType   state_type);
    	

    Initialize background pixmap or color for the given window.

  •   GdkPixbuf * (* render_icon)   (GtkStyle               *style,
                                     const GtkIconSource    *source,
                                     GtkTextDirection        direction,
                                     GtkStateType            state,
                                     GtkIconSizeType         size,
                                     GtkWidget              *widget,
                                     const gchar            *detail);
    	

    Create an icon image suitable for the given size and state.

Most themes can simply use the default implementation of these two functions.

An example theme engine

As an example of a theme engine, we'll show some portions of the Pixbuf theme engine. We'll omit most of the code for actually parsing options from the engine sections of RC files and the actual drawing code, and concentrate on the administrative parts of the theme.

The main file of the engine is quite short; it defines four entry points: the theme_init(), theme_exit(), and theme_create_rc() entry points described above, and another entry point g_module_check_init (), that, instead of being specific to theme engines, is generic to all modules loaded via the GModule library.

In the theme_init() function, we call functions that register the two types we define in the engine - the GtkRcStyle and GtkStyle subclasses. As we'll see below, while these are simply normal GObject classes, we define them in a somewhat different way from object classes defined in a static library

G_MODULE_EXPORT void
theme_init(GtkThemeEngine * engine)
{
  pixbuf_rc_style_register_type (engine);
  pixbuf_style_register_type (engine);
}
    

The theme_exit() function is empty. We don't need to do anything when our theme engine is unloaded.

G_MODULE_EXPORT void
theme_exit(void)
{
}
    

In the theme_create_rc_style() function, we create an instance of the PixbufRcStyle type registered in the theme_init() function.

G_MODULE_EXPORT GtkRcStyle *
theme_create_rc_style (void)
{
  return GTK_RC_STYLE (g_object_new (PIXBUF_TYPE_RC_STYLE));  
}
    

The g_module_check_init() performs a checks to make sure that the version of GTK+ that we are linking against is compatible with the version for which we were compiled

G_MODULE_EXPORT const gchar* g_module_check_init (GModule *module);
const gchar*
g_module_check_init (GModule *module)
{
  return gtk_check_version (GTK_MAJOR_VERSION,
			    GTK_MINOR_VERSION,
			    GTK_MICRO_VERSION - GTK_INTERFACE_AGE);
}
    

The constants GTK_MAJOR_VERSION, and so forth, come from the header files at compilation time; the function gtk_check_version compares them with versions stored when GTK+ was compiled. If they are compatible, the function returns NULL, otherwise it returns an error message. Since this convention is compatible with what GModule expects for g_module_check_init(), we simply return the value that gtk_check_version() returns directly.

We now turn to the definition of PixbufRcStyle. The header file for PixbufRcStyle looks rather similar to that for a normal statically defined GObject type. First we define structures for the instance and class structures.

typedef struct _PixbufRcStyle PixbufRcStyle;
typedef struct _PixbufRcStyleClass PixbufRcStyleClass;

struct _PixbufRcStyle
{
  GtkRcStyle parent_instance;
  
  GList *img_list;
};

struct _PixbufRcStyleClass
{
  GtkRcStyleClass parent_class;
};
    

As with a normal type, these structures include the parent class structures as their first element so that inheritance works properly. For the instance structure, we add an extra variable to store the image definitions that are declared in engine section of the RC file. Then we define macros for casting and checking types:

extern GType pixbuf_type_rc_style;

#define PIXBUF_TYPE_RC_STYLE              pixbuf_type_rc_style
#define PIXBUF_RC_STYLE(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), PIXBUF_TYPE_RC_STYLE, PixbufRcStyle))
#define PIXBUF_RC_STYLE_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), PIXBUF_TYPE_RC_STYLE, PixbufRcStyleClass))
#define PIXBUF_IS_RC_STYLE(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), PIXBUF_TYPE_RC_STYLE))
#define PIXBUF_IS_RC_STYLE_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), PIXBUF_TYPE_RC_STYLE))
#define PIXBUF_RC_STYLE_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), PIXBUF_TYPE_RC_STYLE, PixbufRcStyleClass))
    

The main difference to notice here from the normal GObject macros is that instead of calling a function to retrieve the object type, we simply use a global variable. Since we've registered the type from our theme_init() function by calling pixbuf_rc_style_register_type(), we don't need a function to deal with the fact that the type might need to be registered on the fly.

The reason we do the registration with an explicitly called function is that we need to associate the class for PixbufRcStyle with the theme engine module. If we didn't take care to make the association, then the engine module might be unloaded while there were still extent instances of the class. Since the module includes the definitions of the class's virtual functions, this would have very bad effects. To make the association, we call:

GType gtk_theme_engine_get_type (GtkThemeEngine  *engine,
				 GType            parent_type,
				 const gchar     *type_name,
				 const GTypeInfo *type_info);
    

This function checks to see if a type with the name type_name is already been defined by some module. If this module has registered the type, it returns the type ID, if another module currently loaded has registered the type, then it dies with an error (so, make sure that the type of the classes you define have a unique name). If no module has yet registered the type, it registers the type using parent_type and type_info, and returns the resulting ID. The type is registered using functions in GObject for dealing with dynamically loaded types; as long instances of the type are extent, GTK+ will keep a reference to the engine and keep it from being unloaded. Once all instances have been finalized, GTK+ will drop the reference, and the engine can be unloaded.

The function to register the type, looks like:

GType pixbuf_type_rc_style = 0;

void
pixbuf_rc_style_register_type (GtkThemeEngine *engine)
{
  static const GTypeInfo object_info =
  {
    sizeof (PixbufRcStyleClass),
    (GBaseInitFunc) NULL,
    (GBaseFinalizeFunc) NULL,
    (GClassInitFunc) pixbuf_rc_style_class_init,
    NULL,           /* class_finalize */
    NULL,           /* class_data */
    sizeof (PixbufRcStyle),
    0,              /* n_preallocs */
    (GInstanceInitFunc) pixbuf_rc_style_init,
  };
  
  pixbuf_type_rc_style = gtk_theme_engine_get_type (engine,
						    GTK_TYPE_RC_STYLE,
						    "PixbufRcStyle",
						    &object_info);
}
    

The GTypeInfo structure is the same as for a static type, so the main difference is that we are calling gtk_theme_engine_get_type() rather than g_type_register_static().

The class_init and init functions for PixbufRcStyle are rather uninteresting.

static void
pixbuf_rc_style_init (PixbufRcStyle *style)
{
}

static void
pixbuf_rc_style_class_init (PixbufRcStyleClass *klass)
{
  GtkRcStyleClass *rc_style_class = GTK_RC_STYLE_CLASS (klass);
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  parent_class = g_type_class_peek_parent (klass);

  rc_style_class->parse = pixbuf_rc_style_parse;
  rc_style_class->merge = pixbuf_rc_style_merge;
  rc_style_class->create_style = pixbuf_rc_style_create_style;
  
  object_class->finalize = pixbuf_rc_style_finalize;
}
    

The init() function, in fact, does nothing at all (The default initialization of img_list to NULL suffices. The class_init function saves a pointer to the parent class and then initializes the class structure with pointers to the virtual functions that we override in our implementation.

Since this document is about theme engines, not about GScanner, we won't explain the parsing code from the theme engine in detail. However, certain aspects that we can see in the top-level parse() function are rather unusual, so we'll go through them briefly.

The first thing that our parse() function does is set up a scope within the scanner that is passed in. The idea of a scope is that within the particular engine block we need to recognize additional keywords.

static struct
  {
    gchar              *name;
    guint               token;
  }
theme_symbols[] =
{
  { "image", 		TOKEN_IMAGE  },
  { "function", 	TOKEN_FUNCTION },
  { "file", 		TOKEN_FILE },
  { "stretch", 		TOKEN_STRETCH },
[...]
};
      
static guint
pixbuf_rc_style_parse (GtkRcStyle *rc_style,
		       GScanner   *scanner)
		     
{
  static GQuark scope_id = 0;
  PixbufRcStyle *pixbuf_style = PIXBUF_RC_STYLE (rc_style);

  guint old_scope;
  guint token;
  gint i;
  ThemeImage *img;
  
  /* Set up a new scope in this scanner. */

  if (!scope_id)
    scope_id = g_quark_from_string("pixbuf_theme_engine");

  /* If we bail out due to errors, we *don't* reset the scope, so the
   * error messaging code can make sense of our tokens.
   */
  old_scope = g_scanner_set_scope(scanner, scope_id);

  /* Now check if we already added our symbols to this scope
   * (in some previous call to theme_parse_rc_style for the
   * same scanner.
   */

  if (!g_scanner_lookup_symbol(scanner, theme_symbols[0].name))
    {
      g_scanner_freeze_symbol_table(scanner);
      for (i = 0; i < G_N_ELEMENTS (theme_symbols); i++)
	g_scanner_scope_add_symbol(scanner, scope_id,
				   theme_symbols[i].name,
				   GINT_TO_POINTER(theme_symbols[i].token));
      g_scanner_thaw_symbol_table(scanner);
    }
    

Scopes are identified by a particular quark (integer identifier for a string). If the scope_id static variable has not yet been initialized, we call g_quark_from_static() string to get the quark corresponding to the string we use, pixbuf_theme_engine. Since our module could be unloaded later, we are careful to use g_quark_from_string() instead of g_quark_from_static_string(). Then we check to see if we've previously loaded our symbols into this scope. If not, we load them. (The GScanner object is used for a single RC style, so we'll load the symbols for the first RC style using our engine in the file, but don't need to do that for subsequent RC styles in the same file.)

Once we've loaded the appropriate set of tokens for parsing the block, we go into the main parsing loop. Note that the opening '{' of the block has already been consumed by the time we are called.

  /* We're ready to go, now parse the top level */

  token = g_scanner_peek_next_token(scanner);
  while (token != G_TOKEN_RIGHT_CURLY)
    {
      switch (token)
	{
	case TOKEN_IMAGE:
	  img = NULL;
	  token = theme_parse_image(scanner, pixbuf_style, &img);
	  break;
	default:
	  g_scanner_get_next_token(scanner);
	  token = G_TOKEN_RIGHT_CURLY;
	  break;
	}

      if (token != G_TOKEN_NONE)
	return token;
      else
	pixbuf_style->img_list = g_list_append(pixbuf_style->img_list, img);

      token = g_scanner_peek_next_token(scanner);
    }
  g_scanner_get_next_token(scanner);
    

The top-level syntax that that the Pixbuf engine expects is quite simple, we can either have an image { ... } declaration, which is parsed by calling theme_parse_image(), or we can have the closing '}' for the block. Anything else is an error.

  g_scanner_set_scope(scanner, old_scope);

  return G_TOKEN_NONE;
}
    

Finally, in case of success, we set the scope back to the original scope and return G_TOKEN_NONE. Returning a different token value indicates we were expecting some token we didn't get, and GTK+ will output an appropriate diagnostic warning for the parsing error. We only reset the scope in case of success and not of failure so that the code for printing out the diagnostic warning can interpret the token that we return with respect to the scope where the error occurred.

While parsing, we add all the image declarations we find to pixbuf_style->img_list. When we merge pixbuf styles together, we need to combine the lists. We do that in the merge() function. In a somewhat idealized form, this looks like:

static void
pixbuf_rc_style_merge (GtkRcStyle *dest,
		       GtkRcStyle *src)
{
  if (PIXBUF_IS_RC_STYLE (src))
    {
      PixbufRcStyle *pixbuf_dest = PIXBUF_RC_STYLE (dest);
      PixbufRcStyle *pixbuf_src = PIXBUF_RC_STYLE (src);
      
      GList *new_list = g_list_copy (pixbuf_src->img_list);
      g_list_foreach (new_list, (GFunc)theme_imag_ref, NULL);

      pixbuf_dest->img_list = g_list_concat (pixbuf_dest->img_list, new_list);
    }

  parent_class->merge (dest, src);
}
    

Since we can either be merging another pixbuf RC style, or a non-pixbuf RC style into the destination RC style (which will always be a pixbuf RC style), we first check to make sure that src is in fact pixbuf RC style using PIXBUF_IS_RC_STYLE (). We then concatenate the images from the source style onto the list in the destination style. Since, when looking up an image for drawing, we examine the images in the list in sequence, concatenating the images in the source at the end of the destination list properly ensures that the information already stored in the destination style takes precedence over the information in the source style. Whether or not the source style was a pixbuf style, we then chain to the parent implementation to merge the core parts of GtkRcStyle.

The final virtual function we override is create_style(). In this function we simply create a new object of the style subclass we registered in theme_init and return it.

static GtkStyle *
pixbuf_rc_style_create_style (GtkRcStyle *rc_style)
{
  return GTK_STYLE (g_object_new (PIXBUF_TYPE_STYLE));
}
    

The header file and pixbuf_style_register_type() functions for PixbufStyle follow identical lines to those of PixbufRcStyle so we'll omit them here.

In our class init function, we override all the drawing functions with our own implementations, while keeping the default implementations of the administrative functions.

static void
pixbuf_style_class_init (PixbufStyleClass *klass)
{
  GtkStyleClass *style_class = GTK_STYLE_CLASS (klass);

  style_class->draw_hline = draw_hline;
  style_class->draw_vline = draw_vline;
  style_class->draw_shadow = draw_shadow;
  style_class->draw_polygon = draw_polygon;
  style_class->draw_arrow = draw_arrow;
  style_class->draw_diamond = draw_diamond;
  style_class->draw_oval = draw_oval;
  style_class->draw_string = draw_string;
  style_class->draw_box = draw_box;
  style_class->draw_flat_box = draw_flat_box;
  style_class->draw_check = draw_check;
  style_class->draw_option = draw_option;
  style_class->draw_cross = draw_cross;
  style_class->draw_ramp = draw_ramp;
  style_class->draw_tab = draw_tab;
  style_class->draw_shadow_gap = draw_shadow_gap;
  style_class->draw_box_gap = draw_box_gap;
  style_class->draw_extension = draw_extension;
  style_class->draw_focus = draw_focus;
  style_class->draw_slider = draw_slider;
  style_class->draw_handle = draw_handle;
}
    

To give an example of what one of these drawing functions looks like, we can take a look at draw_box().

static void
draw_box(GtkStyle      *style,
	 GdkWindow     *window,
	 GtkStateType   state,
	 GtkShadowType  shadow,
	 GdkRectangle  *area,
	 GtkWidget     *widget,
	 const gchar   *detail,
	 gint           x,
	 gint           y,
	 gint           width,
	 gint           height)
{
  ThemeMatchData match_data;

  g_return_if_fail(style != NULL);
  g_return_if_fail(window != NULL);

  match_data.function = TOKEN_D_BOX;
  match_data.detail = (char *)detail;
  match_data.flags = THEME_MATCH_SHADOW | THEME_MATCH_STATE;
  match_data.shadow = shadow;
  match_data.state = state;
  
  draw_simple_image (style, window, area, widget, &match_data, TRUE, TRUE,
		     x, y, width, height);
}
    

Since in the pixbuf theme all graphical elements are drawn with pixmaps, the implementations of the various draw_* functions are rather monotonous. We initialize a structure with information from our arguments and then call a generic function that matches the argument information against the image list for the style, and does the actual drawing. draw_simple_image will eventually extract the relevant image list with code like:

  image_list = PIXBUF_RC_STYLE (style->rc_style)->img_list;
    

As noted above, we don't bother copying the state from the RC style into the style. Instead, we simply use the pointer that GTK+ maintains from the style to the composite RC style as needed.

We've now gone over the essentials of creating a theme engine. When writing a theme engine, it is often easiest to take an existing theme engine that is similar to what you want to create and modify it to match your needs. You need to be careful when doing this that the theme engine you choose has been updated for GTK+-2.0. While the essentials of theme engines in GTK+-1.2 were quite similar, the details changed substantially with the introduction of GObject.

Modifying geometry and layout

What if you are satisfied with simply changing the way that drawing is done? What if you also want to change the size of various elements, or even move elements around. What if you want to make scrollbars wider, or move both arrows of a scrollbar to the same side of the trough? Well, it isn't possible within the official theme engine API. Nonetheless, various themes that are available actually do this. We'll describe what they are doing to achieve these effects, why what they are doing is unreliable, and what would need to be added to GTK+ to do this the right way.

The way that theme engines achieve these effects is by modifying the class structures of the widget classes. In some cases, geometry parameters (such as the scrollbar width) are stored in these structures, so the theme engine can simply replace those parameters with other more congenial parameters. In other cases, the options aren't stored in class variables. In these cases, the themes are actually replacing the virtual function pointers to such functions as gtk_vscrollbar_size_allocate() with pointers to there own their own versions.

There are various problems with this technique. First, as noted above, a theme can specify different theme engines for different widgets. However, changes to a widget class effect all widgets, not just some widgets. Second, there is no guarantee that when switching themes, the first theme will be unloaded before the second theme is loaded. This means that the there is no way a theme can reliably undo its changes to the class structures. In fact, the worst case scenario is that the function pointer that a theme engine saves when it is loaded points to some internal function of the previous theme engine which was then unloaded. If the theme engine tries to restore the function pointer to the previous value, it will then cause the application to segfault. The final problem with modifying the class structure is that the exact details of the way a function like gtk_vscrollbar_size_allocate() is an internal detail of GTK+. If you copy the code into your theme engine, your theme will most likely break with future versions of GTK+.

So, don't do that. If you do, despite the above warnings, decide to do this in one of your themes, what you should do after modifying the class structures in your theme_init function is to call gtk_theme_engine_ref(engine) to add an extra reference count so that your theme engine will never be unloaded. This makes sure that at least the application won't crash if one of your function pointers is not properly reset, or if a subsequent theme accidentally restores your pointer instead of the default one. (The appearance may be funny and the user may see mixtures of themes, but that is better than crashing.)

So, how should it work? The plan for future versions of GTK+ is that widget properties will be modifiable from RC files, so you will be able to do something like [ hypothetical syntax ]:

properties "my-scrollbar-props" {
    slider_size = 20
    top_arrows = both
}

class GtkVScrollBar properties "my-scrollbar-props"
    

There are two parts to implementing this. First, the basic mechanism needs to be implemented. Second, all the attributes of widgets that are interesting for themes to configure need to be made configurable through the property system.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值