Merge branch 'backport' into 3.4-working
authorDana Jansens <danakj@orodu.net>
Thu, 14 Feb 2008 20:16:10 +0000 (15:16 -0500)
committerDana Jansens <danakj@orodu.net>
Thu, 14 Feb 2008 20:16:10 +0000 (15:16 -0500)
Conflicts:

openbox/actions/focus.c

53 files changed:
HACKING
Makefile.am
data/autostart.sh
data/rc.xml
data/rc.xsd
m4/openbox.m4
openbox/actions.c
openbox/actions/focus.c
openbox/client.c
openbox/client.h
openbox/client_list_combined_menu.c
openbox/client_list_menu.c
openbox/config.c
openbox/config.h
openbox/event.c
openbox/focus_cycle_indicator.c
openbox/focus_cycle_popup.c
openbox/framerender.c
openbox/geom.h
openbox/menu.c
openbox/menu.h
openbox/menuframe.c
openbox/misc.h
openbox/mouse.c
openbox/mouse.h
openbox/openbox.c
openbox/openbox.h
openbox/ping.c
openbox/ping.h
openbox/popup.c
openbox/popup.h
openbox/prompt.c [new file with mode: 0644]
openbox/prompt.h [new file with mode: 0644]
openbox/screen.c
openbox/window.c
openbox/window.h
po/POTFILES.in
po/nl.po
render/font.c
render/gradient.c
render/icon.h
render/image.c
render/image.h
render/imagecache.c [new file with mode: 0644]
render/imagecache.h [new file with mode: 0644]
render/render.c
render/render.h
render/test.c
render/theme.c
render/theme.h
tests/Makefile
tests/icons.c
tools/xdg-autostart/xdg-autostart

diff --git a/HACKING b/HACKING
index dab0aad..e450bba 100644 (file)
--- a/HACKING
+++ b/HACKING
@@ -27,8 +27,7 @@ not all, guidelines:
    lines
  * most other constructs should have braces on the same line as the
    statement
+ * else appears on a new line, just like an if
  * when in doubt look at the rest of the source
  * vim users can use "set expandtab tabstop=4 shiftwidth=4
    softtabstop=4" for some of this
-
-
index bd91870..f061a0f 100644 (file)
@@ -85,6 +85,8 @@ render_libobrender_la_SOURCES = \
        render/icon.h \
        render/image.h \
        render/image.c \
+       render/imagecache.h \
+       render/imagecache.c \
        render/instance.h \
        render/instance.c \
        render/mask.h \
@@ -250,6 +252,8 @@ openbox_openbox_SOURCES = \
        openbox/ping.h \
        openbox/place.c \
        openbox/place.h \
+       openbox/prompt.c \
+       openbox/prompt.h \
        openbox/popup.c \
        openbox/popup.h \
        openbox/prop.c \
index ab54284..5306326 100644 (file)
@@ -22,7 +22,9 @@ if which dbus-launch >/dev/null && test -z "$DBUS_SESSION_BUS_ADDRESS"; then
 fi
 
 # Make GTK apps look and behave how they were set up in the gnome config tools
-if which gnome-settings-daemon >/dev/null; then
+if which /usr/libexec/gnome-settings-daemon >/dev/null; then
+  /usr/libexec/gnome-settings-daemon &
+elif which gnome-settings-daemon >/dev/null; then
   gnome-settings-daemon &
 fi
 
@@ -34,6 +36,6 @@ fi
 # Run XDG autostart things.  By default don't run anything desktop-specific
 # See xdg-autostart --help more info
 DESKTOP_ENV=""
-if which xdg-autostart; then
+if which xdg-autostart >/dev/null; then
   xdg-autostart $DESKTOP_ENV
 fi
index ec17145..2b65b25 100644 (file)
 
   <application name="first element of window's WM_CLASS property (see xprop)"
               class="second element of window's WM_CLASS property (see xprop)"
-               role="the window's WM_WINDOW_ROLE property (see xprop)">
+               role="the window's WM_WINDOW_ROLE property (see xprop)"
+               type="the window's _NET_WM_WINDOW_TYPE (if unspecified, then
+                       it is dialog for child windows)">
   # the name or the class can be set, or both. this is used to match
   # windows when they appear. role can optionally be set as well, to
   # further restrict your matches.
   # used by a shell. you can use * to match any characters and ? to match
   # any single character.
 
+  # the type is one of: normal, dialog, splash, utility, menu, toolbar, dock,
+  #    or desktop
+
   # when multiple rules match a window, they will all be applied, in the
   # order that they appear in this list
 
index 31df2f7..c51135b 100644 (file)
         <xsd:element minOccurs="0" name="skip_taskbar" type="ob:bool"/>
         <xsd:element minOccurs="0" name="fullscreen" type="ob:bool"/>
         <xsd:element minOccurs="0" name="maximized" type="ob:maximization"/>
+        <xsd:attribute name="role" type="xsd:string"/>
+        <xsd:attribute name="type" type="ob:clienttype"/>
         <!-- at least one of these must be present -->
         <xsd:attribute name="name" type="xsd:string"/>
         <xsd:attribute name="class" type="xsd:string"/>
-        <xsd:attribute name="role" type="xsd:string"/>
     </xsd:complexType>
     <xsd:complexType name="applications">
         <xsd:element minOccurs="0" maxOccurs="unbounded" name="application" type="ob:application"/>
             <xsd:enumeration value="UnShowDesktop"/>
         </xsd:restriction>
     </xsd:simpleType>
+    <xsd:simpleType name="clienttype">
+        <xsd:restriction base="xsd:string">
+            <xsd:enumeration value="desktop"/>
+            <xsd:enumeration value="dock"/>
+            <xsd:enumeration value="toolbar"/>
+            <xsd:enumeration value="menu"/>
+            <xsd:enumeration value="splash"/>
+            <xsd:enumeration value="utility"/>
+            <xsd:enumeration value="dialog"/>
+            <xsd:enumeration value="normal"/>
+        </xsd:restriction>
+    </xsd:simpleType>
     <xsd:simpleType name="bool">
         <!-- this is copied to maximization.  Keep that in sync. -->
         <xsd:restriction base="xsd:string">
