theme.c

Go to the documentation of this file.
00001 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
00002 
00003 /* Metacity Theme Rendering */
00004 
00005 /*
00006  * Copyright (C) 2001 Havoc Pennington
00007  *
00008  * This program is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU General Public License as
00010  * published by the Free Software Foundation; either version 2 of the
00011  * License, or (at your option) any later version.
00012  *
00013  * This program is distributed in the hope that it will be useful, but
00014  * WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016  * General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU General Public License
00019  * along with this program; if not, write to the Free Software
00020  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
00021  * 02111-1307, USA.
00022  */
00023 
00055 #include <config.h>
00056 #include "theme.h"
00057 #include "theme-parser.h"
00058 #include "util.h"
00059 #include "gradient.h"
00060 #include <gtk/gtkwidget.h>
00061 #include <gtk/gtkimage.h>
00062 #include <gtk/gtkicontheme.h>
00063 #include <string.h>
00064 #include <stdlib.h>
00065 #include <math.h>
00066 
00067 #define GDK_COLOR_RGBA(color)                                           \
00068                          ((guint32) (0xff                         |     \
00069                                      (((color).red / 256) << 24)   |    \
00070                                      (((color).green / 256) << 16) |    \
00071                                      (((color).blue / 256) << 8)))
00072 
00073 #define GDK_COLOR_RGB(color)                                            \
00074                          ((guint32) ((((color).red / 256) << 16)   |    \
00075                                      (((color).green / 256) << 8)  |    \
00076                                      (((color).blue / 256))))
00077 
00078 #define ALPHA_TO_UCHAR(d) ((unsigned char) ((d) * 255))
00079 
00080 #define DEBUG_FILL_STRUCT(s) memset ((s), 0xef, sizeof (*(s)))
00081 #define CLAMP_UCHAR(v) ((guchar) (CLAMP (((int)v), (int)0, (int)255)))
00082 #define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
00083 
00084 static void gtk_style_shade             (GdkColor        *a,
00085                                          GdkColor        *b,
00086                                          gdouble          k);
00087 static void rgb_to_hls                  (gdouble         *r,
00088                                          gdouble         *g,
00089                                          gdouble         *b);
00090 static void hls_to_rgb                  (gdouble         *h,
00091                                          gdouble         *l,
00092                                          gdouble         *s);
00093 
00097 static MetaTheme *meta_current_theme = NULL;
00098 
00099 static GdkPixbuf *
00100 colorize_pixbuf (GdkPixbuf *orig,
00101                  GdkColor  *new_color)
00102 {
00103   GdkPixbuf *pixbuf;
00104   double intensity;
00105   int x, y;
00106   const guchar *src;
00107   guchar *dest;
00108   int orig_rowstride;
00109   int dest_rowstride;
00110   int width, height;
00111   gboolean has_alpha;
00112   const guchar *src_pixels;
00113   guchar *dest_pixels;
00114   
00115   pixbuf = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (orig), gdk_pixbuf_get_has_alpha (orig),
00116                            gdk_pixbuf_get_bits_per_sample (orig),
00117                            gdk_pixbuf_get_width (orig), gdk_pixbuf_get_height (orig));
00118 
00119   if (pixbuf == NULL)
00120     return NULL;
00121   
00122   orig_rowstride = gdk_pixbuf_get_rowstride (orig);
00123   dest_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
00124   width = gdk_pixbuf_get_width (pixbuf);
00125   height = gdk_pixbuf_get_height (pixbuf);
00126   has_alpha = gdk_pixbuf_get_has_alpha (orig);
00127   src_pixels = gdk_pixbuf_get_pixels (orig);
00128   dest_pixels = gdk_pixbuf_get_pixels (pixbuf);
00129   
00130   for (y = 0; y < height; y++)
00131     {
00132       src = src_pixels + y * orig_rowstride;
00133       dest = dest_pixels + y * dest_rowstride;
00134 
00135       for (x = 0; x < width; x++)
00136         {
00137           double dr, dg, db;
00138           
00139           intensity = INTENSITY (src[0], src[1], src[2]) / 255.0;
00140 
00141           if (intensity <= 0.5)
00142             {
00143               /* Go from black at intensity = 0.0 to new_color at intensity = 0.5 */
00144               dr = (new_color->red * intensity * 2.0) / 65535.0;
00145               dg = (new_color->green * intensity * 2.0) / 65535.0;
00146               db = (new_color->blue * intensity * 2.0) / 65535.0;
00147             }
00148           else
00149             {
00150               /* Go from new_color at intensity = 0.5 to white at intensity = 1.0 */
00151               dr = (new_color->red + (65535 - new_color->red) * (intensity - 0.5) * 2.0) / 65535.0;
00152               dg = (new_color->green + (65535 - new_color->green) * (intensity - 0.5) * 2.0) / 65535.0;
00153               db = (new_color->blue + (65535 - new_color->blue) * (intensity - 0.5) * 2.0) / 65535.0;
00154             }
00155           
00156           dest[0] = CLAMP_UCHAR (255 * dr);
00157           dest[1] = CLAMP_UCHAR (255 * dg);
00158           dest[2] = CLAMP_UCHAR (255 * db);
00159           
00160           if (has_alpha)
00161             {
00162               dest[3] = src[3];
00163               src += 4;
00164               dest += 4;
00165             }
00166           else
00167             {
00168               src += 3;
00169               dest += 3;
00170             }
00171         }
00172     }
00173 
00174   return pixbuf;
00175 }
00176 
00177 static void
00178 color_composite (const GdkColor *bg,
00179                  const GdkColor *fg,
00180                  double          alpha_d,
00181                  GdkColor       *color)
00182 {
00183   guint16 alpha;
00184 
00185   *color = *bg;
00186   alpha = alpha_d * 0xffff;
00187   color->red = color->red + (((fg->red - color->red) * alpha + 0x8000) >> 16);
00188   color->green = color->green + (((fg->green - color->green) * alpha + 0x8000) >> 16);
00189   color->blue = color->blue + (((fg->blue - color->blue) * alpha + 0x8000) >> 16);
00190 }
00191 
00197 static void
00198 init_border (GtkBorder *border)
00199 {
00200   border->top = -1;
00201   border->bottom = -1;
00202   border->left = -1;
00203   border->right = -1;
00204 }
00205 
00212 MetaFrameLayout*
00213 meta_frame_layout_new  (void)
00214 {
00215   MetaFrameLayout *layout;
00216 
00217   layout = g_new0 (MetaFrameLayout, 1);
00218 
00219   layout->refcount = 1;
00220 
00221   /* Fill with -1 values to detect invalid themes */
00222   layout->left_width = -1;
00223   layout->right_width = -1;
00224   layout->bottom_height = -1;
00225 
00226   init_border (&layout->title_border);
00227 
00228   layout->title_vertical_pad = -1;
00229   
00230   layout->right_titlebar_edge = -1;
00231   layout->left_titlebar_edge = -1;
00232 
00233   layout->button_sizing = META_BUTTON_SIZING_LAST;
00234   layout->button_aspect = 1.0;
00235   layout->button_width = -1;
00236   layout->button_height = -1;
00237 
00238   layout->has_title = TRUE;
00239   layout->title_scale = 1.0;
00240   
00241   init_border (&layout->button_border);
00242 
00243   return layout;
00244 }
00245 
00249 static gboolean
00250 validate_border (const GtkBorder *border,
00251                  const char     **bad)
00252 {
00253   *bad = NULL;
00254   
00255   if (border->top < 0)
00256     *bad = _("top");
00257   else if (border->bottom < 0)
00258     *bad = _("bottom");
00259   else if (border->left < 0)
00260     *bad = _("left");
00261   else if (border->right < 0)
00262     *bad = _("right");
00263 
00264   return *bad == NULL;
00265 }
00266 
00280 static gboolean
00281 validate_geometry_value (int         val,
00282                          const char *name,
00283                          GError    **error)
00284 {
00285   if (val < 0)
00286     {
00287       g_set_error (error, META_THEME_ERROR,
00288                    META_THEME_ERROR_FRAME_GEOMETRY,
00289                    _("frame geometry does not specify \"%s\" dimension"),
00290                    name);
00291       return FALSE;
00292     }
00293   else
00294     return TRUE;
00295 }
00296 
00297 static gboolean
00298 validate_geometry_border (const GtkBorder *border,
00299                           const char      *name,
00300                           GError         **error)
00301 {
00302   const char *bad;
00303 
00304   if (!validate_border (border, &bad))
00305     {
00306       g_set_error (error, META_THEME_ERROR,
00307                    META_THEME_ERROR_FRAME_GEOMETRY,
00308                    _("frame geometry does not specify dimension \"%s\" for border \"%s\""),
00309                    bad, name);
00310       return FALSE;
00311     }
00312   else
00313     return TRUE;
00314 }
00315 
00316 gboolean
00317 meta_frame_layout_validate (const MetaFrameLayout *layout,
00318                             GError               **error)
00319 {
00320   g_return_val_if_fail (layout != NULL, FALSE);
00321 
00322 #define CHECK_GEOMETRY_VALUE(vname) if (!validate_geometry_value (layout->vname, #vname, error)) return FALSE
00323 
00324 #define CHECK_GEOMETRY_BORDER(bname) if (!validate_geometry_border (&layout->bname, #bname, error)) return FALSE
00325 
00326   CHECK_GEOMETRY_VALUE (left_width);
00327   CHECK_GEOMETRY_VALUE (right_width);
00328   CHECK_GEOMETRY_VALUE (bottom_height);
00329 
00330   CHECK_GEOMETRY_BORDER (title_border);
00331 
00332   CHECK_GEOMETRY_VALUE (title_vertical_pad);
00333 
00334   CHECK_GEOMETRY_VALUE (right_titlebar_edge);
00335   CHECK_GEOMETRY_VALUE (left_titlebar_edge);
00336 
00337   switch (layout->button_sizing)
00338     {
00339     case META_BUTTON_SIZING_ASPECT:
00340       if (layout->button_aspect < (0.1) ||
00341           layout->button_aspect > (15.0))
00342         {
00343           g_set_error (error, META_THEME_ERROR,
00344                        META_THEME_ERROR_FRAME_GEOMETRY,
00345                        _("Button aspect ratio %g is not reasonable"),
00346                        layout->button_aspect);
00347           return FALSE;
00348         }
00349       break;
00350     case META_BUTTON_SIZING_FIXED:
00351       CHECK_GEOMETRY_VALUE (button_width);
00352       CHECK_GEOMETRY_VALUE (button_height);
00353       break;
00354     case META_BUTTON_SIZING_LAST:
00355       g_set_error (error, META_THEME_ERROR,
00356                    META_THEME_ERROR_FRAME_GEOMETRY,
00357                    _("Frame geometry does not specify size of buttons"));
00358       return FALSE;
00359     }
00360 
00361   CHECK_GEOMETRY_BORDER (button_border);
00362 
00363   return TRUE;
00364 }
00365 
00366 MetaFrameLayout*
00367 meta_frame_layout_copy (const MetaFrameLayout *src)
00368 {
00369   MetaFrameLayout *layout;
00370 
00371   layout = g_new0 (MetaFrameLayout, 1);
00372 
00373   *layout = *src;
00374 
00375   layout->refcount = 1;
00376 
00377   return layout;
00378 }
00379 
00380 void
00381 meta_frame_layout_ref (MetaFrameLayout *layout)
00382 {
00383   g_return_if_fail (layout != NULL);
00384 
00385   layout->refcount += 1;
00386 }
00387 
00388 void
00389 meta_frame_layout_unref (MetaFrameLayout *layout)
00390 {
00391   g_return_if_fail (layout != NULL);
00392   g_return_if_fail (layout->refcount > 0);
00393 
00394   layout->refcount -= 1;
00395 
00396   if (layout->refcount == 0)
00397     {
00398       DEBUG_FILL_STRUCT (layout);
00399       g_free (layout);
00400     }
00401 }
00402 
00403 void
00404 meta_frame_layout_get_borders (const MetaFrameLayout *layout,
00405                                int                    text_height,
00406                                MetaFrameFlags         flags,
00407                                int                   *top_height,
00408                                int                   *bottom_height,
00409                                int                   *left_width,
00410                                int                   *right_width)
00411 {
00412   int buttons_height, title_height;
00413   
00414   g_return_if_fail (top_height != NULL);
00415   g_return_if_fail (bottom_height != NULL);
00416   g_return_if_fail (left_width != NULL);
00417   g_return_if_fail (right_width != NULL);
00418 
00419   if (!layout->has_title)
00420     text_height = 0;
00421   
00422   buttons_height = layout->button_height +
00423     layout->button_border.top + layout->button_border.bottom;
00424   title_height = text_height +
00425     layout->title_vertical_pad +
00426     layout->title_border.top + layout->title_border.bottom;
00427 
00428   if (top_height)
00429     {
00430       *top_height = MAX (buttons_height, title_height);
00431     }
00432 
00433   if (left_width)
00434     *left_width = layout->left_width;
00435   if (right_width)
00436     *right_width = layout->right_width;
00437 
00438   if (bottom_height)
00439     {
00440       if (flags & META_FRAME_SHADED)
00441         *bottom_height = 0;
00442       else
00443         *bottom_height = layout->bottom_height;
00444     }
00445 
00446   if (flags & META_FRAME_FULLSCREEN)
00447     {
00448       if (top_height)
00449         *top_height = 0;
00450       if (bottom_height)
00451         *bottom_height = 0;
00452       if (left_width)
00453         *left_width = 0;
00454       if (right_width)
00455         *right_width = 0;
00456     }
00457 }
00458 
00459 static MetaButtonSpace*
00460 rect_for_function (MetaFrameGeometry *fgeom,
00461                    MetaFrameFlags     flags,
00462                    MetaButtonFunction function,
00463                    MetaTheme         *theme)
00464 {
00465 
00466   /* Firstly, check version-specific things. */
00467   
00468   if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS))
00469     {
00470       switch (function)
00471         {
00472         case META_BUTTON_FUNCTION_SHADE:
00473           if ((flags & META_FRAME_ALLOWS_SHADE) && !(flags & META_FRAME_SHADED))
00474             return &fgeom->shade_rect;
00475           else
00476             return NULL;
00477         case META_BUTTON_FUNCTION_ABOVE:
00478           if (!(flags & META_FRAME_ABOVE))
00479             return &fgeom->above_rect;
00480           else
00481             return NULL;
00482         case META_BUTTON_FUNCTION_STICK:
00483           if (!(flags & META_FRAME_STUCK))
00484             return &fgeom->stick_rect;
00485           else
00486             return NULL;
00487         case META_BUTTON_FUNCTION_UNSHADE:
00488           if ((flags & META_FRAME_ALLOWS_SHADE) && (flags & META_FRAME_SHADED))
00489             return &fgeom->unshade_rect;
00490           else
00491             return NULL;
00492         case META_BUTTON_FUNCTION_UNABOVE:
00493           if (flags & META_FRAME_ABOVE)
00494             return &fgeom->unabove_rect;
00495           else
00496             return NULL;
00497         case META_BUTTON_FUNCTION_UNSTICK:
00498           if (flags & META_FRAME_STUCK)
00499             return &fgeom->unstick_rect;
00500         default:
00501           /* just go on to the next switch block */;
00502         }
00503     }
00504 
00505   /* now consider the buttons which exist in all versions */
00506 
00507   switch (function)
00508     {
00509     case META_BUTTON_FUNCTION_MENU:
00510       if (flags & META_FRAME_ALLOWS_MENU)
00511         return &fgeom->menu_rect;
00512       else
00513         return NULL;
00514     case META_BUTTON_FUNCTION_MINIMIZE:
00515       if (flags & META_FRAME_ALLOWS_MINIMIZE)
00516         return &fgeom->min_rect;
00517       else
00518         return NULL;
00519     case META_BUTTON_FUNCTION_MAXIMIZE:
00520       if (flags & META_FRAME_ALLOWS_MAXIMIZE)
00521         return &fgeom->max_rect;
00522       else
00523         return NULL;
00524     case META_BUTTON_FUNCTION_CLOSE:
00525       if (flags & META_FRAME_ALLOWS_DELETE)
00526         return &fgeom->close_rect;
00527       else
00528         return NULL;
00529     case META_BUTTON_FUNCTION_STICK:
00530     case META_BUTTON_FUNCTION_SHADE:
00531     case META_BUTTON_FUNCTION_ABOVE:
00532     case META_BUTTON_FUNCTION_UNSTICK:
00533     case META_BUTTON_FUNCTION_UNSHADE:
00534     case META_BUTTON_FUNCTION_UNABOVE:
00535       /* we are being asked for a >v1 button which hasn't been handled yet,
00536        * so obviously we're not in a theme which supports that version.
00537        * therefore, we don't show the button. return NULL and all will
00538        * be well.
00539        */
00540       return NULL;
00541       
00542     case META_BUTTON_FUNCTION_LAST:
00543       return NULL;
00544     }
00545 
00546   return NULL;
00547 }
00548 
00549 static gboolean
00550 strip_button (MetaButtonSpace *func_rects[MAX_BUTTONS_PER_CORNER],
00551               GdkRectangle    *bg_rects[MAX_BUTTONS_PER_CORNER],
00552               int             *n_rects,
00553               MetaButtonSpace *to_strip)
00554 {
00555   int i;
00556   
00557   i = 0;
00558   while (i < *n_rects)
00559     {
00560       if (func_rects[i] == to_strip)
00561         {
00562           *n_rects -= 1;
00563 
00564           /* shift the other rects back in the array */
00565           while (i < *n_rects)
00566             {
00567               func_rects[i] = func_rects[i+1];
00568               bg_rects[i] = bg_rects[i+1];
00569 
00570               ++i;
00571             }
00572 
00573           func_rects[i] = NULL;
00574           bg_rects[i] = NULL;
00575           
00576           return TRUE;
00577         }
00578 
00579       ++i;
00580     }
00581 
00582   return FALSE; /* did not strip anything */
00583 }
00584 
00585 void
00586 meta_frame_layout_calc_geometry (const MetaFrameLayout  *layout,
00587                                  int                     text_height,
00588                                  MetaFrameFlags          flags,
00589                                  int                     client_width,
00590                                  int                     client_height,
00591                                  const MetaButtonLayout *button_layout,
00592                                  MetaFrameGeometry      *fgeom,
00593                                  MetaTheme              *theme)
00594 {
00595   int i, n_left, n_right, n_left_spacers, n_right_spacers;
00596   int x;
00597   int button_y;
00598   int title_right_edge;
00599   int width, height;
00600   int button_width, button_height;
00601   int min_size_for_rounding;
00602   
00603   /* the left/right rects in order; the max # of rects
00604    * is the number of button functions
00605    */
00606   MetaButtonSpace *left_func_rects[MAX_BUTTONS_PER_CORNER];
00607   MetaButtonSpace *right_func_rects[MAX_BUTTONS_PER_CORNER];
00608   GdkRectangle *left_bg_rects[MAX_BUTTONS_PER_CORNER];
00609   gboolean left_buttons_has_spacer[MAX_BUTTONS_PER_CORNER];
00610   GdkRectangle *right_bg_rects[MAX_BUTTONS_PER_CORNER];
00611   gboolean right_buttons_has_spacer[MAX_BUTTONS_PER_CORNER];
00612   
00613   meta_frame_layout_get_borders (layout, text_height,
00614                                  flags,
00615                                  &fgeom->top_height,
00616                                  &fgeom->bottom_height,
00617                                  &fgeom->left_width,
00618                                  &fgeom->right_width);
00619 
00620   width = client_width + fgeom->left_width + fgeom->right_width;
00621 
00622   height = ((flags & META_FRAME_SHADED) ? 0: client_height) +
00623     fgeom->top_height + fgeom->bottom_height;
00624 
00625   fgeom->width = width;
00626   fgeom->height = height;
00627 
00628   fgeom->top_titlebar_edge = layout->title_border.top;
00629   fgeom->bottom_titlebar_edge = layout->title_border.bottom;
00630   fgeom->left_titlebar_edge = layout->left_titlebar_edge;
00631   fgeom->right_titlebar_edge = layout->right_titlebar_edge;
00632 
00633   /* gcc warnings */
00634   button_width = -1;
00635   button_height = -1;
00636   
00637   switch (layout->button_sizing)
00638     {
00639     case META_BUTTON_SIZING_ASPECT:
00640       button_height = fgeom->top_height - layout->button_border.top - layout->button_border.bottom;
00641       button_width = button_height / layout->button_aspect;
00642       break;
00643     case META_BUTTON_SIZING_FIXED:
00644       button_width = layout->button_width;
00645       button_height = layout->button_height;
00646       break;
00647     case META_BUTTON_SIZING_LAST:
00648       g_assert_not_reached ();
00649       break;
00650     }
00651 
00652   /* FIXME all this code sort of pretends that duplicate buttons
00653    * with the same function are allowed, but that breaks the
00654    * code in frames.c, so isn't really allowed right now.
00655    * Would need left_close_rect, right_close_rect, etc.
00656    */
00657   
00658   /* Init all button rects to 0, lame hack */
00659   memset (ADDRESS_OF_BUTTON_RECTS (fgeom), '\0',
00660           LENGTH_OF_BUTTON_RECTS);
00661   
00662   n_left = 0;
00663   n_right = 0;
00664   n_left_spacers = 0;
00665   n_right_spacers = 0;
00666 
00667   if (!layout->hide_buttons)
00668     {
00669       /* Try to fill in rects */
00670       for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->left_buttons[i] != META_BUTTON_FUNCTION_LAST; i++)
00671         {
00672           left_func_rects[n_left] = rect_for_function (fgeom, flags,
00673                                                        button_layout->left_buttons[i],
00674                                                        theme);
00675           if (left_func_rects[n_left] != NULL)
00676             {
00677               left_buttons_has_spacer[n_left] = button_layout->left_buttons_has_spacer[i];
00678               if (button_layout->left_buttons_has_spacer[i])
00679                 ++n_left_spacers;
00680 
00681               ++n_left;
00682             }
00683         }
00684       
00685       for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->right_buttons[i] != META_BUTTON_FUNCTION_LAST; i++)
00686         {
00687           right_func_rects[n_right] = rect_for_function (fgeom, flags,
00688                                                          button_layout->right_buttons[i],
00689                                                          theme);
00690           if (right_func_rects[n_right] != NULL)
00691             {
00692               right_buttons_has_spacer[n_right] = button_layout->right_buttons_has_spacer[i];
00693               if (button_layout->right_buttons_has_spacer[i])
00694                 ++n_right_spacers;
00695 
00696               ++n_right;
00697             }
00698         }
00699     }
00700 
00701   for (i = 0; i < MAX_BUTTONS_PER_CORNER; i++)
00702     {
00703       left_bg_rects[i] = NULL;
00704       right_bg_rects[i] = NULL;
00705     }
00706 
00707   for (i = 0; i < n_left; i++)
00708     {
00709       if (i == 0) /* prefer left background if only one button */
00710         left_bg_rects[i] = &fgeom->left_left_background;
00711       else if (i == (n_left - 1))
00712         left_bg_rects[i] = &fgeom->left_right_background;
00713       else
00714         left_bg_rects[i] = &fgeom->left_middle_backgrounds[i - 1];
00715     }
00716 
00717   for (i = 0; i < n_right; i++)
00718     {
00719       /* prefer right background if only one button */
00720       if (i == (n_right - 1))
00721         right_bg_rects[i] = &fgeom->right_right_background;
00722       else if (i == 0)
00723         right_bg_rects[i] = &fgeom->right_left_background;
00724       else
00725         right_bg_rects[i] = &fgeom->right_middle_backgrounds[i - 1];
00726     }
00727   
00728   /* Be sure buttons fit */
00729   while (n_left > 0 || n_right > 0)
00730     {
00731       int space_used_by_buttons;
00732       int space_available;
00733 
00734       space_available = fgeom->width - layout->left_titlebar_edge - layout->right_titlebar_edge;
00735       
00736       space_used_by_buttons = 0;
00737 
00738       space_used_by_buttons += button_width * n_left;
00739       space_used_by_buttons += (button_width * 0.75) * n_left_spacers;
00740       space_used_by_buttons += layout->button_border.left * n_left;
00741       space_used_by_buttons += layout->button_border.right * n_left;
00742 
00743       space_used_by_buttons += button_width * n_right;
00744       space_used_by_buttons += (button_width * 0.75) * n_right_spacers;
00745       space_used_by_buttons += layout->button_border.left * n_right;
00746       space_used_by_buttons += layout->button_border.right * n_right;
00747 
00748       if (space_used_by_buttons <= space_available)
00749         break; /* Everything fits, bail out */
00750       
00751       /* First try to remove separators */
00752       if (n_left_spacers > 0)
00753         {
00754           left_buttons_has_spacer[--n_left_spacers] = FALSE;
00755           continue;
00756         }
00757       else if (n_right_spacers > 0)
00758         {
00759           right_buttons_has_spacer[--n_right_spacers] = FALSE;
00760           continue;
00761         }
00762 
00763       /* Otherwise we need to shave out a button. Shave
00764        * above, stick, shade, min, max, close, then menu (menu is most useful);
00765        * prefer the default button locations.
00766        */
00767       if (strip_button (left_func_rects, left_bg_rects,
00768                         &n_left, &fgeom->above_rect))
00769         continue;
00770       else if (strip_button (right_func_rects, right_bg_rects,
00771                              &n_right, &fgeom->above_rect))
00772         continue;
00773       else if (strip_button (left_func_rects, left_bg_rects,
00774                         &n_left, &fgeom->stick_rect))
00775         continue;
00776       else if (strip_button (right_func_rects, right_bg_rects,
00777                              &n_right, &fgeom->stick_rect))
00778         continue;
00779       else if (strip_button (left_func_rects, left_bg_rects,
00780                         &n_left, &fgeom->shade_rect))
00781         continue;
00782       else if (strip_button (right_func_rects, right_bg_rects,
00783                              &n_right, &fgeom->shade_rect))
00784         continue;
00785       else if (strip_button (left_func_rects, left_bg_rects,
00786                         &n_left, &fgeom->min_rect))
00787         continue;
00788       else if (strip_button (right_func_rects, right_bg_rects,
00789                              &n_right, &fgeom->min_rect))
00790         continue;
00791       else if (strip_button (left_func_rects, left_bg_rects,
00792                              &n_left, &fgeom->max_rect))
00793         continue;
00794       else if (strip_button (right_func_rects, right_bg_rects,
00795                              &n_right, &fgeom->max_rect))
00796         continue;
00797       else if (strip_button (left_func_rects, left_bg_rects,
00798                              &n_left, &fgeom->close_rect))
00799         continue;
00800       else if (strip_button (right_func_rects, right_bg_rects,
00801                              &n_right, &fgeom->close_rect))
00802         continue;
00803       else if (strip_button (right_func_rects, right_bg_rects,
00804                              &n_right, &fgeom->menu_rect))
00805         continue;
00806       else if (strip_button (left_func_rects, left_bg_rects,
00807                              &n_left, &fgeom->menu_rect))
00808         continue;
00809       else
00810         {
00811           meta_bug ("Could not find a button to strip. n_left = %d n_right = %d\n",
00812                     n_left, n_right);
00813         }
00814     }
00815   
00816   /* center buttons vertically */
00817   button_y = (fgeom->top_height -
00818               (button_height + layout->button_border.top + layout->button_border.bottom)) / 2 + layout->button_border.top;
00819 
00820   /* right edge of farthest-right button */
00821   x = width - layout->right_titlebar_edge;
00822   
00823   i = n_right - 1;
00824   while (i >= 0)
00825     {
00826       MetaButtonSpace *rect;
00827 
00828       if (x < 0) /* if we go negative, leave the buttons we don't get to as 0-width */
00829         break;
00830       
00831       rect = right_func_rects[i];
00832       rect->visible.x = x - layout->button_border.right - button_width;
00833       if (right_buttons_has_spacer[i])
00834         rect->visible.x -= (button_width * 0.75);
00835 
00836       rect->visible.y = button_y;
00837       rect->visible.width = button_width;
00838       rect->visible.height = button_height;
00839 
00840       if (flags & META_FRAME_MAXIMIZED)
00841         {
00842           rect->clickable.x = rect->visible.x;
00843           rect->clickable.y = 0;
00844           rect->clickable.width = rect->visible.width;
00845           rect->clickable.height = button_height + button_y;
00846 
00847           if (i == n_right - 1)
00848             rect->clickable.width += layout->right_titlebar_edge + layout->right_width + layout->button_border.right;
00849 
00850         }
00851       else
00852         g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable));
00853 
00854       *(right_bg_rects[i]) = rect->visible;
00855       
00856       x = rect->visible.x - layout->button_border.left;
00857       
00858       --i;
00859     }
00860 
00861   /* save right edge of titlebar for later use */
00862   title_right_edge = x - layout->title_border.right;
00863 
00864   /* Now x changes to be position from the left and we go through
00865    * the left-side buttons
00866    */
00867   x = layout->left_titlebar_edge;
00868   for (i = 0; i < n_left; i++)
00869     {
00870       MetaButtonSpace *rect;
00871 
00872       rect = left_func_rects[i];
00873       
00874       rect->visible.x = x + layout->button_border.left;
00875       rect->visible.y = button_y;
00876       rect->visible.width = button_width;
00877       rect->visible.height = button_height;
00878 
00879       if (flags & META_FRAME_MAXIMIZED)
00880         {
00881           if (i==0)
00882             {
00883               rect->clickable.x = 0;
00884               rect->clickable.width = button_width + x;
00885             }
00886           else
00887             {
00888               rect->clickable.x = rect->visible.x;
00889               rect->clickable.width = button_width;
00890             }
00891 
00892             rect->clickable.y = 0;
00893             rect->clickable.height = button_height + button_y;
00894           }
00895         else
00896           g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable));
00897 
00898 
00899       x = rect->visible.x + rect->visible.width + layout->button_border.right;
00900       if (left_buttons_has_spacer[i])
00901         x += (button_width * 0.75);
00902 
00903       *(left_bg_rects[i]) = rect->visible;
00904     }
00905 
00906   /* We always fill as much vertical space as possible with title rect,
00907    * rather than centering it like the buttons
00908    */
00909   fgeom->title_rect.x = x + layout->title_border.left;
00910   fgeom->title_rect.y = layout->title_border.top;
00911   fgeom->title_rect.width = title_right_edge - fgeom->title_rect.x;
00912   fgeom->title_rect.height = fgeom->top_height - layout->title_border.top - layout->title_border.bottom;
00913 
00914   /* Nuke title if it won't fit */
00915   if (fgeom->title_rect.width < 0 ||
00916       fgeom->title_rect.height < 0)
00917     {
00918       fgeom->title_rect.width = 0;
00919       fgeom->title_rect.height = 0;
00920     }
00921 
00922   if (flags & META_FRAME_SHADED)
00923     min_size_for_rounding = 0;
00924   else
00925     min_size_for_rounding = 5;
00926   
00927   fgeom->top_left_corner_rounded_radius = 0;
00928   fgeom->top_right_corner_rounded_radius = 0;
00929   fgeom->bottom_left_corner_rounded_radius = 0;
00930   fgeom->bottom_right_corner_rounded_radius = 0;
00931 
00932   if (fgeom->top_height + fgeom->left_width >= min_size_for_rounding)
00933     fgeom->top_left_corner_rounded_radius = layout->top_left_corner_rounded_radius;
00934   if (fgeom->top_height + fgeom->right_width >= min_size_for_rounding)
00935     fgeom->top_right_corner_rounded_radius = layout->top_right_corner_rounded_radius;
00936 
00937   if (fgeom->bottom_height + fgeom->left_width >= min_size_for_rounding)
00938     fgeom->bottom_left_corner_rounded_radius = layout->bottom_left_corner_rounded_radius;
00939   if (fgeom->bottom_height + fgeom->right_width >= min_size_for_rounding)
00940     fgeom->bottom_right_corner_rounded_radius = layout->bottom_right_corner_rounded_radius;
00941 }
00942 
00943 MetaGradientSpec*
00944 meta_gradient_spec_new (MetaGradientType type)
00945 {
00946   MetaGradientSpec *spec;
00947 
00948   spec = g_new (MetaGradientSpec, 1);
00949 
00950   spec->type = type;
00951   spec->color_specs = NULL;
00952 
00953   return spec;
00954 }
00955 
00956 static void
00957 free_color_spec (gpointer spec, gpointer user_data)
00958 {
00959   meta_color_spec_free (spec);
00960 }
00961 
00962 void
00963 meta_gradient_spec_free (MetaGradientSpec *spec)
00964 {
00965   g_return_if_fail (spec != NULL);
00966 
00967   g_slist_foreach (spec->color_specs, free_color_spec, NULL);
00968   g_slist_free (spec->color_specs);
00969   
00970   DEBUG_FILL_STRUCT (spec);
00971   g_free (spec);
00972 }
00973 
00974 GdkPixbuf*
00975 meta_gradient_spec_render (const MetaGradientSpec *spec,
00976                            GtkWidget              *widget,
00977                            int                     width,
00978                            int                     height)
00979 {
00980   int n_colors;
00981   GdkColor *colors;
00982   GSList *tmp;
00983   int i;
00984   GdkPixbuf *pixbuf;
00985 
00986   n_colors = g_slist_length (spec->color_specs);
00987 
00988   if (n_colors == 0)
00989     return NULL;
00990 
00991   colors = g_new (GdkColor, n_colors);
00992 
00993   i = 0;
00994   tmp = spec->color_specs;
00995   while (tmp != NULL)
00996     {
00997       meta_color_spec_render (tmp->data, widget, &colors[i]);
00998 
00999       tmp = tmp->next;
01000       ++i;
01001     }
01002 
01003   pixbuf = meta_gradient_create_multi (width, height,
01004                                        colors, n_colors,
01005                                        spec->type);
01006 
01007   g_free (colors);
01008 
01009   return pixbuf;
01010 }
01011 
01012 gboolean
01013 meta_gradient_spec_validate (MetaGradientSpec *spec,
01014                              GError          **error)
01015 {
01016   g_return_val_if_fail (spec != NULL, FALSE);
01017   
01018   if (g_slist_length (spec->color_specs) < 2)
01019     {
01020       g_set_error (error, META_THEME_ERROR,
01021                    META_THEME_ERROR_FAILED,
01022                    _("Gradients should have at least two colors"));
01023       return FALSE;
01024     }
01025 
01026   return TRUE;
01027 }
01028 
01029 MetaAlphaGradientSpec*
01030 meta_alpha_gradient_spec_new (MetaGradientType       type,
01031                               int                    n_alphas)
01032 {
01033   MetaAlphaGradientSpec *spec;
01034 
01035   g_return_val_if_fail (n_alphas > 0, NULL);
01036   
01037   spec = g_new0 (MetaAlphaGradientSpec, 1);
01038 
01039   spec->type = type;
01040   spec->alphas = g_new0 (unsigned char, n_alphas);
01041   spec->n_alphas = n_alphas;
01042 
01043   return spec;
01044 }
01045 
01046 void
01047 meta_alpha_gradient_spec_free (MetaAlphaGradientSpec *spec)
01048 {
01049   g_return_if_fail (spec != NULL);
01050 
01051   g_free (spec->alphas);
01052   g_free (spec);
01053 }
01054 
01055 MetaColorSpec*
01056 meta_color_spec_new (MetaColorSpecType type)
01057 {
01058   MetaColorSpec *spec;
01059   MetaColorSpec dummy;
01060   int size;
01061 
01062   size = G_STRUCT_OFFSET (MetaColorSpec, data);
01063 
01064   switch (type)
01065     {
01066     case META_COLOR_SPEC_BASIC:
01067       size += sizeof (dummy.data.basic);
01068       break;
01069 
01070     case META_COLOR_SPEC_GTK:
01071       size += sizeof (dummy.data.gtk);
01072       break;
01073 
01074     case META_COLOR_SPEC_BLEND:
01075       size += sizeof (dummy.data.blend);
01076       break;
01077 
01078     case META_COLOR_SPEC_SHADE:
01079       size += sizeof (dummy.data.shade);
01080       break;
01081     }
01082 
01083   spec = g_malloc0 (size);
01084 
01085   spec->type = type;
01086 
01087   return spec;
01088 }
01089 
01090 void
01091 meta_color_spec_free (MetaColorSpec *spec)
01092 {
01093   g_return_if_fail (spec != NULL);
01094 
01095   switch (spec->type)
01096     {
01097     case META_COLOR_SPEC_BASIC:
01098       DEBUG_FILL_STRUCT (&spec->data.basic);
01099       break;
01100 
01101     case META_COLOR_SPEC_GTK:
01102       DEBUG_FILL_STRUCT (&spec->data.gtk);
01103       break;
01104 
01105     case META_COLOR_SPEC_BLEND:
01106       if (spec->data.blend.foreground)
01107         meta_color_spec_free (spec->data.blend.foreground);
01108       if (spec->data.blend.background)
01109         meta_color_spec_free (spec->data.blend.background);
01110       DEBUG_FILL_STRUCT (&spec->data.blend);
01111       break;
01112 
01113     case META_COLOR_SPEC_SHADE:
01114       if (spec->data.shade.base)
01115         meta_color_spec_free (spec->data.shade.base);
01116       DEBUG_FILL_STRUCT (&spec->data.shade);
01117       break;
01118     }
01119 
01120   g_free (spec);
01121 }
01122 
01123 MetaColorSpec*
01124 meta_color_spec_new_from_string (const char *str,
01125                                  GError    **err)
01126 {
01127   MetaColorSpec *spec;
01128 
01129   spec = NULL;
01130   
01131   if (str[0] == 'g' && str[1] == 't' && str[2] == 'k' && str[3] == ':')
01132     {
01133       /* GTK color */
01134       const char *bracket;
01135       const char *end_bracket;
01136       char *tmp;
01137       GtkStateType state;
01138       MetaGtkColorComponent component;
01139       
01140       bracket = str;
01141       while (*bracket && *bracket != '[')
01142         ++bracket;
01143 
01144       if (*bracket == '\0')
01145         {
01146           g_set_error (err, META_THEME_ERROR,
01147                        META_THEME_ERROR_FAILED,
01148                        _("GTK color specification must have the state in brackets, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""),
01149                        str);
01150           return NULL;
01151         }
01152 
01153       end_bracket = bracket;
01154       ++end_bracket;
01155       while (*end_bracket && *end_bracket != ']')
01156         ++end_bracket;
01157       
01158       if (*end_bracket == '\0')
01159         {
01160           g_set_error (err, META_THEME_ERROR,
01161                        META_THEME_ERROR_FAILED,
01162                        _("GTK color specification must have a close bracket after the state, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""),
01163                        str);
01164           return NULL;
01165         }
01166 
01167       tmp = g_strndup (bracket + 1, end_bracket - bracket - 1);
01168       state = meta_gtk_state_from_string (tmp);
01169       if (((int) state) == -1)
01170         {
01171           g_set_error (err, META_THEME_ERROR,
01172                        META_THEME_ERROR_FAILED,
01173                        _("Did not understand state \"%s\" in color specification"),
01174                        tmp);
01175           g_free (tmp);
01176           return NULL;
01177         }
01178       g_free (tmp);
01179       
01180       tmp = g_strndup (str + 4, bracket - str - 4);
01181       component = meta_color_component_from_string (tmp);
01182       if (component == META_GTK_COLOR_LAST)
01183         {
01184           g_set_error (err, META_THEME_ERROR,
01185                        META_THEME_ERROR_FAILED,
01186                        _("Did not understand color component \"%s\" in color specification"),
01187                        tmp);
01188           g_free (tmp);
01189           return NULL;
01190         }
01191       g_free (tmp);
01192 
01193       spec = meta_color_spec_new (META_COLOR_SPEC_GTK);
01194       spec->data.gtk.state = state;
01195       spec->data.gtk.component = component;
01196       g_assert (spec->data.gtk.state < N_GTK_STATES);
01197       g_assert (spec->data.gtk.component < META_GTK_COLOR_LAST);
01198     }
01199   else if (str[0] == 'b' && str[1] == 'l' && str[2] == 'e' && str[3] == 'n' &&
01200            str[4] == 'd' && str[5] == '/')
01201     {
01202       /* blend */
01203       char **split;
01204       double alpha;
01205       char *end;
01206       MetaColorSpec *fg;
01207       MetaColorSpec *bg;
01208       
01209       split = g_strsplit (str, "/", 4);
01210       
01211       if (split[0] == NULL || split[1] == NULL ||
01212           split[2] == NULL || split[3] == NULL)
01213         {
01214           g_set_error (err, META_THEME_ERROR,
01215                        META_THEME_ERROR_FAILED,
01216                        _("Blend format is \"blend/bg_color/fg_color/alpha\", \"%s\" does not fit the format"),
01217                        str);
01218           g_strfreev (split);
01219           return NULL;
01220         }
01221 
01222       alpha = g_ascii_strtod (split[3], &end);
01223       if (end == split[3])
01224         {
01225           g_set_error (err, META_THEME_ERROR,
01226                        META_THEME_ERROR_FAILED,
01227                        _("Could not parse alpha value \"%s\" in blended color"),
01228                        split[3]);
01229           g_strfreev (split);
01230           return NULL;
01231         }
01232 
01233       if (alpha < (0.0 - 1e6) || alpha > (1.0 + 1e6))
01234         {
01235           g_set_error (err, META_THEME_ERROR,
01236                        META_THEME_ERROR_FAILED,
01237                        _("Alpha value \"%s\" in blended color is not between 0.0 and 1.0"),
01238                        split[3]);
01239           g_strfreev (split);
01240           return NULL;
01241         }
01242       
01243       fg = NULL;
01244       bg = NULL;
01245 
01246       bg = meta_color_spec_new_from_string (split[1], err);
01247       if (bg == NULL)
01248         {
01249           g_strfreev (split);
01250           return NULL;
01251         }
01252 
01253       fg = meta_color_spec_new_from_string (split[2], err);
01254       if (fg == NULL)
01255         {
01256           meta_color_spec_free (bg);
01257           g_strfreev (split);
01258           return NULL;
01259         }
01260 
01261       g_strfreev (split);
01262       
01263       spec = meta_color_spec_new (META_COLOR_SPEC_BLEND);
01264       spec->data.blend.alpha = alpha;
01265       spec->data.blend.background = bg;
01266       spec->data.blend.foreground = fg;
01267     }
01268   else if (str[0] == 's' && str[1] == 'h' && str[2] == 'a' && str[3] == 'd' &&
01269            str[4] == 'e' && str[5] == '/')
01270     {
01271       /* shade */
01272       char **split;
01273       double factor;
01274       char *end;
01275       MetaColorSpec *base;
01276       
01277       split = g_strsplit (str, "/", 3);
01278       
01279       if (split[0] == NULL || split[1] == NULL ||
01280           split[2] == NULL)
01281         {
01282           g_set_error (err, META_THEME_ERROR,
01283                        META_THEME_ERROR_FAILED,
01284                        _("Shade format is \"shade/base_color/factor\", \"%s\" does not fit the format"),
01285                        str);
01286           g_strfreev (split);
01287           return NULL;
01288         }
01289 
01290       factor = g_ascii_strtod (split[2], &end);
01291       if (end == split[2])
01292         {
01293           g_set_error (err, META_THEME_ERROR,
01294                        META_THEME_ERROR_FAILED,
01295                        _("Could not parse shade factor \"%s\" in shaded color"),
01296                        split[2]);
01297           g_strfreev (split);
01298           return NULL;
01299         }
01300 
01301       if (factor < (0.0 - 1e6))
01302         {
01303           g_set_error (err, META_THEME_ERROR,
01304                        META_THEME_ERROR_FAILED,
01305                        _("Shade factor \"%s\" in shaded color is negative"),
01306                        split[2]);
01307           g_strfreev (split);
01308           return NULL;
01309         }
01310       
01311       base = NULL;
01312 
01313       base = meta_color_spec_new_from_string (split[1], err);
01314       if (base == NULL)
01315         {
01316           g_strfreev (split);
01317           return NULL;
01318         }
01319 
01320       g_strfreev (split);
01321       
01322       spec = meta_color_spec_new (META_COLOR_SPEC_SHADE);
01323       spec->data.shade.factor = factor;
01324       spec->data.shade.base = base;
01325     }
01326   else
01327     {
01328       spec = meta_color_spec_new (META_COLOR_SPEC_BASIC);
01329       
01330       if (!gdk_color_parse (str, &spec->data.basic.color))
01331         {
01332           g_set_error (err, META_THEME_ERROR,
01333                        META_THEME_ERROR_FAILED,
01334                        _("Could not parse color \"%s\""),
01335                        str);
01336           meta_color_spec_free (spec);
01337           return NULL;
01338         }
01339     }
01340 
01341   g_assert (spec);
01342   
01343   return spec;
01344 }
01345 
01346 MetaColorSpec*
01347 meta_color_spec_new_gtk (MetaGtkColorComponent component,
01348                          GtkStateType          state)
01349 {
01350   MetaColorSpec *spec;
01351 
01352   spec = meta_color_spec_new (META_COLOR_SPEC_GTK);
01353 
01354   spec->data.gtk.component = component;
01355   spec->data.gtk.state = state;
01356 
01357   return spec;
01358 }
01359 
01360 void
01361 meta_color_spec_render (MetaColorSpec *spec,
01362                         GtkWidget     *widget,
01363                         GdkColor      *color)
01364 {
01365   g_return_if_fail (spec != NULL);
01366   g_return_if_fail (GTK_IS_WIDGET (widget));
01367   g_return_if_fail (widget->style != NULL);
01368 
01369   switch (spec->type)
01370     {
01371     case META_COLOR_SPEC_BASIC:
01372       *color = spec->data.basic.color;
01373       break;
01374 
01375     case META_COLOR_SPEC_GTK:
01376       switch (spec->data.gtk.component)
01377         {
01378         case META_GTK_COLOR_BG:
01379           *color = widget->style->bg[spec->data.gtk.state];
01380           break;
01381         case META_GTK_COLOR_FG:
01382           *color = widget->style->fg[spec->data.gtk.state];
01383           break;
01384         case META_GTK_COLOR_BASE:
01385           *color = widget->style->base[spec->data.gtk.state];
01386           break;
01387         case META_GTK_COLOR_TEXT:
01388           *color = widget->style->text[spec->data.gtk.state];
01389           break;
01390         case META_GTK_COLOR_LIGHT:
01391           *color = widget->style->light[spec->data.gtk.state];
01392           break;
01393         case META_GTK_COLOR_DARK:
01394           *color = widget->style->dark[spec->data.gtk.state];
01395           break;
01396         case META_GTK_COLOR_MID:
01397           *color = widget->style->mid[spec->data.gtk.state];
01398           break;
01399         case META_GTK_COLOR_TEXT_AA:
01400           *color = widget->style->text_aa[spec->data.gtk.state];
01401           break;
01402         case META_GTK_COLOR_LAST:
01403           g_assert_not_reached ();
01404           break;
01405         }
01406       break;
01407 
01408     case META_COLOR_SPEC_BLEND:
01409       {
01410         GdkColor bg, fg;
01411 
01412         meta_color_spec_render (spec->data.blend.background, widget, &bg);
01413         meta_color_spec_render (spec->data.blend.foreground, widget, &fg);
01414 
01415         color_composite (&bg, &fg, spec->data.blend.alpha, 
01416                          &spec->data.blend.color);
01417 
01418         *color = spec->data.blend.color;
01419       }
01420       break;
01421 
01422     case META_COLOR_SPEC_SHADE:
01423       {
01424         meta_color_spec_render (spec->data.shade.base, widget, 
01425                                 &spec->data.shade.color);
01426             
01427         gtk_style_shade (&spec->data.shade.color, 
01428                          &spec->data.shade.color, spec->data.shade.factor);
01429 
01430         *color = spec->data.shade.color;
01431       }
01432       break;
01433     }
01434 }
01435 
01442 static const char*
01443 op_name (PosOperatorType type)
01444 {
01445   switch (type)
01446     {
01447     case POS_OP_ADD:
01448       return "+";
01449     case POS_OP_SUBTRACT:
01450       return "-";
01451     case POS_OP_MULTIPLY:
01452       return "*";
01453     case POS_OP_DIVIDE:
01454       return "/";
01455     case POS_OP_MOD:
01456       return "%";
01457     case POS_OP_MAX:
01458       return "`max`";
01459     case POS_OP_MIN:
01460       return "`min`";
01461     case POS_OP_NONE:
01462       break;
01463     }
01464 
01465   return "<unknown>";
01466 }
01467 
01476 static PosOperatorType
01477 op_from_string (const char *p,
01478                 int        *len)
01479 {
01480   *len = 0;
01481   
01482   switch (*p)
01483     {
01484     case '+':
01485       *len = 1;
01486       return POS_OP_ADD;
01487     case '-':
01488       *len = 1;
01489       return POS_OP_SUBTRACT;
01490     case '*':
01491       *len = 1;
01492       return POS_OP_MULTIPLY;
01493     case '/':
01494       *len = 1;
01495       return POS_OP_DIVIDE;
01496     case '%':
01497       *len = 1;
01498       return POS_OP_MOD;
01499 
01500     case '`':
01501       if (p[0] == '`' &&
01502           p[1] == 'm' &&
01503           p[2] == 'a' &&
01504           p[3] == 'x' &&
01505           p[4] == '`')
01506         {
01507           *len = 5;
01508           return POS_OP_MAX;
01509         }
01510       else if (p[0] == '`' &&
01511                p[1] == 'm' &&
01512                p[2] == 'i' &&
01513                p[3] == 'n' &&
01514                p[4] == '`')
01515         {
01516           *len = 5;
01517           return POS_OP_MIN;
01518         }
01519     }
01520 
01521   return POS_OP_NONE;
01522 }
01523 
01531 static void
01532 free_tokens (PosToken *tokens,
01533              int       n_tokens)
01534 {
01535   int i;
01536 
01537   /* n_tokens can be 0 since tokens may have been allocated more than
01538    * it was initialized
01539    */
01540 
01541   for (i = 0; i < n_tokens; i++)
01542     if (tokens[i].type == POS_TOKEN_VARIABLE)
01543       g_free (tokens[i].d.v.name);
01544 
01545   g_free (tokens);
01546 }
01547 
01564 static gboolean
01565 parse_number (const char  *p,
01566               const char **end_return,
01567               PosToken    *next,
01568               GError     **err)
01569 {
01570   const char *start = p;
01571   char *end;
01572   gboolean is_float;
01573   char *num_str;
01574 
01575   while (*p && (*p == '.' || g_ascii_isdigit (*p)))
01576     ++p;
01577 
01578   if (p == start)
01579     {
01580       char buf[7] = { '\0' };
01581       buf[g_unichar_to_utf8 (g_utf8_get_char (p), buf)] = '\0';
01582       g_set_error (err, META_THEME_ERROR,
01583                    META_THEME_ERROR_BAD_CHARACTER,
01584                    _("Coordinate expression contains character '%s' which is not allowed"),
01585                    buf);
01586       return FALSE;
01587     }
01588 
01589   *end_return = p;
01590 
01591   /* we need this to exclude floats like "1e6" */
01592   num_str = g_strndup (start, p - start);
01593   start = num_str;
01594   is_float = FALSE;
01595   while (*start)
01596     {
01597       if (*start == '.')
01598         is_float = TRUE;
01599       ++start;
01600     }
01601 
01602   if (is_float)
01603     {
01604       next->type = POS_TOKEN_DOUBLE;
01605       next->d.d.val = g_ascii_strtod (num_str, &end);
01606 
01607       if (end == num_str)
01608         {
01609           g_set_error (err, META_THEME_ERROR,
01610                        META_THEME_ERROR_FAILED,
01611                        _("Coordinate expression contains floating point number '%s' which could not be parsed"),
01612                        num_str);
01613           g_free (num_str);
01614           return FALSE;
01615         }
01616     }
01617   else
01618     {
01619       next->type = POS_TOKEN_INT;
01620       next->d.i.val = strtol (num_str, &end, 10);
01621       if (end == num_str)
01622         {
01623           g_set_error (err, META_THEME_ERROR,
01624                        META_THEME_ERROR_FAILED,
01625                        _("Coordinate expression contains integer '%s' which could not be parsed"),
01626                        num_str);
01627           g_free (num_str);
01628           return FALSE;
01629         }
01630     }
01631 
01632   g_free (num_str);
01633 
01634   g_assert (next->type == POS_TOKEN_INT || next->type == POS_TOKEN_DOUBLE);
01635 
01636   return TRUE;
01637 }
01638 
01642 #define IS_VARIABLE_CHAR(c) (g_ascii_isalpha ((c)) || (c) == '_')
01643 
01644 #if 0
01645 static void
01646 debug_print_tokens (PosToken *tokens,
01647                     int       n_tokens)
01648 {
01649   int i;
01650   
01651   for (i = 0; i < n_tokens; i++)
01652     {
01653       PosToken *t = &tokens[i];
01654 
01655       g_print (" ");
01656 
01657       switch (t->type)
01658         {
01659         case POS_TOKEN_INT:
01660           g_print ("\"%d\"", t->d.i.val);
01661           break;
01662         case POS_TOKEN_DOUBLE:
01663           g_print ("\"%g\"", t->d.d.val);
01664           break;
01665         case POS_TOKEN_OPEN_PAREN:
01666           g_print ("\"(\"");
01667           break;
01668         case POS_TOKEN_CLOSE_PAREN:
01669           g_print ("\")\"");
01670           break;
01671         case POS_TOKEN_VARIABLE:
01672           g_print ("\"%s\"", t->d.v.name);
01673           break;
01674         case POS_TOKEN_OPERATOR:
01675           g_print ("\"%s\"", op_name (t->d.o.op));
01676           break;
01677         }
01678     }
01679 
01680   g_print ("\n");
01681 }
01682 #endif
01683 
01696 static gboolean
01697 pos_tokenize (const char  *expr,
01698               PosToken   **tokens_p,
01699               int         *n_tokens_p,
01700               GError     **err)
01701 {
01702   PosToken *tokens;
01703   int n_tokens;
01704   int allocated;
01705   const char *p;
01706   
01707   *tokens_p = NULL;
01708   *n_tokens_p = 0;
01709 
01710   allocated = 3;
01711   n_tokens = 0;
01712   tokens = g_new (PosToken, allocated);
01713 
01714   p = expr;
01715   while (*p)
01716     {
01717       PosToken *next;
01718       int len;
01719       
01720       if (n_tokens == allocated)
01721         {
01722           allocated *= 2;
01723           tokens = g_renew (PosToken, tokens, allocated);
01724         }
01725 
01726       next = &tokens[n_tokens];
01727 
01728       switch (*p)
01729         {
01730         case '*':
01731         case '/':
01732         case '+':
01733         case '-': /* negative numbers aren't allowed so this is easy */
01734         case '%':
01735         case '`':
01736           next->type = POS_TOKEN_OPERATOR;
01737           next->d.o.op = op_from_string (p, &len);
01738           if (next->d.o.op != POS_OP_NONE)
01739             {
01740               ++n_tokens;
01741               p = p + (len - 1); /* -1 since we ++p later */
01742             }
01743           else
01744             {
01745               g_set_error (err, META_THEME_ERROR,
01746                            META_THEME_ERROR_FAILED,
01747                            _("Coordinate expression contained unknown operator at the start of this text: \"%s\""),
01748                            p);
01749               
01750               goto error;
01751             }
01752           break;
01753 
01754         case '(':
01755           next->type = POS_TOKEN_OPEN_PAREN;
01756           ++n_tokens;
01757           break;
01758 
01759         case ')':
01760           next->type = POS_TOKEN_CLOSE_PAREN;
01761           ++n_tokens;
01762           break;
01763 
01764         case ' ':
01765         case '\t':
01766         case '\n':              
01767           break;
01768 
01769         default:
01770           if (IS_VARIABLE_CHAR (*p))
01771             {
01772               /* Assume variable */
01773               const char *start = p;
01774               while (*p && IS_VARIABLE_CHAR (*p))
01775                 ++p;
01776               g_assert (p != start);
01777               next->type = POS_TOKEN_VARIABLE;
01778               next->d.v.name = g_strndup (start, p - start);
01779               ++n_tokens;
01780               --p; /* since we ++p again at the end of while loop */
01781             }
01782           else
01783             {
01784               /* Assume number */
01785               const char *end;
01786 
01787               if (!parse_number (p, &end, next, err))
01788                 goto error;
01789 
01790               ++n_tokens;
01791               p = end - 1; /* -1 since we ++p again at the end of while loop */
01792             }
01793 
01794           break;
01795         }
01796 
01797       ++p;
01798     }
01799 
01800   if (n_tokens == 0)
01801     {
01802       g_set_error (err, META_THEME_ERROR,
01803                    META_THEME_ERROR_FAILED,
01804                    _("Coordinate expression was empty or not understood"));
01805 
01806       goto error;
01807     }
01808 
01809   *tokens_p = tokens;
01810   *n_tokens_p = n_tokens;
01811 
01812   return TRUE;
01813 
01814  error:
01815   g_assert (err == NULL || *err != NULL);
01816 
01817   free_tokens (tokens, n_tokens);
01818   return FALSE;
01819 }
01820 
01825 typedef enum
01826 {
01827   POS_EXPR_INT,
01828   POS_EXPR_DOUBLE,
01829   POS_EXPR_OPERATOR
01830 } PosExprType;
01831 
01842 typedef struct
01843 {
01844   PosExprType type;
01845   union
01846   {
01847     double double_val;
01848     int int_val;
01849     char operator;
01850   } d;
01851 } PosExpr;
01852 
01853 #if 0
01854 static void
01855 debug_print_exprs (PosExpr *exprs,
01856                    int      n_exprs)
01857 {
01858   int i;
01859 
01860   for (i = 0; i < n_exprs; i++)
01861     {
01862       switch (exprs[i].type)
01863         {
01864         case POS_EXPR_INT:
01865           g_print (" %d", exprs[i].d.int_val);
01866           break;
01867         case POS_EXPR_DOUBLE:
01868           g_print (" %g", exprs[i].d.double_val);
01869           break;
01870         case POS_EXPR_OPERATOR:
01871           g_print (" %s", op_name (exprs[i].d.operator));
01872           break;
01873         }
01874     }
01875   g_print ("\n");
01876 }
01877 #endif
01878 
01879 static gboolean
01880 do_operation (PosExpr *a,
01881               PosExpr *b,
01882               PosOperatorType op,
01883               GError **err)
01884 {
01885   /* Promote types to double if required */
01886   if (a->type == POS_EXPR_DOUBLE ||
01887       b->type == POS_EXPR_DOUBLE)
01888     {
01889       if (a->type != POS_EXPR_DOUBLE)
01890         {
01891           a->type = POS_EXPR_DOUBLE;
01892           a->d.double_val = a->d.int_val;
01893         }
01894       if (b->type != POS_EXPR_DOUBLE)
01895         {
01896           b->type = POS_EXPR_DOUBLE;
01897           b->d.double_val = b->d.int_val;
01898         }
01899     }
01900 
01901   g_assert (a->type == b->type);
01902 
01903   if (a->type == POS_EXPR_INT)
01904     {
01905       switch (op)
01906         {
01907         case POS_OP_MULTIPLY:
01908           a->d.int_val = a->d.int_val * b->d.int_val;
01909           break;
01910         case POS_OP_DIVIDE:
01911           if (b->d.int_val == 0)
01912             {
01913               g_set_error (err, META_THEME_ERROR,
01914                            META_THEME_ERROR_DIVIDE_BY_ZERO,
01915                            _("Coordinate expression results in division by zero"));
01916               return FALSE;
01917             }
01918           a->d.int_val = a->d.int_val / b->d.int_val;
01919           break;
01920         case POS_OP_MOD:
01921           if (b->d.int_val == 0)
01922             {
01923               g_set_error (err, META_THEME_ERROR,
01924                            META_THEME_ERROR_DIVIDE_BY_ZERO,
01925                            _("Coordinate expression results in division by zero"));
01926               return FALSE;
01927             }
01928           a->d.int_val = a->d.int_val % b->d.int_val;
01929           break;
01930         case POS_OP_ADD:
01931           a->d.int_val = a->d.int_val + b->d.int_val;
01932           break;
01933         case POS_OP_SUBTRACT:
01934           a->d.int_val = a->d.int_val - b->d.int_val;
01935           break;
01936         case POS_OP_MAX:
01937           a->d.int_val = MAX (a->d.int_val, b->d.int_val);
01938           break;
01939         case POS_OP_MIN:
01940           a->d.int_val = MIN (a->d.int_val, b->d.int_val);
01941           break;
01942         case POS_OP_NONE:
01943           g_assert_not_reached ();
01944           break;
01945         }
01946     }
01947   else if (a->type == POS_EXPR_DOUBLE)
01948     {
01949       switch (op)
01950         {
01951         case POS_OP_MULTIPLY:
01952           a->d.double_val = a->d.double_val * b->d.double_val;
01953           break;
01954         case POS_OP_DIVIDE:
01955           if (b->d.double_val == 0.0)
01956             {
01957               g_set_error (err, META_THEME_ERROR,
01958                            META_THEME_ERROR_DIVIDE_BY_ZERO,
01959                            _("Coordinate expression results in division by zero"));
01960               return FALSE;
01961             }
01962           a->d.double_val = a->d.double_val / b->d.double_val;
01963           break;
01964         case POS_OP_MOD:
01965           g_set_error (err, META_THEME_ERROR,
01966                        META_THEME_ERROR_MOD_ON_FLOAT,
01967                        _("Coordinate expression tries to use mod operator on a floating-point number"));
01968           return FALSE;
01969         case POS_OP_ADD:
01970           a->d.double_val = a->d.double_val + b->d.double_val;
01971           break;
01972         case POS_OP_SUBTRACT:
01973           a->d.double_val = a->d.double_val - b->d.double_val;
01974           break;
01975         case POS_OP_MAX:
01976           a->d.double_val = MAX (a->d.double_val, b->d.double_val);
01977           break;
01978         case POS_OP_MIN:
01979           a->d.double_val = MIN (a->d.double_val, b->d.double_val);
01980           break;
01981         case POS_OP_NONE:
01982           g_assert_not_reached ();
01983           break;
01984         }
01985     }
01986   else
01987     g_assert_not_reached ();
01988 
01989   return TRUE;
01990 }
01991 
01992 static gboolean
01993 do_operations (PosExpr *exprs,
01994                int     *n_exprs,
01995                int      precedence,
01996                GError **err)
01997 {
01998   int i;
01999 
02000 #if 0
02001   g_print ("Doing prec %d ops on %d exprs\n", precedence, *n_exprs);
02002   debug_print_exprs (exprs, *n_exprs);
02003 #endif
02004 
02005   i = 1;
02006   while (i < *n_exprs)
02007     {
02008       gboolean compress;
02009 
02010       /* exprs[i-1] first operand
02011        * exprs[i]   operator
02012        * exprs[i+1] second operand
02013        *
02014        * we replace first operand with result of mul/div/mod,
02015        * or skip over operator and second operand if we have
02016        * an add/subtract
02017        */
02018 
02019       if (exprs[i-1].type == POS_EXPR_OPERATOR)
02020         {
02021           g_set_error (err, META_THEME_ERROR,
02022                        META_THEME_ERROR_FAILED,
02023                        _("Coordinate expression has an operator \"%s\" where an operand was expected"),
02024                        op_name (exprs[i-1].d.operator));
02025           return FALSE;
02026         }
02027 
02028       if (exprs[i].type != POS_EXPR_OPERATOR)
02029         {
02030           g_set_error (err, META_THEME_ERROR,
02031                        META_THEME_ERROR_FAILED,
02032                        _("Coordinate expression had an operand where an operator was expected"));
02033           return FALSE;
02034         }
02035 
02036       if (i == (*n_exprs - 1))
02037         {
02038           g_set_error (err, META_THEME_ERROR,
02039                        META_THEME_ERROR_FAILED,
02040                        _("Coordinate expression ended with an operator instead of an operand"));
02041           return FALSE;
02042         }
02043 
02044       g_assert ((i+1) < *n_exprs);
02045 
02046       if (exprs[i+1].type == POS_EXPR_OPERATOR)
02047         {
02048           g_set_error (err, META_THEME_ERROR,
02049                        META_THEME_ERROR_FAILED,
02050                        _("Coordinate expression has operator \"%c\" following operator \"%c\" with no operand in between"),
02051                        exprs[i+1].d.operator,
02052                        exprs[i].d.operator);
02053           return FALSE;
02054         }
02055 
02056       compress = FALSE;
02057 
02058       switch (precedence)
02059         {
02060         case 2:
02061           switch (exprs[i].d.operator)
02062             {
02063             case POS_OP_DIVIDE:
02064             case POS_OP_MOD:
02065             case POS_OP_MULTIPLY:
02066               compress = TRUE;
02067               if (!do_operation (&exprs[i-1], &exprs[i+1],
02068                                  exprs[i].d.operator,
02069                                  err))
02070                 return FALSE;
02071               break;
02072             }
02073           break;
02074         case 1:
02075           switch (exprs[i].d.operator)
02076             {
02077             case POS_OP_ADD:
02078             case POS_OP_SUBTRACT:
02079               compress = TRUE;
02080               if (!do_operation (&exprs[i-1], &exprs[i+1],
02081                                  exprs[i].d.operator,
02082                                  err))
02083                 return FALSE;
02084               break;
02085             }
02086           break;
02087           /* I have no rationale at all for making these low-precedence */
02088         case 0:
02089           switch (exprs[i].d.operator)
02090             {
02091             case POS_OP_MAX:
02092             case POS_OP_MIN:
02093               compress = TRUE;
02094               if (!do_operation (&exprs[i-1], &exprs[i+1],
02095                                  exprs[i].d.operator,
02096                                  err))
02097                 return FALSE;
02098               break;
02099             }
02100           break;
02101         }
02102 
02103       if (compress)
02104         {
02105           /* exprs[i-1] first operand (now result)
02106            * exprs[i]   operator
02107            * exprs[i+1] second operand
02108            * exprs[i+2] new operator
02109            *
02110            * we move new operator just after first operand
02111            */
02112           if ((i+2) < *n_exprs)
02113             {
02114               g_memmove (&exprs[i], &exprs[i+2],
02115                          sizeof (PosExpr) * (*n_exprs - i - 2));
02116             }
02117 
02118           *n_exprs -= 2;
02119         }
02120       else
02121         {
02122           /* Skip operator and next operand */
02123           i += 2;
02124         }
02125     }
02126 
02127   return TRUE;
02128 }
02129 
02155 static gboolean
02156 pos_eval_get_variable (PosToken                  *t,
02157                        int                       *result,
02158                        const MetaPositionExprEnv *env,
02159                        GError                   **err)
02160 {
02161   if (env->theme)
02162     {
02163       if (t->d.v.name_quark == env->theme->quark_width)
02164         *result = env->rect.width;
02165       else if (t->d.v.name_quark == env->theme->quark_height)
02166         *result = env->rect.height;
02167       else if (env->object_width >= 0 &&
02168                t->d.v.name_quark == env->theme->quark_object_width)
02169         *result = env->object_width;
02170       else if (env->object_height >= 0 &&
02171                t->d.v.name_quark == env->theme->quark_object_height)
02172         *result = env->object_height;
02173       else if (t->d.v.name_quark == env->theme->quark_left_width)
02174         *result = env->left_width;
02175       else if (t->d.v.name_quark == env->theme->quark_right_width)
02176         *result = env->right_width;
02177       else if (t->d.v.name_quark == env->theme->quark_top_height)
02178         *result = env->top_height;
02179       else if (t->d.v.name_quark == env->theme->quark_bottom_height)
02180         *result = env->bottom_height;
02181       else if (t->d.v.name_quark == env->theme->quark_mini_icon_width)
02182         *result = env->mini_icon_width;
02183       else if (t->d.v.name_quark == env->theme->quark_mini_icon_height)
02184         *result = env->mini_icon_height;
02185       else if (t->d.v.name_quark == env->theme->quark_icon_width)
02186         *result = env->icon_width;
02187       else if (t->d.v.name_quark == env->theme->quark_icon_height)
02188         *result = env->icon_height;
02189       else if (t->d.v.name_quark == env->theme->quark_title_width)
02190         *result = env->title_width;
02191       else if (t->d.v.name_quark == env->theme->quark_title_height)
02192         *result = env->title_height;
02193       else
02194         {
02195           g_set_error (err, META_THEME_ERROR,
02196                        META_THEME_ERROR_UNKNOWN_VARIABLE,
02197                        _("Coordinate expression had unknown variable or constant \"%s\""),
02198                        t->d.v.name);
02199           return FALSE;
02200         }
02201     }
02202   else 
02203     {
02204       if (strcmp (t->d.v.name, "width") == 0)
02205         *result = env->rect.width;
02206       else if (strcmp (t->d.v.name, "height") == 0)
02207         *result = env->rect.height;
02208       else if (env->object_width >= 0 &&
02209                strcmp (t->d.v.name, "object_width") == 0)
02210         *result = env->object_width;
02211       else if (env->object_height >= 0 &&
02212                strcmp (t->d.v.name, "object_height") == 0)
02213         *result = env->object_height;
02214       else if (strcmp (t->d.v.name, "left_width") == 0)
02215         *result = env->left_width;
02216       else if (strcmp (t->d.v.name, "right_width") == 0)
02217         *result = env->right_width;
02218       else if (strcmp (t->d.v.name, "top_height") == 0)
02219         *result = env->top_height;
02220       else if (strcmp (t->d.v.name, "bottom_height") == 0)
02221         *result = env->bottom_height;
02222       else if (strcmp (t->d.v.name, "mini_icon_width") == 0)
02223         *result = env->mini_icon_width;
02224       else if (strcmp (t->d.v.name, "mini_icon_height") == 0)
02225         *result = env->mini_icon_height;
02226       else if (strcmp (t->d.v.name, "icon_width") == 0)
02227         *result = env->icon_width;
02228       else if (strcmp (t->d.v.name, "icon_height") == 0)
02229         *result = env->icon_height;
02230       else if (strcmp (t->d.v.name, "title_width") == 0)
02231         *result = env->title_width;
02232       else if (strcmp (t->d.v.name, "title_height") == 0)
02233         *result = env->title_height;
02234       else
02235         {
02236           g_set_error (err, META_THEME_ERROR,
02237                        META_THEME_ERROR_UNKNOWN_VARIABLE,
02238                        _("Coordinate expression had unknown variable or constant \"%s\""),
02239                        t->d.v.name);
02240           return FALSE;
02241         }
02242     }
02243 
02244   return TRUE;
02245 }
02246 
02261 static gboolean
02262 pos_eval_helper (PosToken                   *tokens,
02263                  int                         n_tokens,
02264                  const MetaPositionExprEnv  *env,
02265                  PosExpr                    *result,
02266                  GError                    **err)
02267 {
02268   /* Lazy-ass hardcoded limit on number of terms in expression */
02269 #define MAX_EXPRS 32
02270   int paren_level;
02271   int first_paren;
02272   int i;
02273   PosExpr exprs[MAX_EXPRS];
02274   int n_exprs;
02275   int precedence;
02276   
02277   /* Our first goal is to get a list of PosExpr, essentially
02278    * substituting variables and handling parentheses.
02279    */
02280 
02281   first_paren = 0;
02282   paren_level = 0;
02283   n_exprs = 0;
02284   for (i = 0; i < n_tokens; i++)
02285     {
02286       PosToken *t = &tokens[i];
02287 
02288       if (n_exprs >= MAX_EXPRS)
02289         {
02290           g_set_error (err, META_THEME_ERROR,
02291                        META_THEME_ERROR_FAILED,
02292                        _("Coordinate expression parser overflowed its buffer."));
02293           return FALSE;
02294         }
02295 
02296       if (paren_level == 0)
02297         {
02298           switch (t->type)
02299             {
02300             case POS_TOKEN_INT:
02301               exprs[n_exprs].type = POS_EXPR_INT;
02302               exprs[n_exprs].d.int_val = t->d.i.val;
02303               ++n_exprs;
02304               break;
02305 
02306             case POS_TOKEN_DOUBLE:
02307               exprs[n_exprs].type = POS_EXPR_DOUBLE;
02308               exprs[n_exprs].d.double_val = t->d.d.val;
02309               ++n_exprs;
02310               break;
02311 
02312             case POS_TOKEN_OPEN_PAREN:
02313               ++paren_level;
02314               if (paren_level == 1)
02315                 first_paren = i;
02316               break;
02317 
02318             case POS_TOKEN_CLOSE_PAREN:
02319               g_set_error (err, META_THEME_ERROR,
02320                            META_THEME_ERROR_BAD_PARENS,
02321                            _("Coordinate expression had a close parenthesis with no open parenthesis"));
02322               return FALSE;
02323 
02324             case POS_TOKEN_VARIABLE:
02325               exprs[n_exprs].type = POS_EXPR_INT;
02326 
02327               /* FIXME we should just dump all this crap
02328                * in a hash, maybe keep width/height out
02329                * for optimization purposes
02330                */
02331               if (!pos_eval_get_variable (t, &exprs[n_exprs].d.int_val, env, err))
02332                 return FALSE;
02333                   
02334               ++n_exprs;
02335               break;
02336 
02337             case POS_TOKEN_OPERATOR:
02338               exprs[n_exprs].type = POS_EXPR_OPERATOR;
02339               exprs[n_exprs].d.operator = t->d.o.op;
02340               ++n_exprs;
02341               break;
02342             }
02343         }
02344       else
02345         {
02346           g_assert (paren_level > 0);
02347 
02348           switch (t->type)
02349             {
02350             case POS_TOKEN_INT:
02351             case POS_TOKEN_DOUBLE:
02352             case POS_TOKEN_VARIABLE:
02353             case POS_TOKEN_OPERATOR:
02354               break;
02355 
02356             case POS_TOKEN_OPEN_PAREN:
02357               ++paren_level;
02358               break;
02359 
02360             case POS_TOKEN_CLOSE_PAREN:
02361               if (paren_level == 1)
02362                 {
02363                   /* We closed a toplevel paren group, so recurse */
02364                   if (!pos_eval_helper (&tokens[first_paren+1],
02365                                         i - first_paren - 1,
02366                                         env,
02367                                         &exprs[n_exprs],
02368                                         err))
02369                     return FALSE;
02370 
02371                   ++n_exprs;
02372                 }
02373 
02374               --paren_level;
02375               break;
02376 
02377             }
02378         }
02379     }
02380 
02381   if (paren_level > 0)
02382     {
02383       g_set_error (err, META_THEME_ERROR,
02384                    META_THEME_ERROR_BAD_PARENS,
02385                    _("Coordinate expression had an open parenthesis with no close parenthesis"));
02386       return FALSE;
02387     }
02388 
02389   /* Now we have no parens and no vars; so we just do all the multiplies
02390    * and divides, then all the add and subtract.
02391    */
02392   if (n_exprs == 0)
02393     {
02394       g_set_error (err, META_THEME_ERROR,
02395                    META_THEME_ERROR_FAILED,
02396                    _("Coordinate expression doesn't seem to have any operators or operands"));
02397       return FALSE;
02398     }
02399 
02400   /* precedence 1 ops */
02401   precedence = 2;
02402   while (precedence >= 0)
02403     {
02404       if (!do_operations (exprs, &n_exprs, precedence, err))
02405         return FALSE;
02406       --precedence;
02407     }
02408 
02409   g_assert (n_exprs == 1);
02410 
02411   *result = *exprs;
02412 
02413   return TRUE;
02414 }
02415 
02416 /*
02417  *   expr = int | double | expr * expr | expr / expr |
02418  *          expr + expr | expr - expr | (expr)
02419  *
02420  *   so very not worth fooling with bison, yet so very painful by hand.
02421  */
02438 static gboolean
02439 pos_eval (MetaDrawSpec              *spec,
02440           const MetaPositionExprEnv *env,
02441           int                       *val_p,
02442           GError                   **err)
02443 {
02444   PosExpr expr;
02445 
02446   *val_p = 0;
02447 
02448   if (pos_eval_helper (spec->tokens, spec->n_tokens, env, &expr, err))
02449     {
02450       switch (expr.type)
02451         {
02452         case POS_EXPR_INT:
02453           *val_p = expr.d.int_val;
02454           break;
02455         case POS_EXPR_DOUBLE:
02456           *val_p = expr.d.double_val;
02457           break;
02458         case POS_EXPR_OPERATOR:
02459           g_assert_not_reached ();
02460           break;
02461         }
02462       return TRUE;
02463     }
02464   else
02465     {
02466       return FALSE;
02467     }
02468 }
02469 
02470 /* We always return both X and Y, but only one will be meaningful in
02471  * most contexts.
02472  */
02473 
02474 gboolean
02475 meta_parse_position_expression (MetaDrawSpec              *spec,
02476                                 const MetaPositionExprEnv *env,
02477                                 int                       *x_return,
02478                                 int                       *y_return,
02479                                 GError                   **err)
02480 {
02481   /* All positions are in a coordinate system with x, y at the origin.
02482    * The expression can have -, +, *, / as operators, floating point
02483    * or integer constants, and the variables "width" and "height" and
02484    * optionally "object_width" and object_height". Negative numbers
02485    * aren't allowed.
02486    */
02487   int val;
02488 
02489   if (spec->constant)
02490     val = spec->value;
02491   else
02492     {
02493       if (pos_eval (spec, env, &spec->value, err) == FALSE)
02494         {
02495           g_assert (err == NULL || *err != NULL);
02496           return FALSE;
02497         }
02498 
02499       val = spec->value;
02500     }
02501 
02502   if (x_return)
02503     *x_return = env->rect.x + val;
02504   if (y_return)
02505     *y_return = env->rect.y + val;
02506 
02507   return TRUE;
02508 }
02509 
02510 
02511 gboolean
02512 meta_parse_size_expression (MetaDrawSpec              *spec,
02513                             const MetaPositionExprEnv *env,
02514                             int                       *val_return,
02515                             GError                   **err)
02516 {
02517   int val;
02518 
02519   if (spec->constant)
02520     val = spec->value;
02521   else 
02522     {
02523       if (pos_eval (spec, env, &spec->value, err) == FALSE)
02524         {
02525           g_assert (err == NULL || *err != NULL);
02526           return FALSE;
02527         }
02528 
02529       val = spec->value;
02530     }
02531 
02532   if (val_return)
02533     *val_return = MAX (val, 1); /* require that sizes be at least 1x1 */
02534 
02535   return TRUE;
02536 }
02537 
02538 /* To do this we tokenize, replace variable tokens
02539  * that are constants, then reassemble. The purpose
02540  * here is to optimize expressions so we don't do hash
02541  * lookups to eval them. Obviously it's a tradeoff that
02542  * slows down theme load times.
02543  */
02544 gboolean
02545 meta_theme_replace_constants (MetaTheme   *theme,
02546                               PosToken    *tokens,
02547                               int          n_tokens,
02548                               GError     **err)
02549 {
02550   int i;
02551   double dval;
02552   int ival;
02553   gboolean is_constant = TRUE;
02554   
02555   /* Loop through tokenized string looking for variables to replace */
02556   for (i = 0; i < n_tokens; i++)
02557     {
02558       PosToken *t = &tokens[i];      
02559 
02560       if (t->type == POS_TOKEN_VARIABLE)
02561         {
02562           if (meta_theme_lookup_int_constant (theme, t->d.v.name, &ival))
02563             {
02564               t->type = POS_TOKEN_INT;
02565               t->d.i.val = ival;
02566             }
02567           else if (meta_theme_lookup_float_constant (theme, t->d.v.name, &dval))
02568             {
02569               t->type = POS_TOKEN_DOUBLE;
02570               t->d.d.val = dval;
02571             }
02572           else 
02573             {
02574               /* If we've found a variable that cannot be replaced then the
02575                  expression is not a constant expression and we want to 
02576                  replace it with a GQuark */
02577 
02578               t->d.v.name_quark = g_quark_from_string (t->d.v.name);
02579               is_constant = FALSE;
02580             }
02581         }
02582     }  
02583 
02584   return is_constant;
02585 }
02586 
02587 static int
02588 parse_x_position_unchecked (MetaDrawSpec              *spec,
02589                             const MetaPositionExprEnv *env)
02590 {
02591   int retval;
02592   GError *error;
02593 
02594   retval = 0;
02595   error = NULL;
02596   if (!meta_parse_position_expression (spec, env, &retval, NULL, &error))
02597     {
02598       meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
02599                     error->message);
02600       
02601       g_error_free (error);
02602     }
02603   
02604   return retval;
02605 }
02606 
02607 static int
02608 parse_y_position_unchecked (MetaDrawSpec              *spec,
02609                             const MetaPositionExprEnv *env)
02610 {
02611   int retval;
02612   GError *error;
02613 
02614   retval = 0;
02615   error = NULL;
02616   if (!meta_parse_position_expression (spec, env, NULL, &retval, &error))
02617     {
02618       meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
02619                     error->message);
02620 
02621       g_error_free (error);
02622     }
02623 
02624   return retval;
02625 }
02626 
02627 static int
02628 parse_size_unchecked (MetaDrawSpec        *spec,
02629                       MetaPositionExprEnv *env)
02630 {
02631   int retval;
02632   GError *error;
02633 
02634   retval = 0;
02635   error = NULL;
02636   if (!meta_parse_size_expression (spec, env, &retval, &error))
02637     {
02638       meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
02639                     error->message);
02640 
02641       g_error_free (error);
02642     }
02643 
02644   return retval;
02645 }
02646 
02647 void
02648 meta_draw_spec_free (MetaDrawSpec *spec)
02649 {
02650   free_tokens (spec->tokens, spec->n_tokens);
02651   g_slice_free (MetaDrawSpec, spec);
02652 }
02653 
02654 MetaDrawSpec *
02655 meta_draw_spec_new (MetaTheme  *theme,
02656                     const char *expr,
02657                     GError    **error)
02658 {
02659   MetaDrawSpec *spec;
02660 
02661   spec = g_slice_new0 (MetaDrawSpec);
02662 
02663   pos_tokenize (expr, &spec->tokens, &spec->n_tokens, NULL);
02664   
02665   spec->constant = meta_theme_replace_constants (theme, spec->tokens, 
02666                                                  spec->n_tokens, NULL);
02667   if (spec->constant) 
02668     {
02669       gboolean result;
02670 
02671       result = pos_eval (spec, NULL, &spec->value, error);
02672       if (result == FALSE)
02673         {
02674           meta_draw_spec_free (spec);
02675           return NULL;
02676         }
02677     }
02678     
02679   return spec;
02680 }
02681 
02682 MetaDrawOp*
02683 meta_draw_op_new (MetaDrawType type)
02684 {
02685   MetaDrawOp *op;
02686   MetaDrawOp dummy;
02687   int size;
02688 
02689   size = G_STRUCT_OFFSET (MetaDrawOp, data);
02690 
02691   switch (type)
02692     {
02693     case META_DRAW_LINE:
02694       size += sizeof (dummy.data.line);
02695       break;
02696 
02697     case META_DRAW_RECTANGLE:
02698       size += sizeof (dummy.data.rectangle);
02699       break;
02700 
02701     case META_DRAW_ARC:
02702       size += sizeof (dummy.data.arc);
02703       break;
02704 
02705     case META_DRAW_CLIP:
02706       size += sizeof (dummy.data.clip);
02707       break;
02708       
02709     case META_DRAW_TINT:
02710       size += sizeof (dummy.data.tint);
02711       break;
02712 
02713     case META_DRAW_GRADIENT:
02714       size += sizeof (dummy.data.gradient);
02715       break;
02716 
02717     case META_DRAW_IMAGE:
02718       size += sizeof (dummy.data.image);
02719       break;
02720 
02721     case META_DRAW_GTK_ARROW:
02722       size += sizeof (dummy.data.gtk_arrow);
02723       break;
02724 
02725     case META_DRAW_GTK_BOX:
02726       size += sizeof (dummy.data.gtk_box);
02727       break;
02728 
02729     case META_DRAW_GTK_VLINE:
02730       size += sizeof (dummy.data.gtk_vline);
02731       break;
02732 
02733     case META_DRAW_ICON:
02734       size += sizeof (dummy.data.icon);
02735       break;
02736 
02737     case META_DRAW_TITLE:
02738       size += sizeof (dummy.data.title);
02739       break;
02740     case META_DRAW_OP_LIST:
02741       size += sizeof (dummy.data.op_list);
02742       break;
02743     case META_DRAW_TILE:
02744       size += sizeof (dummy.data.tile);
02745       break;
02746     }
02747 
02748   op = g_malloc0 (size);
02749 
02750   op->type = type;
02751 
02752   return op;
02753 }
02754 
02755 void
02756 meta_draw_op_free (MetaDrawOp *op)
02757 {
02758   g_return_if_fail (op != NULL);
02759 
02760   switch (op->type)
02761     {
02762     case META_DRAW_LINE:
02763       if (op->data.line.color_spec)
02764         meta_color_spec_free (op->data.line.color_spec);
02765 
02766       meta_draw_spec_free (op->data.line.x1);
02767       meta_draw_spec_free (op->data.line.y1);
02768       meta_draw_spec_free (op->data.line.x2);
02769       meta_draw_spec_free (op->data.line.y2);
02770       break;
02771 
02772     case META_DRAW_RECTANGLE:
02773       if (op->data.rectangle.color_spec)
02774         g_free (op->data.rectangle.color_spec);
02775 
02776       meta_draw_spec_free (op->data.rectangle.x);
02777       meta_draw_spec_free (op->data.rectangle.y);
02778       meta_draw_spec_free (op->data.rectangle.width);
02779       meta_draw_spec_free (op->data.rectangle.height);
02780       break;
02781 
02782     case META_DRAW_ARC:
02783       if (op->data.arc.color_spec)
02784         g_free (op->data.arc.color_spec);
02785 
02786       meta_draw_spec_free (op->data.arc.x);
02787       meta_draw_spec_free (op->data.arc.y);
02788       meta_draw_spec_free (op->data.arc.width);
02789       meta_draw_spec_free (op->data.arc.height);
02790       break;
02791 
02792     case META_DRAW_CLIP:
02793       meta_draw_spec_free (op->data.clip.x);
02794       meta_draw_spec_free (op->data.clip.y);
02795       meta_draw_spec_free (op->data.clip.width);
02796       meta_draw_spec_free (op->data.clip.height);
02797       break;
02798       
02799     case META_DRAW_TINT:
02800       if (op->data.tint.color_spec)
02801         meta_color_spec_free (op->data.tint.color_spec);
02802 
02803       if (op->data.tint.alpha_spec)
02804         meta_alpha_gradient_spec_free (op->data.tint.alpha_spec);
02805 
02806       meta_draw_spec_free (op->data.tint.x);
02807       meta_draw_spec_free (op->data.tint.y);
02808       meta_draw_spec_free (op->data.tint.width);
02809       meta_draw_spec_free (op->data.tint.height);
02810       break;
02811 
02812     case META_DRAW_GRADIENT:
02813       if (op->data.gradient.gradient_spec)
02814         meta_gradient_spec_free (op->data.gradient.gradient_spec);
02815 
02816       if (op->data.gradient.alpha_spec)
02817         meta_alpha_gradient_spec_free (op->data.gradient.alpha_spec);
02818 
02819       meta_draw_spec_free (op->data.gradient.x);
02820       meta_draw_spec_free (op->data.gradient.y);
02821       meta_draw_spec_free (op->data.gradient.width);
02822       meta_draw_spec_free (op->data.gradient.height);
02823       break;
02824 
02825     case META_DRAW_IMAGE:
02826       if (op->data.image.alpha_spec)
02827         meta_alpha_gradient_spec_free (op->data.image.alpha_spec);
02828 
02829       if (op->data.image.pixbuf)
02830         g_object_unref (G_OBJECT (op->data.image.pixbuf));
02831 
02832       if (op->data.image.colorize_spec)
02833         meta_color_spec_free (op->data.image.colorize_spec);
02834 
02835       if (op->data.image.colorize_cache_pixbuf)
02836         g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
02837 
02838       meta_draw_spec_free (op->data.image.x);
02839       meta_draw_spec_free (op->data.image.y);
02840       meta_draw_spec_free (op->data.image.width);
02841       meta_draw_spec_free (op->data.image.height);
02842       break;
02843 
02844     case META_DRAW_GTK_ARROW:
02845       meta_draw_spec_free (op->data.gtk_arrow.x);
02846       meta_draw_spec_free (op->data.gtk_arrow.y);
02847       meta_draw_spec_free (op->data.gtk_arrow.width);
02848       meta_draw_spec_free (op->data.gtk_arrow.height);
02849       break;
02850 
02851     case META_DRAW_GTK_BOX:
02852       meta_draw_spec_free (op->data.gtk_box.x);
02853       meta_draw_spec_free (op->data.gtk_box.y);
02854       meta_draw_spec_free (op->data.gtk_box.width);
02855       meta_draw_spec_free (op->data.gtk_box.height);
02856       break;
02857 
02858     case META_DRAW_GTK_VLINE:
02859       meta_draw_spec_free (op->data.gtk_vline.x);
02860       meta_draw_spec_free (op->data.gtk_vline.y1);
02861       meta_draw_spec_free (op->data.gtk_vline.y2);
02862       break;
02863 
02864     case META_DRAW_ICON:
02865       if (op->data.icon.alpha_spec)
02866         meta_alpha_gradient_spec_free (op->data.icon.alpha_spec);
02867 
02868       meta_draw_spec_free (op->data.icon.x);
02869       meta_draw_spec_free (op->data.icon.y);
02870       meta_draw_spec_free (op->data.icon.width);
02871       meta_draw_spec_free (op->data.icon.height);
02872       break;
02873 
02874     case META_DRAW_TITLE:
02875       if (op->data.title.color_spec)
02876         meta_color_spec_free (op->data.title.color_spec);
02877 
02878       meta_draw_spec_free (op->data.title.x);
02879       meta_draw_spec_free (op->data.title.y);
02880       break;
02881 
02882     case META_DRAW_OP_LIST:
02883       if (op->data.op_list.op_list)
02884         meta_draw_op_list_unref (op->data.op_list.op_list);
02885 
02886       meta_draw_spec_free (op->data.op_list.x);
02887       meta_draw_spec_free (op->data.op_list.y);
02888       meta_draw_spec_free (op->data.op_list.width);
02889       meta_draw_spec_free (op->data.op_list.height);
02890       break;
02891 
02892     case META_DRAW_TILE:
02893       if (op->data.tile.op_list)
02894         meta_draw_op_list_unref (op->data.tile.op_list);
02895 
02896       meta_draw_spec_free (op->data.tile.x);
02897       meta_draw_spec_free (op->data.tile.y);
02898       meta_draw_spec_free (op->data.tile.width);
02899       meta_draw_spec_free (op->data.tile.height);
02900       meta_draw_spec_free (op->data.tile.tile_xoffset);
02901       meta_draw_spec_free (op->data.tile.tile_yoffset);
02902       meta_draw_spec_free (op->data.tile.tile_width);
02903       meta_draw_spec_free (op->data.tile.tile_height);
02904       break;
02905     }
02906 
02907   g_free (op);
02908 }
02909 
02910 static GdkGC*
02911 get_gc_for_primitive (GtkWidget          *widget,
02912                       GdkDrawable        *drawable,
02913                       MetaColorSpec      *color_spec,
02914                       const GdkRectangle *clip,
02915                       int                 line_width)
02916 {
02917   GdkGC *gc;
02918   GdkGCValues values;
02919   GdkColor color;
02920 
02921   meta_color_spec_render (color_spec, widget, &color);
02922 
02923   values.foreground = color;
02924 
02925   gdk_rgb_find_color (gdk_drawable_get_colormap (drawable),
02926                       &values.foreground);
02927 
02928   values.line_width = line_width;
02929 
02930   gc = gdk_gc_new_with_values (drawable, &values,
02931                                GDK_GC_FOREGROUND | GDK_GC_LINE_WIDTH);
02932 
02933   if (clip)
02934     gdk_gc_set_clip_rectangle (gc,
02935                                (GdkRectangle*) clip); /* const cast */
02936 
02937   return gc;
02938 }
02939 
02940 static GdkPixbuf*
02941 apply_alpha (GdkPixbuf             *pixbuf,
02942              MetaAlphaGradientSpec *spec,
02943              gboolean               force_copy)
02944 {
02945   GdkPixbuf *new_pixbuf;
02946   gboolean needs_alpha;
02947   
02948   g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
02949   
02950   needs_alpha = spec && (spec->n_alphas > 1 ||
02951                          spec->alphas[0] != 0xff);
02952 
02953   if (!needs_alpha)
02954     return pixbuf;
02955   
02956   if (!gdk_pixbuf_get_has_alpha (pixbuf))
02957     {
02958       new_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
02959       g_object_unref (G_OBJECT (pixbuf));
02960       pixbuf = new_pixbuf;
02961     }
02962   else if (force_copy)
02963     {
02964       new_pixbuf = gdk_pixbuf_copy (pixbuf);
02965       g_object_unref (G_OBJECT (pixbuf));
02966       pixbuf = new_pixbuf;
02967     }
02968   
02969   g_assert (gdk_pixbuf_get_has_alpha (pixbuf));
02970 
02971   meta_gradient_add_alpha (pixbuf, spec->alphas, spec->n_alphas, spec->type);
02972   
02973   return pixbuf;
02974 }
02975 
02976 static void
02977 render_pixbuf (GdkDrawable        *drawable,
02978                const GdkRectangle *clip,
02979                GdkPixbuf          *pixbuf,
02980                int                 x,
02981                int                 y)
02982 {
02983   /* grumble, render_to_drawable_alpha does not accept a clip
02984    * mask, so we have to go through some BS
02985    */
02986   /* FIXME once GTK 1.3.13 has been out a while we can use
02987    * render_to_drawable() which now does alpha with clip.
02988    *
02989    * Though the gdk_rectangle_intersect() check may be a useful
02990    * optimization anyway.
02991    */
02992   GdkRectangle pixbuf_rect;
02993   GdkRectangle draw_rect;
02994 
02995   pixbuf_rect.x = x;
02996   pixbuf_rect.y = y;
02997   pixbuf_rect.width = gdk_pixbuf_get_width (pixbuf);
02998   pixbuf_rect.height = gdk_pixbuf_get_height (pixbuf);
02999 
03000   if (clip)
03001     {
03002       if (!gdk_rectangle_intersect ((GdkRectangle*)clip,
03003                                     &pixbuf_rect, &draw_rect))
03004         return;
03005     }
03006   else
03007     {
03008       draw_rect = pixbuf_rect;
03009     }
03010 
03011   gdk_draw_pixbuf (drawable,
03012                    NULL,
03013                    pixbuf,
03014                    draw_rect.x - pixbuf_rect.x,
03015                    draw_rect.y - pixbuf_rect.y,
03016                    draw_rect.x, draw_rect.y,
03017                    draw_rect.width,
03018                    draw_rect.height,
03019                    GDK_RGB_DITHER_NORMAL,
03020                    draw_rect.x - pixbuf_rect.x,
03021                    draw_rect.y - pixbuf_rect.y);
03022 }
03023 
03024 static GdkPixbuf*
03025 pixbuf_tile (GdkPixbuf *tile,
03026              int        width,
03027              int        height)
03028 {
03029   GdkPixbuf *pixbuf;
03030   int tile_width;
03031   int tile_height;
03032   int i, j;
03033   
03034   tile_width = gdk_pixbuf_get_width (tile);
03035   tile_height = gdk_pixbuf_get_height (tile);
03036   
03037   pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
03038                            gdk_pixbuf_get_has_alpha (tile),
03039                            8, width, height);
03040 
03041   i = 0;
03042   while (i < width)
03043     {
03044       j = 0;
03045       while (j < height)
03046         {
03047           int w, h;
03048 
03049           w = MIN (tile_width, width - i);
03050           h = MIN (tile_height, height - j);
03051           
03052           gdk_pixbuf_copy_area (tile,
03053                                 0, 0,
03054                                 w, h,
03055                                 pixbuf,
03056                                 i, j);
03057 
03058           j += tile_height;
03059         }
03060       
03061       i += tile_width;
03062     }
03063   
03064   return pixbuf;
03065 }
03066 
03067 static GdkPixbuf *
03068 replicate_rows (GdkPixbuf  *src,
03069                 int         src_x,
03070                 int         src_y,
03071                 int         width,
03072                 int         height)
03073 {
03074   unsigned int n_channels = gdk_pixbuf_get_n_channels (src);
03075   unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src);
03076   unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x
03077                            * n_channels);
03078   unsigned char *dest_pixels;
03079   GdkPixbuf *result;
03080   unsigned int dest_rowstride;
03081   int i;
03082 
03083   result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
03084                            width, height);
03085   dest_rowstride = gdk_pixbuf_get_rowstride (result);
03086   dest_pixels = gdk_pixbuf_get_pixels (result);
03087   
03088   for (i = 0; i < height; i++)
03089     memcpy (dest_pixels + dest_rowstride * i, pixels, n_channels * width);
03090 
03091   return result;
03092 }
03093 
03094 static GdkPixbuf *
03095 replicate_cols (GdkPixbuf  *src,
03096                 int         src_x,
03097                 int         src_y,
03098                 int         width,
03099                 int         height)
03100 {
03101   unsigned int n_channels = gdk_pixbuf_get_n_channels (src);
03102   unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src);
03103   unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x
03104                            * n_channels);
03105   unsigned char *dest_pixels;
03106   GdkPixbuf *result;
03107   unsigned int dest_rowstride;
03108   int i, j;
03109 
03110   result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
03111                            width, height);
03112   dest_rowstride = gdk_pixbuf_get_rowstride (result);
03113   dest_pixels = gdk_pixbuf_get_pixels (result);
03114 
03115   for (i = 0; i < height; i++)
03116     {
03117       unsigned char *p = dest_pixels + dest_rowstride * i;
03118       unsigned char *q = pixels + src_rowstride * i;
03119 
03120       unsigned char r = *(q++);
03121       unsigned char g = *(q++);
03122       unsigned char b = *(q++);
03123       
03124       if (n_channels == 4)
03125         {
03126           unsigned char a;
03127           
03128           a = *(q++);
03129           
03130           for (j = 0; j < width; j++)
03131             {
03132               *(p++) = r;
03133               *(p++) = g;
03134               *(p++) = b;                    
03135               *(p++) = a;
03136             }
03137         }
03138       else
03139         {
03140           for (j = 0; j < width; j++)
03141             {
03142               *(p++) = r;
03143               *(p++) = g;
03144               *(p++) = b;
03145             }
03146         }
03147     }
03148 
03149   return result;
03150 }
03151 
03152 static GdkPixbuf*
03153 scale_and_alpha_pixbuf (GdkPixbuf             *src,
03154                         MetaAlphaGradientSpec *alpha_spec,
03155                         MetaImageFillType      fill_type,
03156                         int                    width,
03157                         int                    height,
03158                         gboolean               vertical_stripes,
03159                         gboolean               horizontal_stripes)
03160 {
03161   GdkPixbuf *pixbuf;
03162   GdkPixbuf *temp_pixbuf;
03163 
03164   pixbuf = NULL;
03165 
03166   pixbuf = src;
03167 
03168   if (gdk_pixbuf_get_width (pixbuf) == width &&
03169       gdk_pixbuf_get_height (pixbuf) == height)
03170     {
03171       g_object_ref (G_OBJECT (pixbuf));
03172     }
03173   else
03174     {
03175       if (fill_type == META_IMAGE_FILL_TILE)
03176         {
03177           pixbuf = pixbuf_tile (pixbuf, width, height);
03178         }
03179       else
03180         {
03181           int src_h, src_w, dest_h, dest_w;
03182           src_h = gdk_pixbuf_get_height (src);
03183           src_w = gdk_pixbuf_get_width (src);
03184 
03185           /* prefer to replicate_cols if possible, as that
03186            * is faster (no memory reads)
03187            */
03188           if (horizontal_stripes)
03189             {
03190               dest_w = gdk_pixbuf_get_width (src);
03191               dest_h = height;
03192             }
03193           else if (vertical_stripes)
03194             {
03195               dest_w = width;
03196               dest_h = gdk_pixbuf_get_height (src);
03197             }
03198 
03199           else
03200             {
03201               dest_w = width;
03202               dest_h = height;
03203             }
03204 
03205           if (dest_w == src_w && dest_h == src_h)
03206             {
03207               temp_pixbuf = src;
03208               g_object_ref (G_OBJECT (temp_pixbuf));
03209             }
03210           else
03211             {
03212               temp_pixbuf = gdk_pixbuf_scale_simple (src,
03213                                                      dest_w, dest_h,
03214                                                      GDK_INTERP_BILINEAR);
03215             }
03216 
03217           /* prefer to replicate_cols if possible, as that
03218            * is faster (no memory reads)
03219            */
03220           if (horizontal_stripes)
03221             {
03222               pixbuf = replicate_cols (temp_pixbuf, 0, 0, width, height);
03223               g_object_unref (G_OBJECT (temp_pixbuf));
03224             }
03225           else if (vertical_stripes)
03226             {
03227               pixbuf = replicate_rows (temp_pixbuf, 0, 0, width, height);
03228               g_object_unref (G_OBJECT (temp_pixbuf));
03229             }
03230           else 
03231             {
03232               pixbuf = temp_pixbuf;
03233             }
03234         }
03235     }
03236 
03237   if (pixbuf)
03238     pixbuf = apply_alpha (pixbuf, alpha_spec, pixbuf == src);
03239   
03240   return pixbuf;
03241 }
03242 
03243 static GdkPixbuf*
03244 draw_op_as_pixbuf (const MetaDrawOp    *op,
03245                    GtkWidget           *widget,
03246                    const MetaDrawInfo  *info,
03247                    int                  width,
03248                    int                  height)
03249 {
03250   /* Try to get the op as a pixbuf, assuming w/h in the op
03251    * matches the width/height passed in. return NULL
03252    * if the op can't be converted to an equivalent pixbuf.
03253    */
03254   GdkPixbuf *pixbuf;
03255 
03256   pixbuf = NULL;
03257 
03258   switch (op->type)
03259     {
03260     case META_DRAW_LINE:
03261       break;
03262 
03263     case META_DRAW_RECTANGLE:
03264       if (op->data.rectangle.filled)
03265         {
03266           GdkColor color;
03267 
03268           meta_color_spec_render (op->data.rectangle.color_spec,
03269                                   widget,
03270                                   &color);
03271 
03272           pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
03273                                    FALSE,
03274                                    8, width, height);
03275 
03276           gdk_pixbuf_fill (pixbuf, GDK_COLOR_RGBA (color));
03277         }
03278       break;
03279 
03280     case META_DRAW_ARC:
03281       break;
03282 
03283     case META_DRAW_CLIP:
03284       break;
03285       
03286     case META_DRAW_TINT:
03287       {
03288         GdkColor color;
03289         guint32 rgba;
03290         gboolean has_alpha;
03291 
03292         meta_color_spec_render (op->data.rectangle.color_spec,
03293                                 widget,
03294                                 &color);
03295 
03296         has_alpha =
03297           op->data.tint.alpha_spec &&
03298           (op->data.tint.alpha_spec->n_alphas > 1 ||
03299            op->data.tint.alpha_spec->alphas[0] != 0xff);
03300         
03301         pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
03302                                  has_alpha,
03303                                  8, width, height);
03304 
03305         if (!has_alpha)
03306           {
03307             rgba = GDK_COLOR_RGBA (color);
03308             
03309             gdk_pixbuf_fill (pixbuf, rgba);
03310           }
03311         else if (op->data.tint.alpha_spec->n_alphas == 1)
03312           {
03313             rgba = GDK_COLOR_RGBA (color);
03314             rgba &= ~0xff;
03315             rgba |= op->data.tint.alpha_spec->alphas[0];
03316             
03317             gdk_pixbuf_fill (pixbuf, rgba);
03318           }
03319         else
03320           {
03321             rgba = GDK_COLOR_RGBA (color);
03322             
03323             gdk_pixbuf_fill (pixbuf, rgba);
03324 
03325             meta_gradient_add_alpha (pixbuf,
03326                                      op->data.tint.alpha_spec->alphas,
03327                                      op->data.tint.alpha_spec->n_alphas,
03328                                      op->data.tint.alpha_spec->type);
03329           }
03330       }
03331       break;
03332 
03333     case META_DRAW_GRADIENT:
03334       {
03335         pixbuf = meta_gradient_spec_render (op->data.gradient.gradient_spec,
03336                                             widget, width, height);
03337 
03338         pixbuf = apply_alpha (pixbuf,
03339                               op->data.gradient.alpha_spec,
03340                               FALSE);
03341       }
03342       break;
03343 
03344       
03345     case META_DRAW_IMAGE:
03346       {
03347         if (op->data.image.colorize_spec)
03348           {
03349             GdkColor color;
03350 
03351             meta_color_spec_render (op->data.image.colorize_spec,
03352                                     widget, &color);
03353             
03354             if (op->data.image.colorize_cache_pixbuf == NULL ||
03355                 op->data.image.colorize_cache_pixel != GDK_COLOR_RGB (color))
03356               {
03357                 if (op->data.image.colorize_cache_pixbuf)
03358                   g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
03359                 
03360                 /* const cast here */
03361                 ((MetaDrawOp*)op)->data.image.colorize_cache_pixbuf =
03362                   colorize_pixbuf (op->data.image.pixbuf,
03363                                    &color);
03364                 ((MetaDrawOp*)op)->data.image.colorize_cache_pixel =
03365                   GDK_COLOR_RGB (color);
03366               }
03367             
03368             if (op->data.image.colorize_cache_pixbuf)
03369               {
03370                 pixbuf = scale_and_alpha_pixbuf (op->data.image.colorize_cache_pixbuf,
03371                                                  op->data.image.alpha_spec,
03372                                                  op->data.image.fill_type,
03373                                                  width, height,
03374                                                  op->data.image.vertical_stripes,
03375                                                  op->data.image.horizontal_stripes);
03376               }
03377           }
03378         else
03379           {
03380             pixbuf = scale_and_alpha_pixbuf (op->data.image.pixbuf,
03381                                              op->data.image.alpha_spec,
03382                                              op->data.image.fill_type,
03383                                              width, height,
03384                                              op->data.image.vertical_stripes,
03385                                              op->data.image.horizontal_stripes);
03386           }
03387         break;
03388       }
03389       
03390     case META_DRAW_GTK_ARROW:
03391     case META_DRAW_GTK_BOX:
03392     case META_DRAW_GTK_VLINE:
03393       break;
03394 
03395     case META_DRAW_ICON:
03396       if (info->mini_icon &&
03397           width <= gdk_pixbuf_get_width (info->mini_icon) &&
03398           height <= gdk_pixbuf_get_height (info->mini_icon))
03399         pixbuf = scale_and_alpha_pixbuf (info->mini_icon,
03400                                          op->data.icon.alpha_spec,
03401                                          op->data.icon.fill_type,
03402                                          width, height,
03403                                          FALSE, FALSE);
03404       else if (info->icon)
03405         pixbuf = scale_and_alpha_pixbuf (info->icon,
03406                                          op->data.icon.alpha_spec,
03407                                          op->data.icon.fill_type,
03408                                          width, height,
03409                                          FALSE, FALSE);
03410       break;
03411 
03412     case META_DRAW_TITLE:
03413       break;
03414 
03415     case META_DRAW_OP_LIST:
03416       break;
03417 
03418     case META_DRAW_TILE:
03419       break;
03420     }
03421 
03422   return pixbuf;
03423 }
03424 
03425 static void
03426 fill_env (MetaPositionExprEnv *env,
03427           const MetaDrawInfo  *info,
03428           MetaRectangle        logical_region)
03429 {
03430   /* FIXME this stuff could be raised into draw_op_list_draw() probably
03431    */
03432   env->rect = logical_region;
03433   env->object_width = -1;
03434   env->object_height = -1;
03435   if (info->fgeom)
03436     {
03437       env->left_width = info->fgeom->left_width;
03438       env->right_width = info->fgeom->right_width;
03439       env->top_height = info->fgeom->top_height;
03440       env->bottom_height = info->fgeom->bottom_height;
03441     }
03442   else
03443     {
03444       env->left_width = 0;
03445       env->right_width = 0;
03446       env->top_height = 0;
03447       env->bottom_height = 0;
03448     }
03449   
03450   env->mini_icon_width = info->mini_icon ? gdk_pixbuf_get_width (info->mini_icon) : 0;
03451   env->mini_icon_height = info->mini_icon ? gdk_pixbuf_get_height (info->mini_icon) : 0;
03452   env->icon_width = info->icon ? gdk_pixbuf_get_width (info->icon) : 0;
03453   env->icon_height = info->icon ? gdk_pixbuf_get_height (info->icon) : 0;
03454 
03455   env->title_width = info->title_layout_width;
03456   env->title_height = info->title_layout_height;
03457   env->theme = meta_current_theme;
03458 }
03459 
03460 static void
03461 meta_draw_op_draw_with_env (const MetaDrawOp    *op,
03462                             GtkWidget           *widget,
03463                             GdkDrawable         *drawable,
03464                             const GdkRectangle  *clip,
03465                             const MetaDrawInfo  *info,
03466                             MetaRectangle        rect,
03467                             MetaPositionExprEnv *env)
03468 {
03469   GdkGC *gc;
03470   
03471   switch (op->type)
03472     {
03473     case META_DRAW_LINE:
03474       {
03475         int x1, x2, y1, y2;
03476 
03477         gc = get_gc_for_primitive (widget, drawable,
03478                                    op->data.line.color_spec,
03479                                    clip,
03480                                    op->data.line.width);
03481 
03482         if (op->data.line.dash_on_length > 0 &&
03483             op->data.line.dash_off_length > 0)
03484           {
03485             gint8 dash_list[2];
03486             dash_list[0] = op->data.line.dash_on_length;
03487             dash_list[1] = op->data.line.dash_off_length;
03488             gdk_gc_set_dashes (gc, 0, dash_list, 2);
03489           }
03490 
03491         x1 = parse_x_position_unchecked (op->data.line.x1, env);
03492         y1 = parse_y_position_unchecked (op->data.line.y1, env); 
03493         x2 = parse_x_position_unchecked (op->data.line.x2, env);
03494         y2 = parse_y_position_unchecked (op->data.line.y2, env);
03495 
03496         gdk_draw_line (drawable, gc, x1, y1, x2, y2);
03497 
03498         g_object_unref (G_OBJECT (gc));
03499       }
03500       break;
03501 
03502     case META_DRAW_RECTANGLE:
03503       {
03504         int rx, ry, rwidth, rheight;
03505 
03506         gc = get_gc_for_primitive (widget, drawable,
03507                                    op->data.rectangle.color_spec,
03508                                    clip, 0);
03509 
03510         rx = parse_x_position_unchecked (op->data.rectangle.x, env);
03511         ry = parse_y_position_unchecked (op->data.rectangle.y, env);
03512         rwidth = parse_size_unchecked (op->data.rectangle.width, env);
03513         rheight = parse_size_unchecked (op->data.rectangle.height, env);
03514 
03515         gdk_draw_rectangle (drawable, gc,
03516                             op->data.rectangle.filled,
03517                             rx, ry, rwidth, rheight);
03518 
03519         g_object_unref (G_OBJECT (gc));
03520       }
03521       break;
03522 
03523     case META_DRAW_ARC:
03524       {
03525         int rx, ry, rwidth, rheight;
03526 
03527         gc = get_gc_for_primitive (widget, drawable,
03528                                    op->data.arc.color_spec,
03529                                    clip, 0);
03530 
03531         rx = parse_x_position_unchecked (op->data.arc.x, env);
03532         ry = parse_y_position_unchecked (op->data.arc.y, env);
03533         rwidth = parse_size_unchecked (op->data.arc.width, env);
03534         rheight = parse_size_unchecked (op->data.arc.height, env);
03535 
03536         gdk_draw_arc (drawable,
03537                       gc,
03538                       op->data.arc.filled,
03539                       rx, ry, rwidth, rheight,
03540                       op->data.arc.start_angle * (360.0 * 64.0) -
03541                       (90.0 * 64.0), /* start at 12 instead of 3 oclock */
03542                       op->data.arc.extent_angle * (360.0 * 64.0));
03543 
03544         g_object_unref (G_OBJECT (gc));
03545       }
03546       break;
03547 
03548     case META_DRAW_CLIP:
03549       break;
03550       
03551     case META_DRAW_TINT:
03552       {
03553         int rx, ry, rwidth, rheight;
03554         gboolean needs_alpha;
03555         
03556         needs_alpha = op->data.tint.alpha_spec &&
03557           (op->data.tint.alpha_spec->n_alphas > 1 ||
03558            op->data.tint.alpha_spec->alphas[0] != 0xff);
03559         
03560         rx = parse_x_position_unchecked (op->data.tint.x, env);
03561         ry = parse_y_position_unchecked (op->data.tint.y, env);
03562         rwidth = parse_size_unchecked (op->data.tint.width, env);
03563         rheight = parse_size_unchecked (op->data.tint.height, env);
03564 
03565         if (!needs_alpha)
03566           {
03567             gc = get_gc_for_primitive (widget, drawable,
03568                                        op->data.tint.color_spec,
03569                                        clip, 0);
03570 
03571             gdk_draw_rectangle (drawable, gc,
03572                                 TRUE,
03573                                 rx, ry, rwidth, rheight);
03574 
03575             g_object_unref (G_OBJECT (gc));
03576           }
03577         else
03578           {
03579             GdkPixbuf *pixbuf;
03580 
03581             pixbuf = draw_op_as_pixbuf (op, widget, info,
03582                                         rwidth, rheight);
03583 
03584             if (pixbuf)
03585               {
03586                 render_pixbuf (drawable, clip, pixbuf, rx, ry);
03587 
03588                 g_object_unref (G_OBJECT (pixbuf));
03589               }
03590           }
03591       }
03592       break;
03593 
03594     case META_DRAW_GRADIENT:
03595       {
03596         int rx, ry, rwidth, rheight;
03597         GdkPixbuf *pixbuf;
03598 
03599         rx = parse_x_position_unchecked (op->data.gradient.x, env);
03600         ry = parse_y_position_unchecked (op->data.gradient.y, env);
03601         rwidth = parse_size_unchecked (op->data.gradient.width, env);
03602         rheight = parse_size_unchecked (op->data.gradient.height, env);
03603 
03604         pixbuf = draw_op_as_pixbuf (op, widget, info,
03605                                     rwidth, rheight);
03606 
03607         if (pixbuf)
03608           {
03609             render_pixbuf (drawable, clip, pixbuf, rx, ry);
03610 
03611             g_object_unref (G_OBJECT (pixbuf));
03612           }
03613       }
03614       break;
03615 
03616     case META_DRAW_IMAGE:
03617       {
03618         int rx, ry, rwidth, rheight;
03619         GdkPixbuf *pixbuf;
03620 
03621         if (op->data.image.pixbuf)
03622           {
03623             env->object_width = gdk_pixbuf_get_width (op->data.image.pixbuf);
03624             env->object_height = gdk_pixbuf_get_height (op->data.image.pixbuf);
03625           }
03626 
03627         rwidth = parse_size_unchecked (op->data.image.width, env);
03628         rheight = parse_size_unchecked (op->data.image.height, env);
03629         
03630         pixbuf = draw_op_as_pixbuf (op, widget, info,
03631                                     rwidth, rheight);
03632 
03633         if (pixbuf)
03634           {
03635             rx = parse_x_position_unchecked (op->data.image.x, env);
03636             ry = parse_y_position_unchecked (op->data.image.y, env);
03637 
03638             render_pixbuf (drawable, clip, pixbuf, rx, ry);
03639 
03640             g_object_unref (G_OBJECT (pixbuf));
03641           }
03642       }
03643       break;
03644 
03645     case META_DRAW_GTK_ARROW:
03646       {
03647         int rx, ry, rwidth, rheight;
03648 
03649         rx = parse_x_position_unchecked (op->data.gtk_arrow.x, env);
03650         ry = parse_y_position_unchecked (op->data.gtk_arrow.y, env);
03651         rwidth = parse_size_unchecked (op->data.gtk_arrow.width, env);
03652         rheight = parse_size_unchecked (op->data.gtk_arrow.height, env);
03653 
03654         gtk_paint_arrow (widget->style,
03655                          drawable,
03656                          op->data.gtk_arrow.state,
03657                          op->data.gtk_arrow.shadow,
03658                          (GdkRectangle*) clip,
03659                          widget,
03660                          "metacity",
03661                          op->data.gtk_arrow.arrow,
03662                          op->data.gtk_arrow.filled,
03663                          rx, ry, rwidth, rheight);
03664       }
03665       break;
03666 
03667     case META_DRAW_GTK_BOX:
03668       {
03669         int rx, ry, rwidth, rheight;
03670 
03671         rx = parse_x_position_unchecked (op->data.gtk_box.x, env);
03672         ry = parse_y_position_unchecked (op->data.gtk_box.y, env);
03673         rwidth = parse_size_unchecked (op->data.gtk_box.width, env);
03674         rheight = parse_size_unchecked (op->data.gtk_box.height, env);
03675 
03676         gtk_paint_box (widget->style,
03677                        drawable,
03678                        op->data.gtk_box.state,
03679                        op->data.gtk_box.shadow,
03680                        (GdkRectangle*) clip,
03681                        widget,
03682                        "metacity",
03683                        rx, ry, rwidth, rheight);
03684       }
03685       break;
03686 
03687     case META_DRAW_GTK_VLINE:
03688       {
03689         int rx, ry1, ry2;
03690 
03691         rx = parse_x_position_unchecked (op->data.gtk_vline.x, env);
03692         ry1 = parse_y_position_unchecked (op->data.gtk_vline.y1, env);
03693         ry2 = parse_y_position_unchecked (op->data.gtk_vline.y2, env);
03694         
03695         gtk_paint_vline (widget->style,
03696                          drawable,
03697                          op->data.gtk_vline.state,
03698                          (GdkRectangle*) clip,
03699                          widget,
03700                          "metacity",
03701                          ry1, ry2, rx);
03702       }
03703       break;
03704 
03705     case META_DRAW_ICON:
03706       {
03707         int rx, ry, rwidth, rheight;
03708         GdkPixbuf *pixbuf;
03709 
03710         rwidth = parse_size_unchecked (op->data.icon.width, env);
03711         rheight = parse_size_unchecked (op->data.icon.height, env);
03712         
03713         pixbuf = draw_op_as_pixbuf (op, widget, info,
03714                                     rwidth, rheight);
03715 
03716         if (pixbuf)
03717           {
03718             rx = parse_x_position_unchecked (op->data.icon.x, env);
03719             ry = parse_y_position_unchecked (op->data.icon.y, env);
03720 
03721             render_pixbuf (drawable, clip, pixbuf, rx, ry);
03722 
03723             g_object_unref (G_OBJECT (pixbuf));
03724           }
03725       }
03726       break;
03727 
03728     case META_DRAW_TITLE:
03729       if (info->title_layout)
03730         {
03731           int rx, ry;
03732 
03733           gc = get_gc_for_primitive (widget, drawable,
03734                                      op->data.title.color_spec,
03735                                      clip, 0);
03736 
03737           rx = parse_x_position_unchecked (op->data.title.x, env);
03738           ry = parse_y_position_unchecked (op->data.title.y, env);
03739 
03740           gdk_draw_layout (drawable, gc,
03741                            rx, ry,
03742                            info->title_layout);
03743 
03744           g_object_unref (G_OBJECT (gc));
03745         }
03746       break;
03747 
03748     case META_DRAW_OP_LIST:
03749       {
03750         MetaRectangle d_rect;
03751 
03752         d_rect.x = parse_x_position_unchecked (op->data.op_list.x, env);
03753         d_rect.y = parse_y_position_unchecked (op->data.op_list.y, env);
03754         d_rect.width = parse_size_unchecked (op->data.op_list.width, env);
03755         d_rect.height = parse_size_unchecked (op->data.op_list.height, env);
03756 
03757         meta_draw_op_list_draw (op->data.op_list.op_list,
03758                                 widget, drawable, clip, info,
03759                                 d_rect);
03760       }
03761       break;
03762 
03763     case META_DRAW_TILE:
03764       {
03765         int rx, ry, rwidth, rheight;
03766         int tile_xoffset, tile_yoffset; 
03767         GdkRectangle new_clip;
03768         MetaRectangle tile;
03769         
03770         rx = parse_x_position_unchecked (op->data.tile.x, env);
03771         ry = parse_y_position_unchecked (op->data.tile.y, env);
03772         rwidth = parse_size_unchecked (op->data.tile.width, env);
03773         rheight = parse_size_unchecked (op->data.tile.height, env);
03774 
03775         new_clip.x = rx;
03776         new_clip.y = ry;
03777         new_clip.width = rwidth;
03778         new_clip.height = rheight;
03779 
03780         if (clip == NULL || gdk_rectangle_intersect ((GdkRectangle*)clip, &new_clip,
03781                                                      &new_clip))
03782           {
03783             tile_xoffset = parse_x_position_unchecked (op->data.tile.tile_xoffset, env);
03784             tile_yoffset = parse_y_position_unchecked (op->data.tile.tile_yoffset, env);
03785             /* tile offset should not include x/y */
03786             tile_xoffset -= rect.x;
03787             tile_yoffset -= rect.y;
03788             
03789             tile.width = parse_size_unchecked (op->data.tile.tile_width, env);
03790             tile.height = parse_size_unchecked (op->data.tile.tile_height, env);
03791 
03792             tile.x = rx - tile_xoffset;
03793         
03794             while (tile.x < (rx + rwidth))
03795               {
03796                 tile.y = ry - tile_yoffset;
03797                 while (tile.y < (ry + rheight))
03798                   {
03799                     meta_draw_op_list_draw (op->data.tile.op_list,
03800                                             widget, drawable, &new_clip, info,
03801                                             tile);
03802 
03803                     tile.y += tile.height;
03804                   }
03805 
03806                 tile.x += tile.width;
03807               }
03808           }
03809       }
03810       break;
03811     }
03812 }
03813 
03814 void
03815 meta_draw_op_draw (const MetaDrawOp    *op,
03816                    GtkWidget           *widget,
03817                    GdkDrawable         *drawable,
03818                    const GdkRectangle  *clip,
03819                    const MetaDrawInfo  *info,
03820                    MetaRectangle        logical_region)
03821 {
03822   MetaPositionExprEnv env;
03823 
03824   fill_env (&env, info, logical_region);
03825 
03826   meta_draw_op_draw_with_env (op, widget, drawable, clip,
03827                               info, logical_region,
03828                               &env);
03829 
03830 }
03831 
03832 MetaDrawOpList*
03833 meta_draw_op_list_new (int n_preallocs)
03834 {
03835   MetaDrawOpList *op_list;
03836 
03837   g_return_val_if_fail (n_preallocs >= 0, NULL);
03838 
03839   op_list = g_new (MetaDrawOpList, 1);
03840 
03841   op_list->refcount = 1;
03842   op_list->n_allocated = n_preallocs;
03843   op_list->ops = g_new (MetaDrawOp*, op_list->n_allocated);
03844   op_list->n_ops = 0;
03845 
03846   return op_list;
03847 }
03848 
03849 void
03850 meta_draw_op_list_ref (MetaDrawOpList *op_list)
03851 {
03852   g_return_if_fail (op_list != NULL);
03853 
03854   op_list->refcount += 1;
03855 }
03856 
03857 void
03858 meta_draw_op_list_unref (MetaDrawOpList *op_list)
03859 {
03860   g_return_if_fail (op_list != NULL);
03861   g_return_if_fail (op_list->refcount > 0);
03862 
03863   op_list->refcount -= 1;
03864 
03865   if (op_list->refcount == 0)
03866     {
03867       int i;
03868 
03869       for (i = 0; i < op_list->n_ops; i++)
03870         meta_draw_op_free (op_list->ops[i]);
03871 
03872       g_free (op_list->ops);
03873 
03874       DEBUG_FILL_STRUCT (op_list);
03875       g_free (op_list);
03876     }
03877 }
03878 
03879 void
03880 meta_draw_op_list_draw  (const MetaDrawOpList *op_list,
03881                          GtkWidget            *widget,
03882                          GdkDrawable          *drawable,
03883                          const GdkRectangle   *clip,
03884                          const MetaDrawInfo   *info,
03885                          MetaRectangle         rect)
03886 {
03887   int i;
03888   GdkRectangle active_clip;
03889   GdkRectangle orig_clip;
03890   MetaPositionExprEnv env;
03891 
03892   if (op_list->n_ops == 0)
03893     return;
03894   
03895   fill_env (&env, info, rect);
03896   
03897   /* FIXME this can be optimized, potentially a lot, by
03898    * compressing multiple ops when possible. For example,
03899    * anything convertible to a pixbuf can be composited
03900    * client-side, and putting a color tint over a pixbuf
03901    * can be done without creating the solid-color pixbuf.
03902    *
03903    * To implement this my plan is to have the idea of a
03904    * compiled draw op (with the string expressions already
03905    * evaluated), we make an array of those, and then fold
03906    * adjacent items when possible.
03907    */
03908   if (clip)
03909     {
03910       orig_clip = *clip;
03911     }
03912   else
03913     {
03914       orig_clip.x = rect.x;
03915       orig_clip.y = rect.y;
03916       orig_clip.width = rect.width;
03917       orig_clip.height = rect.height;
03918     }
03919 
03920   active_clip = orig_clip;
03921 
03922   for (i = 0; i < op_list->n_ops; i++)
03923     {
03924       MetaDrawOp *op = op_list->ops[i];
03925       
03926       if (op->type == META_DRAW_CLIP)
03927         {
03928           active_clip.x = parse_x_position_unchecked (op->data.clip.x, &env);
03929           active_clip.y = parse_y_position_unchecked (op->data.clip.y, &env);
03930           active_clip.width = parse_size_unchecked (op->data.clip.width, &env);
03931           active_clip.height = parse_size_unchecked (op->data.clip.height, &env);
03932           
03933           gdk_rectangle_intersect (&orig_clip, &active_clip, &active_clip);
03934         }
03935       else if (active_clip.width > 0 &&
03936                active_clip.height > 0)
03937         {
03938           meta_draw_op_draw_with_env (op,
03939                                       widget, drawable, &active_clip, info,
03940                                       rect,
03941                                       &env);
03942         }
03943     }
03944 }
03945 
03946 void
03947 meta_draw_op_list_append (MetaDrawOpList       *op_list,
03948                           MetaDrawOp           *op)
03949 {
03950   if (op_list->n_ops == op_list->n_allocated)
03951     {
03952       op_list->n_allocated *= 2;
03953       op_list->ops = g_renew (MetaDrawOp*, op_list->ops, op_list->n_allocated);
03954     }
03955 
03956   op_list->ops[op_list->n_ops] = op;
03957   op_list->n_ops += 1;
03958 }
03959 
03960 gboolean
03961 meta_draw_op_list_validate (MetaDrawOpList    *op_list,
03962                             GError           **error)
03963 {
03964   g_return_val_if_fail (op_list != NULL, FALSE);
03965 
03966   /* empty lists are OK, nothing else to check really */
03967 
03968   return TRUE;
03969 }
03970 
03971 /* This is not done in validate, since we wouldn't know the name
03972  * of the list to report the error. It might be nice to
03973  * store names inside the list sometime.
03974  */
03975 gboolean
03976 meta_draw_op_list_contains (MetaDrawOpList    *op_list,
03977                             MetaDrawOpList    *child)
03978 {
03979   int i;
03980 
03981   /* mmm, huge tree recursion */
03982 
03983   for (i = 0; i < op_list->n_ops; i++)
03984     {
03985       if (op_list->ops[i]->type == META_DRAW_OP_LIST)
03986         {
03987           if (op_list->ops[i]->data.op_list.op_list == child)
03988             return TRUE;
03989           
03990           if (meta_draw_op_list_contains (op_list->ops[i]->data.op_list.op_list,
03991                                           child))
03992             return TRUE;
03993         }
03994       else if (op_list->ops[i]->type == META_DRAW_TILE)
03995         {
03996           if (op_list->ops[i]->data.tile.op_list == child)
03997             return TRUE;
03998           
03999           if (meta_draw_op_list_contains (op_list->ops[i]->data.tile.op_list,
04000                                           child))
04001             return TRUE;
04002         }
04003     }
04004 
04005   return FALSE;
04006 }
04007 
04017 MetaFrameStyle*
04018 meta_frame_style_new (MetaFrameStyle *parent)
04019 {
04020   MetaFrameStyle *style;
04021 
04022   style = g_new0 (MetaFrameStyle, 1);
04023 
04024   style->refcount = 1;
04025 
04026   /* Default alpha is fully opaque */
04027   style->window_background_alpha = 255;
04028 
04029   style->parent = parent;
04030   if (parent)
04031     meta_frame_style_ref (parent);
04032 
04033   return style;
04034 }
04035 
04042 void
04043 meta_frame_style_ref (MetaFrameStyle *style)
04044 {
04045   g_return_if_fail (style != NULL);
04046 
04047   style->refcount += 1;
04048 }
04049 
04050 static void
04051 free_button_ops (MetaDrawOpList *op_lists[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST])
04052 {
04053   int i, j;
04054 
04055   for (i = 0; i < META_BUTTON_TYPE_LAST; i++)
04056     for (j = 0; j < META_BUTTON_STATE_LAST; j++)
04057       if (op_lists[i][j])
04058         meta_draw_op_list_unref (op_lists[i][j]);
04059 }
04060 
04061 void
04062 meta_frame_style_unref (MetaFrameStyle *style)
04063 {
04064   g_return_if_fail (style != NULL);
04065   g_return_if_fail (style->refcount > 0);
04066 
04067   style->refcount -= 1;
04068 
04069   if (style->refcount == 0)
04070     {
04071       int i;
04072 
04073       free_button_ops (style->buttons);
04074 
04075       for (i = 0; i < META_FRAME_PIECE_LAST; i++)
04076         if (style->pieces[i])
04077           meta_draw_op_list_unref (style->pieces[i]);
04078 
04079       if (style->layout)
04080         meta_frame_layout_unref (style->layout);
04081 
04082       if (style->window_background_color)
04083         meta_color_spec_free (style->window_background_color);
04084 
04085       /* we hold a reference to any parent style */
04086       if (style->parent)
04087         meta_frame_style_unref (style->parent);
04088 
04089       DEBUG_FILL_STRUCT (style);
04090       g_free (style);
04091     }
04092 }
04093 
04094 static MetaDrawOpList*
04095 get_button (MetaFrameStyle *style,
04096             MetaButtonType  type,
04097             MetaButtonState state)
04098 {
04099   MetaDrawOpList *op_list;
04100   MetaFrameStyle *parent;
04101   
04102   parent = style;
04103   op_list = NULL;
04104   while (parent && op_list == NULL)
04105     {
04106       op_list = parent->buttons[type][state];
04107       parent = parent->parent;
04108     }
04109 
04110   /* We fall back to middle button backgrounds if we don't
04111    * have the ones on the sides
04112    */
04113 
04114   if (op_list == NULL &&
04115       (type == META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND ||
04116        type == META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND))
04117     return get_button (style, META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND,
04118                        state);
04119 
04120   if (op_list == NULL &&
04121       (type == META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND ||
04122        type == META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND))
04123     return get_button (style, META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND,
04124                        state);
04125   
04126   /* We fall back to normal if no prelight */
04127   if (op_list == NULL &&
04128       state == META_BUTTON_STATE_PRELIGHT)
04129     return get_button (style, type, META_BUTTON_STATE_NORMAL);
04130 
04131   return op_list;
04132 }
04133 
04134 gboolean
04135 meta_frame_style_validate (MetaFrameStyle    *style,
04136                            guint              current_theme_version,
04137                            GError           **error)
04138 {
04139   int i, j;
04140   
04141   g_return_val_if_fail (style != NULL, FALSE);
04142   g_return_val_if_fail (style->layout != NULL, FALSE);
04143 
04144   for (i = 0; i < META_BUTTON_TYPE_LAST; i++)
04145     {
04146       /* for now the "positional" buttons are optional */
04147       if (i >= META_BUTTON_TYPE_CLOSE)
04148         {
04149           for (j = 0; j < META_BUTTON_STATE_LAST; j++)
04150             {
04151               if (get_button (style, i, j) == NULL &&
04152                   meta_theme_earliest_version_with_button (i) <= current_theme_version
04153                   )
04154                 {
04155                   g_set_error (error, META_THEME_ERROR,
04156                                META_THEME_ERROR_FAILED,
04157                                _("<button function=\"%s\" state=\"%s\" draw_ops=\"whatever\"/> must be specified for this frame style"),
04158                                meta_button_type_to_string (i),
04159                                meta_button_state_to_string (j));
04160                   return FALSE;
04161                 }
04162             }
04163         }
04164     }
04165   
04166   return TRUE;
04167 }
04168 
04169 static void
04170 button_rect (MetaButtonType           type,
04171              const MetaFrameGeometry *fgeom,
04172              int                      middle_background_offset,
04173              GdkRectangle            *rect)
04174 {
04175   switch (type)
04176     {
04177     case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
04178       *rect = fgeom->left_left_background;
04179       break;
04180 
04181     case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
04182       *rect = fgeom->left_middle_backgrounds[middle_background_offset];
04183       break;
04184       
04185     case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
04186       *rect = fgeom->left_right_background;
04187       break;
04188       
04189     case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
04190       *rect = fgeom->right_left_background;
04191       break;
04192       
04193     case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
04194       *rect = fgeom->right_middle_backgrounds[middle_background_offset];
04195       break;
04196       
04197     case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
04198       *rect = fgeom->right_right_background;
04199       break;
04200       
04201     case META_BUTTON_TYPE_CLOSE:
04202       *rect = fgeom->close_rect.visible;
04203       break;
04204 
04205     case META_BUTTON_TYPE_SHADE:
04206       *rect = fgeom->shade_rect.visible;
04207       break;
04208 
04209     case META_BUTTON_TYPE_UNSHADE:
04210       *rect = fgeom->unshade_rect.visible;
04211       break;
04212 
04213     case META_BUTTON_TYPE_ABOVE:
04214       *rect = fgeom->above_rect.visible;
04215       break;
04216 
04217     case META_BUTTON_TYPE_UNABOVE:
04218       *rect = fgeom->unabove_rect.visible;
04219       break;
04220 
04221     case META_BUTTON_TYPE_STICK:
04222       *rect = fgeom->stick_rect.visible;
04223       break;
04224 
04225     case META_BUTTON_TYPE_UNSTICK:
04226       *rect = fgeom->unstick_rect.visible;
04227       break;
04228 
04229     case META_BUTTON_TYPE_MAXIMIZE:
04230       *rect = fgeom->max_rect.visible;
04231       break;
04232 
04233     case META_BUTTON_TYPE_MINIMIZE:
04234       *rect = fgeom->min_rect.visible;
04235       break;
04236 
04237     case META_BUTTON_TYPE_MENU:
04238       *rect = fgeom->menu_rect.visible;
04239       break;
04240       
04241     case META_BUTTON_TYPE_LAST:
04242       g_assert_not_reached ();
04243       break;
04244     }
04245 }
04246 
04247 void
04248 meta_frame_style_draw (MetaFrameStyle          *style,
04249                        GtkWidget               *widget,
04250                        GdkDrawable             *drawable,
04251                        int                      x_offset,
04252                        int                      y_offset,
04253                        const GdkRectangle      *clip,
04254                        const MetaFrameGeometry *fgeom,
04255                        int                      client_width,
04256                        int                      client_height,
04257                        PangoLayout             *title_layout,
04258                        int                      text_height,
04259                        MetaButtonState          button_states[META_BUTTON_TYPE_LAST],
04260                        GdkPixbuf               *mini_icon,
04261                        GdkPixbuf               *icon)
04262 {
04263   int i, j;
04264   GdkRectangle titlebar_rect;
04265   GdkRectangle left_titlebar_edge;
04266   GdkRectangle right_titlebar_edge;
04267   GdkRectangle bottom_titlebar_edge;
04268   GdkRectangle top_titlebar_edge;
04269   GdkRectangle left_edge, right_edge, bottom_edge;
04270   PangoRectangle extents;
04271   MetaDrawInfo draw_info;
04272   
04273   titlebar_rect.x = 0;
04274   titlebar_rect.y = 0;
04275   titlebar_rect.width = fgeom->width;
04276   titlebar_rect.height = fgeom->top_height;
04277 
04278   left_titlebar_edge.x = titlebar_rect.x;
04279   left_titlebar_edge.y = titlebar_rect.y + fgeom->top_titlebar_edge;
04280   left_titlebar_edge.width = fgeom->left_titlebar_edge;
04281   left_titlebar_edge.height = titlebar_rect.height - fgeom->top_titlebar_edge - fgeom->bottom_titlebar_edge;
04282 
04283   right_titlebar_edge.y = left_titlebar_edge.y;
04284   right_titlebar_edge.height = left_titlebar_edge.height;
04285   right_titlebar_edge.width = fgeom->right_titlebar_edge;
04286   right_titlebar_edge.x = titlebar_rect.x + titlebar_rect.width - right_titlebar_edge.width;
04287 
04288   top_titlebar_edge.x = titlebar_rect.x;
04289   top_titlebar_edge.y = titlebar_rect.y;
04290   top_titlebar_edge.width = titlebar_rect.width;
04291   top_titlebar_edge.height = fgeom->top_titlebar_edge;
04292 
04293   bottom_titlebar_edge.x = titlebar_rect.x;
04294   bottom_titlebar_edge.width = titlebar_rect.width;
04295   bottom_titlebar_edge.height = fgeom->bottom_titlebar_edge;
04296   bottom_titlebar_edge.y = titlebar_rect.y + titlebar_rect.height - bottom_titlebar_edge.height;
04297 
04298   left_edge.x = 0;
04299   left_edge.y = fgeom->top_height;
04300   left_edge.width = fgeom->left_width;
04301   left_edge.height = fgeom->height - fgeom->top_height - fgeom->bottom_height;
04302 
04303   right_edge.x = fgeom->width - fgeom->right_width;
04304   right_edge.y = fgeom->top_height;
04305   right_edge.width = fgeom->right_width;
04306   right_edge.height = fgeom->height - fgeom->top_height - fgeom->bottom_height;
04307 
04308   bottom_edge.x = 0;
04309   bottom_edge.y = fgeom->height - fgeom->bottom_height;
04310   bottom_edge.width = fgeom->width;
04311   bottom_edge.height = fgeom->bottom_height;
04312 
04313   if (title_layout)
04314     pango_layout_get_pixel_extents (title_layout,
04315                                     NULL, &extents);
04316 
04317   draw_info.mini_icon = mini_icon;
04318   draw_info.icon = icon;
04319   draw_info.title_layout = title_layout;
04320   draw_info.title_layout_width = title_layout ? extents.width : 0;
04321   draw_info.title_layout_height = title_layout ? extents.height : 0;
04322   draw_info.fgeom = fgeom;
04323   
04324   /* The enum is in the order the pieces should be rendered. */
04325   i = 0;
04326   while (i < META_FRAME_PIECE_LAST)
04327     {
04328       GdkRectangle rect;
04329       GdkRectangle combined_clip;
04330       
04331       switch ((MetaFramePiece) i)
04332         {
04333         case META_FRAME_PIECE_ENTIRE_BACKGROUND:
04334           rect.x = 0;
04335           rect.y = 0;
04336           rect.width = fgeom->width;
04337           rect.height = fgeom->height;
04338           break;
04339 
04340         case META_FRAME_PIECE_TITLEBAR:
04341           rect = titlebar_rect;
04342           break;
04343 
04344         case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE:
04345           rect = left_titlebar_edge;
04346           break;
04347 
04348         case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE:
04349           rect = right_titlebar_edge;
04350           break;
04351 
04352         case META_FRAME_PIECE_TOP_TITLEBAR_EDGE:
04353           rect = top_titlebar_edge;
04354           break;
04355 
04356         case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE:
04357           rect = bottom_titlebar_edge;
04358           break;
04359 
04360         case META_FRAME_PIECE_TITLEBAR_MIDDLE:
04361           rect.x = left_titlebar_edge.x + left_titlebar_edge.width;
04362           rect.y = top_titlebar_edge.y + top_titlebar_edge.height;
04363           rect.width = titlebar_rect.width - left_titlebar_edge.width -
04364             right_titlebar_edge.width;
04365           rect.height = titlebar_rect.height - top_titlebar_edge.height - bottom_titlebar_edge.height;
04366           break;
04367 
04368         case META_FRAME_PIECE_TITLE:
04369           rect = fgeom->title_rect;
04370           break;
04371 
04372         case META_FRAME_PIECE_LEFT_EDGE:
04373           rect = left_edge;
04374           break;
04375 
04376         case META_FRAME_PIECE_RIGHT_EDGE:
04377           rect = right_edge;
04378           break;
04379 
04380         case META_FRAME_PIECE_BOTTOM_EDGE:
04381           rect = bottom_edge;
04382           break;
04383 
04384         case META_FRAME_PIECE_OVERLAY:
04385           rect.x = 0;
04386           rect.y = 0;
04387           rect.width = fgeom->width;
04388           rect.height = fgeom->height;
04389           break;
04390 
04391         case META_FRAME_PIECE_LAST:
04392           g_assert_not_reached ();
04393           break;
04394         }
04395 
04396       rect.x += x_offset;
04397       rect.y += y_offset;
04398 
04399       if (clip == NULL)
04400         combined_clip = rect;
04401       else
04402         gdk_rectangle_intersect ((GdkRectangle*) clip, /* const cast */
04403                                  &rect,
04404                                  &combined_clip);
04405 
04406       if (combined_clip.width > 0 && combined_clip.height > 0)
04407         {
04408           MetaDrawOpList *op_list;
04409           MetaFrameStyle *parent;
04410 
04411           parent = style;
04412           op_list = NULL;
04413           while (parent && op_list == NULL)
04414             {
04415               op_list = parent->pieces[i];
04416               parent = parent->parent;
04417             }
04418 
04419           if (op_list)
04420             {
04421               MetaRectangle m_rect;
04422               m_rect = meta_rect (rect.x, rect.y, rect.width, rect.height);
04423               meta_draw_op_list_draw (op_list,
04424                                       widget,
04425                                       drawable,
04426                                       &combined_clip,
04427                                       &draw_info,
04428                                       m_rect);
04429             }
04430         }
04431 
04432 
04433       /* Draw buttons just before overlay */
04434       if ((i + 1) == META_FRAME_PIECE_OVERLAY)
04435         {
04436           int middle_bg_offset;
04437 
04438           middle_bg_offset = 0;
04439           j = 0;
04440           while (j < META_BUTTON_TYPE_LAST)
04441             {
04442               button_rect (j, fgeom, middle_bg_offset, &rect);
04443               
04444               rect.x += x_offset;
04445               rect.y += y_offset;
04446               
04447               if (clip == NULL)
04448                 combined_clip = rect;
04449               else
04450                 gdk_rectangle_intersect ((GdkRectangle*) clip, /* const cast */
04451                                          &rect,
04452                                          &combined_clip);
04453               
04454               if (combined_clip.width > 0 && combined_clip.height > 0)
04455                 {
04456                   MetaDrawOpList *op_list;
04457                   
04458                   op_list = get_button (style, j, button_states[j]);
04459                   
04460                   if (op_list)
04461                     {
04462                       MetaRectangle m_rect;
04463                       m_rect = meta_rect (rect.x, rect.y,
04464                                           rect.width, rect.height);
04465                       meta_draw_op_list_draw (op_list,
04466                                               widget,
04467                                               drawable,
04468                                               &combined_clip,
04469                                               &draw_info,
04470                                               m_rect);
04471                     }
04472                 }
04473 
04474               /* MIDDLE_BACKGROUND type may get drawn more than once */
04475               if ((j == META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND ||
04476                    j == META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND) &&
04477                   middle_bg_offset < MAX_MIDDLE_BACKGROUNDS)
04478                 {
04479                   ++middle_bg_offset;
04480                 }
04481               else
04482                 {
04483                   middle_bg_offset = 0;
04484                   ++j;
04485                 }
04486             }
04487         }
04488       
04489       ++i;
04490     }
04491 }
04492 
04493 MetaFrameStyleSet*
04494 meta_frame_style_set_new (MetaFrameStyleSet *parent)
04495 {
04496   MetaFrameStyleSet *style_set;
04497 
04498   style_set = g_new0 (MetaFrameStyleSet, 1);
04499 
04500   style_set->parent = parent;
04501   if (parent)
04502     meta_frame_style_set_ref (parent);
04503 
04