index 2911436..5c3aeec 100644 (file)
@@ -10,14 +10,23 @@ AC_DEFUN([OB_DEBUG],
     AC_ARG_ENABLE([strict-ansi],
     AC_HELP_STRING([--enable-strict-ansi],[Enable strict ANSI compliance build [[default=no]]]),
     [STRICT=$enableval], [STRICT="no"])
-    if test "$GCC" = "yes" && test "$STRICT" = "yes"; then
-       CFLAGS="$CFLAGS -ansi -pedantic -D_XOPEN_SOURCE"
-    fi
 
     AC_ARG_ENABLE([debug],
     AC_HELP_STRING([--enable-debug],[build a debug version [[default=no]]]),
     [DEBUG=$enableval], [DEBUG="no"])
 
+    AC_ARG_ENABLE([gprof],
+    AC_HELP_STRING([--enable-gprof],[Enable gprof profiling output [[default=no]]]),
+    [PROF=$enableval], [PROF="no"])
+
+    AC_ARG_ENABLE([gprof-libc],
+    AC_HELP_STRING([--enable-gprof-libc],[Link against libc with profiling support [[default=no]]]),
+    [PROFLC=$enableval], [PROFLC="no"])
+
+    if test "$PROFLC" = "yes"; then
+        PROF="yes" # always enable profiling then
+    fi
+
     TEST=""
     test "${PACKAGE_VERSION%*alpha*}" != "$PACKAGE_VERSION" && TEST="yes"
     test "${PACKAGE_VERSION%*beta*}" != "$PACKAGE_VERSION" && TEST="yes"
@@ -52,6 +61,7 @@ AC_DEFUN([OB_COMPILER_FLAGS],
     AC_REQUIRE([AC_PROG_CC])
 
     FLAGS=""
+    L=""
 
     if test "$DEBUG" = "yes"; then
         FLAGS="-DDEBUG"
@@ -73,11 +83,18 @@ AC_DEFUN([OB_COMPILER_FLAGS],
        if test "$STRICT" = "yes"; then
            FLAGS="$FLAGS -ansi -pedantic -D_XOPEN_SOURCE"
        fi
+       if test "$PROF" = "yes"; then
+           FLAGS="$FLAGS -pg -fno-inline"
+       fi
+       if test "$PROFLC" = "yes"; then
+           L="$L -lc_p -lm_p"
+       fi
        FLAGS="$FLAGS -fno-strict-aliasing"
     fi
     AC_MSG_CHECKING([for compiler specific flags])
     AC_MSG_RESULT([$FLAGS])
     CFLAGS="$CFLAGS $FLAGS"
+    LIBS="$LIBS $L"
 ])
 
 AC_DEFUN([OB_NLS],
index b7ba5b4..32b3d27 100644 (file)
@@ -36,7 +36,6 @@ static ObActionsAct* actions_build_act_from_string(const gchar *name);
 
 static ObActionsAct *interactive_act = NULL;
 static guint         interactive_initial_state = 0;
-static gboolean      replay_pointer = FALSE;
 
 struct _ObActionsDefinition {
     guint ref;
@@ -224,16 +223,6 @@ static void actions_setup_data(ObActionsData *data,
     data->client = client;
 }
 
-void actions_set_need_pointer_replay_before_move(gboolean replay)
-{
-    replay_pointer = replay;
-}
-
-gboolean actions_get_need_pointer_replay_before_move()
-{
-    return replay_pointer;
-}
-
 void actions_run_acts(GSList *acts,
                       ObUserAction uact,
                       guint state,
@@ -346,14 +335,8 @@ gboolean actions_interactive_input_event(XEvent *e)
 void actions_client_move(ObActionsData *data, gboolean start)
 {
     static gulong ignore_start = 0;
-    if (start) {
+    if (start)
         ignore_start = event_start_ignore_all_enters();
-        if (replay_pointer) {
-            /* replay the pointer event before any windows move */
-            XAllowEvents(ob_display, ReplayPointer, event_curtime);
-            replay_pointer = FALSE;
-        }
-    }
     else if (config_focus_follow &&
              data->context != OB_FRAME_CONTEXT_CLIENT)
     {
@@ -364,8 +347,14 @@ void actions_client_move(ObActionsData *data, gboolean start)
                that moves windows our from under the cursor, the enter
                event will come as a GrabNotify which is ignored, so this
                makes a fake enter event
+
+               don't do this if there is a grab on the pointer.  enter events
+               are ignored during a grab, so don't force fake ones when they
+               should be ignored
             */
-            if ((c = client_under_pointer()) && c != data->client) {
+            if ((c = client_under_pointer()) && c != data->client &&
+                !grab_on_pointer())
+            {
                 ob_debug_type(OB_DEBUG_FOCUS,
                               "Generating fake enter because we did a "
                               "mouse-event action");
index 1dceb86..a157b4a 100644 (file)
@@ -71,8 +71,10 @@ static gboolean run_func(ObActionsData *data, gpointer options)
             (data->context != OB_FRAME_CONTEXT_CLIENT &&
              data->context != OB_FRAME_CONTEXT_FRAME))
         {
+            actions_client_move(data, TRUE);
             client_activate(data->client, o->here,
                             o->activate, o->activate, TRUE);
+            actions_client_move(data, FALSE);
         }
     } else if (data->context == OB_FRAME_CONTEXT_DESKTOP) {
         /* focus action on the root window. make keybindings work for this
index e7290ca..9fa311c 100644 (file)
@@ -32,6 +32,7 @@
 #include "session.h"
 #include "event.h"
 #include "grab.h"
+#include "prompt.h"
 #include "focus.h"
 #include "stacking.h"
 #include "openbox.h"
@@ -67,9 +68,10 @@ typedef struct
     gpointer data;
 } ClientCallback;
 
-GList            *client_list          = NULL;
+GList            *client_list           = NULL;
 
-static GSList *client_destroy_notifies = NULL;
+static GSList  *client_destroy_notifies = NULL;
+static RrImage *client_default_icon     = NULL;
 
 static void client_get_all(ObClient *self, gboolean real);
 static void client_get_startup_id(ObClient *self);
@@ -104,10 +106,24 @@ static GSList *client_search_all_top_parents_internal(ObClient *self,
                                                       ObStackingLayer layer);
 static void client_call_notifies(ObClient *self, GSList *list);
 static void client_ping_event(ObClient *self, gboolean dead);
+static void client_prompt_kill(ObClient *self);
 
 
 void client_startup(gboolean reconfig)
 {
+    if ((client_default_icon = RrImageCacheFind(ob_rr_icons,
+                                                ob_rr_theme->def_win_icon,
+                                                ob_rr_theme->def_win_icon_w,
+                                                ob_rr_theme->def_win_icon_h)))
+        RrImageRef(client_default_icon);
+    else {
+        client_default_icon = RrImageNew(ob_rr_icons);
+        RrImageAddPicture(client_default_icon,
+                          ob_rr_theme->def_win_icon,
+                          ob_rr_theme->def_win_icon_w,
+                          ob_rr_theme->def_win_icon_h);
+    }
+
     if (reconfig) return;
 
     client_set_list();
@@ -115,6 +131,9 @@ void client_startup(gboolean reconfig)
 
 void client_shutdown(gboolean reconfig)
 {
+    RrImageUnref(client_default_icon);
+    client_default_icon = NULL;
+
     if (reconfig) return;
 }
 
@@ -211,13 +230,13 @@ void client_manage_all(void)
             if (attrib.override_redirect) continue;
 
             if (attrib.map_state != IsUnmapped)
-                client_manage(children[i]);
+                client_manage(children[i], NULL);
         }
     }
     XFree(children);
 }
 
-void client_manage(Window window)
+void client_manage(Window window, ObPrompt *prompt)
 {
     ObClient *self;
     XEvent e;
@@ -269,8 +288,10 @@ void client_manage(Window window)
 
     map_time = event_get_server_time();
 
-    /* choose the events we want to receive on the CLIENT window */
-    attrib_set.event_mask = CLIENT_EVENTMASK;
+    /* choose the events we want to receive on the CLIENT window
+       (ObPrompt windows can request events too) */
+    attrib_set.event_mask = CLIENT_EVENTMASK |
+        (prompt ? prompt->event_mask : 0);
     attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
     XChangeWindowAttributes(ob_display, window,
                             CWEventMask|CWDontPropagate, &attrib_set);
@@ -280,6 +301,7 @@ void client_manage(Window window)
     self = g_new0(ObClient, 1);
     self->obwin.type = Window_Client;
     self->window = window;
+    self->prompt = prompt;
 
     /* non-zero defaults */
     self->wmstate = WithdrawnState; /* make sure it gets updated first time */
@@ -298,8 +320,10 @@ void client_manage(Window window)
     client_setup_decor_and_functions(self, FALSE);
 
     /* specify that if we exit, the window should not be destroyed and
-       should be reparented back to root automatically */
-    XChangeSaveSet(ob_display, window, SetModeInsert);
+       should be reparented back to root automatically, unless we are managing
+       an internal ObPrompt window  */
+    if (!self->prompt)
+        XChangeSaveSet(ob_display, window, SetModeInsert);
 
     /* create the decoration frame for the client window */
     self->frame = frame_new(self);
@@ -612,15 +636,6 @@ void client_manage(Window window)
     /* update the list hints */
     client_set_list();
 
-    /* watch for when the application stops responding.  only do this for
-       normal windows, i.e. windows which have titlebars and close buttons 
-       and things like that.
-       we don't need to stop pinging on unmanage, because it will be handled
-       automatically by the destroy callback!
-    */
-    if (self->ping && client_normal(self))
-        ping_start(self, client_ping_event);
-
     /* free the ObAppSettings shallow copy */
     g_free(settings);
 
@@ -672,7 +687,6 @@ void client_unmanage_all(void)
 
 void client_unmanage(ObClient *self)
 {
-    guint j;
     GSList *it;
     gulong ignore_start;
 
@@ -699,8 +713,10 @@ void client_unmanage(ObClient *self)
 
     mouse_grab_for_client(self, FALSE);
 
-    /* remove the window from our save set */
-    XChangeSaveSet(ob_display, self->window, SetModeDelete);
+    /* remove the window from our save set, unless we are managing an internal
+       ObPrompt window */
+    if (!self->prompt)
+        XChangeSaveSet(ob_display, self->window, SetModeDelete);
 
     /* update the focus lists */
     focus_order_remove(self);
@@ -709,6 +725,10 @@ void client_unmanage(ObClient *self)
         focus_client = NULL;
     }
 
+    /* if we're prompting to kill the client, close that */
+    prompt_unref(self->kill_prompt);
+    self->kill_prompt = NULL;
+
     client_list = g_list_remove(client_list, self);
     stacking_remove(self);
     g_hash_table_remove(window_map, &self->window);
@@ -798,15 +818,13 @@ void client_unmanage(ObClient *self)
     ob_debug("Unmanaged window 0x%lx\n", self->window);
 
     /* free all data allocated in the client struct */
+    RrImageUnref(self->icon_set);
     g_slist_free(self->transients);
-    for (j = 0; j < self->nicons; ++j)
-        g_free(self->icons[j].data);
-    if (self->nicons > 0)
-        g_free(self->icons);
     g_free(self->startup_id);
     g_free(self->wm_command);
     g_free(self->title);
     g_free(self->icon_title);
+    g_free(self->original_title);
     g_free(self->name);
     g_free(self->class);
     g_free(self->role);
@@ -844,13 +862,15 @@ static ObAppSettings *client_get_settings_state(ObClient *self)
             !g_pattern_match(app->name, strlen(self->name), self->name, NULL))
             match = FALSE;
         else if (app->class &&
-                !g_pattern_match(app->class,
-                                 strlen(self->class), self->class, NULL))
+                 !g_pattern_match(app->class,
+                                  strlen(self->class), self->class, NULL))
             match = FALSE;
         else if (app->role &&
                  !g_pattern_match(app->role,
                                   strlen(self->role), self->role, NULL))
             match = FALSE;
+        else if ((signed)app->type >= 0 && app->type != self->type)
+            match = FALSE;
 
         if (match) {
             ob_debug("Window matching: %s\n", app->name);
@@ -1959,6 +1979,7 @@ void client_update_title(ObClient *self)
     gchar *visible = NULL;
 
     g_free(self->title);
+    g_free(self->original_title);
 
     /* try netwm */
     if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) {
@@ -1975,6 +1996,7 @@ void client_update_title(ObClient *self)
                 data = g_strdup("Unnamed Window");
         }
     }
+    self->original_title = g_strdup(data);
 
     if (self->client_machine) {
         visible = g_strdup_printf("%s (%s)", data, self->client_machine);
@@ -1984,7 +2006,7 @@ void client_update_title(ObClient *self)
 
     if (self->not_responding) {
         data = visible;
-        if (self->close_tried_term)
+        if (self->kill_level > 0)
             visible = g_strdup_printf("%s - [%s]", data, _("Killing..."));
         else
             visible = g_strdup_printf("%s - [%s]", data, _("Not Responding"));
@@ -2016,7 +2038,7 @@ void client_update_title(ObClient *self)
 
     if (self->not_responding) {
         data = visible;
-        if (self->close_tried_term)
+        if (self->kill_level > 0)
             visible = g_strdup_printf("%s - [%s]", data, _("Killing..."));
         else
             visible = g_strdup_printf("%s - [%s]", data, _("Not Responding"));
@@ -2081,143 +2103,134 @@ void client_update_strut(ObClient *self)
     }
 }
 
-/* Avoid storing icons above this size if possible */
-#define AVOID_ABOVE 64
-
 void client_update_icons(ObClient *self)
 {
     guint num;
     guint32 *data;
     guint w, h, i, j;
     guint num_seen;  /* number of icons present */
-    guint num_small_seen;  /* number of icons small enough present */
-    guint smallest, smallest_area;
+    RrImage *img;
+
+    img = NULL;
 
-    for (i = 0; i < self->nicons; ++i)
-        g_free(self->icons[i].data);
-    if (self->nicons > 0)
-        g_free(self->icons);
-    self->nicons = 0;
+    /* grab the server, because we might be setting the window's icon and
+       we don't want them to set it in between and we overwrite their own
+       icon */
+    grab_server(TRUE);
 
     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
         /* figure out how many valid icons are in here */
         i = 0;
-        num_seen = num_small_seen = 0;
-        smallest = smallest_area = 0;
-        if (num > 2)
-            while (i < num) {
+        num_seen = 0;
+        while (i + 2 < num) { /* +2 is to make sure there is a w and h */
+            w = data[i++];
+            h = data[i++];
+            /* watch for the data being too small for the specified size,
+               or for zero sized icons. */
+            if (i + w*h > num || w == 0 || h == 0) break;
+
+            /* convert it to the right bit order for ObRender */
+            for (j = 0; j < w*h; ++j)
+                data[i+j] =
+                    (((data[i+j] >> 24) & 0xff) << RrDefaultAlphaOffset) +
+                    (((data[i+j] >> 16) & 0xff) << RrDefaultRedOffset)   +
+                    (((data[i+j] >>  8) & 0xff) << RrDefaultGreenOffset) +
+                    (((data[i+j] >>  0) & 0xff) << RrDefaultBlueOffset);
+
+            /* is it in the cache? */
+            img = RrImageCacheFind(ob_rr_icons, &data[i], w, h);
+            if (img) RrImageRef(img); /* own it */
+
+            i += w*h;
+            ++num_seen;
+
+            /* don't bother looping anymore if we already found it in the cache
+               since we'll just use that! */
+            if (img) break;
+        }
+
+        /* if it's not in the cache yet, then add it to the cache now.
+           we have already converted it to the correct bit order above */
+        if (!img && num_seen > 0) {
+            img = RrImageNew(ob_rr_icons);
+            i = 0;
+            for (j = 0; j < num_seen; ++j) {
                 w = data[i++];
                 h = data[i++];
-                i += w * h;
-                /* watch for it being too small for the specified size, or for
-                   zero sized icons. */
-                if (i > num || w == 0 || h == 0) break;
-
-                if (!smallest_area || w*h < smallest_area) {
-                    smallest = num_seen;
-                    smallest_area = w*h;
-                }
-                ++num_seen;
-                if (w <= AVOID_ABOVE && h <= AVOID_ABOVE)
-                    ++num_small_seen;
-            }
-        if (num_small_seen > 0)
-            self->nicons = num_small_seen;
-        else if (num_seen)
-            self->nicons = 1;
-
-        self->icons = g_new(ObClientIcon, self->nicons);
-
-        /* store the icons */
-        i = 0;
-        for (j = 0; j < self->nicons;) {
-            guint x, y, t;
-
-            w = self->icons[j].width = data[i++];
-            h = self->icons[j].height = data[i++];
-
-            /* if there are some icons smaller than the threshold, we're
-               skipping all the ones above */
-            if (num_small_seen > 0) {
-                if (w > AVOID_ABOVE || h > AVOID_ABOVE) {
-                    i += w*h;
-                    continue;
-                }
-            }
-            /* if there were no icons smaller than the threshold, then we are
-               only taking the smallest available one we saw */
-            else if (j != smallest) {
+                RrImageAddPicture(img, &data[i], w, h);
                 i += w*h;
-                continue;
-            }
-
-            self->icons[j].data = g_new(RrPixel32, w * h);
-            for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
-                if (x >= w) {
-                    x = 0;
-                    ++y;
-                }
-                self->icons[j].data[t] =
-                    (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
-                    (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
-                    (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
-                    (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
             }
-            g_assert(i <= num);
-
-            ++j;
         }
 
         g_free(data);
-    } else {
+    }
+
+    /* if we didn't find an image from the NET_WM_ICON stuff, then try the
+       legacy X hints */
+    if (!img) {
         XWMHints *hints;
 
         if ((hints = XGetWMHints(ob_display, self->window))) {
             if (hints->flags & IconPixmapHint) {
-                self->nicons = 1;
-                self->icons = g_new(ObClientIcon, self->nicons);
+                gboolean xicon;
                 xerror_set_ignore(TRUE);
-                if (!RrPixmapToRGBA(ob_rr_inst,
-                                    hints->icon_pixmap,
-                                    (hints->flags & IconMaskHint ?
-                                     hints->icon_mask : None),
-                                    &self->icons[0].width,
-                                    &self->icons[0].height,
-                                    &self->icons[0].data))
-                {
-                    g_free(self->icons);
-                    self->nicons = 0;
-                }
+                xicon = RrPixmapToRGBA(ob_rr_inst,
+                                       hints->icon_pixmap,
+                                       (hints->flags & IconMaskHint ?
+                                        hints->icon_mask : None),
+                                       (gint*)&w, (gint*)&h, &data);
                 xerror_set_ignore(FALSE);
+
+
+                if (xicon) {
+                    if (w > 0 && h > 0) {
+                        /* is this icon in the cache yet? */
+                        img = RrImageCacheFind(ob_rr_icons, data, w, h);
+                        if (img) RrImageRef(img); /* own it */
+
+                        /* if not, then add it */
+                        if (!img) {
+                            img = RrImageNew(ob_rr_icons);
+                            RrImageAddPicture(img, data, w, h);
+                        }
+                    }
+
+                    g_free(data);
+                }
             }
             XFree(hints);
         }
     }
 
-    /* set the default icon onto the window
-       in theory, this could be a race, but if a window doesn't set an icon
-       or removes it entirely, it's not very likely it is going to set one
-       right away afterwards
+    /* set the client's icons to be whatever we found */
+    RrImageUnref(self->icon_set);
+    self->icon_set = img;
 
-       if it has parents, then one of them will have an icon already
+    /* if the client has no icon at all, then we set a default icon onto it.
+       but, if it has parents, then one of them will have an icon already
     */
-    if (self->nicons == 0 && !self->parents) {
+    if (!self->icon_set && !self->parents) {
         RrPixel32 *icon = ob_rr_theme->def_win_icon;
-        gulong *data;
-
-        data = g_new(gulong, 48*48+2);
-        data[0] = data[1] =  48;
-        for (i = 0; i < 48*48; ++i)
-            data[i+2] = (((icon[i] >> RrDefaultAlphaOffset) & 0xff) << 24) +
+        gulong *ldata; /* use a long here to satisfy OBT_PROP_SETA32 */
+
+        w = ob_rr_theme->def_win_icon_w;
+        h = ob_rr_theme->def_win_icon_h;
+        ldata = g_new(gulong, w*h+2);
+        ldata[0] = w;
+        ldata[1] = h;
+        for (i = 0; i < w*h; ++i)
+            ldata[i+2] = (((icon[i] >> RrDefaultAlphaOffset) & 0xff) << 24) +
                 (((icon[i] >> RrDefaultRedOffset) & 0xff) << 16) +
                 (((icon[i] >> RrDefaultGreenOffset) & 0xff) << 8) +
                 (((icon[i] >> RrDefaultBlueOffset) & 0xff) << 0);
-        PROP_SETA32(self->window, net_wm_icon, cardinal, data, 48*48+2);
-        g_free(data);
+        PROP_SETA32(self->window, net_wm_icon, cardinal, ldata, w*h+2);
+        g_free(ldata);
     } else if (self->frame)
         /* don't draw the icon empty if we're just setting one now anyways,
            we'll get the property change any second */
         frame_adjust_icon(self->frame);
+
+    grab_server(FALSE);
 }
 
 void client_update_icon_geometry(ObClient *self)
@@ -2572,6 +2585,10 @@ gboolean client_show(ObClient *self)
     gboolean show = FALSE;
 
     if (client_should_show(self)) {
+        /* replay pending pointer event before showing the window, in case it
+           should be going to something under the window */
+        mouse_replay_pointer();
+
         frame_show(self->frame);
         show = TRUE;
 
@@ -2613,6 +2630,10 @@ gboolean client_hide(ObClient *self)
            so trying to ignore them is futile in case 3 anyways
         */
 
+        /* replay pending pointer event before hiding the window, in case it
+           should be going to the window */
+        mouse_replay_pointer();
+
         frame_hide(self->frame);
         hide = TRUE;
 
@@ -3028,6 +3049,10 @@ void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
         if (!user)
             ignore_start = event_start_ignore_all_enters();
 
+        /* replay pending pointer event before move the window, in case it
+           would change what window gets the event */
+        mouse_replay_pointer();
+
         frame_adjust_area(self->frame, fmoved, fresized, FALSE);
 
         if (!user)
@@ -3314,13 +3339,23 @@ void client_shade(ObClient *self, gboolean shade)
 
 static void client_ping_event(ObClient *self, gboolean dead)
 {
-    self->not_responding = dead;
-    client_update_title(self);
+    if (self->not_responding != dead) {
+        self->not_responding = dead;
+        client_update_title(self);
 
-    if (!dead) {
-        /* try kill it nicely the first time again, if it started responding
-           at some point */
-        self->close_tried_term = FALSE;
+        if (dead)
+            /* the client isn't responding, so ask to kill it */
+            client_prompt_kill(self);
+        else {
+            /* it came back to life ! */
+
+            if (self->kill_prompt) {
+                prompt_unref(self->kill_prompt);
+                self->kill_prompt = NULL;
+            }
+
+            self->kill_level = 0;
+        }
     }
 }
 
@@ -3328,30 +3363,100 @@ void client_close(ObClient *self)
 {
     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
 
+    /* if closing an internal obprompt, that is just cancelling it */
+    if (self->prompt) {
+        prompt_cancel(self->prompt);
+        return;
+    }
+
     /* in the case that the client provides no means to requesting that it
        close, we just kill it */
     if (!self->delete_window)
         /* don't use client_kill(), we should only kill based on PID in
            response to a lack of PING replies */
         XKillClient(ob_display, self->window);
-    else if (self->not_responding)
-        client_kill(self);
-    else
+    else {
         /* request the client to close with WM_DELETE_WINDOW */
         PROP_MSG_TO(self->window, self->window, wm_protocols,
                     prop_atoms.wm_delete_window, event_curtime, 0, 0, 0,
                     NoEventMask);
+
+        /* we're trying to close the window, so see if it is responding. if it
+           is not, then we will let them kill the window */
+        if (self->ping)
+            ping_start(self, client_ping_event);
+
+        /* if we already know the window isn't responding (maybe they clicked
+           no in the kill dialog but it hasn't come back to life), then show
+           the kill dialog */
+        if (self->not_responding)
+            client_prompt_kill(self);
+    }
+}
+
+#define OB_KILL_RESULT_NO 0
+#define OB_KILL_RESULT_YES 1
+
+static void client_kill_requested(ObPrompt *p, gint result, gpointer data)
+{
+    ObClient *self = data;
+
+    if (result == OB_KILL_RESULT_YES)
+        client_kill(self);
+
+    prompt_unref(self->kill_prompt);
+    self->kill_prompt = NULL;
+}
+
+static void client_prompt_kill(ObClient *self)
+{
+    /* check if we're already prompting */
+    if (!self->kill_prompt) {
+        ObPromptAnswer answers[] = {
+            { _("No"), OB_KILL_RESULT_NO },
+            { _("Yes"), OB_KILL_RESULT_YES }
+        };
+        gchar *m;
+
+        if (client_on_localhost(self)) {
+            const gchar *sig;
+
+            if (self->kill_level == 0)
+                sig = "terminate";
+            else
+                sig = "kill";
+
+            m = g_strdup_printf
+                (_("The window \"%s\" does not seem to be responding.  Do you want to force it to exit by sending the %s signal?"), self->original_title, sig);
+        }
+        else
+            m = g_strdup_printf
+                (_("The window \"%s\" does not seem to be responding.  Do you want to disconnect it from the X server?"), self->original_title);
+
+
+        self->kill_prompt = prompt_new(m, answers,
+                                       sizeof(answers)/sizeof(answers[0]),
+                                       OB_KILL_RESULT_NO, /* default = no */
+                                       OB_KILL_RESULT_NO, /* cancel = no */
+                                       client_kill_requested, self);
+        g_free(m);
+    }
+
+    prompt_show(self->kill_prompt, self);
 }
 
 void client_kill(ObClient *self)
 {
-    if (!self->client_machine && self->pid) {
+    /* don't kill our own windows */
+    if (self->prompt) return;
+
+    if (client_on_localhost(self) && self->pid) {
         /* running on the local host */
-        if (!self->close_tried_term) {
-            ob_debug("killing window 0x%x with pid %lu, with SIGTERM\n",
+        if (self->kill_level == 0) {
+            ob_debug("killing window 0x%x with pid %lu, with SIGTERM",
                      self->window, self->pid);
             kill(self->pid, SIGTERM);
-            self->close_tried_term = TRUE;
+            ++self->kill_level;
 
             /* show that we're trying to kill it */
             client_update_title(self);
@@ -3362,8 +3467,10 @@ void client_kill(ObClient *self)
             kill(self->pid, SIGKILL); /* kill -9 */
         }
     }
-    else
+    else {
+        /* running on a remote host */
         XKillClient(ob_display, self->window);
+    }
 }
 
 void client_hilite(ObClient *self, gboolean hilite)
@@ -3816,53 +3923,21 @@ gboolean client_focused(ObClient *self)
     return self == focus_client;
 }
 
-static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
-{
-    guint i;
-    gulong min_diff, min_i;
 
-    if (!self->nicons) {
-        ObClientIcon *parent = NULL;
-        GSList *it;
-
-        for (it = self->parents; it; it = g_slist_next(it)) {
-            ObClient *c = it->data;
-            if ((parent = client_icon_recursive(c, w, h)))
-                break;
-        }
 
-        return parent;
-    }
-
-    /* some kind of crappy approximation to find the icon closest in size to
-       what we requested, but icons are generally all the same ratio as
-       eachother so it's good enough. */
-
-    min_diff = ABS(self->icons[0].width - w) + ABS(self->icons[0].height - h);
-    min_i = 0;
-
-    for (i = 1; i < self->nicons; ++i) {
-        gulong diff;
-
-        diff = ABS(self->icons[i].width - w) + ABS(self->icons[i].height - h);
-        if (diff < min_diff) {
-            min_diff = diff;
-            min_i = i;
-        }
-    }
-    return &self->icons[min_i];
-}
-
-const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
+RrImage* client_icon(ObClient *self)
 {
-    ObClientIcon *ret;
-    static ObClientIcon deficon;
+    RrImage *ret = NULL;
 
-    if (!(ret = client_icon_recursive(self, w, h))) {
-        deficon.width = deficon.height = 48;
-        deficon.data = ob_rr_theme->def_win_icon;
-        ret = &deficon;
+    if (self->icon_set)
+        ret = self->icon_set;
+    else if (self->parents) {
+        GSList *it;
+        for (it = self->parents; it && !ret; it = g_slist_next(it))
+            ret = client_icon(it->data);
     }
+    if (!ret)
+        ret = client_default_icon;
     return ret;
 }
 
@@ -4333,3 +4408,9 @@ gboolean client_has_group_siblings(ObClient *self)
 {
     return self->group && self->group->members->next;
 }
+
+/*! Returns TRUE if the client is running on the same machine as Openbox */
+gboolean client_on_localhost(ObClient *self)
+{
+    return self->client_machine == NULL;
+}
index 83fdc9a..c2461cb 100644 (file)
 struct _ObFrame;
 struct _ObGroup;
 struct _ObSessionState;
+struct _ObPrompt;
 
 typedef struct _ObClient      ObClient;
-typedef struct _ObClientIcon  ObClientIcon;
-
-/*! Holds an icon in ARGB format */
-struct _ObClientIcon
-{
-    gint width;
-    gint height;
-    RrPixel32 *data;
-};
 
 /*! Possible window types */
 typedef enum
@@ -82,6 +74,10 @@ struct _ObClient
     ObWindow obwin;
     Window  window;
 
+    /*! If this client is managing an ObPrompt window, then this is set to the
+      prompt */
+    struct _ObPrompt *prompt;
+
     /*! The window's decorations. NULL while the window is being managed! */
     struct _ObFrame *frame;
 
@@ -115,6 +111,8 @@ struct _ObClient
     gchar *title;
     /*! Window title when iconified */
     gchar *icon_title;
+    /*! The title as requested by the client, without any of our own changes */
+    gchar *original_title;
     /*! Hostname of machine running the client */
     gchar *client_machine;
     /*! The command used to run the program. Pre-XSMP window identification. */
@@ -231,8 +229,11 @@ struct _ObClient
     /*! Indicates if the client is trying to close but has stopped responding
       to pings */
     gboolean not_responding;
+    /*! A prompt shown when you are trying to close a client that is not
+      responding.  It asks if you want to kill the client */
+    struct _ObPrompt *kill_prompt;
     /*! We tried to close the window with a SIGTERM */
-    gboolean close_tried_term;
+    gint kill_level;
 
 #ifdef SYNC
     /*! The client wants to sync during resizes */
@@ -297,10 +298,8 @@ struct _ObClient
     */
     guint functions;
 
-    /*! Icons for the client as specified on the client window */
-    ObClientIcon *icons;
-    /*! The number of icons in icons */
-    guint nicons;
+    /* The window's icon, in a variety of shapes and sizes */
+    RrImage *icon_set;
 
     /*! Where the window should iconify to/from */
     Rect icon_geometry;
@@ -325,8 +324,10 @@ void client_remove_destroy_notify(ObClientCallback func);
 /*! Manages all existing windows */
 void client_manage_all();
 /*! Manages a given window
+  @param prompt This specifies an ObPrompt which is being managed.  It is
+                possible to manage Openbox-owned windows through this.
 */
-void client_manage(Window win);
+void client_manage(Window win, struct _ObPrompt *prompt);
 /*! Unmanages all managed windows */
 void client_unmanage_all();
 /*! Unmanages a given client */
@@ -632,7 +633,10 @@ void client_setup_decor_and_functions(ObClient *self, gboolean reconfig);
 /*! Sets the window's type and transient flag */
 void client_get_type_and_transientness(ObClient *self);
 
-const ObClientIcon *client_icon(ObClient *self, gint w, gint h);
+/*! Returns a client's icon set, or its parents (recursively) if it doesn't
+  have one
+*/
+RrImage* client_icon(ObClient *self);
 
 /*! Return TRUE if the client is transient for some other window. Return
   FALSE if it's not transient or there is no window for it to be
@@ -717,4 +721,7 @@ ObClient* client_under_pointer();
 
 gboolean client_has_group_siblings(ObClient *self);
 
+/*! Returns TRUE if the client is running on the same machine as Openbox */
+gboolean client_on_localhost(ObClient *self);
+
 #endif
index 76a819f..a04d07d 100644 (file)
@@ -56,8 +56,6 @@ static gboolean self_update(ObMenuFrame *frame, gpointer data)
             if (client_normal(c) && (!c->skip_taskbar || c->iconic) &&
                 (c->desktop == desktop || c->desktop == DESKTOP_ALL))
             {
-                const ObClientIcon *icon;
-
                 empty = FALSE;
 
                 if (c->iconic) {
@@ -69,11 +67,9 @@ static gboolean self_update(ObMenuFrame *frame, gpointer data)
                     e = menu_add_normal(menu, desktop, c->title, NULL, FALSE);
                 }
 
-                if (config_menu_client_list_icons
-                        && (icon = client_icon(c, 32, 32))) {
-                    e->data.normal.icon_width = icon->width;
-                    e->data.normal.icon_height = icon->height;
-                    e->data.normal.icon_data = icon->data;
+                if (config_menu_client_list_icons) {
+                    e->data.normal.icon = client_icon(c);
+                    RrImageRef(e->data.normal.icon);
                     e->data.normal.icon_alpha =
                         c->iconic ? OB_ICONIC_ALPHA : 0xff;
                 }
index e6521a0..4ec6e78 100644 (file)
@@ -58,7 +58,6 @@ static gboolean desk_menu_update(ObMenuFrame *frame, gpointer data)
             (c->desktop == d->desktop || c->desktop == DESKTOP_ALL))
         {
             ObMenuEntry *e;
-            const ObClientIcon *icon;
 
             empty = FALSE;
 
@@ -71,11 +70,9 @@ static gboolean desk_menu_update(ObMenuFrame *frame, gpointer data)
                 e = menu_add_normal(menu, d->desktop, c->title, NULL, FALSE);
             }
 
-            if (config_menu_client_list_icons
-                && (icon = client_icon(c, 32, 32))) {
-                e->data.normal.icon_width = icon->width;
-                e->data.normal.icon_height = icon->height;
-                e->data.normal.icon_data = icon->data;
+            if (config_menu_client_list_icons) {
+                e->data.normal.icon = client_icon(c);
+                RrImageRef(e->data.normal.icon);
                 e->data.normal.icon_alpha = c->iconic ? OB_ICONIC_ALPHA : 0xff;
             }
 
index 6818f72..8c22d35 100644 (file)
@@ -101,6 +101,7 @@ GSList *config_per_app_settings;
 ObAppSettings* config_create_app_settings(void)
 {
     ObAppSettings *settings = g_new0(ObAppSettings, 1);
+    settings->type = -1;
     settings->decor = -1;
     settings->shade = -1;
     settings->monitor = -1;
@@ -124,6 +125,7 @@ void config_app_settings_copy_non_defaults(const ObAppSettings *src,
     g_assert(src != NULL);
     g_assert(dst != NULL);
 
+    copy_if(type, (ObClientType)-1);
     copy_if(decor, -1);
     copy_if(shade, -1);
     copy_if(focus, -1);
@@ -193,15 +195,16 @@ static void parse_per_app_settings(ObParseInst *inst, xmlDocPtr doc,
                                    xmlNodePtr node, gpointer data)
 {
     xmlNodePtr app = parse_find_node("application", node->children);
-    gchar *name = NULL, *class = NULL, *role = NULL;
-    gboolean name_set, class_set;
+    gchar *name = NULL, *class = NULL, *role = NULL, *type = NULL;
+    gboolean name_set, class_set, type_set;
     gboolean x_pos_given;
 
     while (app) {
-        name_set = class_set = x_pos_given = FALSE;
+        name_set = class_set = type_set = x_pos_given = FALSE;
 
         class_set = parse_attr_string("class", app, &class);
         name_set = parse_attr_string("name", app, &name);
+        type_set = parse_attr_string("type", app, &type);
         if (class_set || name_set) {
             xmlNodePtr n, c;
             ObAppSettings *settings = config_create_app_settings();;
@@ -212,6 +215,25 @@ static void parse_per_app_settings(ObParseInst *inst, xmlDocPtr doc,
             if (class_set)
                 settings->class = g_pattern_spec_new(class);
 
+            if (type_set) {
+                if (!g_ascii_strcasecmp(type, "normal"))
+                    settings->type = OB_CLIENT_TYPE_NORMAL;
+                else if (!g_ascii_strcasecmp(type, "dialog"))
+                    settings->type = OB_CLIENT_TYPE_DIALOG;
+                else if (!g_ascii_strcasecmp(type, "splash"))
+                    settings->type = OB_CLIENT_TYPE_SPLASH;
+                else if (!g_ascii_strcasecmp(type, "utility"))
+                    settings->type = OB_CLIENT_TYPE_UTILITY;
+                else if (!g_ascii_strcasecmp(type, "menu"))
+                    settings->type = OB_CLIENT_TYPE_MENU;
+                else if (!g_ascii_strcasecmp(type, "toolbar"))
+                    settings->type = OB_CLIENT_TYPE_TOOLBAR;
+                else if (!g_ascii_strcasecmp(type, "dock"))
+                    settings->type = OB_CLIENT_TYPE_DOCK;
+                else if (!g_ascii_strcasecmp(type, "desktop"))
+                    settings->type = OB_CLIENT_TYPE_DESKTOP;
+            }
+
             if (parse_attr_string("role", app, &role))
                 settings->role = g_pattern_spec_new(role);
 
@@ -675,6 +697,11 @@ static void parse_resize(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
                 if ((n2 = parse_find_node("y", n->children)))
                     config_parse_gravity_coord(doc, n2,
                                                &config_resize_popup_fixed.y);
+
+                config_resize_popup_fixed.x.pos =
+                    MAX(config_resize_popup_fixed.x.pos, 0);
+                config_resize_popup_fixed.y.pos =
+                    MAX(config_resize_popup_fixed.y.pos, 0);
             }
         }
     }
index 3fd1b87..62b9247 100644 (file)
@@ -23,6 +23,7 @@
 #include "misc.h"
 #include "stacking.h"
 #include "place.h"
+#include "client.h"
 #include "geom.h"
 #include "moveresize.h"
 #include "render/render.h"
@@ -38,6 +39,7 @@ struct _ObAppSettings
     GPatternSpec *class;
     GPatternSpec *name;
     GPatternSpec *role;
+    ObClientType  type;
 
     GravityPoint position;
     gboolean pos_given;
index 7e2766f..ea24971 100644 (file)
@@ -31,6 +31,7 @@
 #include "frame.h"
 #include "grab.h"
 #include "menu.h"
+#include "prompt.h"
 #include "menuframe.h"
 #include "keyboard.h"
 #include "modkeys.h"
@@ -88,6 +89,7 @@ static void event_process(const XEvent *e, gpointer data);
 static void event_handle_root(XEvent *e);
 static gboolean event_handle_menu_keyboard(XEvent *e);
 static gboolean event_handle_menu(XEvent *e);
+static gboolean event_handle_prompt(ObPrompt *p, XEvent *e);
 static void event_handle_dock(ObDock *s, XEvent *e);
 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
 static void event_handle_client(ObClient *c, XEvent *e);
@@ -459,6 +461,7 @@ static void event_process(const XEvent *ec, gpointer data)
     ObWindow *obwin = NULL;
     XEvent ee, *e;
     ObEventData *ed = data;
+    ObPrompt *prompt = NULL;
 
     /* make a copy we can mangle */
     ee = *ec;
@@ -475,6 +478,8 @@ static void event_process(const XEvent *ec, gpointer data)
             break;
         case Window_Client:
             client = WINDOW_AS_CLIENT(obwin);
+            /* events on clients can be events on prompt windows too */
+            prompt = client->prompt;
             break;
         case Window_Menu:
             /* not to be used for events */
@@ -483,6 +488,9 @@ static void event_process(const XEvent *ec, gpointer data)
         case Window_Internal:
             /* we don't do anything with events directly on these windows */
             break;
+        case Window_Prompt:
+            prompt = WINDOW_AS_PROMPT(obwin);
+            break;
         }
     }
 
@@ -639,7 +647,7 @@ static void event_process(const XEvent *ec, gpointer data)
     else if (window == RootWindow(ob_display, ob_screen))
         event_handle_root(e);
     else if (e->type == MapRequest)
-        client_manage(window);
+        client_manage(window, NULL);
     else if (e->type == MappingNotify) {
         /* keyboard layout changes for modifier mapping changes. reload the
            modifier map, and rebind all the key bindings as appropriate */
@@ -699,7 +707,9 @@ static void event_process(const XEvent *ec, gpointer data)
     }
 #endif
 
-    if (e->type == ButtonPress || e->type == ButtonRelease) {
+    if (prompt && event_handle_prompt(prompt, e))
+        ;
+    else if (e->type == ButtonPress || e->type == ButtonRelease) {
         /* If the button press was on some non-root window, or was physically
            on the root window, then process it */
         if (window != RootWindow(ob_display, ob_screen) ||
@@ -1355,7 +1365,7 @@ static void event_handle_client(ObClient *client, XEvent *e)
                 ob_debug_type(OB_DEBUG_APP_BUGS,
                               "_NET_ACTIVE_WINDOW message for window %s is "
                               "missing source indication\n");
-            client_activate(client, FALSE, TRUE, TRUE,
+            client_activate(client, TRUE, TRUE, TRUE,
                             (e->xclient.data.l[0] == 0 ||
                              e->xclient.data.l[0] == 2));
         } else if (msgtype == prop_atoms.net_wm_moveresize) {
@@ -1667,6 +1677,21 @@ static ObMenuFrame* find_active_or_last_menu(void)
     return ret;
 }
 
+static gboolean event_handle_prompt(ObPrompt *p, XEvent *e)
+{
+    switch (e->type) {
+    case ButtonPress:
+    case ButtonRelease:
+    case MotionNotify:
+        return prompt_mouse_event(p, e);
+        break;
+    case KeyPress:
+        return prompt_key_event(p, e);
+        break;
+    }
+    return FALSE;
+}
+
 static gboolean event_handle_menu_keyboard(XEvent *ev)
 {
     guint keycode, state;
index 7c5d529..79de673 100644 (file)
@@ -59,23 +59,23 @@ void focus_cycle_indicator_startup(gboolean reconfig)
 
     if (reconfig) return;
 
-    focus_indicator.top.obwin.type = Window_Internal;
-    focus_indicator.left.obwin.type = Window_Internal;
-    focus_indicator.right.obwin.type = Window_Internal;
-    focus_indicator.bottom.obwin.type = Window_Internal;
+    focus_indicator.top.type = Window_Internal;
+    focus_indicator.left.type = Window_Internal;
+    focus_indicator.right.type = Window_Internal;
+    focus_indicator.bottom.type = Window_Internal;
 
     attr.override_redirect = True;
     attr.background_pixel = BlackPixel(ob_display, ob_screen);
-    focus_indicator.top.win =
+    focus_indicator.top.window =
         create_window(RootWindow(ob_display, ob_screen),
                       CWOverrideRedirect | CWBackPixel, &attr);
-    focus_indicator.left.win =
+    focus_indicator.left.window =
         create_window(RootWindow(ob_display, ob_screen),
                       CWOverrideRedirect | CWBackPixel, &attr);
-    focus_indicator.right.win =
+    focus_indicator.right.window =
         create_window(RootWindow(ob_display, ob_screen),
                       CWOverrideRedirect | CWBackPixel, &attr);
-    focus_indicator.bottom.win =
+    focus_indicator.bottom.window =
         create_window(RootWindow(ob_display, ob_screen),
                       CWOverrideRedirect | CWBackPixel, &attr);
 
@@ -83,13 +83,13 @@ void focus_cycle_indicator_startup(gboolean reconfig)
     stacking_add(INTERNAL_AS_WINDOW(&focus_indicator.left));
     stacking_add(INTERNAL_AS_WINDOW(&focus_indicator.right));
     stacking_add(INTERNAL_AS_WINDOW(&focus_indicator.bottom));
-    g_hash_table_insert(window_map, &focus_indicator.top.win,
+    g_hash_table_insert(window_map, &focus_indicator.top.window,
                         &focus_indicator.top);
-    g_hash_table_insert(window_map, &focus_indicator.left.win,
+    g_hash_table_insert(window_map, &focus_indicator.left.window,
                         &focus_indicator.left);
-    g_hash_table_insert(window_map, &focus_indicator.right.win,
+    g_hash_table_insert(window_map, &focus_indicator.right.window,
                         &focus_indicator.right);
-    g_hash_table_insert(window_map, &focus_indicator.bottom.win,
+    g_hash_table_insert(window_map, &focus_indicator.bottom.window,
                         &focus_indicator.bottom);
 
     color_white = RrColorNew(ob_rr_inst, 0xff, 0xff, 0xff);
@@ -117,20 +117,20 @@ void focus_cycle_indicator_shutdown(gboolean reconfig)
 
     RrAppearanceFree(a_focus_indicator);
 
-    g_hash_table_remove(window_map, &focus_indicator.top.win);
-    g_hash_table_remove(window_map, &focus_indicator.left.win);
-    g_hash_table_remove(window_map, &focus_indicator.right.win);
-    g_hash_table_remove(window_map, &focus_indicator.bottom.win);
+    g_hash_table_remove(window_map, &focus_indicator.top.window);
+    g_hash_table_remove(window_map, &focus_indicator.left.window);
+    g_hash_table_remove(window_map, &focus_indicator.right.window);
+    g_hash_table_remove(window_map, &focus_indicator.bottom.window);
 
     stacking_remove(INTERNAL_AS_WINDOW(&focus_indicator.top));
     stacking_remove(INTERNAL_AS_WINDOW(&focus_indicator.left));
     stacking_remove(INTERNAL_AS_WINDOW(&focus_indicator.right));
     stacking_remove(INTERNAL_AS_WINDOW(&focus_indicator.bottom));
 
-    XDestroyWindow(ob_display, focus_indicator.top.win);
-    XDestroyWindow(ob_display, focus_indicator.left.win);
-    XDestroyWindow(ob_display, focus_indicator.right.win);
-    XDestroyWindow(ob_display, focus_indicator.bottom.win);
+    XDestroyWindow(ob_display, focus_indicator.top.window);
+    XDestroyWindow(ob_display, focus_indicator.left.window);
+    XDestroyWindow(ob_display, focus_indicator.right.window);
+    XDestroyWindow(ob_display, focus_indicator.bottom.window);
 }
 
 void focus_cycle_draw_indicator(ObClient *c)
@@ -141,10 +141,10 @@ void focus_cycle_draw_indicator(ObClient *c)
         /* kill enter events cause by this unmapping */
         ignore_start = event_start_ignore_all_enters();
 
-        XUnmapWindow(ob_display, focus_indicator.top.win);
-        XUnmapWindow(ob_display, focus_indicator.left.win);
-        XUnmapWindow(ob_display, focus_indicator.right.win);
-        XUnmapWindow(ob_display, focus_indicator.bottom.win);
+        XUnmapWindow(ob_display, focus_indicator.top.window);
+        XUnmapWindow(ob_display, focus_indicator.left.window);
+        XUnmapWindow(ob_display, focus_indicator.right.window);
+        XUnmapWindow(ob_display, focus_indicator.bottom.window);
 
         event_end_ignore_all_enters(ignore_start);
 
@@ -170,7 +170,7 @@ void focus_cycle_draw_indicator(ObClient *c)
         /* kill enter events cause by this moving */
         ignore_start = event_start_ignore_all_enters();
 
-        XMoveResizeWindow(ob_display, focus_indicator.top.win,
+        XMoveResizeWindow(ob_display, focus_indicator.top.window,
                           x, y, w, h);
         a_focus_indicator->texture[0].data.lineart.x1 = 0;
         a_focus_indicator->texture[0].data.lineart.y1 = h-1;
@@ -188,7 +188,7 @@ void focus_cycle_draw_indicator(ObClient *c)
         a_focus_indicator->texture[3].data.lineart.y1 = h-1;
         a_focus_indicator->texture[3].data.lineart.x2 = w - wr;
         a_focus_indicator->texture[3].data.lineart.y2 = h-1;
-        RrPaint(a_focus_indicator, focus_indicator.top.win,
+        RrPaint(a_focus_indicator, focus_indicator.top.window,
                 w, h);
 
         x = c->frame->area.x;
@@ -196,7 +196,7 @@ void focus_cycle_draw_indicator(ObClient *c)
         w = wl;
         h = c->frame->area.height;
 
-        XMoveResizeWindow(ob_display, focus_indicator.left.win,
+        XMoveResizeWindow(ob_display, focus_indicator.left.window,
                           x, y, w, h);
         a_focus_indicator->texture[0].data.lineart.x1 = w-1;
         a_focus_indicator->texture[0].data.lineart.y1 = 0;
@@ -214,7 +214,7 @@ void focus_cycle_draw_indicator(ObClient *c)
         a_focus_indicator->texture[3].data.lineart.y1 = wt-1;
         a_focus_indicator->texture[3].data.lineart.x2 = w-1;
         a_focus_indicator->texture[3].data.lineart.y2 = h - wb;
-        RrPaint(a_focus_indicator, focus_indicator.left.win,
+        RrPaint(a_focus_indicator, focus_indicator.left.window,
                 w, h);
 
         x = c->frame->area.x + c->frame->area.width - wr;
@@ -222,7 +222,7 @@ void focus_cycle_draw_indicator(ObClient *c)
         w = wr;
         h = c->frame->area.height ;
 
-        XMoveResizeWindow(ob_display, focus_indicator.right.win,
+        XMoveResizeWindow(ob_display, focus_indicator.right.window,
                           x, y, w, h);
         a_focus_indicator->texture[0].data.lineart.x1 = 0;
         a_focus_indicator->texture[0].data.lineart.y1 = 0;
@@ -240,7 +240,7 @@ void focus_cycle_draw_indicator(ObClient *c)
         a_focus_indicator->texture[3].data.lineart.y1 = wt-1;
         a_focus_indicator->texture[3].data.lineart.x2 = 0;
         a_focus_indicator->texture[3].data.lineart.y2 = h - wb;
-        RrPaint(a_focus_indicator, focus_indicator.right.win,
+        RrPaint(a_focus_indicator, focus_indicator.right.window,
                 w, h);
 
         x = c->frame->area.x;
@@ -248,7 +248,7 @@ void focus_cycle_draw_indicator(ObClient *c)
         w = c->frame->area.width;
         h = wb;
 
-        XMoveResizeWindow(ob_display, focus_indicator.bottom.win,
+        XMoveResizeWindow(ob_display, focus_indicator.bottom.window,
                           x, y, w, h);
         a_focus_indicator->texture[0].data.lineart.x1 = 0;
         a_focus_indicator->texture[0].data.lineart.y1 = 0;
@@ -266,13 +266,13 @@ void focus_cycle_draw_indicator(ObClient *c)
         a_focus_indicator->texture[3].data.lineart.y1 = 0;
         a_focus_indicator->texture[3].data.lineart.x2 = w - wr;
         a_focus_indicator->texture[3].data.lineart.y2 = 0;
-        RrPaint(a_focus_indicator, focus_indicator.bottom.win,
+        RrPaint(a_focus_indicator, focus_indicator.bottom.window,
                 w, h);
 
-        XMapWindow(ob_display, focus_indicator.top.win);
-        XMapWindow(ob_display, focus_indicator.left.win);
-        XMapWindow(ob_display, focus_indicator.right.win);
-        XMapWindow(ob_display, focus_indicator.bottom.win);
+        XMapWindow(ob_display, focus_indicator.top.window);
+        XMapWindow(ob_display, focus_indicator.left.window);
+        XMapWindow(ob_display, focus_indicator.right.window);
+        XMapWindow(ob_display, focus_indicator.bottom.window);
 
         event_end_ignore_all_enters(ignore_start);
 
index f0cf25e..df3558d 100644 (file)
@@ -42,6 +42,7 @@ typedef struct _ObFocusCyclePopupTarget ObFocusCyclePopupTarget;
 struct _ObFocusCyclePopupTarget
 {
     ObClient *client;
+    RrImage *icon;
     gchar *text;
     Window win;
 };
@@ -106,7 +107,8 @@ void focus_cycle_popup_startup(gboolean reconfig)
     popup.a_text->surface.parent = popup.a_bg;
     popup.a_icon->surface.parent = popup.a_bg;
 
-    popup.a_icon->texture[0].type = RR_TEXTURE_RGBA;
+    RrAppearanceClearTextures(popup.a_icon);
+    popup.a_icon->texture[0].type = RR_TEXTURE_IMAGE;
 
     RrAppearanceAddTextures(popup.a_bg, 1);
     popup.a_bg->texture[0].type = RR_TEXTURE_RGBA;
@@ -141,8 +143,10 @@ void focus_cycle_popup_shutdown(gboolean reconfig)
     while(popup.targets) {
         ObFocusCyclePopupTarget *t = popup.targets->data;
 
+        RrImageUnref(t->icon);
         g_free(t->text);
         XDestroyWindow(ob_display, t->win);
+        g_free(t);
 
         popup.targets = g_list_delete_link(popup.targets, popup.targets);
     }
@@ -196,6 +200,8 @@ static void popup_setup(ObFocusCyclePopup *p, gboolean create_targets,
 
                 t->client = ft;
                 t->text = text;
+                t->icon = client_icon(t->client);
+                RrImageRef(t->icon); /* own the icon so it won't go away */
                 t->win = create_window(p->bg, 0, 0, NULL);
 
                 XMapWindow(ob_display, t->win);
@@ -399,7 +405,6 @@ static void popup_render(ObFocusCyclePopup *p, const ObClient *c)
         /* have to redraw the targetted icon and last targetted icon,
            they can pick up the hilite changes in the backgroud */
         if (!p->mapped || newtarget == target || p->last_target == target) {
-            const ObClientIcon *icon;
             const gint row = i / icons_per_row; /* starting from 0 */
             const gint col = i % icons_per_row; /* starting from 0 */
             gint innerx, innery;
@@ -415,12 +420,9 @@ static void popup_render(ObFocusCyclePopup *p, const ObClient *c)
                               innerx, innery, innerw, innerh);
 
             /* get the icon from the client */
-            icon = client_icon(target->client, innerw, innerh);
-            p->a_icon->texture[0].data.rgba.width = icon->width;
-            p->a_icon->texture[0].data.rgba.height = icon->height;
-            p->a_icon->texture[0].data.rgba.alpha =
+            p->a_icon->texture[0].data.image.alpha =
                 target->client->iconic ? OB_ICONIC_ALPHA : 0xff;
-            p->a_icon->texture[0].data.rgba.data = icon->data;
+            p->a_icon->texture[0].data.image.image = target->icon;
 
             /* draw the icon */
             p->a_icon->surface.parentx = innerx;
@@ -479,6 +481,7 @@ void focus_cycle_popup_hide(void)
     while(popup.targets) {
         ObFocusCyclePopupTarget *t = popup.targets->data;
 
+        RrImageUnref(t->icon);
         g_free(t->text);
         XDestroyWindow(ob_display, t->win);
         g_free(t);
@@ -522,7 +525,7 @@ void focus_cycle_popup_single_show(struct _ObClient *c,
     }
 
     text = popup_get_name(c);
-    icon_popup_show(single_popup, text, client_icon(c, ICON_SIZE, ICON_SIZE));
+    icon_popup_show(single_popup, text, client_icon(c));
     g_free(text);
     screen_hide_desktop_popup();
 }
index e4db2a0..4720d76 100644 (file)
@@ -354,21 +354,21 @@ static void framerender_label(ObFrame *self, RrAppearance *a)
 
 static void framerender_icon(ObFrame *self, RrAppearance *a)
 {
-    const ObClientIcon *icon;
+    RrImage *icon;
 
     if (!self->icon_on) return;
 
-    icon = client_icon(self->client,
-                       ob_rr_theme->button_size + 2,
-                       ob_rr_theme->button_size + 2);
+    icon = client_icon(self->client);
+
     if (icon) {
-        a->texture[0].type = RR_TEXTURE_RGBA;
-        a->texture[0].data.rgba.width = icon->width;
-        a->texture[0].data.rgba.height = icon->height;
-        a->texture[0].data.rgba.alpha = 0xff;
-        a->texture[0].data.rgba.data = icon->data;
-    } else
+        RrAppearanceClearTextures(a);
+        a->texture[0].type = RR_TEXTURE_IMAGE;
+        a->texture[0].data.image.alpha = 0xff;
+        a->texture[0].data.image.image = icon;
+    } else {
+        RrAppearanceClearTextures(a);
         a->texture[0].type = RR_TEXTURE_NONE;
+    }
 
     RrPaint(a, self->icon,
             ob_rr_theme->button_size + 2, ob_rr_theme->button_size + 2);
index bdcd3c5..7c5ee32 100644 (file)
@@ -23,7 +23,7 @@
 #include <glib.h>
 
 typedef struct _GravityCoord {
-    int pos;
+    gint pos;
     gboolean center;
     gboolean opposite;
 } GravityCoord;
index d9426e9..f2458fa 100644 (file)
@@ -504,6 +504,7 @@ void menu_entry_unref(ObMenuEntry *self)
     if (self && --self->ref == 0) {
         switch (self->type) {
         case OB_MENU_ENTRY_TYPE_NORMAL:
+            RrImageUnref(self->data.normal.icon);
             g_free(self->data.normal.label);
             while (self->data.normal.actions) {
                 actions_act_unref(self->data.normal.actions->data);
index 8832a12..7e11545 100644 (file)
@@ -116,11 +116,9 @@ struct _ObNormalMenuEntry {
     /* List of ObActions */
     GSList *actions;
 
-    /* Icon shit */
-    gint icon_width;
-    gint icon_height;
-    gint icon_alpha;
-    RrPixel32 *icon_data;
+    /* Icon stuff.  If you set this, make sure you RrImageRef() it too. */
+    RrImage *icon;
+    gint     icon_alpha;
 
     /* Mask icon */
     RrPixmapMask *mask;
index fb9b6c5..ce77fc0 100644 (file)
@@ -249,23 +249,24 @@ static void menu_frame_place_topmenu(ObMenuFrame *self, gint *x, gint *y)
 
 static void menu_frame_place_submenu(ObMenuFrame *self, gint *x, gint *y)
 {
-    gint overlap;
+    gint overlapx, overlapy;
     gint bwidth;
 
-    overlap = ob_rr_theme->menu_overlap;
+    overlapx = ob_rr_theme->menu_overlap_x;
+    overlapy = ob_rr_theme->menu_overlap_y;
     bwidth = ob_rr_theme->mbwidth;
 
     if (self->direction_right)
         *x = self->parent->area.x + self->parent->area.width -
-            overlap - bwidth;
+            overlapx - bwidth;
     else
-        *x = self->parent->area.x - self->area.width + overlap + bwidth;
+        *x = self->parent->area.x - self->area.width + overlapx + bwidth;
 
     *y = self->parent->area.y + self->parent_entry->area.y;
     if (config_menu_middle)
         *y -= (self->area.height - (bwidth * 2) - ITEM_HEIGHT) / 2;
     else
-        *y += overlap;
+        *y += overlapy;
 }
 
 void menu_frame_move_on_screen(ObMenuFrame *self, gint x, gint y,
@@ -333,6 +334,7 @@ static void menu_entry_frame_render(ObMenuEntryFrame *self)
     default:
         g_assert_not_reached();
     }
+
     RECT_SET_SIZE(self->area, self->frame->inner_w, th);
     XResizeWindow(ob_display, self->window,
                   self->area.width, self->area.height);
@@ -437,6 +439,7 @@ static void menu_entry_frame_render(ObMenuEntryFrame *self)
                               self->area.width - 2*PADDING, SEPARATOR_HEIGHT);
 
             clear = ob_rr_theme->a_clear_tex;
+            RrAppearanceClearTextures(clear);
             clear->texture[0].type = RR_TEXTURE_LINE_ART;
             clear->surface.parent = item_a;
             clear->surface.parentx = PADDING;
@@ -454,7 +457,7 @@ static void menu_entry_frame_render(ObMenuEntryFrame *self)
     }
 
     if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
-        self->entry->data.normal.icon_data)
+        self->entry->data.normal.icon)
     {
         RrAppearance *clear;
 
@@ -466,15 +469,12 @@ static void menu_entry_frame_render(ObMenuEntryFrame *self)
                           - frame->item_margin.bottom);
 
         clear = ob_rr_theme->a_clear_tex;
-        clear->texture[0].type = RR_TEXTURE_RGBA;
-        clear->texture[0].data.rgba.width =
-            self->entry->data.normal.icon_width;
-        clear->texture[0].data.rgba.height =
-            self->entry->data.normal.icon_height;
-        clear->texture[0].data.rgba.alpha =
+        RrAppearanceClearTextures(clear);
+        clear->texture[0].type = RR_TEXTURE_IMAGE;
+        clear->texture[0].data.image.image =
+            self->entry->data.normal.icon;
+        clear->texture[0].data.image.alpha =
             self->entry->data.normal.icon_alpha;
-        clear->texture[0].data.rgba.data =
-            self->entry->data.normal.icon_data;
         clear->surface.parent = item_a;
         clear->surface.parentx = PADDING;
         clear->surface.parenty = frame->item_margin.top;
@@ -498,6 +498,7 @@ static void menu_entry_frame_render(ObMenuEntryFrame *self)
                           - frame->item_margin.bottom);
 
         clear = ob_rr_theme->a_clear_tex;
+        RrAppearanceClearTextures(clear);
         clear->texture[0].type = RR_TEXTURE_MASK;
         clear->texture[0].data.mask.mask =
             self->entry->data.normal.mask;
@@ -689,7 +690,7 @@ void menu_frame_render(ObMenuFrame *self)
             tw = MIN(tw, MAX_MENU_WIDTH);
             th = ob_rr_theme->menu_font_height;
 
-            if (e->entry->data.normal.icon_data ||
+            if (e->entry->data.normal.icon ||
                 e->entry->data.normal.mask)
                 has_icon = TRUE;
             break;
@@ -700,7 +701,7 @@ void menu_frame_render(ObMenuFrame *self)
             tw = MIN(tw, MAX_MENU_WIDTH);
             th = ob_rr_theme->menu_font_height;
 
-            if (e->entry->data.normal.icon_data ||
+            if (e->entry->data.normal.icon ||
                 e->entry->data.normal.mask)
                 has_icon = TRUE;
 
index 2b5584d..c73c926 100644 (file)
@@ -51,6 +51,8 @@ typedef enum
     OB_KEY_RIGHT,
     OB_KEY_UP,
     OB_KEY_DOWN,
+    OB_KEY_TAB,
+    OB_KEY_SPACE,
     OB_NUM_KEYS
 } ObKey;
 
index 711317e..f2e13cd 100644 (file)
@@ -46,6 +46,9 @@ typedef struct {
 
 /* Array of GSList*s of ObMouseBinding*s. */
 static GSList *bound_contexts[OB_FRAME_NUM_CONTEXTS];
+/* TRUE when we have a grab on the pointer and need to replay the pointer event
+   to send it to other applications */
+static gboolean replay_pointer_needed;
 
 ObFrameContext mouse_button_frame_context(ObFrameContext context,
                                           guint button,
@@ -200,6 +203,15 @@ static gboolean fire_binding(ObMouseAction a, ObFrameContext context,
     return TRUE;
 }
 
+void mouse_replay_pointer()
+{
+    if (replay_pointer_needed) {
+        /* replay the pointer event before any windows move */
+        XAllowEvents(ob_display, ReplayPointer, event_curtime);
+        replay_pointer_needed = FALSE;
+    }
+}
+
 void mouse_event(ObClient *client, XEvent *e)
 {
     static Time ltime;
@@ -229,12 +241,17 @@ void mouse_event(ObClient *client, XEvent *e)
            XAllowEvents with ReplayPointer at some point, to send the event
            through to the client.  when this happens though depends.  if
            windows are going to be moved on screen, then the click will end
-           up going somewhere wrong, so have the action system perform the
-           ReplayPointer for us if that is the case. */
+           up going somewhere wrong, set that we need it, and if nothing
+           else causes the replay pointer to be run, then we will do it
+           after all the actions are finished.
+
+           (We do it after all the actions because FocusIn interrupts
+           dragging for kdesktop, so if we send the button event now, and
+           then they get a focus event after, it breaks.  Instead, wait to send
+           the button press until after the actions when possible.)
+        */
         if (CLIENT_CONTEXT(context, client))
-            actions_set_need_pointer_replay_before_move(TRUE);
-        else
-            actions_set_need_pointer_replay_before_move(FALSE);
+            replay_pointer_needed = TRUE;
 
         fire_binding(OB_MOUSE_ACTION_PRESS, context,
                      client, e->xbutton.state,
@@ -248,8 +265,7 @@ void mouse_event(ObClient *client, XEvent *e)
 
         /* replay the pointer event if it hasn't been replayed yet (i.e. no
            windows were moved) */
-        if (actions_get_need_pointer_replay_before_move())
-            XAllowEvents(ob_display, ReplayPointer, event_curtime);
+        mouse_replay_pointer();
 
         /* in the client context, we won't get a button release because of the
            way it is grabbed, so just fake one */
index 44d563a..a862fe5 100644 (file)
@@ -40,4 +40,9 @@ void mouse_grab_for_client(struct _ObClient *client, gboolean grab);
 ObFrameContext mouse_button_frame_context(ObFrameContext context,
                                           guint button, guint state);
 
+/*! If a replay pointer is needed, then do it.  Call this when windows are
+  going to be moving/appearing/disappearing, so that you know the mouse click
+  will go to the right window */
+void mouse_replay_pointer();
+
 #endif
index 12106f6..46da8fe 100644 (file)
@@ -46,6 +46,7 @@
 #include "config.h"
 #include "ping.h"
 #include "mainloop.h"
+#include "prompt.h"
 #include "gettext.h"
 #include "parser/parse.h"
 #include "render/render.h"
 #include <X11/keysym.h>
 
 
-RrInstance *ob_rr_inst;
-RrTheme    *ob_rr_theme;
-ObMainLoop *ob_main_loop;
-Display    *ob_display;
-gint        ob_screen;
-gboolean    ob_replace_wm = FALSE;
-gboolean    ob_sm_use = TRUE;
-gchar      *ob_sm_id = NULL;
-gchar      *ob_sm_save_file = NULL;
-gboolean    ob_sm_restore = TRUE;
-gboolean    ob_debug_xinerama = FALSE;
+RrInstance   *ob_rr_inst;
+RrImageCache *ob_rr_icons;
+RrTheme      *ob_rr_theme;
+ObMainLoop   *ob_main_loop;
+Display      *ob_display;
+gint          ob_screen;
+gboolean      ob_replace_wm = FALSE;
+gboolean      ob_sm_use = TRUE;
+gchar        *ob_sm_id = NULL;
+gchar        *ob_sm_save_file = NULL;
+gboolean      ob_sm_restore = TRUE;
+gboolean      ob_debug_xinerama = FALSE;
 
 static ObState   state;
 static gboolean  xsync = FALSE;
@@ -181,6 +183,11 @@ gint main(gint argc, gchar **argv)
     ob_rr_inst = RrInstanceNew(ob_display, ob_screen);
     if (ob_rr_inst == NULL)
         ob_exit_with_error(_("Failed to initialize the obrender library."));
+    /* Saving 3 resizes of an RrImage makes a lot of sense for icons, as there
+       are generally 3 icon sizes needed: the titlebar icon, the menu icon,
+       and the alt-tab icon
+    */
+    ob_rr_icons = RrImageCacheNew(3);
 
     XSynchronize(ob_display, xsync);
 
@@ -231,6 +238,8 @@ gint main(gint argc, gchar **argv)
             keys[OB_KEY_RIGHT] = modkeys_sym_to_code(XK_Right);
             keys[OB_KEY_UP] = modkeys_sym_to_code(XK_Up);
             keys[OB_KEY_DOWN] = modkeys_sym_to_code(XK_Down);
+            keys[OB_KEY_TAB] = modkeys_sym_to_code(XK_Tab);
+            keys[OB_KEY_SPACE] = modkeys_sym_to_code(XK_space);
 
             {
                 ObParseInst *i;
@@ -314,6 +323,7 @@ gint main(gint argc, gchar **argv)
             grab_startup(reconfigure);
             group_startup(reconfigure);
             ping_startup(reconfigure);
+            prompt_startup(reconfigure);
             client_startup(reconfigure);
             dock_startup(reconfigure);
             moveresize_startup(reconfigure);
@@ -373,6 +383,7 @@ gint main(gint argc, gchar **argv)
             moveresize_shutdown(reconfigure);
             dock_shutdown(reconfigure);
             client_shutdown(reconfigure);
+            prompt_shutdown(reconfigure);
             ping_shutdown(reconfigure);
             group_shutdown(reconfigure);
             grab_shutdown(reconfigure);
@@ -393,6 +404,7 @@ gint main(gint argc, gchar **argv)
     XSync(ob_display, FALSE);
 
     RrThemeFree(ob_rr_theme);
+    RrImageCacheUnref(ob_rr_icons);
     RrInstanceFree(ob_rr_inst);
 
     session_shutdown(being_replaced);
index b768fee..4f2310f 100644 (file)
@@ -30,6 +30,7 @@
 struct _ObMainLoop;
 
 extern RrInstance *ob_rr_inst;
+extern RrImageCache *ob_rr_icons;
 extern RrTheme    *ob_rr_theme;
 
 extern struct _ObMainLoop *ob_main_loop;
index 37b5d30..748c0c8 100644 (file)
@@ -38,7 +38,7 @@ static guint32     ping_next_id = 1;
 
 #define PING_TIMEOUT (G_USEC_PER_SEC * 3)
 /*! Warn the user after this many PING_TIMEOUT intervals */
-#define PING_TIMEOUT_WARN 3
+#define PING_TIMEOUT_WARN 1
 
 static void     ping_send(ObPingTarget *t);
 static void     ping_end(ObClient *client, gpointer data);
@@ -69,11 +69,12 @@ void ping_start(struct _ObClient *client, ObPingEventHandler h)
 {
     ObPingTarget *t;
 
-    /* make sure we're not already pinging the client */
-    g_assert(g_hash_table_find(ping_ids, find_client, client) == NULL);
-
+    /* make sure the client supports ping! */
     g_assert(client->ping == TRUE);
 
+    /* make sure we're not already pinging the client */
+    if (g_hash_table_find(ping_ids, find_client, client) != NULL) return;
+
     t = g_new0(ObPingTarget, 1);
     t->client = client;
     t->h = h;
@@ -89,11 +90,6 @@ void ping_start(struct _ObClient *client, ObPingEventHandler h)
     g_assert(g_hash_table_find(ping_ids, find_client, client) != NULL);
 }
 
-void ping_stop(struct _ObClient *c)
-{
-    ping_end(c, NULL);
-}
-
 void ping_got_pong(guint32 id)
 {
     ObPingTarget *t;
@@ -106,6 +102,9 @@ void ping_got_pong(guint32 id)
             t->h(t->client, FALSE);
         }
         t->waiting = 0; /* not waiting for a reply anymore */
+
+        /* we got a pong so we're happy now */
+        ping_end(t->client, NULL);
     }
     else
         ob_debug("Got PONG with id %u but not waiting for one\n", id);
index 1333ea0..ceb0bdb 100644 (file)
@@ -37,7 +37,6 @@ void ping_startup(gboolean reconfigure);
 void ping_shutdown(gboolean reconfigure);
 
 void ping_start(struct _ObClient *c, ObPingEventHandler h);
-void ping_stop(struct _ObClient *c);
 
 void ping_got_pong(guint32 id);
 
index 15d1bf5..770f33d 100644 (file)
@@ -367,16 +367,17 @@ void icon_popup_free(ObIconPopup *self)
 }
 
 void icon_popup_delay_show(ObIconPopup *self, gulong usec,
-                           gchar *text, const ObClientIcon *icon)
+                           gchar *text, RrImage *icon)
 {
     if (icon) {
-        self->a_icon->texture[0].type = RR_TEXTURE_RGBA;
-        self->a_icon->texture[0].data.rgba.width = icon->width;
-        self->a_icon->texture[0].data.rgba.height = icon->height;
-        self->a_icon->texture[0].data.rgba.alpha = 0xff;
-        self->a_icon->texture[0].data.rgba.data = icon->data;
-    } else
+        RrAppearanceClearTextures(self->a_icon);
+        self->a_icon->texture[0].type = RR_TEXTURE_IMAGE;
+        self->a_icon->texture[0].data.image.alpha = 0xff;
+        self->a_icon->texture[0].data.image.image = icon;
+    } else {
+        RrAppearanceClearTextures(self->a_icon);
         self->a_icon->texture[0].type = RR_TEXTURE_NONE;
+    }
 
     popup_delay_show(self->popup, usec, text);
 }
index 0072f53..2b01ce2 100644 (file)
@@ -110,7 +110,7 @@ void icon_popup_free(ObIconPopup *self);
 
 #define icon_popup_show(s, t, i) icon_popup_delay_show((s),0,(t),(i))
 void icon_popup_delay_show(ObIconPopup *self, gulong usec,
-                           gchar *text, const struct _ObClientIcon *icon);
+                           gchar *text, RrImage *icon);
 #define icon_popup_hide(p) popup_hide((p)->popup)
 #define icon_popup_position(p, g, x, y) popup_position((p)->popup,(g),(x),(y))
 #define icon_popup_text_width(p, w) popup_text_width((p)->popup,(w))
diff --git a/openbox/prompt.c b/openbox/prompt.c
new file mode 100644 (file)
index 0000000..b964de2
--- /dev/null
@@ -0,0 +1,585 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+   prompt.c for the Openbox window manager
+   Copyright (c) 2008        Dana Jansens
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   See the COPYING file for a copy of the GNU General Public License.
+*/
+
+#include "prompt.h"
+#include "openbox.h"
+#include "screen.h"
+#include "openbox.h"
+#include "client.h"
+#include "prop.h"
+#include "modkeys.h"
+#include "event.h"
+#include "gettext.h"
+
+static GList *prompt_list = NULL;
+
+/* we construct these */
+static RrAppearance *prompt_a_bg;
+static RrAppearance *prompt_a_button;
+static RrAppearance *prompt_a_focus;
+static RrAppearance *prompt_a_press;
+static RrAppearance *prompt_a_pfocus;
+/* we change the max width which would screw with others */
+static RrAppearance *prompt_a_msg;
+
+/* sizing stuff */
+#define OUTSIDE_MARGIN 4
+#define MSG_BUTTON_SEPARATION 4
+#define BUTTON_SEPARATION 4
+#define BUTTON_VMARGIN 4
+#define BUTTON_HMARGIN 12
+#define MAX_WIDTH 400
+
+static void prompt_layout(ObPrompt *self);
+static void render_all(ObPrompt *self);
+static void render_button(ObPrompt *self, ObPromptElement *e);
+static void prompt_resize(ObPrompt *self, gint w, gint h);
+
+void prompt_startup(gboolean reconfig)
+{
+    RrColor *c_button, *c_focus, *c_press, *c_pfocus;
+
+    /* note: this is not a copy, don't free it */
+    prompt_a_bg = ob_rr_theme->osd_hilite_bg;
+
+    prompt_a_button = RrAppearanceCopy(ob_rr_theme->a_focused_unpressed_close);
+    prompt_a_focus = RrAppearanceCopy(ob_rr_theme->a_hover_focused_close);
+    prompt_a_press = RrAppearanceCopy(ob_rr_theme->a_focused_pressed_close);
+    prompt_a_pfocus = RrAppearanceCopy(ob_rr_theme->a_focused_pressed_close);
+
+    c_button = prompt_a_button->texture[0].data.mask.color;
+    c_focus = prompt_a_focus->texture[0].data.mask.color;
+    c_press = prompt_a_press->texture[0].data.mask.color;
+    c_pfocus = prompt_a_press->texture[0].data.mask.color;
+
+    RrAppearanceRemoveTextures(prompt_a_button);
+    RrAppearanceRemoveTextures(prompt_a_focus);
+    RrAppearanceRemoveTextures(prompt_a_press);
+    RrAppearanceRemoveTextures(prompt_a_pfocus);
+
+    /* texture[0] is the text and texture[1-4] (for prompt_a_focus and
+       prompt_a_pfocus) is lineart to show where keyboard focus is */
+    RrAppearanceAddTextures(prompt_a_button, 1);
+    RrAppearanceAddTextures(prompt_a_focus, 5);
+    RrAppearanceAddTextures(prompt_a_press, 1);
+    RrAppearanceAddTextures(prompt_a_pfocus, 5);
+
+    /* totally cheating here.. */
+    prompt_a_button->texture[0] = ob_rr_theme->osd_hilite_label->texture[0];
+    prompt_a_focus->texture[0] = ob_rr_theme->osd_hilite_label->texture[0];
+    prompt_a_press->texture[0] = ob_rr_theme->osd_hilite_label->texture[0];
+    prompt_a_pfocus->texture[0] = ob_rr_theme->osd_hilite_label->texture[0];
+
+    prompt_a_button->texture[0].data.text.justify = RR_JUSTIFY_CENTER;
+    prompt_a_focus->texture[0].data.text.justify = RR_JUSTIFY_CENTER;
+    prompt_a_press->texture[0].data.text.justify = RR_JUSTIFY_CENTER;
+    prompt_a_pfocus->texture[0].data.text.justify = RR_JUSTIFY_CENTER;
+
+    prompt_a_button->texture[0].data.text.color = c_button;
+    prompt_a_focus->texture[0].data.text.color = c_focus;
+    prompt_a_press->texture[0].data.text.color = c_press;
+    prompt_a_pfocus->texture[0].data.text.color = c_press;
+
+    prompt_a_focus->texture[1].data.lineart.color = c_focus;
+    prompt_a_focus->texture[2].data.lineart.color = c_focus;
+    prompt_a_focus->texture[3].data.lineart.color = c_focus;
+    prompt_a_focus->texture[4].data.lineart.color = c_focus;
+
+    prompt_a_pfocus->texture[1].data.lineart.color = c_press;
+    prompt_a_pfocus->texture[2].data.lineart.color = c_press;
+    prompt_a_pfocus->texture[3].data.lineart.color = c_press;
+    prompt_a_pfocus->texture[4].data.lineart.color = c_press;
+
+    prompt_a_msg = RrAppearanceCopy(ob_rr_theme->osd_hilite_label);
+    prompt_a_msg->texture[0].data.text.flow = TRUE;
+
+    if (reconfig) {
+        GList *it;
+        for (it = prompt_list; it; it = g_list_next(it)) {
+            ObPrompt *p = it->data;
+            prompt_layout(p);
+            render_all(p);
+        }
+    }
+}
+
+void prompt_shutdown(gboolean reconfig)
+{
+    RrAppearanceFree(prompt_a_button);
+    RrAppearanceFree(prompt_a_focus);
+    RrAppearanceFree(prompt_a_press);
+    RrAppearanceFree(prompt_a_pfocus);
+    RrAppearanceFree(prompt_a_msg);
+}
+
+ObPrompt* prompt_new(const gchar *msg,
+                     const ObPromptAnswer *answers, gint n_answers,
+                     gint default_result, gint cancel_result,
+                     ObPromptCallback func, gpointer data)
+{
+    ObPrompt *self;
+    XSetWindowAttributes attrib;
+    gint i;
+
+    attrib.override_redirect = FALSE;
+
+    self = g_new0(ObPrompt, 1);
+    self->ref = 1;
+    self->func = func;
+    self->data = data;
+    self->default_result = default_result;
+    self->cancel_result = cancel_result;
+    self->super.type = Window_Prompt;
+    self->super.window = XCreateWindow(ob_display,
+                                       RootWindow(ob_display, ob_screen),
+                                       0, 0, 1, 1, 0,
+                                       CopyFromParent, InputOutput,
+                                       CopyFromParent,
+                                       CWOverrideRedirect,
+                                       &attrib);
+
+    /* make it a dialog type window */
+    PROP_SET32(self->super.window, net_wm_window_type, atom,
+               prop_atoms.net_wm_window_type_dialog);
+
+    /* listen for key presses on the window */
+    self->event_mask = KeyPressMask;
+
+    /* set up the text message widow */
+    self->msg.text = g_strdup(msg);
+    self->msg.window = XCreateWindow(ob_display, self->super.window,
+                                     0, 0, 1, 1, 0,
+                                     CopyFromParent, InputOutput,
+                                     CopyFromParent, 0, NULL);
+    XMapWindow(ob_display, self->msg.window);
+
+    /* set up the buttons from the answers */
+
+    self->n_buttons = n_answers;
+    if (!self->n_buttons)
+        self->n_buttons = 1;
+
+    self->button = g_new0(ObPromptElement, self->n_buttons);
+
+    if (n_answers == 0) {
+        g_assert(self->n_buttons == 1); /* should be set to this above.. */
+        self->button[0].text = g_strdup(_("OK"));
+    }
+    else {
+        g_assert(self->n_buttons > 0);
+        for (i = 0; i < self->n_buttons; ++i) {
+            self->button[i].text = g_strdup(answers[i].text);
+            self->button[i].result = answers[i].result;
+        }
+    }
+
+    for (i = 0; i < self->n_buttons; ++i) {
+        self->button[i].window = XCreateWindow(ob_display, self->super.window,
+                                               0, 0, 1, 1, 0,
+                                               CopyFromParent, InputOutput,
+                                               CopyFromParent, 0, NULL);
+        XMapWindow(ob_display, self->button[i].window);
+        g_hash_table_insert(window_map, &self->button[i].window,
+                            PROMPT_AS_WINDOW(self));
+
+        /* listen for button presses on the buttons */
+        XSelectInput(ob_display, self->button[i].window,
+                     ButtonPressMask | ButtonReleaseMask | ButtonMotionMask);
+    }
+
+    prompt_list = g_list_prepend(prompt_list, self);
+
+    return self;
+}
+
+void prompt_ref(ObPrompt *self)
+{
+    ++self->ref;
+}
+
+void prompt_unref(ObPrompt *self)
+{
+    if (self && --self->ref == 0) {
+        gint i;
+
+        prompt_list = g_list_remove(prompt_list, self);
+
+        for (i = 0; i < self->n_buttons; ++i) {
+            g_hash_table_remove(window_map, &self->button[i].window);
+            XDestroyWindow(ob_display, self->button[i].window);
+        }
+
+        XDestroyWindow(ob_display, self->msg.window);
+        XDestroyWindow(ob_display, self->super.window);
+        g_free(self);
+    }
+}
+
+static void prompt_layout(ObPrompt *self)
+{
+    gint l, r, t, b;
+    gint i;
+    gint allbuttonsw, allbuttonsh, buttonx;
+    gint w, h;
+    gint maxw;
+
+    RrMargins(prompt_a_bg, &l, &t, &r, &b);
+    l += OUTSIDE_MARGIN;
+    t += OUTSIDE_MARGIN;
+    r += OUTSIDE_MARGIN;
+    b += OUTSIDE_MARGIN;
+
+    {
+        Rect *area = screen_physical_area_all_monitors();
+        maxw = MIN(MAX_WIDTH, area->width*4/5);
+        g_free(area);
+    }
+
+    /* find the button sizes and how much space we need for them */
+    allbuttonsw = allbuttonsh = 0;
+    for (i = 0; i < self->n_buttons; ++i) {
+        gint bw, bh;
+
+        prompt_a_button->texture[0].data.text.string = self->button[i].text;
+        prompt_a_focus->texture[0].data.text.string = self->button[i].text;
+        prompt_a_press->texture[0].data.text.string = self->button[i].text;
+        prompt_a_pfocus->texture[0].data.text.string = self->button[i].text;
+        RrMinSize(prompt_a_button, &bw, &bh);
+        self->button[i].width = bw;
+        self->button[i].height = bh;
+        RrMinSize(prompt_a_focus, &bw, &bh);
+        g_print("button w %d h %d\n", bw, bh);
+        self->button[i].width = MAX(self->button[i].width, bw);
+        self->button[i].height = MAX(self->button[i].height, bh);
+        RrMinSize(prompt_a_press, &bw, &bh);
+        self->button[i].width = MAX(self->button[i].width, bw);
+        self->button[i].height = MAX(self->button[i].height, bh);
+        RrMinSize(prompt_a_pfocus, &bw, &bh);
+        self->button[i].width = MAX(self->button[i].width, bw);
+        self->button[i].height = MAX(self->button[i].height, bh);
+
+
+        self->button[i].width += BUTTON_HMARGIN * 2;
+        self->button[i].height += BUTTON_VMARGIN * 2;
+
+        allbuttonsw += self->button[i].width + (i > 0 ? BUTTON_SEPARATION : 0);
+        allbuttonsh = MAX(allbuttonsh, self->button[i].height);
+    }
+
+    self->msg_wbound = MAX(allbuttonsw, maxw);
+
+    /* measure the text message area */
+    prompt_a_msg->texture[0].data.text.string = self->msg.text;
+    prompt_a_msg->texture[0].data.text.maxwidth = self->msg_wbound;
+    RrMinSize(prompt_a_msg, &self->msg.width, &self->msg.height);
+
+    /* width and height inside the outer margins */
+    w = MAX(self->msg.width, allbuttonsw);
+    h = self->msg.height + MSG_BUTTON_SEPARATION + allbuttonsh;
+
+    /* position the text message */
+    self->msg.x = l + (w - self->msg.width) / 2;
+    self->msg.y = t;
+
+    /* position the button buttons on the right of the dialog */
+    buttonx = l + w;
+    for (i = self->n_buttons - 1; i >= 0; --i) {
+        self->button[i].x = buttonx - self->button[i].width;
+        buttonx -= self->button[i].width + BUTTON_SEPARATION;
+        self->button[i].y = t + h - allbuttonsh;
+        self->button[i].y += (allbuttonsh - self->button[i].height) / 2;
+    }
+
+    /* size and position the toplevel window */
+    prompt_resize(self, w + l + r, h + t + b);
+
+    /* move and resize the internal windows */
+    XMoveResizeWindow(ob_display, self->msg.window,
+                      self->msg.x, self->msg.y,
+                      self->msg.width, self->msg.height);
+    for (i = 0; i < self->n_buttons; ++i)
+        XMoveResizeWindow(ob_display, self->button[i].window,
+                          self->button[i].x, self->button[i].y,
+                          self->button[i].width, self->button[i].height);
+}
+
+static void prompt_resize(ObPrompt *self, gint w, gint h)
+{
+    XConfigureRequestEvent req;
+    XSizeHints hints;
+
+    self->width = w;
+    self->height = h;
+
+    /* the user can't resize the prompt */
+    hints.flags = PMinSize | PMaxSize;
+    hints.min_width = hints.max_width = w;
+    hints.min_height = hints.max_height = h;
+    XSetWMNormalHints(ob_display, self->super.window, &hints);
+
+    if (self->mapped) {
+        /* send a configure request like a normal client would */
+        req.type = ConfigureRequest;
+        req.display = ob_display;
+        req.parent = RootWindow(ob_display, ob_screen);
+        req.window = self->super.window;
+        req.width = w;
+        req.height = h;
+        req.value_mask = CWWidth | CWHeight;
+        XSendEvent(req.display, req.window, FALSE, StructureNotifyMask,
+                   (XEvent*)&req);
+    }
+    else
+        XResizeWindow(ob_display, self->super.window, w, h);
+}
+
+static void setup_button_focus_tex(ObPromptElement *e, RrAppearance *a,
+                                   gboolean on)
+{
+    gint l, r, t, b;
+
+    if (!on) {
+        gint i;
+
+        for (i = 1; i < 5; ++i)
+            a->texture[i].type = on ? RR_TEXTURE_LINE_ART : RR_TEXTURE_NONE;
+    }
+
+    if (!on) return;
+
+    RrMargins(a, &l, &t, &r, &b);
+    l += MIN(BUTTON_HMARGIN, BUTTON_VMARGIN) / 2;
+    r += MIN(BUTTON_HMARGIN, BUTTON_VMARGIN) / 2;
+    t += MIN(BUTTON_HMARGIN, BUTTON_VMARGIN) / 2;
+    b += MIN(BUTTON_HMARGIN, BUTTON_VMARGIN) / 2;
+
+    /* top line */
+    a->texture[1].data.lineart.x1 = l;
+    a->texture[1].data.lineart.x2 = e->width - r - 1;
+    a->texture[1].data.lineart.y1 = t;
+    a->texture[1].data.lineart.y2 = t;
+
+    /* bottom line */
+    a->texture[2].data.lineart.x1 = l;
+    a->texture[2].data.lineart.x2 = e->width - r - 1;
+    a->texture[2].data.lineart.y1 = e->height - b - 1;
+    a->texture[2].data.lineart.y2 = e->height - b - 1;
+
+    /* left line */
+    a->texture[3].data.lineart.x1 = l;
+    a->texture[3].data.lineart.x2 = l;
+    a->texture[3].data.lineart.y1 = t;
+    a->texture[3].data.lineart.y2 = e->height - b - 1;
+
+    /* right line */
+    a->texture[4].data.lineart.x1 = e->width - r - 1;
+    a->texture[4].data.lineart.x2 = e->width - r - 1;
+    a->texture[4].data.lineart.y1 = t;
+    a->texture[4].data.lineart.y2 = e->height - b - 1;
+
+    g_print("setting x2 %d\n", e->width - r - 1);
+}
+
+static void render_button(ObPrompt *self, ObPromptElement *e)
+{
+    RrAppearance *a;
+
+    if (e->pressed && self->focus == e) a = prompt_a_pfocus;
+    else if (self->focus == e)          a = prompt_a_focus;
+    else if (e->pressed)                a = prompt_a_press;
+    else                                a = prompt_a_button;
+
+    a->surface.parent = prompt_a_bg;
+    a->surface.parentx = e->x;
+    a->surface.parenty = e->y;
+
+    /* draw the keyfocus line */
+    if (a == prompt_a_pfocus || a == prompt_a_focus)
+        setup_button_focus_tex(e, a, TRUE);
+
+    a->texture[0].data.text.string = e->text;
+    RrPaint(a, e->window, e->width, e->height);
+
+    /* turn off the keyfocus line so that it doesn't affect size calculations
+     */
+    if (a == prompt_a_pfocus || a == prompt_a_focus)
+        setup_button_focus_tex(e, a, FALSE);
+}
+
+static void render_all(ObPrompt *self)
+{
+    gint i;
+
+    RrPaint(prompt_a_bg, self->super.window, self->width, self->height);
+
+    prompt_a_msg->surface.parent = prompt_a_bg;
+    prompt_a_msg->surface.parentx = self->msg.x;
+    prompt_a_msg->surface.parenty = self->msg.y;
+
+    prompt_a_msg->texture[0].data.text.string = self->msg.text;
+    prompt_a_msg->texture[0].data.text.maxwidth = self->msg_wbound;
+    RrPaint(prompt_a_msg, self->msg.window, self->msg.width, self->msg.height);
+
+    for (i = 0; i < self->n_buttons; ++i)
+        render_button(self, &self->button[i]);
+}
+
+void prompt_show(ObPrompt *self, ObClient *parent)
+{
+    gint i;
+
+    if (self->mapped) {
+        /* activate the prompt */
+        PROP_MSG(self->super.window, net_active_window,
+                 1, /* from an application.. */
+                 event_curtime,
+                 0,
+                 0);
+        return;
+    }
+
+    /* set the focused button (if not found then the first button is used) */
+    self->focus = &self->button[0];
+    for (i = 0; i < self->n_buttons; ++i)
+        if (self->button[i].result == self->default_result) {
+            self->focus = &self->button[i];
+            break;
+        }
+
+    XSetTransientForHint(ob_display, self->super.window,
+                         (parent ? parent->window : 0));
+
+    /* set up the dialog and render it */
+    prompt_layout(self);
+    render_all(self);
+
+    client_manage(self->super.window, self);
+
+    self->mapped = TRUE;
+}
+
+void prompt_hide(ObPrompt *self)
+{
+    XUnmapWindow(ob_display, self->super.window);
+    self->mapped = FALSE;
+}
+
+gboolean prompt_key_event(ObPrompt *self, XEvent *e)
+{
+    gboolean shift;
+    guint shift_mask;
+
+    if (e->type != KeyPress) return FALSE;
+
+    g_print("key 0x%x 0x%x\n", e->xkey.keycode, ob_keycode(OB_KEY_TAB));
+
+    shift_mask = modkeys_key_to_mask(OB_MODKEY_KEY_SHIFT);
+    shift = !!(e->xkey.state & shift_mask);
+
+    /* only accept shift */
+    if (e->xkey.state != 0 && e->xkey.state != shift_mask)
+        return FALSE;
+
+    if (e->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
+        prompt_cancel(self);
+    else if (e->xkey.keycode == ob_keycode(OB_KEY_RETURN) ||
+             e->xkey.keycode == ob_keycode(OB_KEY_SPACE))
+    {
+        if (self->func) self->func(self, self->focus->result, self->data);
+        prompt_hide(self);
+    }
+    else if (e->xkey.keycode == ob_keycode(OB_KEY_TAB) ||
+             e->xkey.keycode == ob_keycode(OB_KEY_LEFT) ||
+             e->xkey.keycode == ob_keycode(OB_KEY_RIGHT))
+    {
+        gint i;
+        gboolean left;
+        ObPromptElement *oldfocus;
+
+        left = e->xkey.keycode == ob_keycode(OB_KEY_LEFT) ||
+            (e->xkey.keycode == ob_keycode(OB_KEY_TAB) && shift);
+        oldfocus = self->focus;
+
+        for (i = 0; i < self->n_buttons; ++i)
+            if (self->focus == &self->button[i]) break;
+        i += (left ? -1 : 1);
+        if (i < 0) i = self->n_buttons - 1;
+        else if (i >= self->n_buttons) i = 0;
+        self->focus = &self->button[i];
+
+        if (oldfocus != self->focus) render_button(self, oldfocus);
+        render_button(self, self->focus);
+    }
+    return TRUE;
+}
+
+gboolean prompt_mouse_event(ObPrompt *self, XEvent *e)
+{
+    gint i;
+    ObPromptElement *but;
+
+    if (e->type != ButtonPress && e->type != ButtonRelease &&
+        e->type != MotionNotify) return FALSE;
+
+    /* find the button */
+    but = NULL;
+    for (i = 0; i < self->n_buttons; ++i)
+        if (self->button[i].window ==
+            (e->type == MotionNotify ? e->xmotion.window : e->xbutton.window))
+        {
+            but = &self->button[i];
+            break;
+        }
+    if (!but) return FALSE;
+
+    if (e->type == ButtonPress) {
+        ObPromptElement *oldfocus;
+
+        oldfocus = self->focus;
+
+        but->pressed = TRUE;
+        self->focus = but;
+
+        if (oldfocus != but) render_button(self, oldfocus);
+        render_button(self, but);
+    }
+    else if (e->type == ButtonRelease) {
+        if (but->pressed) {
+            if (self->func) self->func(self, but->result, self->data);
+            prompt_hide(self);
+        }
+    }
+    else if (e->type == MotionNotify) {
+        gboolean press;
+
+        press = (e->xmotion.x >= 0 && e->xmotion.y >= 0 &&
+                 e->xmotion.x < but->width && e->xmotion.y < but->height);
+
+        if (press != but->pressed) {
+            but->pressed = press;
+            render_button(self, but);
+        }
+    }
+    return TRUE;
+}
+
+void prompt_cancel(ObPrompt *self)
+{
+    if (self->func) self->func(self, self->cancel_result, self->data);
+    prompt_hide(self);
+}
diff --git a/openbox/prompt.h b/openbox/prompt.h
new file mode 100644 (file)
index 0000000..c24f044
--- /dev/null
@@ -0,0 +1,110 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+   prompt.h for the Openbox window manager
+   Copyright (c) 2008        Dana Jansens
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   See the COPYING file for a copy of the GNU General Public License.
+*/
+
+#ifndef ob__prompt_h
+#define ob__prompt_h
+
+#include "window.h"
+#include "geom.h"
+#include "render/render.h"
+#include <glib.h>
+#include <X11/Xlib.h>
+
+typedef struct _ObPrompt       ObPrompt;
+typedef struct _ObPromptElement ObPromptElement;
+typedef struct _ObPromptAnswer ObPromptAnswer;
+
+typedef void (*ObPromptCallback)(ObPrompt *p, gint result, gpointer data);
+
+struct _ObPromptElement {
+    gchar *text;
+    Window window;
+
+    gint x, y, width, height;
+    gboolean pressed;
+    gint result;
+};
+
+struct _ObPrompt
+{
+    InternalWindow super;
+    gint ref;
+
+    guint event_mask;
+
+    /* keep a copy of this because we re-render things that may need it
+       (i.e. the buttons) */
+    RrAppearance *a_bg;
+
+    gboolean mapped;
+    gint width, height;
+    gint msg_wbound;
+
+    ObPromptElement msg;
+
+    /* one for each answer */
+    ObPromptElement *button;
+    gint n_buttons;
+
+    /* points to the button with the focus */
+    ObPromptElement *focus;
+    /* the default button to have selected */
+    gint default_result;
+    /* the cancel result if the dialog is closed */
+    gint cancel_result;
+
+    ObPromptCallback func;
+    gpointer data;
+};
+
+struct _ObPromptAnswer {
+    const gchar *text;
+    gint result;
+};
+
+void prompt_startup(gboolean reconfig);
+void prompt_shutdown(gboolean reconfig);
+
+/*! Create a new prompt
+  @param answers A number of ObPromptAnswers which define the buttons which
+                 will appear in the dialog from left to right, and the result
+                 returned when they are selected.
+  @param n_answers The number of answers
+  @param default_result The result for the answer button selected by default
+  @param cancel_result The result that is given if the dialog is closed instead
+         of having a button presssed
+  @param func The callback function which is called when the dialog is closed
+         or a button is pressed
+  @param data User defined data which will be passed to the callback
+*/
+ObPrompt* prompt_new(const gchar *msg,
+                     const ObPromptAnswer *answers, gint n_answers,
+                     gint default_result, gint cancel_result,
+                     ObPromptCallback func, gpointer data);
+void prompt_ref(ObPrompt *self);
+void prompt_unref(ObPrompt *self);
+
+/*! Show the prompt.  It will be centered within the given area rectangle */
+void prompt_show(ObPrompt *self, struct _ObClient *parent);
+void prompt_hide(ObPrompt *self);
+
+gboolean prompt_key_event(ObPrompt *self, XEvent *e);
+gboolean prompt_mouse_event(ObPrompt *self, XEvent *e);
+void prompt_cancel(ObPrompt *self);
+
+#endif
index da49077..5201f78 100644 (file)
@@ -620,10 +620,15 @@ void screen_set_desktop(guint num, gboolean dofocus)
         /* If screen_desktop_timeout is true, then we've been on this desktop
            long enough and we can save it as the last desktop. */
 
-        /* save the "last desktop" as the "old desktop" */
-        screen_old_desktop = screen_last_desktop;
-        /* save the desktop we're coming from as the "last desktop" */
-        screen_last_desktop = previous;
+        if (screen_last_desktop == previous)
+            /* this is the startup state only */
+            screen_old_desktop = screen_desktop;
+        else {
+            /* save the "last desktop" as the "old desktop" */
+            screen_old_desktop = screen_last_desktop;
+            /* save the desktop we're coming from as the "last desktop" */
+            screen_last_desktop = previous;
+        }
     }
     else {
         /* If screen_desktop_timeout is false, then we just got to this desktop
index c5e6a4e..d312fc3 100644 (file)
@@ -22,6 +22,7 @@
 #include "dock.h"
 #include "client.h"
 #include "frame.h"
+#include "prompt.h"
 
 GHashTable *window_map;
 
@@ -57,7 +58,9 @@ Window window_top(ObWindow *self)
     case Window_Client:
         return ((ObClient*)self)->frame->window;
     case Window_Internal:
-        return ((InternalWindow*)self)->win;
+        return ((InternalWindow*)self)->window;
+    case Window_Prompt:
+        return ((ObPrompt*)self)->super.window;
     }
     g_assert_not_reached();
     return None;
@@ -78,6 +81,10 @@ ObStackingLayer window_layer(ObWindow *self)
         return ((ObClient*)self)->layer;
     case Window_Internal:
         return OB_STACKING_LAYER_INTERNAL;
+    case Window_Prompt:
+        /* not used directly for stacking, prompts are managed as clients */
+        g_assert_not_reached();
+        break;
     }
     g_assert_not_reached();
     return None;
index a172cfc..76615c0 100644 (file)
@@ -32,8 +32,9 @@ typedef enum {
     Window_Dock,
     Window_DockApp, /* used for events but not stacking */
     Window_Client,
-    Window_Internal /* used for stacking but not events (except to filter
+    Window_Internal,/* used for stacking but not events (except to filter
                        events on the root window) */
+    Window_Prompt,
 } Window_InternalType;
 
 struct _ObWindow
@@ -44,8 +45,8 @@ struct _ObWindow
 /* Wrapper for internal stuff. If its struct matches this then it can be used
    as an ObWindow */
 typedef struct InternalWindow {
-    ObWindow obwin;
-    Window win;
+    Window_InternalType type;
+    Window window;
 } InternalWindow;
 
 #define WINDOW_IS_MENU(win) (((ObWindow*)win)->type == Window_Menu)
@@ -53,23 +54,27 @@ typedef struct InternalWindow {
 #define WINDOW_IS_DOCKAPP(win) (((ObWindow*)win)->type == Window_DockApp)
 #define WINDOW_IS_CLIENT(win) (((ObWindow*)win)->type == Window_Client)
 #define WINDOW_IS_INTERNAL(win) (((ObWindow*)win)->type == Window_Internal)
+#define WINDOW_IS_PROMPT(win) (((ObWindow*)win)->type == Window_Prompt)
 
 struct _ObMenu;
 struct _ObDock;
 struct _ObDockApp;
 struct _ObClient;
+struct _ObPrompt;
 
 #define WINDOW_AS_MENU(win) ((struct _ObMenuFrame*)win)
 #define WINDOW_AS_DOCK(win) ((struct _ObDock*)win)
 #define WINDOW_AS_DOCKAPP(win) ((struct _ObDockApp*)win)
 #define WINDOW_AS_CLIENT(win) ((struct _ObClient*)win)
 #define WINDOW_AS_INTERNAL(win) ((struct InternalWindow*)win)
+#define WINDOW_AS_PROMPT(win) ((struct _ObPrompt*)win)
 
 #define MENU_AS_WINDOW(menu) ((ObWindow*)menu)
 #define DOCK_AS_WINDOW(dock) ((ObWindow*)dock)
 #define DOCKAPP_AS_WINDOW(dockapp) ((ObWindow*)dockapp)
 #define CLIENT_AS_WINDOW(client) ((ObWindow*)client)
 #define INTERNAL_AS_WINDOW(intern) ((ObWindow*)intern)
+#define PROMPT_AS_WINDOW(prompt) ((ObWindow*)prompt)
 
 extern GHashTable *window_map;
 
index f7918ce..294127c 100644 (file)
@@ -15,3 +15,4 @@ openbox/session.c
 openbox/startupnotify.c
 openbox/translate.c
 openbox/xerror.c
+openbox/prompt.c
index ec77972..70ec701 100644 (file)
--- a/po/nl.po
+++ b/po/nl.po
@@ -3,14 +3,15 @@
 # This file is distributed under the same license as the openbox package.
 # Mark Pustjens <pustjens@dds.nl>, 2007.
 # Jochem Kossen <jkossen@xs4all.nl>, 2007.
+# Marvin Vek, 2008
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: Openbox 3.4.3\n"
+"Project-Id-Version: Openbox 3.4.6.1\n"
 "Report-Msgid-Bugs-To: http://bugzilla.icculus.org\n"
 "POT-Creation-Date: 2008-02-02 11:51-0500\n"
 "PO-Revision-Date: 2007-07-12 13:01+0200\n"
-"Last-Translator: Jochem Kossen <jkossen@xs4all.nl>\n"
+"Last-Translator: Marvin Vek\n"
 "Language-Team: Dutch <nl@li.org>\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
@@ -33,11 +34,11 @@ msgstr "Uitvoeren van '%s' mislukt: %s"
 
 #: openbox/client.c:1988 openbox/client.c:2020
 msgid "Killing..."
-msgstr ""
+msgstr "Termineren..."
 
 #: openbox/client.c:1990 openbox/client.c:2022
 msgid "Not Responding"
-msgstr ""
+msgstr "Reageert Niet"
 
 #: openbox/client_list_combined_menu.c:91 openbox/client_list_menu.c:94
 msgid "Go there..."
@@ -232,7 +233,7 @@ msgstr "  --replace           Vervang de huidig draaiende window manager\n"
 
 #: openbox/openbox.c:502
 msgid "  --config-file FILE  Specify the path to the config file to use\n"
-msgstr ""
+msgstr "  --config-file FILE  Specificeer het pad naar het te gebruiken configuratiebestand\n"
 
 #: openbox/openbox.c:503
 msgid "  --sm-disable        Disable connection to the session manager\n"
@@ -256,7 +257,7 @@ msgstr "  --restart           Herstart Openbox\n"
 
 #: openbox/openbox.c:507
 msgid "  --exit              Exit Openbox\n"
-msgstr ""
+msgstr "  --exit              Openbox afsluiten\n"
 
 #: openbox/openbox.c:508
 msgid ""
@@ -293,7 +294,7 @@ msgstr ""
 
 #: openbox/openbox.c:583
 msgid "--config-file requires an argument\n"
-msgstr ""
+msgstr "--config-file vereist een argument\n"
 
 #: openbox/openbox.c:626
 #, c-format
@@ -321,6 +322,8 @@ msgid ""
 "Openbox is configured for %d desktops, but the current session has %d.  "
 "Overriding the Openbox configuration."
 msgstr ""
+"Openbox is geconfigureerd voor %d bureaubladen, maar de huidige sessie heeft %d.  "
+"Overnemen van de Openbox configuratie."
 
 #: openbox/screen.c:1169
 #, c-format
index 369f262..583c9f7 100644 (file)
@@ -108,7 +108,7 @@ RrFont *RrFontOpen(const RrInstance *inst, const gchar *name, gint size,
 
     /* setup the layout */
     pango_layout_set_font_description(out->layout, out->font_desc);
-    pango_layout_set_single_paragraph_mode(out->layout, TRUE);
+    pango_layout_set_wrap(out->layout, PANGO_WRAP_WORD_CHAR);
 
     /* get the ascent and descent */
     measure_font(inst, out);
@@ -139,12 +139,23 @@ void RrFontClose(RrFont *f)
 }
 
 static void font_measure_full(const RrFont *f, const gchar *str,
-                              gint *x, gint *y, gint shadow_x, gint shadow_y)
+                              gint *x, gint *y, gint shadow_x, gint shadow_y,
+                              gboolean flow, gint maxwidth)
 {
     PangoRectangle rect;
 
     pango_layout_set_text(f->layout, str, -1);
-    pango_layout_set_width(f->layout, -1);
+    if (flow) {
+        pango_layout_set_single_paragraph_mode(f->layout, FALSE);
+        pango_layout_set_width(f->layout, maxwidth * PANGO_SCALE);
+        pango_layout_set_ellipsize(f->layout, PANGO_ELLIPSIZE_NONE);
+    }
+    else {
+        /* single line mode */
+        pango_layout_set_single_paragraph_mode(f->layout, TRUE);
+        pango_layout_set_width(f->layout, -1);
+        pango_layout_set_ellipsize(f->layout, PANGO_ELLIPSIZE_MIDDLE);
+    }
 
     /* pango_layout_get_pixel_extents lies! this is the right way to get the
        size of the text's area */
@@ -163,11 +174,16 @@ static void font_measure_full(const RrFont *f, const gchar *str,
 }
 
 RrSize *RrFontMeasureString(const RrFont *f, const gchar *str,
-                            gint shadow_x, gint shadow_y)
+                            gint shadow_x, gint shadow_y,
+                            gboolean flow, gint maxwidth)
 {
     RrSize *size;
+
+    g_assert(!flow || maxwidth > 0);
+
     size = g_new(RrSize, 1);
-    font_measure_full(f, str, &size->width, &size->height, shadow_x, shadow_y);
+    font_measure_full(f, str, &size->width, &size->height, shadow_x, shadow_y,
+                      flow, maxwidth);
     return size;
 }
 
@@ -208,36 +224,47 @@ void RrFontDraw(XftDraw *d, RrTextureText *t, RrRect *area)
     PangoAttrList *attrlist;
     PangoEllipsizeMode ell;
 
-    /* center the text vertically
-       We do this centering based on the 'baseline' since different fonts have
-       different top edges. It looks bad when the whole string is moved when 1
-       character from a non-default language is included in the string */
-    y = area->y +
-        font_calculate_baseline(t->font, area->height);
+    g_assert(!t->flow || t->maxwidth > 0);
+
+    y = area->y;
+    if (!t->flow)
+        /* center the text vertically
+           We do this centering based on the 'baseline' since different fonts
+           have different top edges. It looks bad when the whole string is
+           moved when 1 character from a non-default language is included in
+           the string */
+        y += font_calculate_baseline(t->font, area->height);
 
     /* the +2 and -4 leave a small blank edge on the sides */
     x = area->x + 2;
-    w = area->width - 4;
+    w = area->width;
+    if (t->flow) w = MAX(w, t->maxwidth);
+    w -= 4;
     h = area->height;
 
-    switch (t->ellipsize) {
-    case RR_ELLIPSIZE_NONE:
+    if (t->flow)
         ell = PANGO_ELLIPSIZE_NONE;
-        break;
-    case RR_ELLIPSIZE_START:
-        ell = PANGO_ELLIPSIZE_START;
-        break;
-    case RR_ELLIPSIZE_MIDDLE:
-        ell = PANGO_ELLIPSIZE_MIDDLE;
-        break;
-    case RR_ELLIPSIZE_END:
-        ell = PANGO_ELLIPSIZE_END;
-        break;
+    else {
+        switch (t->ellipsize) {
+        case RR_ELLIPSIZE_NONE:
+            ell = PANGO_ELLIPSIZE_NONE;
+            break;
+        case RR_ELLIPSIZE_START:
+            ell = PANGO_ELLIPSIZE_START;
+            break;
+        case RR_ELLIPSIZE_MIDDLE:
+            ell = PANGO_ELLIPSIZE_MIDDLE;
+            break;
+        case RR_ELLIPSIZE_END:
+            ell = PANGO_ELLIPSIZE_END;
+            break;
+        }
     }
 
     pango_layout_set_text(t->font->layout, t->string, -1);
     pango_layout_set_width(t->font->layout, w * PANGO_SCALE);
     pango_layout_set_ellipsize(t->font->layout, ell);
+    pango_layout_set_single_paragraph_mode(t->font->layout, !t->flow);
 
     /* * * end of setting up the layout * * */
 
@@ -276,10 +303,23 @@ void RrFontDraw(XftDraw *d, RrTextureText *t, RrRect *area)
         c.pixel = t->shadow_color->pixel;
 
         /* see below... */
-        pango_xft_render_layout_line
-            (d, &c, pango_layout_get_line(t->font->layout, 0),
-             (x + t->shadow_offset_x) * PANGO_SCALE,
-             (y + t->shadow_offset_y) * PANGO_SCALE);
+        if (!t->flow) {
+            pango_xft_render_layout_line
+                (d, &c,
+#if PANGO_VERSION_MAJOR > 1 || \
+    (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
+                 pango_layout_get_line_readonly(t->font->layout, 0),
+#else
+                 pango_layout_get_line(t->font->layout, 0),
+#endif
+                 (x + t->shadow_offset_x) * PANGO_SCALE,
+                 (y + t->shadow_offset_y) * PANGO_SCALE);
+        }
+        else {
+            pango_xft_render_layout(d, &c, t->font->layout,
+                                    (x + t->shadow_offset_x) * PANGO_SCALE,
+                                    (y + t->shadow_offset_y) * PANGO_SCALE);
+        }
     }
 
     c.color.red = t->color->r | t->color->r << 8;
@@ -306,9 +346,23 @@ void RrFontDraw(XftDraw *d, RrTextureText *t, RrRect *area)
 
     /* layout_line() uses y to specify the baseline
        The line doesn't need to be freed, it's a part of the layout */
-    pango_xft_render_layout_line
-        (d, &c, pango_layout_get_line(t->font->layout, 0),
-         x * PANGO_SCALE, y * PANGO_SCALE);
+    if (!t->flow) {
+        pango_xft_render_layout_line
+            (d, &c,
+#if PANGO_VERSION_MAJOR > 1 || \
+    (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
+             pango_layout_get_line_readonly(t->font->layout, 0),
+#else
+             pango_layout_get_line(t->font->layout, 0),
+#endif
+             x * PANGO_SCALE,
+             y * PANGO_SCALE);
+    }
+    else {
+        pango_xft_render_layout(d, &c, t->font->layout,
+                                x * PANGO_SCALE,
+                                y * PANGO_SCALE);
+    }
 
     if (t->shortcut) {
         t->font->shortcut_underline->start_index = 0;
index 6439b30..fc75047 100644 (file)
@@ -197,6 +197,74 @@ static void create_bevel_colors(RrAppearance *l)
     l->surface.bevel_dark = RrColorNew(l->inst, r, g, b);
 }
 
+/*! Repeat the first pixel over the entire block of memory
+  @param start The block of memory. start[0] will be copied
+         to the rest of the block.
+  @param w The width of the block of memory (including the already-set first
+           element
+*/
+static inline void repeat_pixel(RrPixel32 *start, gint w)
+{
+    gint x;
+    RrPixel32 *dest;
+
+    dest = start + 1;
+
+    /* for really small things, just copy ourselves */
+    if (w < 8) {
+        for (x = w-1; x > 0; --x)
+            *(dest++) = *start;
+    }
+
+    /* for >= 8, then use O(log n) memcpy's... */
+    else {
+        gchar *cdest;
+        gint lenbytes;
+
+        /* copy the first 3 * 32 bits (3 words) ourselves - then we have
+           3 + the original 1 = 4 words to make copies of at a time
+
+           this is faster than doing memcpy for 1 or 2 words at a time
+        */
+        for (x = 3; x > 0; --x)
+            *(dest++) = *start;
+
+        /* cdest is a pointer to the pixel data that is typed char* so that
+           adding 1 to its position moves it only one byte
+
+           lenbytes is the amount of bytes that we will be copying each
+           iteration.  this doubles each time through the loop.
+
+           x is the number of bytes left to copy into.  lenbytes will alwaysa
+           be bounded by x
+
+           this loop will run O(log n) times (n is the number of bytes we
+           need to copy into), since the size of the copy is doubled each
+           iteration.  it seems that gcc does some nice optimizations to make
+           this memcpy very fast on hardware with support for vector operations
+           such as mmx or see.  here is an idea of the kind of speed up we are
+           getting by doing this (splitvertical3 switches from doing
+           "*(data++) = color" n times to doing this memcpy thing log n times:
+
+           %   cumulative   self              self     total           
+           time   seconds   seconds    calls  ms/call  ms/call  name    
+           49.44      0.88     0.88     1063     0.83     0.83  splitvertical1
+           47.19      1.72     0.84     1063     0.79     0.79  splitvertical2
+            2.81      1.77     0.05     1063     0.05     0.05  splitvertical3
+        */
+        cdest = (gchar*)dest;
+        lenbytes = 4 * sizeof(RrPixel32);
+        for (x = (w - 4) * sizeof(RrPixel32); x > 0;) {
+            memcpy(cdest, start, lenbytes);
+            x -= lenbytes;
+            cdest += lenbytes;
+            lenbytes <<= 1;
+            if (lenbytes > x)
+                lenbytes = x;
+        }
+    }
+}
+
 static void gradient_parentrelative(RrAppearance *a, gint w, gint h)
 {
     RrPixel32 *source, *dest;
@@ -423,10 +491,9 @@ static void gradient_solid(RrAppearance *l, gint w, gint h)
 
 static void gradient_splitvertical(RrAppearance *a, gint w, gint h)
 {
-    gint x, y1, y2, y3;
+    gint y1, y2, y3;
     RrSurface *sf = &a->surface;
-    RrPixel32 *data = sf->pixel_data;
-    RrPixel32 current;
+    RrPixel32 *data;
     gint y1sz, y2sz, y3sz;
 
     VARS(y1);
@@ -455,39 +522,48 @@ static void gradient_splitvertical(RrAppearance *a, gint w, gint h)
     }
     SETUP(y3, sf->secondary, sf->split_secondary,  y3sz);
 
-    for (y1 = y1sz; y1 > 0; --y1) {
-        current = COLOR(y1);
-        for (x = w - 1; x >= 0; --x)
-            *(data++) = current;
+    /* find the color for the first pixel of each row first */
+    data = sf->pixel_data;
 
+    for (y1 = y1sz-1; y1 > 0; --y1) {
+        *data = COLOR(y1);
+        data += w;
         NEXT(y1);
     }
-
-    for (y2 = y2sz; y2 > 0; --y2) {
-        current = COLOR(y2);
-        for (x = w - 1; x >= 0; --x)
-            *(data++) = current;
-
+    *data = COLOR(y1);
+    data += w;
+    for (y2 = y2sz-1; y2 > 0; --y2) {
+        *data = COLOR(y2);
+        data += w;
         NEXT(y2);
     }
-
-    for (y3 = y3sz; y3 > 0; --y3) {
-        current = COLOR(y3);
-        for (x = w - 1; x >= 0; --x)
-            *(data++) = current;
-
+    *data = COLOR(y2);
+    data += w;
+    for (y3 = y3sz-1; y3 > 0; --y3) {
+        *data = COLOR(y3);
+        data += w;
         NEXT(y3);
     }
+    *data = COLOR(y3);
+
+    /* copy the first pixels into the whole rows */
+    data = sf->pixel_data;
+    for (y1 = h; y1 > 0; --y1) {
+        repeat_pixel(data, w);
+        data += w;
+    }
 }
 
 static void gradient_horizontal(RrSurface *sf, gint w, gint h)
 {
-    gint x, y;
+    gint x, y, cpbytes;
     RrPixel32 *data = sf->pixel_data, *datav;
+    gchar *datac;
 
     VARS(x);
     SETUP(x, sf->primary, sf->secondary, w);
 
+    /* set the color values for the first row */
     datav = data;
     for (x = w - 1; x > 0; --x) {  /* 0 -> w - 1 */
         *datav = COLOR(x);
@@ -497,22 +573,32 @@ static void gradient_horizontal(RrSurface *sf, gint w, gint h)
     *datav = COLOR(x);
     ++datav;
 
-    for (y = h - 1; y > 0; --y) { /* 1 -> h */
-        memcpy(datav, data, w * sizeof(RrPixel32));
-        datav += w;
+    /* copy the first row to the rest in O(logn) copies */
+    datac = (gchar*)datav;
+    cpbytes = 1 * w * sizeof(RrPixel32);
+    for (y = (h - 1) * w * sizeof(RrPixel32); y > 0;) {
+        memcpy(datac, data, cpbytes);
+        y -= cpbytes;
+        datac += cpbytes;
+        cpbytes <<= 1;
+        if (cpbytes > y)
+            cpbytes = y;
     }
 }
 
 static void gradient_mirrorhorizontal(RrSurface *sf, gint w, gint h)
 {
-    gint x, y, half1, half2;
+    gint x, y, half1, half2, cpbytes;
     RrPixel32 *data = sf->pixel_data, *datav;
+    gchar *datac;
 
     VARS(x);
 
     half1 = (w + 1) / 2;
     half2 = w / 2;
 
+    /* set the color values for the first row */
+
     SETUP(x, sf->primary, sf->secondary, half1);
     datav = data;
     for (x = half1 - 1; x > 0; --x) {  /* 0 -> half1 - 1 */
@@ -534,31 +620,43 @@ static void gradient_mirrorhorizontal(RrSurface *sf, gint w, gint h)
         ++datav;
     }
 
-    for (y = h - 1; y > 0; --y) {  /* 1 -> h */
-        memcpy(datav, data, w * sizeof(RrPixel32));
-        datav += w;
+    /* copy the first row to the rest in O(logn) copies */
+    datac = (gchar*)datav;
+    cpbytes = 1 * w * sizeof(RrPixel32);
+    for (y = (h - 1) * w * sizeof(RrPixel32); y > 0;) {
+        memcpy(datac, data, cpbytes);
+        y -= cpbytes;
+        datac += cpbytes;
+        cpbytes <<= 1;
+        if (cpbytes > y)
+            cpbytes = y;
     }
 }
 
 static void gradient_vertical(RrSurface *sf, gint w, gint h)
 {
-    gint x, y;
-    RrPixel32 *data = sf->pixel_data;
-    RrPixel32 current;
+    gint y;
+    RrPixel32 *data;
 
     VARS(y);
     SETUP(y, sf->primary, sf->secondary, h);
 
-    for (y = h - 1; y > 0; --y) {  /* 0 -> h-1 */
-        current = COLOR(y);
-        for (x = w - 1; x >= 0; --x)  /* 0 -> w */
-            *(data++) = current;
+    /* find the color for the first pixel of each row first */
+    data = sf->pixel_data;
 
+    for (y = h - 1; y > 0; --y) {  /* 0 -> h-1 */
+        *data = COLOR(y);
+        data += w;
         NEXT(y);
     }
-    current = COLOR(y);
-    for (x = w - 1; x >= 0; --x)  /* 0 -> w */
-        *(data++) = current;
+    *data = COLOR(y);
+
+    /* copy the first pixels into the whole rows */
+    data = sf->pixel_data;
+    for (y = h; y > 0; --y) {
+        repeat_pixel(data, w);
+        data += w;
+    }
 }
 
 
@@ -656,14 +754,13 @@ static void gradient_crossdiagonal(RrSurface *sf, gint w, gint h)
     *data = COLOR(x);
 }
 
-static void gradient_pyramid(RrSurface *sf, gint inw, gint inh)
+static void gradient_pyramid(RrSurface *sf, gint w, gint h)
 {
-    gint x, y, w = (inw >> 1) + 1, h = (inh >> 1) + 1;
-    RrPixel32 *data = sf->pixel_data;
-    RrPixel32 *end = data + inw*inh - 1;
-    RrPixel32 current;
+    RrPixel32 *ldata, *rdata;
+    RrPixel32 *cp;
     RrColor left, right;
     RrColor extracorner;
+    gint x, y, halfw, halfh, midx, midy;
 
     VARS(lefty);
     VARS(righty);
@@ -673,54 +770,64 @@ static void gradient_pyramid(RrSurface *sf, gint inw, gint inh)
     extracorner.g = (sf->primary->g + sf->secondary->g) / 2;
     extracorner.b = (sf->primary->b + sf->secondary->b) / 2;
 
-    SETUP(lefty, (&extracorner), sf->secondary, h);
-    SETUP(righty, sf->primary, (&extracorner), h);
+    halfw = w >> 1;
+    halfh = h >> 1;
+    midx = w - halfw - halfw; /* 0 or 1, depending if w is even or odd */
+    midy = h - halfh - halfh;   /* 0 or 1, depending if h is even or odd */
+
+    SETUP(lefty, sf->primary, (&extracorner), halfh + midy);
+    SETUP(righty, (&extracorner), sf->secondary, halfh + midy);
+
+    /* draw the top half
+
+       it is faster to draw both top quarters together than to draw one and
+       then copy it over to the other side.
+    */
+
+    ldata = sf->pixel_data;
+    rdata = ldata + w - 1;
+    for (y = halfh + midy; y > 0; --y) {  /* 0 -> (h+1)/2 */
+        RrPixel32 c;
 
-    for (y = h - 1; y > 0; --y) {  /* 0 -> h-1 */
         COLOR_RR(lefty, (&left));
         COLOR_RR(righty, (&right));
 
-        SETUP(x, (&left), (&right), w);
+        SETUP(x, (&left), (&right), halfw + midx);
 
-        for (x = w - 1; x > 0; --x) {  /* 0 -> w-1 */
-            current = COLOR(x);
-            *(data+x) = current;
-            *(data+inw-x) = current;
-            *(end-x) = current;
-            *(end-(inw-x)) = current;
+        for (x = halfw + midx - 1; x > 0; --x) {  /* 0 -> (w+1)/2 */
+            c = COLOR(x);
+            *(ldata++) = *(rdata--) = c;
 
             NEXT(x);
         }
-        current = COLOR(x);
-        *(data+x) = current;
-        *(data+inw-x) = current;
-        *(end-x) = current;
-        *(end-(inw-x)) = current;
-
-        data+=inw;
-        end-=inw;
+        c = COLOR(x);
+        *ldata = *rdata = c;
+        ldata += halfw + 1;
+        rdata += halfw - 1 + midx + w;
 
         NEXT(lefty);
         NEXT(righty);
     }
-    COLOR_RR(lefty, (&left));
-    COLOR_RR(righty, (&right));
 
-    SETUP(x, (&left), (&right), w);
+    /* copy the top half into the bottom half, mirroring it, so we can only
+       copy one row at a time
 
-    for (x = w - 1; x > 0; --x) {  /* 0 -> w-1 */
-        current = COLOR(x);
-        *(data+x) = current;
-        *(data+inw-x) = current;
-        *(end-x) = current;
-        *(end-(inw-x)) = current;
+       it is faster, to move the writing pointer forward, and the reading
+       pointer backward
 
-        NEXT(x);
+       this is the current code, moving the write pointer forward and read
+       pointer backward
+       41.78      4.26     1.78      504     3.53     3.53  gradient_pyramid2
+       this is the opposite, moving the read pointer forward and the write
+       pointer backward
+       42.27      4.40     1.86      504     3.69     3.69  gradient_pyramid2
+       
+    */
+    ldata = sf->pixel_data + (halfh - 1) * w;
+    cp = ldata + (midy + 1) * w;
+    for (y = halfh; y > 0; --y) {
+        memcpy(cp, ldata, w * sizeof(RrPixel32));
+        ldata -= w;
+        cp += w;
     }
-    current = COLOR(x);
-    *(data+x) = current;
-    *(data+inw-x) = current;
-    *(end-x) = current;
-    *(end-(inw-x)) = current;
 }
-
index 13eff84..f0b2d26 100644 (file)
@@ -1,3 +1,5 @@
+/* GIMP RGBA C-Source image dump (icon.h) */
+
 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
 
    icon.h for the Openbox window manager
    See the COPYING file for a copy of the GNU General Public License.
 */
 
-/* GIMP RGBA C-Source image dump (icon.h) */
-
 #define OB_DEFAULT_ICON_WIDTH (48)
 #define OB_DEFAULT_ICON_HEIGHT (48)
 #define OB_DEFAULT_ICON_BYTES_PER_PIXEL (4) /* 3:RGB, 4:RGBA */
 #define OB_DEFAULT_ICON_COMMENT \
-  "To recreate this file, save an image as \"C-Source\" in The Gimp. " \
-   "Use \"ob_default_icon\" as the Prefixed Name. Enable Glib Types. " \
-   "Enable Save Alpha Channel. Enable Use Macros instead of Struct."
+  "To recreate this file, save an image as \"C-Source\" in The Gimp. Use \"ob_default_icon\" as the Prefixed Name. Enable Glib Types. Enable Save Alpha Channel. Enable Use Macros instead of Struct."
 #define OB_DEFAULT_ICON_PIXEL_DATA ((guint8*) OB_DEFAULT_ICON_pixel_data)
 static const guint8 OB_DEFAULT_ICON_pixel_data[48 * 48 * 4 + 1] =
+("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
@@ -36,406 +35,388 @@ static const guint8 OB_DEFAULT_ICON_pixel_data[48 * 48 * 4 + 1] =
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\10"
- "\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373"
- "\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10"
- "\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10"
- "\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10"
- "\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373"
- "\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10"
- "\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10"
- "\10\373\10\10\10\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10"
- "\373\10\10\10\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22"
- "\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20"
- "\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373"
- "\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24"
- "\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22"
- "\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20"
- "\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373\20\22\24\373"
- "\20\22\24\373\23\26\32\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\20\22\24\373\277\324\347\373\277\325\347\373\277\324\347\373\277"
- "\324\347\373\277\324\347\373\277\324\347\373\277\324\347\373\277\324\347\373"
- "\277\324\347\373\277\324\347\373\277\324\347\373\277\324\347\373\277\324\347"
- "\373\277\325\347\373\277\324\347\373\300\324\347\373\277\324\347\373\277\324"
- "\347\373\277\324\347\373\277\324\347\373\300\324\347\373\277\324\347\373\277"
- "\324\347\373\277\324\347\373\277\324\347\373\277\324\347\373\277\324\347\373"
- "\277\324\347\373\277\324\347\373\277\324\347\373\277\324\347\373\300\324\347"
- "\373\277\324\347\373\277\324\347\373\277\324\347\373\277\324\347\373\277\325"
- "\347\373\277\324\347\373\277\324\347\373\277\324\347\373\20\22\24\373\10\10"
- "\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\20\22\24\373\277\323"
- "\347\373l\232\277\373n\232\277\373n\232\300\373n\232\300\373o\233\300\373"
- "o\233\300\373o\233\301\373o\235\301\373q\235\303\373q\235\303\373q\235\303"
- "\373r\236\304\373r\236\304\373t\236\304\373t\237\304\373t\237\305\373t\237"
- "\305\373u\237\305\373u\237\306\373u\241\306\373u\241\306\373w\241\307\373"
- "w\242\307\373y\242\307\373y\242\307\373y\242\310\373y\245\310\373z\245\311"
- "\373z\244\311\373z\245\311\373|\245\311\373|\245\313\373|\245\313\373}\247"
- "\313\373}\247\314\373}\247\314\373}\250\314\373\177\251\315\373t\232\277\373"
- "\20\22\24\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\20"
- "\22\24\373\277\323\346\373l\232\276\373l\230\277\373l\230\277\373n\232\277"
- "\373n\232\300\373n\233\300\373o\233\300\373o\233\301\373o\233\301\373o\233"
- "\301\373q\235\301\373q\235\303\373q\236\303\373r\236\303\373r\236\304\373"
- "r\237\304\373t\236\304\373t\237\305\373t\237\305\373t\241\305\373u\241\306"
- "\373u\241\306\373u\242\306\373w\241\306\373w\241\307\373w\242\307\373y\244"
- "\307\373y\244\310\373y\244\310\373y\245\310\373z\245\311\373z\244\311\373"
- "z\245\311\373|\247\313\373|\247\313\373|\245\313\373}\250\313\373}\250\314"
- "\373q\230\276\373\20\22\24\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\10\10\10\373\20\22\24\373\275\322\345\373l\227\276\373k\230\276\373l\230"
- "\276\373l\232\277\373l\230\277\373n\232\277\373n\233\300\373n\232\300\373"
- "n\232\300\373o\233\300\373o\233\301\373o\233\301\373q\233\301\373q\235\303"
- "\373q\235\303\373r\235\303\373r\236\304\373r\237\304\373r\237\304\373t\236"
- "\304\373t\237\305\373t\237\305\373u\237\305\373u\241\306\373u\241\306\373"
- "u\241\306\373w\242\307\373y\241\307\373w\244\307\373y\242\307\373y\244\310"
- "\373z\244\310\373y\244\310\373z\244\311\373z\245\311\373z\245\311\373|\245"
- "\313\373|\245\313\373o\230\275\373\20\22\24\373\10\10\10\373\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\10\10\10\373\17\22\24\373\273\322\344\373k\227\275\373"
- "k\227\275\373k\230\276\373k\227\276\373l\230\276\373l\232\277\373l\232\277"
- "\373n\232\277\373n\232\277\373n\232\300\373n\233\300\373o\232\300\373o\233"
- "\301\373o\233\301\373o\233\301\373q\235\303\373r\236\303\373q\235\303\373"
- "r\235\303\373r\236\304\373r\237\304\373r\237\304\373u\237\305\373t\237\305"
- "\373t\241\305\373u\237\306\373u\237\306\373w\241\306\373w\241\306\373w\242"
- "\307\373w\242\307\373w\242\307\373z\242\310\373y\244\310\373z\244\310\373"
- "y\244\310\373|\244\311\373z\245\311\373n\225\273\373\17\22\24\373\10\10\10"
- "\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\17\22\24\373\273\321\344"
- "\373k\227\275\373i\227\275\373k\227\275\373k\230\276\373k\227\276\373l\230"
- "\276\373l\230\276\373l\232\277\373l\232\277\373l\230\277\373n\232\277\373"
- "n\232\300\373n\233\300\373o\233\300\373o\233\301\373o\235\301\373o\235\301"
- "\373q\235\301\373r\235\303\373q\236\303\373r\235\303\373r\236\304\373r\236"
- "\304\373r\236\304\373t\237\304\373t\236\305\373t\237\305\373t\237\305\373"
- "w\241\306\373u\241\306\373u\241\306\373w\242\306\373w\242\307\373w\242\307"
- "\373w\244\307\373y\244\310\373y\244\310\373z\244\310\373l\225\272\373\17\22"
- "\24\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\17\22\24"
- "\373\272\321\343\373i\225\273\373i\225\273\373i\227\275\373k\225\275\373i"
- "\227\275\373k\227\275\373k\230\276\373l\230\276\373l\230\276\373l\230\276"
- "\373n\232\277\373l\230\277\373n\232\277\373n\232\300\373o\233\300\373o\233"
- "\300\373o\233\300\373o\233\301\373o\235\301\373o\233\301\373q\235\303\373"
- "r\235\303\373q\236\303\373r\236\303\373t\236\304\373r\236\304\373r\237\304"
- "\373t\237\304\373u\237\305\373t\237\305\373t\241\305\373u\241\306\373u\241"
- "\306\373w\241\306\373u\242\306\373w\242\307\373w\242\307\373w\242\307\373"
- "k\224\271\373\17\22\24\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\17\22\24\373\271\320\343\373h\224\272\373_\212\260\373_\212\260"
- "\373`\212\261\373`\212\261\373`\212\261\373`\212\261\373`\212\261\373`\214"
- "\261\373b\214\263\373b\214\263\373b\214\263\373b\214\263\373b\214\263\373"
- "c\214\263\373c\216\264\373e\216\264\373e\216\264\373c\216\264\373e\216\264"
- "\373e\216\264\373f\217\265\373f\217\265\373e\217\265\373f\217\265\373f\217"
- "\265\373f\217\265\373f\221\265\373f\217\266\373f\221\266\373h\221\266\373"
- "i\221\266\373h\221\266\373h\222\266\373h\222\270\373i\221\270\373i\222\270"
- "\373i\222\270\373i\222\270\373\17\22\24\373\10\10\10\373\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\10\10\10\373\17\21\24\373T\201\250\373T\201\250\373T\201\250"
- "\373T\201\250\373T\201\250\373T\201\250\373T\201\250\373T\201\250\373T\201"
- "\250\373T\201\250\373T\201\250\373T\201\250\373T\201\250\373T\201\250\373"
- "T\201\250\373T\201\250\373T\201\250\373T\201\250\373T\201\250\373T\201\250"
- "\373T\201\250\373T\201\250\373T\201\250\373T\201\250\373T\201\250\373T\201"
- "\250\373T\201\250\373T\201\250\373T\201\250\373T\201\250\373T\201\250\373"
- "T\201\250\373T\201\250\373T\201\250\373T\201\250\373T\201\250\373T\201\250"
- "\373T\201\250\373T\201\250\373T\201\250\373\17\21\24\373\10\10\10\373\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\17\21\24\373T\201\250\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373T\201"
- "\250\373\17\21\24\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10"
- "\373\17\21\24\373T\201\250\373\375\376\376\373\375\376\376\373\375\376\376"
- "\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376"
- "\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375"
- "\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373"
- "\375\376\376\373\375\376\376\373\375\375\376\373\375\375\376\373\375\375\376"
- "\373\375\376\376\373\375\375\376\373\375\375\376\373\375\376\376\373\375\375"
- "\376\373\375\375\376\373\375\376\376\373\375\375\376\373\375\375\376\373\375"
- "\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373"
- "\375\375\376\373\375\375\376\373T\201\250\373\17\21\24\373\10\10\10\373\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\17\21\24\373T\201\250\373\373"
- "\375\376\373\373\375\376\373\373\375\376\373\373\375\376\373\373\375\376\373"
- "\373\375\376\373\373\375\376\373\373\375\376\373\373\375\376\373\373\375\376"
- "\373\373\375\376\373\373\375\376\373\373\375\376\373\373\375\376\373\373\375"
- "\376\373\373\375\376\373\373\375\376\373\373\375\376\373\373\375\376\373\373"
- "\375\376\373\373\375\376\373\373\375\376\373\373\375\376\373\373\375\376\373"
- "\373\375\376\373\373\375\376\373\373\375\376\373\373\375\376\373\373\375\376"
- "\373\373\375\376\373\373\375\376\373\373\375\376\373\373\375\376\373\373\375"
- "\376\373\373\375\376\373\373\375\376\373\373\375\376\373\373\375\376\373T"
- "\201\250\373\17\21\24\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\17\21\24\373T\201\250\373\375\376\376\373\375\376\376\373\375\376"
- "\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375"
- "\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373"
- "\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376"
- "\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376"
- "\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375"
- "\376\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373"
- "\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373T\201\250\373\17\21\24\373\10\10\10\373"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\17\21\23\373T\201\250\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373T"
- "\201\250\373\17\21\23\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\17\21\23\373T\201\250\373\375\376\376\373\375\376\376\373\375\376"
- "\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375"
- "\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373"
- "\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376"
- "\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376"
- "\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\375\376\373\375"
- "\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373"
- "\375\375\376\373\375\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373T\201\250\373\17\21\23\373\10\10\10\373"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\17\21\23\373T\201\250\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373T"
- "\201\250\373\17\21\23\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\17\21\23\373T\201\250\373\375\376\376\373\375\376\376\373\375\376"
- "\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375"
- "\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373"
- "\375\376\376\373\375\376\376\373\375\375\376\373\375\375\376\373\375\376\376"
- "\373\375\375\376\373\375\376\376\373\375\375\376\373\375\376\376\373\375\375"
- "\376\373\375\376\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375"
- "\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373T\201\250\373\17\21\23\373\10\10\10\373"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\17\20\23\373T\201\250\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373T"
- "\201\250\373\17\20\23\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\17\20\23\373T\201\250\373\375\376\376\373\375\376\376\373\375\376"
- "\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375"
- "\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\375\376\373"
- "\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376"
- "\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375"
- "\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375"
- "\375\376\373\375\375\376\373\375\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373T\201\250\373\17\20\23\373\10\10\10\373"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\17\20\23\373T\201\250\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373T"
- "\201\250\373\17\20\23\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\16\20\23\373T\201\250\373\375\376\376\373\375\376\376\373\375\376"
- "\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375"
- "\376\376\373\375\376\376\373\375\375\376\373\375\375\376\373\375\375\376\373"
- "\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376"
- "\373\375\375\376\373\375\375\376\373\375\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\375\375\376\373\374\375\376\373\375\375\376\373\375"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373T\201\250\373\17\20\23\373\10\10\10\373"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\16\20\22\373T\201\250\373\373"
- "\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373"
- "\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375"
- "\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375"
- "\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373"
- "\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373"
- "\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375"
- "\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375"
- "\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373T"
- "\201\250\373\16\20\22\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\16\20\22\373T\201\250\373\375\376\376\373\375\376\376\373\375\376"
- "\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375\376\376\373\375"
- "\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373"
- "\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373T\201\250\373\16\20\22\373\10\10\10\373"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\16\20\22\373T\201\250\373\373"
- "\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373"
- "\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375"
- "\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375"
- "\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373"
- "\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373"
- "\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375"
- "\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375"
- "\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373T"
- "\201\250\373\16\20\22\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\16\20\22\373T\201\250\373\375\376\376\373\375\376\376\373\375\376"
- "\376\373\375\376\376\373\375\376\376\373\375\375\376\373\375\375\376\373\375"
- "\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373"
- "\375\375\376\373\375\375\376\373\375\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373T\201\250\373\16\20\22\373\10\10\10\373"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\16\20\22\373T\201\250\373\373"
- "\375\375\373\373\374\375\373\373\374\375\373\373\375\375\373\373\375\375\373"
- "\373\374\375\373\373\375\375\373\373\374\375\373\373\374\375\373\373\374\375"
- "\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374"
- "\375\373\373\375\375\373\373\374\375\373\373\375\375\373\373\374\375\373\373"
- "\375\375\373\373\374\375\373\373\374\375\373\373\375\375\373\373\375\375\373"
- "\373\374\375\373\373\374\375\373\373\375\375\373\373\375\375\373\373\374\375"
- "\373\373\374\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375"
- "\375\373\373\375\375\373\373\375\375\373\373\374\375\373\373\374\375\373T"
- "\201\250\373\16\20\22\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\16\20\22\373T\201\250\373\375\375\376\373\375\375\376\373\375\375"
- "\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375"
- "\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373"
- "\375\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373T\201\250\373\16\20\22\373\10\10\10\373"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\16\20\22\373T\201\250\373\373"
- "\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373"
- "\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375"
- "\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374"
- "\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373"
- "\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373"
- "\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375"
- "\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374"
- "\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373]"
- "\211\261\373\16\20\22\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\16\20\22\373T\201\250\373\375\375\376\373\375\375\376\373\375\375"
- "\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\375"
- "\375\376\373\375\375\376\373\375\375\376\373\375\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\373\375\375\373\\\211\260\373\15\20\22\373\10\10\10\373"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\15\20\22\373T\201\250\373\373"
- "\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373"
- "\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375"
- "\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374"
- "\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373"
- "\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373"
- "\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375"
- "\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374"
- "\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373["
- "\207\257\373\15\20\22\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\15\20\22\373T\201\250\373\375\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\375\373\374\375\375\373\374\375\375\373\374\375\375\373\374"
- "\375\375\373\374\375\375\373\374\375\375\373\374\375\375\373\374\375\375\373"
- "\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375"
- "\373\373\375\375\373\373\375\375\373Y\206\255\373\15\20\22\373\10\10\10\373"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\15\20\22\373T\201\250\373\373"
- "\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373"
- "\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375"
- "\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374"
- "\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373"
- "\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373"
- "\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375"
- "\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374"
- "\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373V"
- "\204\254\373\15\20\22\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\15\20\22\373T\201\250\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\375\373\374\375"
- "\375\373\374\375\375\373\374\375\375\373\374\375\375\373\374\375\375\373\374"
- "\375\375\373\374\375\375\373\374\375\375\373\373\375\375\373\373\375\375\373"
- "\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375"
- "\373\373\375\375\373\373\374\375\373V\202\253\373\15\20\22\373\10\10\10\373"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\15\20\22\373T\201\250\373\373"
- "\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373"
- "\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375"
- "\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374"
- "\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373"
- "\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373"
- "\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375"
- "\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374"
- "\375\373\373\374\375\373\373\374\375\373\373\374\375\373\373\374\375\373V"
- "\202\253\373\15\20\22\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
- "\10\10\373\15\17\22\373T\201\250\373\374\375\376\373\374\375\376\373\374\375"
- "\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374"
- "\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373"
- "\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376\373\374\375\376"
- "\373\374\375\376\373\374\375\375\373\374\375\375\373\374\375\375\373\374\375"
- "\375\373\374\375\375\373\374\375\375\373\374\375\375\373\374\375\375\373\374"
- "\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373"
- "\373\375\375\373\373\375\375\373\373\375\375\373\373\375\375\373\373\374\375"
- "\373\373\374\375\373\373\374\375\373U\201\251\373\15\20\22\373\10\10\10\373"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\15\17\22\373T\201\250\373T\201"
- "\250\373T\177\250\373T\177\250\373T\177\250\373T\177\250\373T\177\250\373"
- "T\177\250\373T\177\250\373T\201\250\373T\177\250\373T\201\250\373T\177\250"
- "\373R\177\250\373T\201\250\373R\177\250\373T\177\250\373T\201\250\373T\177"
- "\250\373R\177\250\373T\201\250\373T\177\250\373T\177\250\373T\177\250\373"
- "T\177\250\373T\201\250\373T\177\250\373T\177\250\373T\201\250\373T\177\250"
- "\373T\177\250\373T\177\250\373T\177\250\373R\177\250\373R\177\250\373T\177"
- "\250\373T\177\250\373T\177\250\373T\177\250\373T\177\250\373\15\17\22\373"
- "\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\15\17\22\373\202"
- "\247\300\373\201\244\300\373}\242\277\373\177\244\277\373\201\245\300\373"
- "\202\247\301\373\204\247\303\373\206\250\303\373Ju\236\373\212\254\305\373"
- "\214\255\306\373\216\257\307\373\217\260\307\373\221\261\310\373\222\261\311"
- "\373\224\263\311\373\225\264\313\373\227\265\314\373\230\266\315\373\233\266"
- "\315\373\235\270\316\373\236\272\317\373\237\273\320\373\237\272\320\373\237"
- "\272\320\373\237\273\320\373\237\273\320\373\237\273\320\373\237\273\320\373"
- "\237\273\320\373\237\273\320\373Ju\236\373\236\273\320\373\237\272\320\373"
- "\237\272\320\373\237\272\320\373\237\273\320\373\236\272\320\373\237\273\320"
- "\373\237\273\320\373\15\17\22\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\10\10\10\373\15\17\21\373\201\245\277\373My\241\373My\241\373Ly\241\373"
- "My\241\373My\241\373My\241\373My\241\3733V\202\373Ny\242\373Nz\242\373Ny\242"
- "\373Nz\242\373Mz\242\373Ny\242\373Nz\242\373Mz\242\373Nz\242\373Nz\242\373"
- "Nz\242\373Nz\242\373Nz\242\373P|\244\373Pz\244\373N|\244\373Pz\244\373P|\244"
- "\373Pz\244\373Pz\244\373P|\244\373P|\244\373-Nz\373Nz\244\373P|\244\373P|"
- "\244\373P|\244\373P|\244\373P|\244\373P|\244\373P|\244\373\15\17\21\373\10"
- "\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\10\10\10\373\15\17"
- "\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15"
- "\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373"
- "\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21"
- "\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17"
- "\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15"
- "\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373"
- "\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\15\17\21\373\10\10\10"
- "\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\373\10\10\10\373"
- "\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10"
- "\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10"
- "\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10"
- "\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373"
- "\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10"
- "\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10"
- "\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10\10\10\373\10"
- "\10\10\373\10\10\10\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0";
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\40J\207\15\40J\207\23\40J\207"
+ "\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J"
+ "\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23"
+ "\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207"
+ "\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J"
+ "\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23"
+ "\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207\23\40J\207"
+ "\23\40J\207\15\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'P\213\267'Q\214\275'Q\214\275"
+ "'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214"
+ "\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275"
+ "'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275(R\215\275'Q\214\275'Q\214"
+ "\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275"
+ "'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275'Q\214\275(R\215"
+ "\275(R\215\275(R\215\275&P\213\267\40J\207\20\0\0\0\0\0\0\0\0\40J\207+Y{\252"
+ "\377\216\253\320\377\204\243\314\377\204\243\314\377\204\243\314\377\204\243"
+ "\314\377\204\243\314\377\204\243\314\377\204\243\314\377\204\243\314\377\204"
+ "\243\314\377\204\243\314\377\204\243\314\377\204\243\314\377\204\243\314\377"
+ "\204\243\314\377\204\243\314\377\204\243\314\377\204\243\314\377\204\243\314"
+ "\377\204\243\314\377\204\243\314\377\204\243\314\377\204\243\314\377\204\243"
+ "\314\377\204\243\314\377\203\243\314\377\203\243\313\377\203\243\313\377\203"
+ "\243\313\377\203\242\313\377\202\242\313\377\202\242\313\377\202\241\313\377"
+ "\201\241\312\377\201\241\312\377\201\240\312\377\201\240\312\377\200\240\312"
+ "\377\200\240\312\377\200\240\312\377\200\240\312\377\211\247\316\377Jn\241"
+ "\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+Wy\251\377]\207\275\377>o\260\377>o"
+ "\260\377>o\260\377>o\260\377>o\260\377>o\260\377>o\260\377>o\260\377>o\260"
+ "\377>o\260\377>o\260\377>o\260\377>o\260\377>o\260\377>o\260\377>o\260\377"
+ ">o\260\377>o\260\377>o\260\377>o\260\377>o\260\377>o\260\377>o\260\377>o\260"
+ "\377>o\260\377>o\260\377>o\260\377>o\260\377=o\260\377=o\260\377=o\260\377"
+ "=o\260\377=o\260\377=o\260\377=o\260\377=o\260\377=o\260\377=o\260\377=o\260"
+ "\377=o\260\377_\210\275\377Hm\241\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+Tw"
+ "\251\377]\207\276\377?r\263\377?r\263\377?r\263\377?r\263\377?r\263\377?r"
+ "\263\377?r\263\377?r\263\377?r\263\377?r\263\377?r\263\377?r\263\377?r\263"
+ "\377?r\263\377?r\263\377?r\263\377?r\263\377?r\263\377?r\263\377?r\263\377"
+ "?r\263\377?r\263\377?r\263\377?r\263\377?r\263\377?r\263\377?r\263\377?r\263"
+ "\377?r\263\377?r\263\377?r\263\377?r\263\377?r\263\377?r\263\377?r\263\377"
+ "?r\263\377?r\263\377?r\263\377?r\263\377?r\263\377]\210\277\377Fl\241\377"
+ "\40J\207+\0\0\0\0\0\0\0\0\40J\207+Rv\250\377\\\210\300\377At\265\377At\265"
+ "\377At\265\377At\265\377At\265\377At\265\377At\265\377At\265\377At\265\377"
+ "At\265\377At\265\377At\265\377At\265\377At\265\377At\265\377At\265\377At\265"
+ "\377At\265\377At\265\377At\265\377At\265\377At\265\377At\265\377At\265\377"
+ "At\265\377At\265\377At\265\377At\265\377At\265\377At\265\377At\265\377At\265"
+ "\377At\265\377At\265\377At\265\377At\265\377At\265\377At\265\377At\265\377"
+ "At\265\377\\\210\300\377Dj\240\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+Ot\247"
+ "\377\\\211\302\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271"
+ "\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377"
+ "Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271"
+ "\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377"
+ "Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377Dw\271"
+ "\377Dw\271\377Dw\271\377Dw\271\377Dw\271\377[\211\302\377Bi\240\377\40J\207"
+ "+\0\0\0\0\0\0\0\0\40J\207+Ls\247\377\\\211\303\377Fy\273\377Fy\273\377Fy\273"
+ "\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377"
+ "Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273"
+ "\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377"
+ "Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273"
+ "\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377Fy\273\377"
+ "\\\211\303\377Ah\240\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+Jq\246\377\\\212"
+ "\305\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277"
+ "\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377"
+ "I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277"
+ "\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377"
+ "I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277"
+ "\377I}\277\377I}\277\377I}\277\377[\212\305\377?g\237\377\40J\207+\0\0\0\0"
+ "\0\0\0\0\40J\207+Jq\246\377\\\212\305\377I}\277\377I}\277\377I}\277\377I}"
+ "\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277"
+ "\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377I}\277\377"
+ "I}\277\377I}\277\377I}\277\377J~\300\377K\177\301\377K\177\301\377K\177\301"
+ "\377K\177\301\377K\177\301\377K\177\301\377K\177\301\377K\177\301\377K\177"
+ "\301\377K\177\301\377K\177\301\377K\177\301\377K\177\301\377K\177\301\377"
+ "K\177\301\377K\177\301\377K\177\301\377K\177\301\377Z\212\307\377=f\237\377"
+ "\40J\207+\0\0\0\0\0\0\0\0\40J\207+Ip\247\377\\\213\307\377J\177\301\377J\177"
+ "\301\377J\177\301\377J\177\301\377J\177\301\377J\177\301\377J\177\301\377"
+ "J\177\301\377J\177\301\377J\177\301\377J\177\301\377J\177\301\377J\177\301"
+ "\377J\177\301\377J\177\301\377J\177\301\377J\177\301\377J\177\301\377J\177"
+ "\301\377J\177\301\377J\177\301\377K\200\302\377K\200\302\377K\200\302\377"
+ "K\200\302\377K\200\302\377K\200\302\377K\200\302\377K\200\302\377K\200\302"
+ "\377K\200\302\377K\200\302\377K\200\302\377K\200\302\377K\200\302\377K\200"
+ "\302\377K\200\302\377K\200\302\377K\200\302\377K\200\302\377Z\212\307\377"
+ "=f\237\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+Ho\247\377e\223\314\377Z\213\310"
+ "\377Z\213\310\377Z\213\310\377Z\213\310\377Z\213\307\377Z\213\307\377Z\213"
+ "\307\377Z\213\307\377Z\213\307\377Y\212\307\377Y\212\307\377Y\212\307\377"
+ "Y\212\307\377Y\212\307\377X\212\307\377X\212\307\377X\212\307\377X\211\307"
+ "\377X\212\307\377X\212\307\377X\211\307\377X\211\307\377X\211\307\377X\211"
+ "\307\377X\211\307\377X\211\307\377X\211\307\377X\211\307\377X\211\307\377"
+ "X\211\307\377W\211\307\377W\211\307\377W\211\307\377W\211\307\377W\211\307"
+ "\377W\211\307\377V\211\307\377V\211\307\377V\210\307\377V\210\307\377`\217"
+ "\312\377<e\237\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+<b\231\377i\212\267\377"
+ "i\211\266\377i\211\266\377i\211\266\377i\211\266\377i\211\266\377h\211\266"
+ "\377h\211\266\377h\211\266\377h\211\266\377h\211\266\377h\211\266\377h\211"
+ "\266\377g\211\266\377g\211\266\377g\211\266\377g\211\266\377g\210\266\377"
+ "g\210\266\377g\211\266\377g\211\266\377g\210\266\377g\210\266\377g\210\265"
+ "\377g\210\265\377g\210\265\377g\210\265\377g\210\265\377g\210\265\377g\210"
+ "\265\377g\210\265\377g\210\265\377f\210\265\377f\210\264\377f\210\264\377"
+ "f\210\264\377f\210\264\377f\210\264\377f\210\264\377f\210\264\377f\207\264"
+ "\377f\207\264\3778^\226\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+b\177\251\377"
+ "\340\344\351\377\337\342\350\377\337\342\350\377\336\342\350\377\336\342\347"
+ "\377\336\342\347\377\336\342\347\377\336\342\347\377\335\341\347\377\335\341"
+ "\346\377\335\341\346\377\335\341\346\377\335\341\346\377\335\341\346\377\335"
+ "\341\346\377\335\341\346\377\335\341\346\377\334\340\346\377\334\340\346\377"
+ "\335\341\346\377\335\341\346\377\334\340\346\377\334\340\346\377\334\340\345"
+ "\377\334\340\345\377\334\340\345\377\333\337\345\377\333\337\345\377\333\337"
+ "\344\377\333\337\344\377\333\337\344\377\333\336\344\377\332\336\344\377\332"
+ "\336\344\377\332\336\344\377\332\336\343\377\332\336\343\377\332\336\343\377"
+ "\332\336\343\377\332\336\343\377\332\336\343\377\333\337\345\377a}\247\377"
+ "\40J\207+\0\0\0\0\0\0\0\0\40J\207+h\204\254\377\355\355\355\377\351\351\351"
+ "\377\351\351\352\377\351\351\351\377\351\351\351\377\351\351\351\377\351\351"
+ "\350\377\351\350\350\377\351\351\351\377\350\350\350\377\350\350\350\377\350"
+ "\350\350\377\350\350\350\377\350\350\350\377\350\350\350\377\350\350\350\377"
+ "\350\347\350\377\350\347\347\377\347\347\347\377\347\347\347\377\347\347\347"
+ "\377\347\347\347\377\347\347\347\377\347\347\347\377\346\347\347\377\347\346"
+ "\347\377\347\346\347\377\346\346\347\377\346\346\346\377\346\346\346\377\346"
+ "\346\346\377\346\346\346\377\346\346\346\377\346\346\346\377\346\346\346\377"
+ "\346\345\345\377\345\345\346\377\345\346\345\377\345\346\345\377\345\346\345"
+ "\377\345\345\345\377\351\351\351\377g\202\252\377\40J\207+\0\0\0\0\0\0\0\0"
+ "\40J\207+h\203\253\377\354\354\354\377\350\350\350\377\347\350\350\377\347"
+ "\347\350\377\347\347\350\377\347\347\347\377\347\347\347\377\347\347\347\377"
+ "\347\347\347\377\347\346\347\377\347\346\346\377\347\346\346\377\347\347\347"
+ "\377\346\346\346\377\346\346\346\377\346\346\346\377\346\346\346\377\346\346"
+ "\346\377\346\346\346\377\345\345\346\377\345\345\345\377\345\345\346\377\345"
+ "\346\346\377\345\345\345\377\345\345\345\377\345\345\345\377\345\345\345\377"
+ "\345\345\345\377\345\344\344\377\345\345\344\377\345\345\345\377\345\344\344"
+ "\377\344\344\344\377\344\344\344\377\344\344\344\377\344\344\344\377\343\344"
+ "\344\377\343\344\344\377\344\344\344\377\344\344\343\377\344\343\343\377\350"
+ "\350\350\377g\202\252\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+h\203\253\377\353"
+ "\352\352\377\346\346\346\377\346\345\345\377\345\346\346\377\345\346\345\377"
+ "\345\345\345\377\345\345\345\377\345\345\345\377\345\345\346\377\345\345\345"
+ "\377\345\345\345\377\345\345\345\377\345\345\345\377\345\345\345\377\345\345"
+ "\345\377\344\345\345\377\344\344\344\377\344\345\344\377\344\344\344\377\344"
+ "\344\344\377\344\344\344\377\344\344\344\377\344\344\344\377\343\343\344\377"
+ "\343\343\343\377\344\343\343\377\343\343\343\377\343\343\343\377\343\343\343"
+ "\377\343\343\343\377\343\343\343\377\342\343\343\377\342\343\343\377\343\342"
+ "\343\377\343\342\342\377\342\342\342\377\342\342\342\377\342\342\342\377\342"
+ "\342\342\377\342\342\342\377\342\342\342\377\346\346\346\377g\202\252\377"
+ "\40J\207+\0\0\0\0\0\0\0\0\40J\207+h\203\253\377\351\351\351\377\345\344\344"
+ "\377\344\344\344\377\344\344\344\377\344\344\344\377\344\344\344\377\343\344"
+ "\344\377\343\344\344\377\343\343\344\377\343\343\343\377\343\343\343\377\343"
+ "\343\343\377\343\343\343\377\343\343\343\377\343\343\343\377\343\343\343\377"
+ "\343\343\343\377\343\343\343\377\343\342\342\377\342\342\342\377\342\342\342"
+ "\377\342\342\342\377\342\342\342\377\342\341\342\377\342\342\342\377\342\342"
+ "\341\377\342\341\342\377\341\341\342\377\341\341\341\377\341\341\341\377\341"
+ "\341\341\377\341\341\341\377\341\341\341\377\341\340\341\377\341\341\341\377"
+ "\340\341\341\377\341\341\341\377\340\340\340\377\340\340\340\377\340\340\340"
+ "\377\340\340\340\377\346\345\346\377g\202\252\377\40J\207+\0\0\0\0\0\0\0\0"
+ "\40J\207+h\203\253\377\350\350\350\377\342\342\343\377\342\342\342\377\342"
+ "\342\342\377\342\342\342\377\342\342\342\377\342\342\342\377\342\342\342\377"
+ "\342\342\342\377\342\342\341\377\342\341\342\377\341\341\341\377\341\341\341"
+ "\377\341\341\341\377\341\341\341\377\341\341\341\377\341\341\341\377\341\341"
+ "\341\377\340\341\341\377\341\340\340\377\341\340\340\377\340\340\340\377\340"
+ "\340\340\377\340\340\340\377\340\340\340\377\340\340\340\377\340\340\340\377"
+ "\340\340\337\377\340\340\340\377\337\337\337\377\340\340\337\377\337\337\337"
+ "\377\337\337\337\377\337\337\337\377\337\337\337\377\337\337\337\377\337\337"
+ "\337\377\337\337\337\377\337\337\336\377\336\337\336\377\336\336\336\377\344"
+ "\344\344\377g\202\252\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+h\203\253\377\347"
+ "\347\347\377\341\341\341\377\341\341\341\377\341\341\341\377\341\341\341\377"
+ "\341\341\341\377\340\341\340\377\340\340\340\377\340\340\340\377\340\340\337"
+ "\377\340\340\340\377\340\340\340\377\340\340\337\377\337\340\337\377\337\340"
+ "\337\377\337\337\340\377\337\337\337\377\337\337\337\377\337\337\337\377\337"
+ "\337\337\377\337\337\337\377\337\337\337\377\336\337\337\377\336\337\336\377"
+ "\337\336\336\377\336\336\336\377\336\336\336\377\336\336\336\377\336\336\336"
+ "\377\336\336\336\377\336\336\336\377\336\336\335\377\336\335\335\377\336\335"
+ "\335\377\336\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335"
+ "\335\335\377\335\335\335\377\335\335\335\377\343\343\343\377g\202\252\377"
+ "\40J\207+\0\0\0\0\0\0\0\0\40J\207+h\203\253\377\345\346\346\377\337\337\340"
+ "\377\340\337\337\377\337\337\337\377\337\337\337\377\337\337\337\377\337\337"
+ "\337\377\337\337\336\377\337\336\336\377\336\336\337\377\336\336\337\377\336"
+ "\336\336\377\336\336\336\377\336\336\336\377\336\336\335\377\336\336\336\377"
+ "\336\335\336\377\336\336\336\377\335\335\336\377\335\335\335\377\335\335\335"
+ "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335"
+ "\334\377\334\335\335\377\334\334\334\377\334\334\334\377\334\334\334\377\334"
+ "\334\334\377\334\334\334\377\334\334\334\377\334\334\334\377\334\334\334\377"
+ "\334\333\334\377\333\333\333\377\333\333\333\377\333\333\333\377\333\333\333"
+ "\377\333\333\333\377\341\341\342\377f\202\252\377\40J\207+\0\0\0\0\0\0\0\0"
+ "\40J\207+h\203\253\377\345\345\345\377\336\335\336\377\336\335\335\377\335"
+ "\335\335\377\336\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377"
+ "\335\334\335\377\335\335\335\377\335\335\335\377\334\335\334\377\334\334\335"
+ "\377\334\335\334\377\334\334\334\377\334\334\334\377\334\334\334\377\334\334"
+ "\334\377\334\334\334\377\334\334\334\377\334\333\334\377\334\333\333\377\333"
+ "\333\333\377\333\333\333\377\333\333\333\377\333\333\333\377\333\333\333\377"
+ "\333\332\333\377\333\333\333\377\333\333\333\377\332\333\333\377\332\333\332"
+ "\377\332\332\332\377\332\332\332\377\332\332\332\377\332\332\332\377\332\332"
+ "\332\377\332\332\332\377\331\332\331\377\331\332\331\377\331\331\332\377\341"
+ "\341\341\377f\202\252\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+g\203\253\377\343"
+ "\343\344\377\334\334\334\377\334\333\334\377\334\334\334\377\333\333\334\377"
+ "\333\333\333\377\333\334\333\377\333\333\333\377\333\333\333\377\333\333\333"
+ "\377\333\333\333\377\333\333\333\377\333\332\333\377\333\332\333\377\333\332"
+ "\332\377\332\332\333\377\332\332\333\377\332\332\332\377\333\332\332\377\332"
+ "\332\332\377\332\332\332\377\331\332\332\377\331\332\331\377\331\332\331\377"
+ "\331\332\331\377\331\331\331\377\331\331\331\377\332\331\331\377\331\331\331"
+ "\377\331\331\331\377\331\331\331\377\330\330\331\377\330\330\330\377\331\330"
+ "\330\377\330\330\330\377\330\330\330\377\331\330\330\377\330\330\330\377\330"
+ "\330\330\377\330\330\330\377\330\330\330\377\337\337\337\377f\202\252\377"
+ "\40J\207+\0\0\0\0\0\0\0\0\40J\207+g\203\253\377\342\342\343\377\332\332\332"
+ "\377\332\332\332\377\332\332\332\377\332\332\332\377\331\332\332\377\331\332"
+ "\332\377\332\332\331\377\332\331\331\377\331\331\331\377\331\331\331\377\331"
+ "\331\331\377\331\331\331\377\331\331\331\377\331\331\331\377\331\331\331\377"
+ "\331\331\331\377\330\330\330\377\331\330\331\377\330\331\330\377\330\330\330"
+ "\377\330\330\330\377\330\330\330\377\330\330\327\377\327\330\330\377\330\330"
+ "\327\377\327\330\330\377\330\330\327\377\327\327\327\377\327\327\327\377\327"
+ "\327\327\377\327\327\327\377\327\327\327\377\327\327\327\377\327\327\327\377"
+ "\326\327\327\377\327\326\326\377\326\327\326\377\326\326\326\377\326\326\326"
+ "\377\326\326\326\377\336\336\337\377f\202\252\377\40J\207+\0\0\0\0\0\0\0\0"
+ "\40J\207+g\203\253\377\341\341\341\377\331\331\331\377\330\331\331\377\330"
+ "\330\330\377\330\330\330\377\330\330\330\377\330\330\330\377\330\330\330\377"
+ "\327\330\330\377\330\330\327\377\330\330\330\377\330\327\330\377\327\327\327"
+ "\377\327\330\327\377\327\330\327\377\327\327\327\377\327\327\327\377\327\327"
+ "\327\377\327\327\327\377\326\327\326\377\326\327\326\377\326\326\326\377\326"
+ "\326\326\377\326\326\326\377\326\326\326\377\326\326\326\377\326\326\326\377"
+ "\326\326\326\377\325\325\326\377\325\326\326\377\326\325\325\377\325\325\325"
+ "\377\325\326\325\377\325\325\325\377\325\325\325\377\325\325\325\377\325\325"
+ "\325\377\325\325\325\377\325\324\325\377\324\324\324\377\324\324\324\377\335"
+ "\335\335\377f\202\252\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+g\203\253\377\340"
+ "\340\340\377\327\327\327\377\327\327\327\377\326\327\327\377\327\326\327\377"
+ "\326\326\326\377\326\326\327\377\326\326\326\377\326\326\326\377\326\326\326"
+ "\377\326\326\326\377\326\326\326\377\326\326\325\377\325\326\325\377\325\326"
+ "\326\377\325\325\325\377\325\325\325\377\325\325\325\377\325\325\325\377\325"
+ "\325\325\377\325\325\325\377\324\325\325\377\325\325\325\377\324\325\325\377"
+ "\324\324\324\377\325\324\324\377\324\324\324\377\324\324\324\377\324\324\324"
+ "\377\323\324\324\377\324\323\324\377\323\324\324\377\323\324\324\377\323\324"
+ "\323\377\323\323\324\377\323\323\323\377\323\323\323\377\323\323\323\377\323"
+ "\323\323\377\323\323\323\377\323\323\323\377\334\334\334\377f\202\252\377"
+ "\40J\207+\0\0\0\0\0\0\0\0\40J\207+g\203\253\377\337\336\337\377\325\325\325"
+ "\377\325\325\325\377\325\325\325\377\325\325\325\377\325\325\325\377\325\325"
+ "\325\377\325\325\324\377\325\325\325\377\324\325\324\377\324\324\324\377\324"
+ "\324\324\377\324\324\324\377\324\324\324\377\324\324\324\377\324\324\324\377"
+ "\324\324\324\377\324\323\323\377\323\323\323\377\324\323\323\377\323\323\323"
+ "\377\323\323\323\377\324\323\323\377\323\323\323\377\323\323\323\377\323\322"
+ "\323\377\323\322\322\377\322\322\322\377\322\322\322\377\322\322\322\377\322"
+ "\322\322\377\322\322\322\377\322\322\322\377\322\322\322\377\321\322\321\377"
+ "\321\321\322\377\321\321\321\377\321\321\321\377\321\321\321\377\321\321\321"
+ "\377\321\321\321\377\334\333\334\377g\202\252\377\40J\207+\0\0\0\0\0\0\0\0"
+ "\40J\207+h\203\253\377\336\336\336\377\323\324\323\377\324\323\324\377\324"
+ "\323\323\377\323\324\323\377\323\323\323\377\323\323\323\377\323\323\323\377"
+ "\323\323\323\377\323\323\323\377\323\323\323\377\322\323\323\377\323\322\323"
+ "\377\322\322\323\377\322\322\322\377\322\322\322\377\322\322\322\377\322\322"
+ "\322\377\322\322\322\377\322\322\322\377\322\322\322\377\321\321\321\377\322"
+ "\321\321\377\321\321\321\377\321\321\321\377\321\321\321\377\321\321\321\377"
+ "\321\321\320\377\321\321\321\377\321\321\321\377\321\321\321\377\320\320\320"
+ "\377\320\320\320\377\320\320\320\377\320\320\320\377\320\317\320\377\317\320"
+ "\320\377\320\320\320\377\320\320\320\377\320\320\320\377\320\317\317\377\332"
+ "\332\332\377g\202\252\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+h\203\253\377\335"
+ "\335\335\377\322\322\322\377\322\322\322\377\322\322\322\377\321\322\322\377"
+ "\321\321\321\377\321\321\321\377\322\321\321\377\321\321\321\377\321\321\321"
+ "\377\321\321\321\377\321\321\321\377\321\320\321\377\320\321\320\377\320\320"
+ "\320\377\320\320\320\377\320\320\320\377\320\320\320\377\320\320\320\377\320"
+ "\320\320\377\320\320\320\377\320\320\320\377\320\320\320\377\317\320\320\377"
+ "\317\320\317\377\317\317\317\377\317\317\317\377\317\317\317\377\317\317\317"
+ "\377\317\317\317\377\316\317\317\377\317\317\317\377\317\316\316\377\316\316"
+ "\316\377\317\316\316\377\316\316\316\377\316\316\316\377\316\316\316\377\316"
+ "\316\316\377\316\316\316\377\316\316\316\377\331\331\331\377f\202\252\377"
+ "\40J\207+\0\0\0\0\0\0\0\0\40J\207+h\203\253\377\334\334\334\377\320\320\320"
+ "\377\320\320\320\377\320\320\320\377\320\320\320\377\320\320\320\377\320\320"
+ "\320\377\320\320\317\377\317\317\320\377\320\317\317\377\317\317\317\377\317"
+ "\317\317\377\317\317\317\377\317\317\317\377\317\317\317\377\317\317\317\377"
+ "\317\316\317\377\317\317\316\377\316\317\317\377\316\316\316\377\316\316\316"
+ "\377\316\316\316\377\316\316\316\377\316\316\316\377\316\316\316\377\316\315"
+ "\315\377\316\316\315\377\315\316\315\377\315\316\316\377\315\315\315\377\315"
+ "\315\315\377\315\315\315\377\315\315\315\377\315\314\315\377\315\315\315\377"
+ "\315\315\314\377\314\314\314\377\314\314\314\377\314\314\314\377\314\314\314"
+ "\377\314\314\314\377\331\330\331\377f\202\252\377\40J\207+\0\0\0\0\0\0\0\0"
+ "\40J\207+h\203\253\377\333\333\333\377\317\317\316\377\316\317\316\377\317"
+ "\316\316\377\317\316\316\377\316\317\316\377\316\316\316\377\316\316\316\377"
+ "\316\316\316\377\316\316\316\377\316\315\316\377\315\315\315\377\315\315\316"
+ "\377\315\315\315\377\315\315\315\377\315\315\315\377\315\315\315\377\315\315"
+ "\315\377\315\315\315\377\315\315\314\377\315\314\314\377\314\314\314\377\314"
+ "\315\314\377\314\314\314\377\314\314\314\377\314\314\314\377\314\314\314\377"
+ "\314\314\314\377\314\314\313\377\313\313\313\377\314\314\313\377\313\313\313"
+ "\377\313\313\313\377\313\313\313\377\313\313\313\377\313\313\313\377\313\313"
+ "\313\377\312\313\313\377\312\312\313\377\312\312\313\377\312\312\313\377\327"
+ "\327\327\377g\203\253\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+h\203\253\377\332"
+ "\332\332\377\315\315\315\377\314\315\315\377\315\315\315\377\315\315\315\377"
+ "\314\315\315\377\314\314\315\377\314\314\314\377\314\314\314\377\314\314\314"
+ "\377\314\314\314\377\314\314\314\377\314\314\314\377\313\314\314\377\313\314"
+ "\313\377\313\313\313\377\313\313\313\377\313\313\313\377\313\313\313\377\313"
+ "\313\313\377\313\313\313\377\313\312\313\377\313\313\313\377\312\313\313\377"
+ "\313\313\313\377\312\312\312\377\312\312\312\377\312\312\312\377\312\312\312"
+ "\377\312\312\312\377\312\312\312\377\312\312\311\377\312\311\311\377\312\312"
+ "\311\377\311\311\311\377\311\311\311\377\311\311\311\377\311\311\311\377\311"
+ "\311\311\377\311\311\311\377\311\311\311\377\327\326\327\377g\203\253\377"
+ "\40J\207+\0\0\0\0\0\0\0\0\40J\207+g\203\253\377\331\331\331\377\313\313\313"
+ "\377\313\313\313\377\313\313\313\377\313\313\313\377\313\313\313\377\313\313"
+ "\313\377\312\312\313\377\312\313\313\377\312\312\312\377\312\312\312\377\312"
+ "\312\312\377\312\312\312\377\312\312\312\377\312\312\312\377\311\311\311\377"
+ "\312\312\312\377\311\311\311\377\311\311\311\377\311\312\311\377\311\311\311"
+ "\377\311\311\311\377\311\311\311\377\311\311\311\377\311\311\311\377\311\310"
+ "\311\377\310\310\310\377\310\310\310\377\310\310\310\377\310\310\310\377\310"
+ "\310\310\377\310\310\310\377\310\310\310\377\310\310\310\377\310\310\310\377"
+ "\307\307\310\377\310\307\310\377\307\307\307\377\307\307\307\377\307\307\307"
+ "\377\307\307\307\377\325\325\325\377g\203\253\377\40J\207+\0\0\0\0\0\0\0\0"
+ "\40J\207+h\204\254\377\330\330\331\377\312\312\311\377\312\312\311\377\312"
+ "\311\311\377\311\311\311\377\311\311\311\377\311\311\311\377\310\311\311\377"
+ "\311\311\311\377\311\311\311\377\311\310\311\377\311\310\310\377\311\311\311"
+ "\377\310\310\311\377\310\310\310\377\310\310\310\377\310\310\310\377\310\310"
+ "\310\377\310\310\310\377\307\310\307\377\310\310\310\377\307\307\307\377\307"
+ "\307\310\377\307\307\307\377\307\307\307\377\307\307\307\377\307\307\307\377"
+ "\307\307\307\377\307\307\307\377\306\306\307\377\306\307\307\377\306\306\306"
+ "\377\306\306\306\377\306\306\306\377\306\306\306\377\306\306\306\377\306\306"
+ "\306\377\305\306\306\377\305\306\305\377\305\306\306\377\305\306\306\377\325"
+ "\325\325\377g\203\253\377\40J\207+\0\0\0\0\0\0\0\0\40J\207+h\204\254\377\327"
+ "\327\327\377\310\310\310\377\310\310\310\377\310\310\310\377\310\307\307\377"
+ "\307\307\310\377\307\307\307\377\307\307\307\377\307\307\307\377\307\307\307"
+ "\377\307\307\307\377\307\307\307\377\307\307\307\377\307\306\306\377\307\306"
+ "\306\377\306\307\306\377\307\306\306\377\306\306\306\377\306\306\306\377\306"
+ "\306\306\377\306\306\306\377\306\306\306\377\306\306\306\377\305\305\305\377"
+ "\305\305\306\377\306\305\305\377\305\305\305\377\305\305\306\377\305\305\305"
+ "\377\305\305\305\377\305\305\305\377\305\304\305\377\304\304\304\377\304\304"
+ "\305\377\304\304\305\377\304\304\304\377\304\304\304\377\304\304\304\377\304"
+ "\304\304\377\304\304\304\377\304\304\304\377\324\324\324\377g\203\253\377"
+ "\40J\207+\0\0\0\0\0\0\0\0\40J\207+h\204\254\377\326\326\326\377\307\306\306"
+ "\377\306\306\306\377\306\306\306\377\306\306\306\377\306\306\306\377\306\305"
+ "\306\377\306\306\305\377\305\305\306\377\305\306\306\377\305\305\305\377\305"
+ "\305\305\377\305\305\305\377\305\305\305\377\305\305\305\377\305\305\305\377"
+ "\305\304\305\377\305\305\305\377\304\304\305\377\304\304\304\377\304\304\304"
+ "\377\304\304\304\377\304\304\304\377\304\304\304\377\304\304\304\377\304\304"
+ "\303\377\304\303\304\377\303\303\304\377\303\303\304\377\303\303\303\377\303"
+ "\303\303\377\303\302\303\377\303\303\303\377\303\303\303\377\303\302\303\377"
+ "\303\302\303\377\303\302\303\377\302\302\302\377\302\302\302\377\302\302\302"
+ "\377\302\302\302\377\323\323\323\377h\204\254\377\40J\207+\0\0\0\0\0\0\0\0"
+ "\40J\206+h\204\254\377\325\325\325\377\304\305\305\377\305\305\305\377\305"
+ "\304\304\377\304\304\305\377\304\305\305\377\304\304\304\377\305\304\305\377"
+ "\304\305\304\377\304\304\304\377\304\304\304\377\304\304\303\377\304\304\304"
+ "\377\304\304\303\377\303\303\304\377\303\303\303\377\304\303\303\377\303\303"
+ "\303\377\303\303\303\377\303\303\303\377\303\302\303\377\303\302\303\377\302"
+ "\303\303\377\302\302\303\377\302\303\303\377\302\303\302\377\302\302\302\377"
+ "\302\302\302\377\301\301\301\377\301\301\301\377\301\301\301\377\301\301\301"
+ "\377\301\301\301\377\301\301\301\377\301\301\301\377\301\301\301\377\301\301"
+ "\301\377\301\301\301\377\301\301\301\377\301\301\301\377\301\301\301\377\322"
+ "\322\322\377g\203\253\377\37H\204,\0\0\0\0\0\0\0\1\35Cy0f\202\252\377\350"
+ "\350\350\377\344\344\344\377\344\344\344\377\344\344\344\377\344\344\344\377"
+ "\344\344\344\377\344\344\344\377\344\344\344\377\344\344\344\377\344\344\344"
+ "\377\344\344\344\377\344\344\344\377\344\344\344\377\344\344\344\377\344\344"
+ "\344\377\344\344\344\377\344\344\344\377\343\343\343\377\343\343\343\377\344"
+ "\344\344\377\344\343\344\377\343\343\343\377\343\343\343\377\343\343\343\377"
+ "\343\343\343\377\343\343\343\377\343\343\343\377\343\343\343\377\342\342\342"
+ "\377\342\342\342\377\342\342\342\377\342\342\342\377\342\342\342\377\342\342"
+ "\342\377\342\342\342\377\342\342\342\377\342\342\342\377\342\342\342\377\342"
+ "\342\342\377\342\342\342\377\342\342\342\377\347\347\347\377f\202\252\377"
+ "\33=p3\0\0\0\5\0\0\0\14\27""5`<+T\216\377<d\233\377<d\233\377<d\233\377<d"
+ "\233\377<d\233\377<d\233\377<c\233\377:b\232\377-U\217\377<c\233\377;c\233"
+ "\377:c\233\377:c\233\377:c\233\377:c\233\377:c\233\377:c\232\377:c\232\377"
+ ":b\232\377:c\232\377:c\232\377:b\232\377:b\232\377:b\232\3779b\232\3779b\232"
+ "\3779b\232\3779b\232\3779b\232\3779b\232\3779b\232\3779b\232\3778b\232\377"
+ "+U\217\3778a\231\3778a\232\3778a\232\3778a\232\3778a\232\3778a\232\3778a\232"
+ "\3777`\231\377)R\216\377\25""1YA\0\0\0\23\0\0\0\25\24.UD4_\234\377R\202\277"
+ "\377R\202\277\377R\202\277\377R\202\277\377R\202\277\377R\202\277\377R\202"
+ "\277\377N\177\273\3771^\232\377R\202\277\377Q\202\277\377Q\202\277\377Q\202"
+ "\277\377Q\202\277\377Q\202\277\377Q\202\277\377Q\201\276\377Q\201\276\377"
+ "P\201\276\377Q\201\276\377Q\201\276\377P\201\276\377P\201\276\377P\201\276"
+ "\377P\201\276\377P\201\276\377P\201\276\377P\201\276\377P\201\276\377P\201"
+ "\276\377O\200\276\377O\200\276\377O\200\276\3770]\232\377N\177\274\377O\200"
+ "\276\377O\200\276\377O\200\276\377N\200\276\377N\200\276\377N\200\276\377"
+ "N\177\275\3771]\233\377\22+OI\0\0\0\34\0\0\0\33\20%C@*S\214\377<b\227\377"
+ "<a\227\377<a\227\377<a\227\377<a\227\377<a\227\377<a\227\377;_\226\377-T\216"
+ "\377;`\226\377<a\227\377<a\227\377<a\227\377<a\227\377<a\227\377<a\227\377"
+ "<a\227\377<a\227\377<a\227\377;`\226\377;`\226\377;`\226\377;`\226\377;`\226"
+ "\377;`\226\377;`\226\377;`\226\377;`\226\377;`\226\377;`\226\377;`\226\377"
+ ";`\226\377;`\226\377-T\215\377;`\226\377;`\226\377;`\226\377;`\226\377;a\226"
+ "\377;a\226\377;a\226\377<a\227\377*R\214\377\17!=G\0\0\0#\0\0\0\33\3\7\14"
+ ")\31""6bw\30""4\\}\26""0W\205\25.S\214\25.R\215\25.R\215\25.R\215\25.R\215"
+ "\25.R\215\25.R\215\25.R\215\25.R\215\25.R\215\25.R\215\25.R\215\25.R\215\25"
+ ".R\215\25.R\215\25.R\215\25.R\215\25.R\215\25.R\215\25.R\215\25.R\215\25."
+ "R\215\25.R\215\25.R\215\25.R\215\25.R\215\25.R\215\25.R\215\25.R\215\25.R"
+ "\215\25.R\215\25.R\215\25.R\215\25.R\215\25.R\215\25.R\215\25.R\215\25-Q\217"
+ "\26/T\212\27""2Y\202\30""4^{\2\5\12""1\0\0\0#\0\0\0\25\0\0\0\37\0\0\0+\0\0"
+ "\0""4\0\0\0=\0\0\0C\0\0\0D\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0"
+ "C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0"
+ "\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0"
+ "C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0C\0\0\0D\0\0\0F\0\0\0B\0\0\0:\0"
+ "\0\0""2\0\0\0&\0\0\0\35\0\0\0\15\0\0\0\26\0\0\0\40\0\0\0'\0\0\0/\0\0\0""3"
+ "\0\0\0""3\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0"
+ "\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0"
+ "\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0"
+ """2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2"
+ "\0\0\0""2\0\0\0""2\0\0\0""3\0\0\0""5\0\0\0""2\0\0\0,\0\0\0%\0\0\0\34\0\0\0"
+ "\23\0\0\0\1\0\0\0\7\0\0\0\16\0\0\0\25\0\0\0\32\0\0\0\35\0\0\0\35\0\0\0\34"
+ "\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0"
+ "\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0"
+ "\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0"
+ "\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34\0\0\0\34"
+ "\0\0\0\35\0\0\0\36\0\0\0\35\0\0\0\30\0\0\0\23\0\0\0\12\0\0\0\3\0\0\0\0\0\0"
+ "\0\0\0\0\0\2\0\0\0\5\0\0\0\10\0\0\0\13\0\0\0\14\0\0\0\13\0\0\0\13\0\0\0\13"
+ "\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0"
+ "\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0"
+ "\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0"
+ "\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\13\0\0\0\14\0\0\0\14"
+ "\0\0\0\12\0\0\0\6\0\0\0\3\0\0\0\0\0\0\0\0");
 
index 2eb043a..6fbd8a8 100644 (file)
@@ -20,6 +20,7 @@
 #include "geom.h"
 #include "image.h"
 #include "color.h"
+#include "imagecache.h"
 
 #include <glib.h>
 
 #define FLOOR(i)        ((i) & (~0UL << FRACTION))
 #define AVERAGE(a, b)   (((((a) ^ (b)) & 0xfefefefeL) >> 1) + ((a) & (b)))
 
-static void ImageCopyResampled(RrPixel32 *dst, RrPixel32 *src,
-                               gulong dstW, gulong dstH,
-                               gulong srcW, gulong srcH)
+void RrImagePicInit(RrImagePic *pic, gint w, gint h, RrPixel32 *data)
 {
+    gint i;
+
+    pic->width = w;
+    pic->height = h;
+    pic->data = data;
+    pic->sum = 0;
+    for (i = w*h; i > 0; --i)
+        pic->sum += *(data++);
+}
+
+static void RrImagePicFree(RrImagePic *pic)
+{
+    if (pic) {
+        g_free(pic->data);
+        g_free(pic);
+    }
+}
+
+/*! Add a picture to an Image, that is, add another copy of the image at
+  another size.  This may add it to the "originals" list or to the
+  "resized" list. */
+static void AddPicture(RrImage *self, RrImagePic ***list, gint *len,
+                       RrImagePic *pic)
+{
+    gint i;
+
+    g_assert(pic->width > 0 && pic->height > 0);
+
+    g_assert(g_hash_table_lookup(self->cache->table, pic) == NULL);
+
+    /* grow the list */
+    *list = g_renew(RrImagePic*, *list, ++*len);
+
+    /* move everything else down one */
+    for (i = *len-1; i > 0; --i)
+        (*list)[i] = (*list)[i-1];
+
+    /* set the new picture up at the front of the list */
+    (*list)[0] = pic;
+
+    /* add the picture as a key to point to this image in the cache */
+    g_hash_table_insert(self->cache->table, (*list)[0], self);
+
+#ifdef DEBUG
+    g_message("Adding %s picture to the cache:\n    "
+              "Image 0x%x, w %d h %d Hash %u",
+              (*list == self->original ? "ORIGINAL" : "RESIZED"),
+              (guint)self, pic->width, pic->height, RrImagePicHash(pic));
+#endif
+}
+
+/*! Remove a picture from an Image.  This may remove it from the "originals"
+  list or the "resized" list. */
+static void RemovePicture(RrImage *self, RrImagePic ***list,
+                          gint i, gint *len)
+{
+    gint j;
+
+#ifdef DEBUG
+    g_message("Removing %s picture from the cache:\n    "
+              "Image 0x%x, w %d h %d Hash %u",
+              (*list == self->original ? "ORIGINAL" : "RESIZED"),
+              (guint)self, (*list)[i]->width, (*list)[i]->height,
+              RrImagePicHash((*list)[i]));
+#endif
+
+    /* remove the picture as a key in the cache */
+    g_hash_table_remove(self->cache->table, (*list)[i]);
+
+    /* free the picture */
+    RrImagePicFree((*list)[i]);
+    /* shift everything down one */
+    for (j = i; j < *len-1; ++j)
+        (*list)[j] = (*list)[j+1];
+    /* shrink the list */
+    *list = g_renew(RrImagePic*, *list, --*len);
+}
+
+/*! Given a picture in RGBA format, of a specified size, resize it to the new
+  requested size (but keep its aspect ratio).  If the image does not need to
+  be resized (it is already the right size) then this returns NULL.  Otherwise
+  it returns a newly allocated RrImagePic with the resized picture inside it
+*/
+static RrImagePic* ResizeImage(RrPixel32 *src,
+                               gulong srcW, gulong srcH,
+                               gulong dstW, gulong dstH)
+{
+    RrPixel32 *dst, *dststart;
+    RrImagePic *pic;
     gulong dstX, dstY, srcX, srcY;
     gulong srcX1, srcX2, srcY1, srcY2;
     gulong ratioX, ratioY;
+    gulong aspectW, aspectH;
+
+    /* keep the aspect ratio */
+    aspectW = dstW;
+    aspectH = (gint)(dstW * ((gdouble)srcH / srcW));
+    if (aspectH > dstH) {
+        aspectH = dstH;
+        aspectW = (gint)(dstH * ((gdouble)srcW / srcH));
+    }
+    dstW = aspectW;
+    dstH = aspectH;
+
+    if (srcW == dstW && srcH == dstH)
+        return NULL; /* no scaling needed ! */
+
+    dststart = dst = g_new(RrPixel32, dstW * dstH);
 
     ratioX = (srcW << FRACTION) / dstW;
     ratioY = (srcH << FRACTION) / dstH;
@@ -104,56 +208,47 @@ static void ImageCopyResampled(RrPixel32 *dst, RrPixel32 *src,
                      (alpha << RrDefaultAlphaOffset);
         }
     }
+
+    pic = g_new(RrImagePic, 1);
+    RrImagePicInit(pic, dstW, dstH, dststart);
+
+    return pic;
 }
 
-void RrImageDraw(RrPixel32 *target, RrTextureRGBA *rgba,
-                 gint target_w, gint target_h,
-                 RrRect *area)
+/*! This drawns an RGBA picture into the target, within the rectangle specified
+  by the area parameter.  If the area's size differs from the source's then it
+  will be centered within the rectangle */
+void DrawRGBA(RrPixel32 *target, gint target_w, gint target_h,
+              RrPixel32 *source, gint source_w, gint source_h,
+              gint alpha, RrRect *area)
 {
     RrPixel32 *dest;
-    RrPixel32 *source;
-    gint sw, sh, dw, dh;
     gint col, num_pixels;
+    gint dw, dh;
 
-    sw = rgba->width;
-    sh = rgba->height;
+    g_assert(source_w <= area->width && source_h <= area->height);
+    g_assert(area->x + area->width <= target_w);
+    g_assert(area->y + area->height <= target_h);
 
-    /* keep the ratio */
+    /* keep the aspect ratio */
     dw = area->width;
-    dh = (gint)(dw * ((gdouble)sh / sw));
+    dh = (gint)(dw * ((gdouble)source_h / source_w));
     if (dh > area->height) {
         dh = area->height;
-        dw = (gint)(dh * ((gdouble)sw / sh));
-    }
-
-    if (!(dw && dh))
-        return; /* XXX sanity check */
-
-    if (sw != dw || sh != dh) {
-        /*if (!(rgba->cache && dw == rgba->cwidth && dh == rgba->cheight))*/ {
-            g_free(rgba->cache);
-            rgba->cache = g_new(RrPixel32, dw * dh);
-            ImageCopyResampled(rgba->cache, rgba->data, dw, dh, sw, sh);
-            rgba->cwidth = dw;
-            rgba->cheight = dh;
-        }
-        source = rgba->cache;
-    } else {
-        source = rgba->data;
+        dw = (gint)(dh * ((gdouble)source_w / source_h));
     }
 
     /* copy source -> dest, and apply the alpha channel.
-
        center the image if it is smaller than the area */
     col = 0;
     num_pixels = dw * dh;
     dest = target + area->x + (area->width - dw) / 2 +
         (target_w * (area->y + (area->height - dh) / 2));
     while (num_pixels-- > 0) {
-        guchar alpha, r, g, b, bgr, bgg, bgb;
+        guchar a, r, g, b, bgr, bgg, bgb;
 
         /* apply the rgba's opacity as well */
-        alpha = ((*source >> RrDefaultAlphaOffset) * rgba->alpha) >> 8;
+        a = ((*source >> RrDefaultAlphaOffset) * alpha) >> 8;
         r = *source >> RrDefaultRedOffset;
         g = *source >> RrDefaultGreenOffset;
         b = *source >> RrDefaultBlueOffset;
@@ -163,9 +258,9 @@ void RrImageDraw(RrPixel32 *target, RrTextureRGBA *rgba,
         bgg = *dest >> RrDefaultGreenOffset;
         bgb = *dest >> RrDefaultBlueOffset;
 
-        r = bgr + (((r - bgr) * alpha) >> 8);
-        g = bgg + (((g - bgg) * alpha) >> 8);
-        b = bgb + (((b - bgb) * alpha) >> 8);
+        r = bgr + (((r - bgr) * a) >> 8);
+        g = bgg + (((g - bgg) * a) >> 8);
+        b = bgb + (((b - bgb) * a) >> 8);
 
         *dest = ((r << RrDefaultRedOffset) |
                  (g << RrDefaultGreenOffset) |
@@ -180,3 +275,223 @@ void RrImageDraw(RrPixel32 *target, RrTextureRGBA *rgba,
         }
     }
 }
+
+/*! Draw an RGBA texture into a target pixel buffer. */
+void RrImageDrawRGBA(RrPixel32 *target, RrTextureRGBA *rgba,
+                     gint target_w, gint target_h,
+                     RrRect *area)
+{
+    RrImagePic *scaled;
+
+    scaled = ResizeImage(rgba->data, rgba->width, rgba->height,
+                         area->width, area->height);
+
+    if (scaled) {
+#ifdef DEBUG
+            g_warning("Scaling an RGBA! You should avoid this and just make "
+                      "it the right size yourself!");
+#endif
+            DrawRGBA(target, target_w, target_h,
+                     scaled->data, scaled->width, scaled->height,
+                     rgba->alpha, area);
+    }
+    else
+        DrawRGBA(target, target_w, target_h,
+                 rgba->data, rgba->width, rgba->height,
+                 rgba->alpha, area);
+}
+
+/*! Create a new RrImage, which is linked to an image cache */
+RrImage* RrImageNew(RrImageCache *cache)
+{
+    RrImage *self;
+
+    g_assert(cache != NULL);
+
+    self = g_new0(RrImage, 1);
+    self->ref = 1;
+    self->cache = cache;
+    return self;
+}
+
+void RrImageRef(RrImage *self)
+{
+    ++self->ref;
+}
+
+void RrImageUnref(RrImage *self)
+{
+    if (self && --self->ref == 0) {
+#ifdef DEBUG
+        g_message("Refcount to 0, removing ALL pictures from the cache:\n    "
+                  "Image 0x%x", (guint)self);
+#endif
+        while (self->n_original > 0)
+            RemovePicture(self, &self->original, 0, &self->n_original);
+        while (self->n_resized > 0)
+            RemovePicture(self, &self->resized, 0, &self->n_resized);
+        g_free(self);
+    }
+}
+
+/*! Add a new picture with the given RGBA pixel data and dimensions into the
+  RrImage.  This adds an "original" picture to the image.
+*/
+void RrImageAddPicture(RrImage *self, RrPixel32 *data, gint w, gint h)
+{
+    gint i;
+    RrImagePic *pic;
+
+    /* make sure we don't already have this size.. */
+    for (i = 0; i < self->n_original; ++i)
+        if (self->original[i]->width == w && self->original[i]->height == h) {
+#ifdef DEBUG
+            g_message("Found duplicate ORIGINAL image:\n    "
+                      "Image 0x%x, w %d h %d", (guint)self, w, h);
+#endif
+            return;
+        }
+
+    /* remove any resized pictures of this same size */
+    for (i = 0; i < self->n_resized; ++i)
+        if (self->resized[i]->width == w || self->resized[i]->height == h) {
+            RemovePicture(self, &self->resized, i, &self->n_resized);
+            break;
+        }
+
+    /* add the new picture */
+    pic = g_new(RrImagePic, 1);
+    RrImagePicInit(pic, w, h, g_memdup(data, w*h*sizeof(RrPixel32)));
+    AddPicture(self, &self->original, &self->n_original, pic);
+}
+
+/*! Remove the picture from the RrImage which has the given dimensions. This
+ removes an "original" picture from the image.
+*/
+void RrImageRemovePicture(RrImage *self, gint w, gint h)
+{
+    gint i;
+
+    /* remove any resized pictures of this same size */
+    for (i = 0; i < self->n_original; ++i)
+        if (self->original[i]->width == w && self->original[i]->height == h) {
+            RemovePicture(self, &self->original, i, &self->n_original);
+            break;
+        }
+}
+
+/*! Draw an RrImage texture into a target pixel buffer.  If the RrImage does
+  not contain a picture of the appropriate size, then one of its "original"
+  pictures will be resized and used (and stored in the RrImage as a "resized"
+  picture).
+ */
+void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img,
+                      gint target_w, gint target_h,
+                      RrRect *area)
+{
+    gint i, min_diff, min_i, min_aspect_diff, min_aspect_i;
+    RrImage *self;
+    RrImagePic *pic;
+    gboolean free_pic;
+
+    self = img->image;
+    pic = NULL;
+    free_pic = FALSE;
+
+    /* is there an original of this size? (only w or h has to be right cuz
+       we maintain aspect ratios) */
+    for (i = 0; i < self->n_original; ++i)
+        if (self->original[i]->width == area->width ||
+            self->original[i]->height == area->height)
+        {
+            pic = self->original[i];
+            break;
+        }
+
+    /* is there a resize of this size? */
+    for (i = 0; i < self->n_resized; ++i)
+        if (self->resized[i]->width == area->width ||
+            self->resized[i]->height == area->height)
+        {
+            gint j;
+            RrImagePic *saved;
+
+            /* save the selected one */
+            saved = self->resized[i];
+
+            /* shift all the others down */
+            for (j = i; j > 0; --j)
+                self->resized[j] = self->resized[j-1];
+
+            /* and move the selected one to the top of the list */
+            self->resized[0] = saved;
+
+            pic = self->resized[0];
+            break;
+        }
+
+    if (!pic) {
+        gdouble aspect;
+
+        /* find an original with a close size */
+        min_diff = min_aspect_diff = -1;
+        min_i = min_aspect_i = 0;
+        aspect = ((gdouble)area->width) / area->height;
+        for (i = 0; i < self->n_original; ++i) {
+            gint diff;
+            gint wdiff, hdiff;
+            gdouble myasp;
+
+            /* our size difference metric.. */
+            wdiff = self->original[i]->width - area->width;
+            hdiff = self->original[i]->height - area->height;
+            diff = (wdiff * wdiff) + (hdiff * hdiff);
+
+            /* find the smallest difference */
+            if (min_diff < 0 || diff < min_diff) {
+                min_diff = diff;
+                min_i = i;
+            }
+            /* and also find the smallest difference with the same aspect
+               ratio (and prefer this one) */
+            myasp = ((gdouble)self->original[i]->width) /
+                self->original[i]->height;
+            if (ABS(aspect - myasp) < 0.0000001 &&
+                (min_aspect_diff < 0 || diff < min_aspect_diff))
+            {
+                min_aspect_diff = diff;
+                min_aspect_i = i;
+            }
+        }
+
+        /* use the aspect ratio correct source if there is one */
+        if (min_aspect_i >= 0)
+            min_i = min_aspect_i;
+
+        /* resize the original to the given area */
+        pic = ResizeImage(self->original[min_i]->data,
+                          self->original[min_i]->width,
+                          self->original[min_i]->height,
+                          area->width, area->height);
+
+        /* add the resized image to the image, as the first in the resized
+           list */
+        if (self->n_resized >= self->cache->max_resized_saved)
+            /* remove the last one (last used one) */
+            RemovePicture(self, &self->resized, self->n_resized - 1,
+                          &self->n_resized);
+        if (self->cache->max_resized_saved)
+            /* add it to the top of the resized list */
+            AddPicture(self, &self->resized, &self->n_resized, pic);
+        else
+            free_pic = TRUE; /* don't leak mem! */
+    }
+
+    g_assert(pic != NULL);
+
+    DrawRGBA(target, target_w, target_h,
+             pic->data, pic->width, pic->height,
+             img->alpha, area);
+    if (free_pic)
+        RrImagePicFree(pic);
+}
index 1c53596..b478daf 100644 (file)
 #include "render.h"
 #include "geom.h"
 
-void RrImageDraw(RrPixel32 *target, RrTextureRGBA *rgba,
-                 gint target_w, gint target_h,
-                 RrRect *area);
+/*! Initialize an RrImagePicture to the specified dimensions and pixel data */
+void RrImagePicInit(RrImagePic *pic, gint w, gint h, RrPixel32 *data);
+
+void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img,
+                      gint target_w, gint target_h,
+                      RrRect *area);
+void RrImageDrawRGBA(RrPixel32 *target, RrTextureRGBA *rgba,
+                     gint target_w, gint target_h,
+                     RrRect *area);
 
 #endif
diff --git a/render/imagecache.c b/render/imagecache.c
new file mode 100644 (file)
index 0000000..9c605f9
--- /dev/null
@@ -0,0 +1,144 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+   imagecache.c for the Openbox window manager
+   Copyright (c) 2008        Dana Jansens
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   See the COPYING file for a copy of the GNU General Public License.
+*/
+
+#include "render.h"
+#include "imagecache.h"
+#include "image.h"
+
+static gboolean RrImagePicEqual(const RrImagePic *p1,
+                                const RrImagePic *p2);
+
+RrImageCache* RrImageCacheNew(gint max_resized_saved)
+{
+    RrImageCache *self;
+
+    g_assert(max_resized_saved >= 0);
+
+    self = g_new(RrImageCache, 1);
+    self->ref = 1;
+    self->max_resized_saved = max_resized_saved;
+    self->table = g_hash_table_new((GHashFunc)RrImagePicHash,
+                                   (GEqualFunc)RrImagePicEqual);
+    return self;
+}
+
+void RrImageCacheRef(RrImageCache *self)
+{
+    ++self->ref;
+}
+
+void RrImageCacheUnref(RrImageCache *self)
+{
+    if (self && --self->ref == 0) {
+        g_assert(g_hash_table_size(self->table) == 0);
+        g_hash_table_unref(self->table);
+
+        g_free(self);
+    }
+}
+
+/*! Finds an image in the cache, if it is already in there */
+RrImage* RrImageCacheFind(RrImageCache *self,
+                          RrPixel32 *data, gint w, gint h)
+{
+    RrImagePic pic;
+
+    RrImagePicInit(&pic, w, h, data);
+    return g_hash_table_lookup(self->table, &pic);
+}
+
+#define hashsize(n) ((RrPixel32)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+/* mix -- mix 3 32-bit values reversibly. */
+#define mix(a,b,c) \
+{ \
+  a -= c;  a ^= rot(c, 4);  c += b; \
+  b -= a;  b ^= rot(a, 6);  a += c; \
+  c -= b;  c ^= rot(b, 8);  b += a; \
+  a -= c;  a ^= rot(c,16);  c += b; \
+  b -= a;  b ^= rot(a,19);  a += c; \
+  c -= b;  c ^= rot(b, 4);  b += a; \
+}
+/* final -- final mixing of 3 32-bit values (a,b,c) into c */
+#define final(a,b,c) \
+{ \
+  c ^= b; c -= rot(b,14); \
+  a ^= c; a -= rot(c,11); \
+  b ^= a; b -= rot(a,25); \
+  c ^= b; c -= rot(b,16); \
+  a ^= c; a -= rot(c,4);  \
+  b ^= a; b -= rot(a,14); \
+  c ^= b; c -= rot(b,24); \
+}
+
+/* This is a fast, reversable hash function called "lookup3", found here:
+   http://burtleburtle.net/bob/c/lookup3.c, by Bob Jenkins
+
+   This hashing algorithm is "reversible", that is, not cryptographically
+   secure at all.  But we don't care about that, we just want something to
+   tell when images are the same or different relatively quickly.
+*/
+guint32 hashword(const guint32 *key, gint length, guint32 initval)
+{
+    guint32 a,b,c;
+
+    /* Set up the internal state */
+    a = b = c = 0xdeadbeef + (((guint32)length)<<2) + initval;
+
+    /* handle most of the key */
+    while (length > 3)
+    {
+        a += key[0];
+        b += key[1];
+        c += key[2];
+        mix(a,b,c);
+        length -= 3;
+        key += 3;
+    }
+
+    /* handle the last 3 guint32's */
+    switch(length)      /* all the case statements fall through */
+    { 
+    case 3: c+=key[2];
+    case 2: b+=key[1];
+    case 1: a+=key[0];
+        final(a,b,c);
+    case 0:             /* case 0: nothing left to add */
+        break;
+    }
+    /* report the result */
+    return c;
+}
+
+/*! This is some arbitrary initial value for the hashing function.  It's
+  constant so that you get the same result from the same data each time.
+*/
+#define HASH_INITVAL 0xf00d
+
+guint RrImagePicHash(const RrImagePic *p)
+{
+    return hashword(p->data, p->width * p->height, HASH_INITVAL);
+}
+
+static gboolean RrImagePicEqual(const RrImagePic *p1,
+                                const RrImagePic *p2)
+{
+    return p1->width == p2->width && p1->height == p2->height &&
+        p1->sum == p2->sum;
+}
diff --git a/render/imagecache.h b/render/imagecache.h
new file mode 100644 (file)
index 0000000..4ad2dea
--- /dev/null
@@ -0,0 +1,51 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+   imagecache.h for the Openbox window manager
+   Copyright (c) 2008        Dana Jansens
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   See the COPYING file for a copy of the GNU General Public License.
+*/
+
+#ifndef __imagecache_h
+#define __imagecache_h
+
+#include <glib.h>
+
+struct _RrImagePic;
+
+guint RrImagePicHash(const struct _RrImagePic *p);
+
+/*! Create a new image cache.  An image cache is basically a hash table to look
+  up RrImages.  Each RrImage in the cache may contain one or more Pictures,
+  that is one or more actual copies of image data at various sizes.  For eg,
+  for a window, all of its various icons are loaded into the same RrImage.
+  When an RrImage is drawn and a picture inside it needs to be resized, that
+  is also saved within the RrImage.
+
+  For each picture that an RrImage has, the picture is hashed and that is used
+  as a key to find the RrImage.  So, given any picture in any RrImage in the
+  cache, if you hash it, you will find the RrImage.
+*/
+struct _RrImageCache {
+    gint ref;
+    /*! When an original picture is resized for an RrImage, the resized picture
+      is saved in the RrImage.  This specifies how many pictures should be
+      saved at a time.  When this is exceeded, the least recently used
+      "resized" picture is deleted.
+    */
+    gint max_resized_saved;
+
+    GHashTable *table;
+};
+
+#endif
index 4119dc7..fd442d2 100644 (file)
@@ -130,12 +130,40 @@ Pixmap RrPaintPixmap(RrAppearance *a, gint w, gint h)
             }
             RrPixmapMaskDraw(a->pixmap, &a->texture[i].data.mask, &tarea);
             break;
+        case RR_TEXTURE_IMAGE:
+            g_assert(!transferred);
+            {
+                RrRect narea = tarea;
+                RrTextureImage *img = &a->texture[i].data.image;
+                if (img->twidth)
+                    narea.width = MIN(tarea.width, img->twidth);
+                if (img->theight)
+                    narea.height = MIN(tarea.height, img->theight);
+                narea.x += img->tx;
+                narea.y += img->ty;
+                RrImageDrawImage(a->surface.pixel_data,
+                                 &a->texture[i].data.image,
+                                 a->w, a->h,
+                                 &narea);
+            }
+            force_transfer = 1;
+            break;
         case RR_TEXTURE_RGBA:
             g_assert(!transferred);
-            RrImageDraw(a->surface.pixel_data,
-                        &a->texture[i].data.rgba,
-                        a->w, a->h,
-                        &tarea);
+            {
+                RrRect narea = tarea;
+                RrTextureRGBA *rgb = &a->texture[i].data.rgba;
+                if (rgb->twidth)
+                    narea.width = MIN(tarea.width, rgb->twidth);
+                if (rgb->theight)
+                    narea.height = MIN(tarea.height, rgb->theight);
+                narea.x += rgb->tx;
+                narea.y += rgb->ty;
+                RrImageDrawRGBA(a->surface.pixel_data,
+                                &a->texture[i].data.rgba,
+                                a->w, a->h,
+                                &narea);
+            }
             force_transfer = 1;
         break;
         }
@@ -178,6 +206,12 @@ RrAppearance *RrAppearanceNew(const RrInstance *inst, gint numtex)
   return out;
 }
 
+void RrAppearanceRemoveTextures(RrAppearance *a)
+{
+    g_free(a->texture);
+    a->textures = 0;
+}
+
 void RrAppearanceAddTextures(RrAppearance *a, gint numtex)
 {
     g_assert(a->textures == 0);
@@ -186,11 +220,15 @@ void RrAppearanceAddTextures(RrAppearance *a, gint numtex)
     if (numtex) a->texture = g_new0(RrTexture, numtex);
 }
 
+void RrAppearanceClearTextures(RrAppearance *a)
+{
+    memset(a->texture, 0, a->textures * sizeof(RrTexture));
+}
+
 RrAppearance *RrAppearanceCopy(RrAppearance *orig)
 {
     RrSurface *spo, *spc;
     RrAppearance *copy = g_new(RrAppearance, 1);
-    gint i;
 
     copy->inst = orig->inst;
 
@@ -266,10 +304,6 @@ RrAppearance *RrAppearanceCopy(RrAppearance *orig)
     copy->textures = orig->textures;
     copy->texture = g_memdup(orig->texture,
                              orig->textures * sizeof(RrTexture));
-    for (i = 0; i < copy->textures; ++i)
-        if (copy->texture[i].type == RR_TEXTURE_RGBA) {
-            copy->texture[i].data.rgba.cache = NULL;
-        }
     copy->pixmap = None;
     copy->xftdraw = NULL;
     copy->w = copy->h = 0;
@@ -278,17 +312,10 @@ RrAppearance *RrAppearanceCopy(RrAppearance *orig)
 
 void RrAppearanceFree(RrAppearance *a)
 {
-    gint i;
-
     if (a) {
         RrSurface *p;
         if (a->pixmap != None) XFreePixmap(RrDisplay(a->inst), a->pixmap);
         if (a->xftdraw != NULL) XftDrawDestroy(a->xftdraw);
-        for (i = 0; i < a->textures; ++i)
-            if (a->texture[i].type == RR_TEXTURE_RGBA) {
-                g_free(a->texture[i].data.rgba.cache);
-                a->texture[i].data.rgba.cache = NULL;
-            }
         if (a->textures)
             g_free(a->texture);
         p = &a->surface;
@@ -367,6 +394,8 @@ gint RrMinWidth(RrAppearance *a)
     gint l, t, r, b;
     gint w = 0;
 
+    RrMargins(a, &l, &t, &r, &b);
+
     for (i = 0; i < a->textures; ++i) {
         switch (a->texture[i].type) {
         case RR_TEXTURE_NONE:
@@ -378,22 +407,25 @@ gint RrMinWidth(RrAppearance *a)
             m = RrFontMeasureString(a->texture[i].data.text.font,
                                     a->texture[i].data.text.string,
                                     a->texture[i].data.text.shadow_offset_x,
-                                    a->texture[i].data.text.shadow_offset_y);
+                                    a->texture[i].data.text.shadow_offset_y,
+                                    a->texture[i].data.text.flow,
+                                    a->texture[i].data.text.maxwidth);
             w = MAX(w, m->width);
             g_free(m);
             break;
         case RR_TEXTURE_RGBA:
             w += MAX(w, a->texture[i].data.rgba.width);
             break;
+        case RR_TEXTURE_IMAGE:
+            /* images resize so they don't contribute anything to the min */
+            break;
         case RR_TEXTURE_LINE_ART:
-            w += MAX(w, MAX(a->texture[i].data.lineart.x1,
-                            a->texture[i].data.lineart.x2));
+            w = MAX(w, MAX(a->texture[i].data.lineart.x1 - l - r,
+                           a->texture[i].data.lineart.x2 - l - r));
             break;
         }
     }
 
-    RrMargins(a, &l, &t, &r, &b);
-
     w += l + r;
 
     if (w < 1) w = 1;
@@ -404,8 +436,11 @@ gint RrMinHeight(RrAppearance *a)
 {
     gint i;
     gint l, t, r, b;
+    RrSize *m;
     gint h = 0;
 
+    RrMargins(a, &l, &t, &r, &b);
+
     for (i = 0; i < a->textures; ++i) {
         switch (a->texture[i].type) {
         case RR_TEXTURE_NONE:
@@ -414,21 +449,38 @@ gint RrMinHeight(RrAppearance *a)
             h = MAX(h, a->texture[i].data.mask.mask->height);
             break;
         case RR_TEXTURE_TEXT:
-            h += MAX(h, RrFontHeight(a->texture[i].data.text.font,
-                                     a->texture[i].data.text.shadow_offset_y));
+            if (a->texture[i].data.text.flow) {
+                g_assert(a->texture[i].data.text.string != NULL);
+
+                m = RrFontMeasureString
+                    (a->texture[i].data.text.font,
+                     a->texture[i].data.text.string,
+                     a->texture[i].data.text.shadow_offset_x,
+                     a->texture[i].data.text.shadow_offset_y,
+                     a->texture[i].data.text.flow,
+                     a->texture[i].data.text.maxwidth);
+                h += MAX(h, m->height);
+                g_free(m);
+            }
+            else
+                h += MAX(h,
+                         RrFontHeight
+                         (a->texture[i].data.text.font,
+                          a->texture[i].data.text.shadow_offset_y));
             break;
         case RR_TEXTURE_RGBA:
             h += MAX(h, a->texture[i].data.rgba.height);
             break;
+        case RR_TEXTURE_IMAGE:
+            /* images resize so they don't contribute anything to the min */
+            break;
         case RR_TEXTURE_LINE_ART:
-            h += MAX(h, MAX(a->texture[i].data.lineart.y1,
-                            a->texture[i].data.lineart.y2));
+            h = MAX(h, MAX(a->texture[i].data.lineart.y1 - t - b,
+                           a->texture[i].data.lineart.y2 - t - b));
             break;
         }
     }
 
-    RrMargins(a, &l, &t, &r, &b);
-
     h += t + b;
 
     if (h < 1) h = 1;
index 1f87c6e..f7bc504 100644 (file)
@@ -37,11 +37,15 @@ typedef struct _RrFont             RrFont;
 typedef struct _RrTexture          RrTexture;
 typedef struct _RrTextureMask      RrTextureMask;
 typedef struct _RrTextureRGBA      RrTextureRGBA;
+typedef struct _RrTextureImage     RrTextureImage;
 typedef struct _RrTextureText      RrTextureText;
 typedef struct _RrTextureLineArt   RrTextureLineArt;
 typedef struct _RrPixmapMask       RrPixmapMask;
 typedef struct _RrInstance         RrInstance;
 typedef struct _RrColor            RrColor;
+typedef struct _RrImage            RrImage;
+typedef struct _RrImagePic         RrImagePic;
+typedef struct _RrImageCache       RrImageCache;
 
 typedef guint32 RrPixel32;
 typedef guint16 RrPixel16;
@@ -76,7 +80,8 @@ typedef enum {
     RR_TEXTURE_MASK,
     RR_TEXTURE_TEXT,
     RR_TEXTURE_LINE_ART,
-    RR_TEXTURE_RGBA
+    RR_TEXTURE_RGBA,
+    RR_TEXTURE_IMAGE
 } RrTextureType;
 
 typedef enum {
@@ -137,10 +142,12 @@ struct _RrTextureText {
     gint shadow_offset_x;
     gint shadow_offset_y;
     RrColor *shadow_color;
-    guchar shadow_alpha;
     gboolean shortcut; /*!< Underline a character */
     guint shortcut_pos; /*!< Position in bytes of the character to underline */
     RrEllipsizeMode ellipsize;
+    gboolean flow; /* allow multiple lines.  must set maxwidth below */
+    gint maxwidth;
+    guchar shadow_alpha; /* at the bottom to improve alignment */
 };
 
 struct _RrPixmapMask {
@@ -161,10 +168,23 @@ struct _RrTextureRGBA {
     gint height;
     gint alpha;
     RrPixel32 *data;
-/* cached scaled so we don't have to scale often */
-    gint cwidth;
-    gint cheight;
-    RrPixel32 *cache;
+    /* size and position to draw at (if these are zero, then it will be
+       drawn to fill the entire texture */
+    gint tx;
+    gint ty;
+    gint twidth;
+    gint theight;
+};
+
+struct _RrTextureImage {
+    RrImage *image;
+    gint alpha;
+    /* size and position to draw at (if these are zero, then it will be
+       drawn to fill the entire texture */
+    gint tx;
+    gint ty;
+    gint twidth;
+    gint theight;
 };
 
 struct _RrTextureLineArt {
@@ -177,12 +197,15 @@ struct _RrTextureLineArt {
 
 union _RrTextureData {
     RrTextureRGBA rgba;
+    RrTextureImage image;
     RrTextureText text;
     RrTextureMask mask;
     RrTextureLineArt lineart;
 };
 
 struct _RrTexture {
+    /* If changing the type of a texture, you should DEFINITELY call
+       RrAppearanceClearTextures() first! */
     RrTextureType type;
     RrTextureData data;
 };
@@ -200,6 +223,35 @@ struct _RrAppearance {
     gint w, h;
 };
 
+/*! Holds a RGBA image picture */
+struct _RrImagePic {
+    gint width, height;
+    RrPixel32 *data;
+    /* The sum of all the pixels.  This is used to compare pictures if their
+       hashes match. */
+    gint sum;
+};
+
+/*! An RrImage is a sort of meta-image.  It can contain multiple versions of
+  an image at different sizes, which may or may not be completely different
+  pictures */
+struct _RrImage {
+    gint ref;
+    RrImageCache *cache;
+
+    /*! An array of "originals", that is of RrPictures that have been added
+      to the image in various sizes, and that have not been resized.  These
+      are explicitly added to the RrImage. */
+    RrImagePic **original;
+    gint n_original;
+    /*! An array of "resized" pictures.  When an "original" RrPicture
+      needs to be resized for drawing, it is saved in here so that it doesn't
+      need to be resized again.  These are automatically added to the
+      RrImage. */
+    RrImagePic **resized;
+    gint n_resized;
+};
+
 /* these are the same on all endian machines because it seems to be dependant
    on the endianness of the gfx card, not the cpu. */
 #define RrDefaultAlphaOffset 24
@@ -244,14 +296,18 @@ GC       RrColorGC    (RrColor *c);
 RrAppearance *RrAppearanceNew  (const RrInstance *inst, gint numtex);
 RrAppearance *RrAppearanceCopy (RrAppearance *a);
 void          RrAppearanceFree (RrAppearance *a);
+void          RrAppearanceRemoveTextures(RrAppearance *a);
 void          RrAppearanceAddTextures(RrAppearance *a, gint numtex);
+/*! Always call this when changing the type of a texture in an appearance */
+void          RrAppearanceClearTextures(RrAppearance *a);
 
 RrFont *RrFontOpen          (const RrInstance *inst, const gchar *name,
                              gint size, RrFontWeight weight, RrFontSlant slant);
 RrFont *RrFontOpenDefault   (const RrInstance *inst);
 void    RrFontClose         (RrFont *f);
 RrSize *RrFontMeasureString (const RrFont *f, const gchar *str,
-                             gint shadow_offset_x, gint shadow_offset_y);
+                             gint shadow_offset_x, gint shadow_offset_y,
+                             gboolean flow, gint maxwidth);
 gint    RrFontHeight        (const RrFont *f, gint shadow_offset_y);
 gint    RrFontMaxCharWidth  (const RrFont *f);
 
@@ -262,6 +318,8 @@ Pixmap RrPaintPixmap (RrAppearance *a, gint w, gint h);
 void   RrPaint       (RrAppearance *a, Window win, gint w, gint h);
 void   RrMinSize     (RrAppearance *a, gint *w, gint *h);
 gint   RrMinWidth    (RrAppearance *a);
+/* For text textures, if flow is TRUE, then the string must be set before
+   calling this, otherwise it doesn't need to be */
 gint   RrMinHeight   (RrAppearance *a);
 void   RrMargins     (RrAppearance *a, gint *l, gint *t, gint *r, gint *b);
 
@@ -269,6 +327,24 @@ gboolean RrPixmapToRGBA(const RrInstance *inst,
                         Pixmap pmap, Pixmap mask,
                         gint *w, gint *h, RrPixel32 **data);
 
+/*! Create a new image cache for RrImages.
+  @param max_resized_saved The number of resized copies of an image to save
+*/
+RrImageCache* RrImageCacheNew(gint max_resized_saved);
+void          RrImageCacheRef(RrImageCache *self);
+void          RrImageCacheUnref(RrImageCache *self);
+
+/*! Finds an image in the cache, if it is already in there */
+RrImage*      RrImageCacheFind(RrImageCache *self,
+                               RrPixel32 *data, gint w, gint h);
+
+RrImage* RrImageNew(RrImageCache *cache);
+void     RrImageRef(RrImage *im);
+void     RrImageUnref(RrImage *im);
+
+void     RrImageAddPicture(RrImage *im, RrPixel32 *data, gint w, gint h);
+void     RrImageRemovePicture(RrImage *im, gint w, gint h);
+
 G_END_DECLS
 
 #endif /*__render_h*/
index 307e262..36c962d 100644 (file)
@@ -44,6 +44,7 @@ gint main()
     Window win;
     RrInstance *inst;
     RrAppearance *look;
+    int done;
 
     Window root;
     XEvent report;
@@ -67,8 +68,10 @@ gint main()
     inst = RrInstanceNew(ob_display, ob_screen);
 
     look = RrAppearanceNew(inst, 0);
-    look->surface.grad = RR_SURFACE_PYRAMID;
+    look->surface.grad = RR_SURFACE_MIRROR_HORIZONTAL;
     look->surface.secondary = RrColorParse(inst, "Yellow");
+    look->surface.split_secondary = RrColorParse(inst, "Red");
+    look->surface.split_primary = RrColorParse(inst, "Green");
     look->surface.primary = RrColorParse(inst, "Blue");
     look->surface.interlaced = FALSE;
     if (ob_display == NULL) {
@@ -76,8 +79,21 @@ gint main()
         return 0;
     }
 
+#if BIGTEST
+    int i;
+    look->surface.pixel_data = g_new(RrPixel32, w*h);
+    for (i = 0; i < 10000; ++i) {
+        printf("\r%d", i);
+        fflush(stdout);
+        RrRender(look, w, h);
+    }
+    exit (0);
+#endif
+
+
     RrPaint(look, win, w, h);
-    while (1) {
+    done = 0;
+    while (!done) {
         XNextEvent(ob_display, &report);
         switch (report.type) {
         case Expose:
@@ -87,8 +103,10 @@ gint main()
                     report.xconfigure.width,
                     report.xconfigure.height);
             break;
+        case UnmapNotify:
+            done = 1;
+            break;
         }
-
     }
 
     RrAppearanceFree (look);
index 9fc1f1d..cd2352c 100644 (file)
@@ -177,9 +177,14 @@ RrTheme* RrThemeNew(const RrInstance *inst, const gchar *name,
         theme->osd_font = RrFontOpenDefault(inst);
 
     /* load direct dimensions */
-    if (!read_int(db, "menu.overlap", &theme->menu_overlap) ||
-        theme->menu_overlap < -100 || theme->menu_overlap > 100)
-        theme->menu_overlap = 0;
+    if ((!read_int(db, "menu.overlap.x", &theme->menu_overlap_x) &&
+         !read_int(db, "menu.overlap", &theme->menu_overlap_x)) ||
+        theme->menu_overlap_x < -100 || theme->menu_overlap_x > 100)
+        theme->menu_overlap_x = 0;
+    if ((!read_int(db, "menu.overlap.y", &theme->menu_overlap_y) &&
+         !read_int(db, "menu.overlap", &theme->menu_overlap_y)) ||
+        theme->menu_overlap_y < -100 || theme->menu_overlap_y > 100)
+        theme->menu_overlap_y = 0;
     if (!read_int(db, "window.handle.width", &theme->handle_height) ||
         theme->handle_height < 0 || theme->handle_height > 100)
         theme->handle_height = 6;
@@ -542,6 +547,9 @@ RrTheme* RrThemeNew(const RrInstance *inst, const gchar *name,
     theme->def_win_icon = read_c_image(OB_DEFAULT_ICON_WIDTH,
                                        OB_DEFAULT_ICON_HEIGHT,
                                        OB_DEFAULT_ICON_pixel_data);
+    theme->def_win_icon_w = OB_DEFAULT_ICON_WIDTH;
+    theme->def_win_icon_h = OB_DEFAULT_ICON_HEIGHT;
+
 
     /* read the decoration textures */
     if (!read_appearance(db, inst,
index 2c35284..1940970 100644 (file)
@@ -45,7 +45,8 @@ struct _RrTheme {
     gint obwidth; /*!< osd border width */
     gint cbwidthx;
     gint cbwidthy;
-    gint menu_overlap;
+    gint menu_overlap_x;
+    gint menu_overlap_y;
     /* these ones are calculated, not set directly by the theme file */
     gint win_font_height;
     gint menu_title_font_height;
@@ -106,7 +107,9 @@ struct _RrTheme {
     gchar    menu_text_disabled_selected_shadow_alpha;
 
     /* style settings - pics */
-    RrPixel32 *def_win_icon; /* 48x48 RGBA */
+    RrPixel32 *def_win_icon; /* RGBA */
+    gint       def_win_icon_w;
+    gint       def_win_icon_h;
 
     /* style settings - masks */
     RrPixmapMask *max_mask;
index 6374b39..9fc5fe8 100644 (file)
@@ -3,4 +3,4 @@ files=$(wildcard *.c)
 all: $(files:.c=)
 
 %: %.c
-       $(CC) $(CFLAGS) -o $@ $^ -lX11 -lXext -L/usr/X11R6/lib -I/usr/X11R6/include
+       $(CC) `pkg-config --cflags --libs glib-2.0` $(CFLAGS) -o $@ $^ -lX11 -lXext -L/usr/X11R6/lib -I/usr/X11R6/include
index 315a10f..af13e0b 100644 (file)
@@ -23,6 +23,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <assert.h>
+#include <glib.h>
 
 Window findClient(Display *d, Window win)
 {
@@ -156,6 +157,70 @@ int main(int argc, char **argv)
         ++image;
     } while (ret_bytesleft > 0 && image < MAX_IMAGES);
 
+#define hashsize(n) ((guint32)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+
+#define mix(a,b,c) \
+{ \
+  a -= c;  a ^= rot(c, 4);  c += b; \
+  b -= a;  b ^= rot(a, 6);  a += c; \
+  c -= b;  c ^= rot(b, 8);  b += a; \
+  a -= c;  a ^= rot(c,16);  c += b; \
+  b -= a;  b ^= rot(a,19);  a += c; \
+  c -= b;  c ^= rot(b, 4);  b += a; \
+}
+
+#define final(a,b,c) \
+{ \
+  c ^= b; c -= rot(b,14); \
+  a ^= c; a -= rot(c,11); \
+  b ^= a; b -= rot(a,25); \
+  c ^= b; c -= rot(b,16); \
+  a ^= c; a -= rot(c,4);  \
+  b ^= a; b -= rot(a,14); \
+  c ^= b; c -= rot(b,24); \
+}
+
+    /* hash the images */
+    for (j = 0; j < image; ++j) {
+        unsigned int w, h, length;
+        guint32 a,b,c;
+        guint32 initval = 0xf00d;
+        const guint32 *k = (guint32*)i[j]->data;
+
+        w = i[j]->width;
+        h = i[j]->height;
+        length = w * h;
+
+        /* Set up the internal state */
+        a = b = c = 0xdeadbeef + (((guint32)length)<<2) + initval;
+
+        /*---------------------------------------- handle most of the key */
+        while (length > 3)
+        {
+            a += k[0];
+            b += k[1];
+            c += k[2];
+            mix(a,b,c);
+            length -= 3;
+            k += 3;
+        }
+
+        /*--------------------------------- handle the last 3 uint32_t's */
+        switch(length)           /* all the case statements fall through */
+        {
+        case 3 : c+=k[2];
+        case 2 : b+=k[1];
+        case 1 : a+=k[0];
+            final(a,b,c);
+        case 0:     /* case 0: nothing left to add */
+            break;
+        }
+        /*------------------------------------ report the result */
+        printf("image[%d] %ux%u %lu\n", j, w, h, c);
+    }
+
     win = XCreateSimpleWindow(d, RootWindow(d, s), 0, 0, winw, winh,
                               0, 0, 0);
     assert(win);
index daa9117..95ee5f5 100755 (executable)
@@ -67,7 +67,7 @@ def main(argv=sys.argv):
         if list: autofile.list(environments)
         else: autofile.run(environments)
 
-class AutostartFile():
+class AutostartFile:
     def __init__(self, path):
         self.path = path
         self.filename = os.path.basename(path)
@@ -178,7 +178,7 @@ def show_help():
     print "  --version     Show version and copyright information"
     print
     print "ENVIRONMENT specifies a list of environments for which to run autostart"
-    print "applications for.  If none are specified, only applications which do not "
+    print "applications.  If none are specified, only applications which do not "
     print "limit themselves to certain environments will be run."
     print
     print "ENVIRONMENT can be one or more of:"