summaryrefslogtreecommitdiff
path: root/libwnck/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'libwnck/widgets')
-rw-r--r--libwnck/widgets/libwnck-3.map6
-rw-r--r--libwnck/widgets/meson.build116
-rw-r--r--libwnck/widgets/pager-accessible-factory.c69
-rw-r--r--libwnck/widgets/pager-accessible-factory.h52
-rw-r--r--libwnck/widgets/pager-accessible.c382
-rw-r--r--libwnck/widgets/pager-accessible.h53
-rw-r--r--libwnck/widgets/pager.c2973
-rw-r--r--libwnck/widgets/pager.h125
-rw-r--r--libwnck/widgets/private.h60
-rw-r--r--libwnck/widgets/selector.c1278
-rw-r--r--libwnck/widgets/selector.h70
-rw-r--r--libwnck/widgets/tasklist.c4895
-rw-r--r--libwnck/widgets/tasklist.h143
-rw-r--r--libwnck/widgets/test-pager.c116
-rw-r--r--libwnck/widgets/test-selector.c77
-rw-r--r--libwnck/widgets/test-tasklist.c167
-rw-r--r--libwnck/widgets/util.c141
-rw-r--r--libwnck/widgets/window-action-menu.c1172
-rw-r--r--libwnck/widgets/window-action-menu.h75
-rw-r--r--libwnck/widgets/wnck-image-menu-item-private.h46
-rw-r--r--libwnck/widgets/wnck-image-menu-item.c240
-rw-r--r--libwnck/widgets/wnck.css3
-rw-r--r--libwnck/widgets/wnck.gresource.xml6
-rw-r--r--libwnck/widgets/workspace-accessible-factory.c62
-rw-r--r--libwnck/widgets/workspace-accessible-factory.h52
-rw-r--r--libwnck/widgets/workspace-accessible.c225
-rw-r--r--libwnck/widgets/workspace-accessible.h55
-rw-r--r--libwnck/widgets/xutils.c394
-rw-r--r--libwnck/widgets/xutils.h87
29 files changed, 13140 insertions, 0 deletions
diff --git a/libwnck/widgets/libwnck-3.map b/libwnck/widgets/libwnck-3.map
new file mode 100644
index 0000000..7b17479
--- /dev/null
+++ b/libwnck/widgets/libwnck-3.map
@@ -0,0 +1,6 @@
+{
+global:
+ wnck_*;
+local:
+ *;
+};
diff --git a/libwnck/widgets/meson.build b/libwnck/widgets/meson.build
new file mode 100644
index 0000000..ceaa95d
--- /dev/null
+++ b/libwnck/widgets/meson.build
@@ -0,0 +1,116 @@
+gnome = import('gnome')
+
+libwnck_includedir = join_paths(PACKAGE_NAME, meson.project_name())
+
+headers = [
+ 'pager.h',
+ 'selector.h',
+ 'tasklist.h',
+ 'window-action-menu.h',
+]
+
+sources = [
+ 'pager.c',
+ 'selector.c',
+ 'tasklist.c',
+ 'util.c',
+ 'window-action-menu.c',
+ 'wnck-image-menu-item-private.h',
+ 'wnck-image-menu-item.c',
+ 'xutils.c',
+]
+
+a11y_sources = [
+ 'pager-accessible.c',
+ 'pager-accessible.h',
+ 'pager-accessible-factory.c',
+ 'pager-accessible-factory.h',
+ 'workspace-accessible.c',
+ 'workspace-accessible.h',
+ 'workspace-accessible-factory.c',
+ 'workspace-accessible-factory.h',
+]
+
+enum_types = gnome.mkenums_simple('wnck-widgets-enum-types',
+ sources : headers,
+ install_header: true,
+ install_dir: join_paths(includedir, libwnck_includedir)
+)
+
+resources = gnome.compile_resources(
+ '@0@-resources'.format(meson.project_name()),
+ 'wnck.gresource.xml',
+ source_dir: '.',
+ c_name: meson.project_name()
+)
+
+libwnck_cflags = [
+ '-DG_LOG_DOMAIN="Wnck"',
+ '-DWNCK_I_KNOW_THIS_IS_UNSTABLE',
+ '-DWNCK_LOCALEDIR="@0@"'.format(localedir),
+ '-DWNCK_COMPILATION',
+ '-DSN_API_NOT_YET_FROZEN=1'
+]
+
+mapfile = MODULE_NAME + '.map'
+libwnck_ldflags = [
+ '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile),
+]
+
+if get_option('deprecation_flags')
+ foreach domain: ['G', 'ATK', 'GDK', 'GDK_PIXBUF', 'GTK', 'WNCK']
+ libwnck_cflags += '-D@0@_DISABLE_DEPRECATED'.format(domain)
+ endforeach
+endif
+
+libwnck_widgets_dep = declare_dependency(
+ link_with: libwnck_lib,
+ include_directories: default_includes,
+ dependencies: LIBWNCK_DEPS,
+ compile_args: libwnck_cflags,
+ sources: headers + [enum_types[1]],
+ link_args: libwnck_ldflags,
+)
+
+MODULE_NAME_W = meson.project_name() + '-widgets-@0@'.format(LIBWNCK_CURRENT)
+LIBNAME_W = MODULE_NAME_W.split('lib')[1]
+
+libwnck_widgets_lib = shared_library(LIBNAME_W,
+ dependencies: libwnck_widgets_dep,
+ sources: sources + a11y_sources + enum_types + resources,
+ version: '@0@.@1@.@2@'.format(LIBWNCK_SOVERSION, LIBWNCK_CURRENT, LIBWNCK_REVISION),
+ soversion: LIBWNCK_SOVERSION,
+ link_depends: mapfile,
+ install: true,
+)
+
+introspection = get_option('introspection')
+if not introspection.disabled()
+ find_program('g-ir-scanner', required: introspection.enabled())
+ gnome.generate_gir(libwnck_widgets_lib,
+ sources: headers + sources + enum_types,
+ namespace: 'Wnck',
+ nsversion: MODULE_VERSION,
+ export_packages: PACKAGE_NAME,
+ includes: ['GObject-2.0', 'GdkPixbuf-2.0', 'Gtk-3.0'],
+ extra_args: '--c-include=@0@/@0@.h'.format(meson.project_name()),
+ install: true
+ )
+endif
+
+install_headers(headers, subdir: libwnck_includedir)
+
+test_progs = [
+ 'test-tasklist',
+ 'test-selector',
+ 'test-pager',
+]
+
+foreach prog: test_progs
+ executable(prog, [prog + '.c'],
+ include_directories: default_includes,
+ dependencies: libwnck_widgets_dep,
+ link_with: libwnck_widgets_lib,
+ install: false,
+ install_dir: bindir)
+endforeach
diff --git a/libwnck/widgets/pager-accessible-factory.c b/libwnck/widgets/pager-accessible-factory.c
new file mode 100644
index 0000000..2773f28
--- /dev/null
+++ b/libwnck/widgets/pager-accessible-factory.c
@@ -0,0 +1,69 @@
+/* vim: set sw=2 et: */
+/*
+ * Copyright 2002 Sun Microsystems Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtk/gtk.h>
+#include "pager-accessible-factory.h"
+#include "pager-accessible.h"
+
+G_DEFINE_TYPE (WnckPagerAccessibleFactory,
+ wnck_pager_accessible_factory, ATK_TYPE_OBJECT_FACTORY);
+
+static AtkObject* wnck_pager_accessible_factory_create_accessible (GObject *obj);
+
+static GType wnck_pager_accessible_factory_get_accessible_type (void);
+
+static void
+wnck_pager_accessible_factory_class_init (WnckPagerAccessibleFactoryClass *klass)
+{
+ AtkObjectFactoryClass *class = ATK_OBJECT_FACTORY_CLASS (klass);
+
+ class->create_accessible = wnck_pager_accessible_factory_create_accessible;
+ class->get_accessible_type = wnck_pager_accessible_factory_get_accessible_type;
+}
+
+static void
+wnck_pager_accessible_factory_init (WnckPagerAccessibleFactory *factory)
+{
+}
+
+AtkObjectFactory*
+wnck_pager_accessible_factory_new (void)
+{
+ GObject *factory;
+
+ factory = g_object_new (WNCK_TYPE_PAGER_ACCESSIBLE_FACTORY, NULL);
+
+ return ATK_OBJECT_FACTORY (factory);
+}
+
+static AtkObject*
+wnck_pager_accessible_factory_create_accessible (GObject *obj)
+{
+ GtkWidget *widget;
+
+ g_return_val_if_fail (GTK_IS_WIDGET (obj), NULL);
+
+ widget = GTK_WIDGET (obj);
+ return wnck_pager_accessible_new (widget);
+}
+
+static GType
+wnck_pager_accessible_factory_get_accessible_type (void)
+{
+ return WNCK_PAGER_TYPE_ACCESSIBLE;
+}
diff --git a/libwnck/widgets/pager-accessible-factory.h b/libwnck/widgets/pager-accessible-factory.h
new file mode 100644
index 0000000..8e2fe30
--- /dev/null
+++ b/libwnck/widgets/pager-accessible-factory.h
@@ -0,0 +1,52 @@
+/* vim: set sw=2 et: */
+/*
+ * Copyright 2002 Sun Microsystems Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __WNCK_PAGER_ACCESSIBLE_FACTORY_H__
+#define __WBCK_PAGER_ACCESSIBLE_FACTORY_H__
+
+#include <atk/atk.h>
+
+G_BEGIN_DECLS
+
+#define WNCK_TYPE_PAGER_ACCESSIBLE_FACTORY (wnck_pager_accessible_factory_get_type())
+#define WNCK_PAGER_ACCESSIBLE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WNCK_TYPE_PAGER_ACCESSIBLE_FACTORY, WnckpagerAccessibleFactory))
+#define WNCK_PAGER_ACCESSIBLE_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WNCK_TYPE_PAGER_ACCESSIBLE_FACTORY, WnckPagerAccessibleFactoryClass))
+#define WNCK_IS_PAGER_ACCESSIBLE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WNCK_TYPE_PAGER_ACCESSIBLE_FACTORY))
+#define WNCK_IS_PAGER_ACCESSIBLE_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WNCK_TYPE_PAGER_ACCESSIBLE_FACTORY))
+#define WNCK_PAGER_ACCESSIBLE_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WNCK_TYPE_PAGER_ACCESSIBLE_FACTORY, WnckPagerAccessibleFactoryClass))
+
+typedef struct _WnckPagerAccessibleFactory WnckPagerAccessibleFactory;
+typedef struct _WnckPagerAccessibleFactoryClass WnckPagerAccessibleFactoryClass;
+
+struct _WnckPagerAccessibleFactory
+{
+ AtkObjectFactory parent;
+};
+
+struct _WnckPagerAccessibleFactoryClass
+{
+ AtkObjectFactoryClass parent_class;
+};
+
+GType wnck_pager_accessible_factory_get_type (void) G_GNUC_CONST;
+
+AtkObjectFactory* wnck_pager_accessible_factory_new (void);
+
+G_END_DECLS
+
+#endif /* __WNCK_PAGER_ACCESSIBLE_FACTORY_H__ */
diff --git a/libwnck/widgets/pager-accessible.c b/libwnck/widgets/pager-accessible.c
new file mode 100644
index 0000000..6c4b2e8
--- /dev/null
+++ b/libwnck/widgets/pager-accessible.c
@@ -0,0 +1,382 @@
+/* vim: set sw=2 et: */
+/*
+ * Copyright 2002 Sun Microsystems Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <libwnck/libwnck.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <atk/atk.h>
+#include "pager-accessible.h"
+#include "pager-accessible-factory.h"
+#include "workspace-accessible.h"
+#include "private.h"
+
+typedef struct _WnckPagerAccessiblePrivate WnckPagerAccessiblePrivate;
+struct _WnckPagerAccessiblePrivate
+{
+ GSList *children;
+};
+
+static const char* wnck_pager_accessible_get_name (AtkObject *obj);
+static const char* wnck_pager_accessible_get_description (AtkObject *obj);
+static int wnck_pager_accessible_get_n_children (AtkObject *obj);
+static AtkObject* wnck_pager_accessible_ref_child (AtkObject *obj,
+ int i);
+static void atk_selection_interface_init (AtkSelectionIface *iface);
+static gboolean wnck_pager_add_selection (AtkSelection *selection,
+ int i);
+static gboolean wnck_pager_is_child_selected (AtkSelection *selection,
+ int i);
+static AtkObject* wnck_pager_ref_selection (AtkSelection *selection,
+ int i);
+static int wnck_pager_selection_count (AtkSelection *selection);
+static void wnck_pager_accessible_update_workspace (AtkObject *aobj_ws,
+ WnckPager *pager,
+ int i);
+static void wnck_pager_accessible_finalize (GObject *gobject);
+
+G_DEFINE_TYPE_WITH_CODE (WnckPagerAccessible,
+ wnck_pager_accessible,
+ GTK_TYPE_ACCESSIBLE,
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_SELECTION,
+ atk_selection_interface_init)
+ G_ADD_PRIVATE (WnckPagerAccessible))
+
+static void
+atk_selection_interface_init (AtkSelectionIface *iface)
+{
+ g_return_if_fail (iface != NULL);
+
+ iface->add_selection = wnck_pager_add_selection;
+ iface->ref_selection = wnck_pager_ref_selection;
+ iface->get_selection_count = wnck_pager_selection_count;
+ iface->is_child_selected = wnck_pager_is_child_selected;
+}
+
+static void
+wnck_pager_accessible_class_init (WnckPagerAccessibleClass *klass)
+{
+ AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
+ GObjectClass *obj_class = G_OBJECT_CLASS (klass);
+
+ class->get_name = wnck_pager_accessible_get_name;
+ class->get_description = wnck_pager_accessible_get_description;
+ class->get_n_children = wnck_pager_accessible_get_n_children;
+ class->ref_child = wnck_pager_accessible_ref_child;
+
+ obj_class->finalize = wnck_pager_accessible_finalize;
+}
+
+static void
+wnck_pager_accessible_init (WnckPagerAccessible *accessible)
+{
+}
+
+static gboolean
+wnck_pager_add_selection (AtkSelection *selection,
+ int i)
+{
+ WnckPager *pager;
+ WnckWorkspace *wspace;
+ GtkWidget *widget;
+ int n_spaces;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection));
+
+ if (widget == NULL)
+ {
+ /*
+ *State is defunct
+ */
+ return FALSE;
+ }
+
+ pager = WNCK_PAGER (widget);
+ n_spaces = _wnck_pager_get_n_workspaces (pager);
+
+ if (i < 0 || i >= n_spaces)
+ return FALSE;
+
+ /*
+ * Activate the following worksapce as current workspace
+ */
+ wspace = _wnck_pager_get_workspace (pager, i);
+ /* FIXME: Is gtk_get_current_event_time() good enough here? I have no idea */
+ _wnck_pager_activate_workspace (wspace, gtk_get_current_event_time ());
+
+ return TRUE;
+}
+
+/*
+ * Returns the AtkObject of the selected WorkSpace
+ */
+static AtkObject*
+wnck_pager_ref_selection (AtkSelection *selection,
+ int i)
+{
+ WnckPager *pager;
+ GtkWidget *widget;
+ WnckWorkspace *active_wspace;
+ AtkObject *accessible;
+ int wsno;
+
+ g_return_val_if_fail (i == 0, NULL);
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection));
+
+ if (widget == NULL)
+ {
+ /*
+ * State is defunct
+ */
+ return NULL;
+ }
+ pager = WNCK_PAGER (widget);
+
+ active_wspace = WNCK_WORKSPACE (_wnck_pager_get_active_workspace (pager));
+ wsno = wnck_workspace_get_number (active_wspace);
+
+ accessible = ATK_OBJECT (wnck_pager_accessible_ref_child (ATK_OBJECT (selection), wsno));
+
+ return accessible;
+}
+
+/*
+ * Returns the no.of child selected, it should be either 1 or 0
+ * b'coz only one child can be selected at a time.
+ */
+static int
+wnck_pager_selection_count (AtkSelection *selection)
+{
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection));
+
+ if (widget == NULL)
+ {
+ /*
+ * State is defunct
+ */
+ return 0;
+ }
+ else
+ {
+ return 1;
+ }
+}
+
+/*
+ *Checks whether the WorkSpace specified by i is selected,
+ *and returns TRUE on selection.
+ */
+static gboolean
+wnck_pager_is_child_selected (AtkSelection *selection,
+ int i)
+{
+ WnckPager *pager;
+ GtkWidget *widget;
+ WnckWorkspace *active_wspace;
+ int wsno;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection));
+
+ if (widget == NULL)
+ {
+ /*
+ * State is defunct
+ */
+ return FALSE;
+ }
+
+ pager = WNCK_PAGER (widget);
+ active_wspace = _wnck_pager_get_active_workspace (pager);
+
+ wsno = wnck_workspace_get_number (active_wspace);
+
+ return (wsno == i);
+}
+
+AtkObject*
+wnck_pager_accessible_new (GtkWidget *widget)
+{
+ GObject *object;
+ AtkObject *aobj_pager;
+ GtkAccessible *gtk_accessible;
+
+ object = g_object_new (WNCK_PAGER_TYPE_ACCESSIBLE, NULL);
+
+ aobj_pager = ATK_OBJECT (object);
+
+ gtk_accessible = GTK_ACCESSIBLE (aobj_pager);
+ gtk_accessible_set_widget (gtk_accessible, widget);
+
+ atk_object_initialize (aobj_pager, widget);
+ aobj_pager->role = ATK_ROLE_PANEL;
+
+ return aobj_pager;
+}
+
+static void
+wnck_pager_accessible_finalize (GObject *gobject)
+{
+ WnckPagerAccessible *accessible;
+ WnckPagerAccessiblePrivate *priv;
+
+ accessible = WNCK_PAGER_ACCESSIBLE (gobject);
+ priv = wnck_pager_accessible_get_instance_private (accessible);
+
+ if (priv)
+ {
+ if (priv->children)
+ {
+ g_slist_free_full (priv->children, g_object_unref);
+ priv->children = NULL;
+ }
+ }
+
+ G_OBJECT_CLASS (wnck_pager_accessible_parent_class)->finalize (gobject);
+}
+
+static const char*
+wnck_pager_accessible_get_name (AtkObject *obj)
+{
+ g_return_val_if_fail (WNCK_PAGER_IS_ACCESSIBLE (obj), NULL);
+
+ if (obj->name == NULL)
+ obj->name = g_strdup (_("Workspace Switcher"));
+
+ return obj->name;
+}
+
+static const char*
+wnck_pager_accessible_get_description (AtkObject *obj)
+{
+ g_return_val_if_fail (WNCK_PAGER_IS_ACCESSIBLE (obj), NULL);
+
+ if (obj->description == NULL)
+ obj->description = g_strdup (_("Tool to switch between workspaces"));
+
+ return obj->description;
+}
+
+/*
+ * Number of workspaces is returned as n_children
+ */
+static int
+wnck_pager_accessible_get_n_children (AtkObject* obj)
+{
+ GtkAccessible *accessible;
+ GtkWidget *widget;
+ WnckPager *pager;
+
+ g_return_val_if_fail (WNCK_PAGER_IS_ACCESSIBLE (obj), 0);
+
+ accessible = GTK_ACCESSIBLE (obj);
+ widget = gtk_accessible_get_widget (accessible);
+
+ if (widget == NULL)
+ /* State is defunct */
+ return 0;
+
+ pager = WNCK_PAGER (widget);
+
+ return _wnck_pager_get_n_workspaces (pager);
+}
+
+/*
+ * Will return appropriate static AtkObject for the workspaces
+ */
+static AtkObject*
+wnck_pager_accessible_ref_child (AtkObject *obj,
+ int i)
+{
+ GtkAccessible *accessible;
+ GtkWidget *widget;
+ WnckPager *pager;
+ int n_spaces = 0;
+ int len;
+ WnckPagerAccessible *pager_accessible;
+ WnckPagerAccessiblePrivate *priv;
+ AtkObject *ret;
+
+ g_return_val_if_fail (WNCK_PAGER_IS_ACCESSIBLE (obj), NULL);
+ g_return_val_if_fail (ATK_IS_OBJECT (obj), NULL);
+
+ accessible = GTK_ACCESSIBLE (obj);
+ widget = gtk_accessible_get_widget (accessible);
+
+ if (widget == NULL)
+ /* State is defunct */
+ return NULL;
+
+ pager = WNCK_PAGER (widget);
+ pager_accessible = WNCK_PAGER_ACCESSIBLE (obj);
+ priv = wnck_pager_accessible_get_instance_private (pager_accessible);
+
+ len = g_slist_length (priv->children);
+ n_spaces = _wnck_pager_get_n_workspaces (pager);
+
+ if (i < 0 || i >= n_spaces)
+ return NULL;
+
+ /* We are really inefficient about this due to all the appending,
+ * and never shrink the list either.
+ */
+ while (n_spaces > len)
+ {
+ AtkRegistry *default_registry;
+ AtkObjectFactory *factory;
+ WnckWorkspace *wspace;
+ WnckWorkspaceAccessible *space_accessible;
+
+ default_registry = atk_get_default_registry ();
+ factory = atk_registry_get_factory (default_registry,
+ WNCK_TYPE_WORKSPACE);
+
+ wspace = _wnck_pager_get_workspace (pager, len);
+ space_accessible = WNCK_WORKSPACE_ACCESSIBLE (atk_object_factory_create_accessible (factory,
+ G_OBJECT (wspace)));
+ atk_object_set_parent (ATK_OBJECT (space_accessible), obj);
+
+ priv->children = g_slist_append (priv->children, space_accessible);
+
+ ++len;
+ }
+
+ ret = g_slist_nth_data (priv->children, i);
+ g_object_ref (G_OBJECT (ret));
+ wnck_pager_accessible_update_workspace (ret, pager, i);
+
+ return ret;
+}
+
+static void
+wnck_pager_accessible_update_workspace (AtkObject *aobj_ws,
+ WnckPager *pager,
+ int i)
+{
+ g_free (aobj_ws->name);
+ aobj_ws->name = g_strdup (_wnck_pager_get_workspace_name (pager, i));
+
+ g_free (aobj_ws->description);
+ aobj_ws->description = g_strdup_printf (_("Click this to switch to workspace %s"),
+ aobj_ws->name);
+ aobj_ws->role = ATK_ROLE_UNKNOWN;
+}
diff --git a/libwnck/widgets/pager-accessible.h b/libwnck/widgets/pager-accessible.h
new file mode 100644
index 0000000..0f0909e
--- /dev/null
+++ b/libwnck/widgets/pager-accessible.h
@@ -0,0 +1,53 @@
+/* vim: set sw=2 et: */
+/*
+ * Copyright 2002 Sun Microsystems Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __WNCK_PAGER_ACCESSIBLE_H__
+#define __WNCK_PAGER_ACCESSIBLE_H__
+
+#include <gtk/gtk.h>
+#include <atk/atk.h>
+
+G_BEGIN_DECLS
+
+#define WNCK_PAGER_TYPE_ACCESSIBLE (wnck_pager_accessible_get_type ())
+#define WNCK_PAGER_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WNCK_PAGER_TYPE_ACCESSIBLE, WnckPagerAccessible))
+#define WNCK_PAGER_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WNCK_PAGER_TYPE_ACCESSIBLE, WnckPagerAccessibleClass))
+#define WNCK_PAGER_IS_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WNCK_PAGER_TYPE_ACCESSIBLE))
+#define WNCK_PAGER_IS_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WnckPagerAccessible))
+#define WNCK_PAGER_ACCESSIBLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WNCK_PAGER_TYPE_ACCESSIBLE, WnckPagerAccessibleClass))
+
+typedef struct _WnckPagerAccessible WnckPagerAccessible;
+typedef struct _WnckPagerAccessibleClass WnckPagerAccessibleClass;
+
+struct _WnckPagerAccessible
+{
+ GtkAccessible parent;
+};
+
+struct _WnckPagerAccessibleClass
+{
+ GtkAccessibleClass parent_class;
+};
+
+GType wnck_pager_accessible_get_type (void) G_GNUC_CONST;
+
+AtkObject* wnck_pager_accessible_new (GtkWidget *widget);
+
+G_END_DECLS
+
+#endif /* __WNCK_PAGER_ACCESSIBLE_H__ */
diff --git a/libwnck/widgets/pager.c b/libwnck/widgets/pager.c
new file mode 100644
index 0000000..b0e0811
--- /dev/null
+++ b/libwnck/widgets/pager.c
@@ -0,0 +1,2973 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 2 -*- */
+/* vim: set sw=2 et: */
+/* pager object */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2003 Kim Woelders
+ * Copyright (C) 2003 Red Hat, Inc.
+ * Copyright (C) 2003, 2005-2007 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <math.h>
+#include <glib/gi18n-lib.h>
+
+#include "pager.h"
+#include "xutils.h"
+#include "pager-accessible-factory.h"
+#include "workspace-accessible-factory.h"
+#include "private.h"
+
+/**
+ * SECTION:pager
+ * @short_description: a pager widget, showing the content of workspaces.
+ * @see_also: #WnckScreen
+ * @stability: Unstable
+ *
+ * A #WnckPager shows a miniature view of the workspaces, representing managed
+ * windows by small rectangles, and allows the user to initiate various window
+ * manager actions by manipulating these representations. The #WnckPager offers
+ * ways to move windows between workspaces and to change the current workspace.
+ *
+ * Alternatively, a #WnckPager can be configured to only show the names of the
+ * workspace instead of their contents.
+ *
+ * The #WnckPager is also responsible for setting the layout of the workspaces.
+ * Since only one application can be responsible for setting the layout on a
+ * screen, the #WnckPager automatically tries to obtain the manager selection
+ * for the screen and only sets the layout if it owns the manager selection.
+ * See wnck_pager_set_orientation() and wnck_pager_set_n_rows() to change the
+ * layout.
+ */
+
+#define N_SCREEN_CONNECTIONS 11
+#define WNCK_NO_MANAGER_TOKEN 0
+
+struct _WnckPagerPrivate
+{
+ WnckScreen *screen;
+
+ int n_rows; /* really columns for vertical orientation */
+ WnckPagerDisplayMode display_mode;
+ WnckPagerScrollMode scroll_mode;
+ gboolean show_all_workspaces;
+ GtkShadowType shadow_type;
+ gboolean wrap_on_scroll;
+
+ GtkOrientation orientation;
+ int workspace_size;
+ guint screen_connections[N_SCREEN_CONNECTIONS];
+ int prelight; /* workspace mouse is hovering over */
+ gboolean prelight_dnd; /* is dnd happening? */
+
+ guint dragging :1;
+ int drag_start_x;
+ int drag_start_y;
+ WnckWindow *drag_window;
+
+ GdkPixbuf *bg_cache;
+
+ int layout_manager_token;
+
+ guint dnd_activate; /* GSource that triggers switching to this workspace during dnd */
+ guint dnd_time; /* time of last event during dnd (for delayed workspace activation) */
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (WnckPager, wnck_pager, GTK_TYPE_WIDGET);
+
+enum
+{
+ dummy, /* remove this when you add more signals */
+ LAST_SIGNAL
+};
+
+#define POINT_IN_RECT(xcoord, ycoord, rect) \
+ ((xcoord) >= (rect).x && \
+ (xcoord) < ((rect).x + (rect).width) && \
+ (ycoord) >= (rect).y && \
+ (ycoord) < ((rect).y + (rect).height))
+
+static void wnck_pager_finalize (GObject *object);
+
+static void wnck_pager_realize (GtkWidget *widget);
+static void wnck_pager_unrealize (GtkWidget *widget);
+static GtkSizeRequestMode wnck_pager_get_request_mode (GtkWidget *widget);
+static void wnck_pager_get_preferred_width (GtkWidget *widget,
+ int *minimum_width,
+ int *natural_width);
+static void wnck_pager_get_preferred_width_for_height (GtkWidget *widget,
+ int height,
+ int *minimum_width,
+ int *natural_width);
+static void wnck_pager_get_preferred_height (GtkWidget *widget,
+ int *minimum_height,
+ int *natural_height);
+static void wnck_pager_get_preferred_height_for_width (GtkWidget *widget,
+ int width,
+ int *minimum_height,
+ int *natural_height);
+static gboolean wnck_pager_draw (GtkWidget *widget,
+ cairo_t *cr);
+static gboolean wnck_pager_button_press (GtkWidget *widget,
+ GdkEventButton *event);
+static gboolean wnck_pager_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time);
+static void wnck_pager_drag_motion_leave (GtkWidget *widget,
+ GdkDragContext *context,
+ guint time);
+static gboolean wnck_pager_drag_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time);
+static void wnck_pager_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time_);
+static void wnck_pager_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time);
+static void wnck_pager_drag_end (GtkWidget *widget,
+ GdkDragContext *context);
+static gboolean wnck_pager_motion (GtkWidget *widget,
+ GdkEventMotion *event);
+static gboolean wnck_pager_leave_notify (GtkWidget *widget,
+ GdkEventCrossing *event);
+static gboolean wnck_pager_button_release (GtkWidget *widget,
+ GdkEventButton *event);
+static gboolean wnck_pager_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event);
+static gboolean wnck_pager_query_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip);
+static void workspace_name_changed_callback (WnckWorkspace *workspace,
+ gpointer data);
+
+static gboolean wnck_pager_window_state_is_relevant (int state);
+static gint wnck_pager_window_get_workspace (WnckWindow *window,
+ gboolean is_state_relevant);
+static void wnck_pager_queue_draw_workspace (WnckPager *pager,
+ gint i);
+static void wnck_pager_queue_draw_window (WnckPager *pager,
+ WnckWindow *window);
+
+static void wnck_pager_connect_screen (WnckPager *pager);
+static void wnck_pager_connect_window (WnckPager *pager,
+ WnckWindow *window);
+static void wnck_pager_disconnect_screen (WnckPager *pager);
+static void wnck_pager_disconnect_window (WnckPager *pager,
+ WnckWindow *window);
+
+static gboolean wnck_pager_set_layout_hint (WnckPager *pager);
+
+static void wnck_pager_clear_drag (WnckPager *pager);
+static void wnck_pager_check_prelight (WnckPager *pager,
+ gint x,
+ gint y,
+ gboolean dnd);
+
+static GdkPixbuf* wnck_pager_get_background (WnckPager *pager,
+ int width,
+ int height);
+
+static AtkObject* wnck_pager_get_accessible (GtkWidget *widget);
+
+
+static void
+wnck_pager_init (WnckPager *pager)
+{
+ int i;
+ static const GtkTargetEntry targets[] = {
+ { (gchar *) "application/x-wnck-window-id", 0, 0}
+ };
+
+ pager->priv = wnck_pager_get_instance_private (pager);
+
+ pager->priv->n_rows = 1;
+ pager->priv->display_mode = WNCK_PAGER_DISPLAY_CONTENT;
+ pager->priv->scroll_mode = WNCK_PAGER_SCROLL_2D;
+ pager->priv->show_all_workspaces = TRUE;
+ pager->priv->shadow_type = GTK_SHADOW_NONE;
+ pager->priv->wrap_on_scroll = FALSE;
+
+ pager->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
+ pager->priv->workspace_size = 16;
+
+ for (i = 0; i < N_SCREEN_CONNECTIONS; i++)
+ pager->priv->screen_connections[i] = 0;
+
+ pager->priv->prelight = -1;
+
+ pager->priv->layout_manager_token = WNCK_NO_MANAGER_TOKEN;
+
+ g_object_set (pager, "has-tooltip", TRUE, NULL);
+
+ gtk_drag_dest_set (GTK_WIDGET (pager), 0, targets, G_N_ELEMENTS (targets), GDK_ACTION_MOVE);
+ gtk_widget_set_can_focus (GTK_WIDGET (pager), TRUE);
+}
+
+static void
+wnck_pager_class_init (WnckPagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = wnck_pager_finalize;
+
+ widget_class->realize = wnck_pager_realize;
+ widget_class->unrealize = wnck_pager_unrealize;
+ widget_class->get_request_mode = wnck_pager_get_request_mode;
+ widget_class->get_preferred_width = wnck_pager_get_preferred_width;
+ widget_class->get_preferred_width_for_height = wnck_pager_get_preferred_width_for_height;
+ widget_class->get_preferred_height = wnck_pager_get_preferred_height;
+ widget_class->get_preferred_height_for_width = wnck_pager_get_preferred_height_for_width;
+ widget_class->draw = wnck_pager_draw;
+ widget_class->button_press_event = wnck_pager_button_press;
+ widget_class->button_release_event = wnck_pager_button_release;
+ widget_class->scroll_event = wnck_pager_scroll_event;
+ widget_class->motion_notify_event = wnck_pager_motion;
+ widget_class->leave_notify_event = wnck_pager_leave_notify;
+ widget_class->get_accessible = wnck_pager_get_accessible;
+ widget_class->drag_leave = wnck_pager_drag_motion_leave;
+ widget_class->drag_motion = wnck_pager_drag_motion;
+ widget_class->drag_drop = wnck_pager_drag_drop;
+ widget_class->drag_data_received = wnck_pager_drag_data_received;
+ widget_class->drag_data_get = wnck_pager_drag_data_get;
+ widget_class->drag_end = wnck_pager_drag_end;
+ widget_class->query_tooltip = wnck_pager_query_tooltip;
+
+ gtk_widget_class_set_css_name (widget_class, "wnck-pager");
+}
+
+static void
+wnck_pager_finalize (GObject *object)
+{
+ WnckPager *pager;
+
+ pager = WNCK_PAGER (object);
+
+ if (pager->priv->bg_cache)
+ {
+ g_object_unref (G_OBJECT (pager->priv->bg_cache));
+ pager->priv->bg_cache = NULL;
+ }
+
+ if (pager->priv->dnd_activate != 0)
+ {
+ g_source_remove (pager->priv->dnd_activate);
+ pager->priv->dnd_activate = 0;
+ }
+
+ G_OBJECT_CLASS (wnck_pager_parent_class)->finalize (object);
+}
+
+static void
+_wnck_pager_set_screen (WnckPager *pager)
+{
+ GdkScreen *gdkscreen;
+
+ if (!gtk_widget_has_screen (GTK_WIDGET (pager)))
+ return;
+
+ gdkscreen = gtk_widget_get_screen (GTK_WIDGET (pager));
+ pager->priv->screen = wnck_screen_get (gdk_x11_screen_get_screen_number (gdkscreen));
+
+ if (!wnck_pager_set_layout_hint (pager))
+ {
+ WnckLayoutOrientation orientation;
+
+ /* we couldn't set the layout on the screen. This means someone else owns
+ * it. Let's at least show the correct layout. */
+ wnck_screen_get_workspace_layout (pager->priv->screen,
+ &orientation,
+ &pager->priv->n_rows,
+ NULL, NULL);
+
+ /* test in this order to default to horizontal in case there was in issue
+ * when fetching the layout */
+ if (orientation == WNCK_LAYOUT_ORIENTATION_VERTICAL)
+ pager->priv->orientation = GTK_ORIENTATION_VERTICAL;
+ else
+ pager->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
+
+ gtk_widget_queue_resize (GTK_WIDGET (pager));
+ }
+
+ wnck_pager_connect_screen (pager);
+}
+
+static void
+wnck_pager_realize (GtkWidget *widget)
+{
+
+ GdkWindowAttr attributes;
+ gint attributes_mask;
+ WnckPager *pager;
+ GtkAllocation allocation;
+ GdkWindow *window;
+
+ pager = WNCK_PAGER (widget);
+
+ /* do not call the parent class realize since we're doing things a bit
+ * differently here */
+ gtk_widget_set_realized (widget, TRUE);
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK |
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_SCROLL_MASK |
+ GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK;
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
+
+ window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
+ gtk_widget_set_window (widget, window);
+ gdk_window_set_user_data (window, widget);
+
+ /* connect to the screen of this pager. In theory, this will already have
+ * been done in wnck_pager_size_request() */
+ if (pager->priv->screen == NULL)
+ _wnck_pager_set_screen (pager);
+ g_assert (pager->priv->screen != NULL);
+}
+
+static void
+wnck_pager_unrealize (GtkWidget *widget)
+{
+ WnckPager *pager;
+
+ pager = WNCK_PAGER (widget);
+
+ wnck_pager_clear_drag (pager);
+ pager->priv->prelight = -1;
+ pager->priv->prelight_dnd = FALSE;
+
+ wnck_screen_release_workspace_layout (pager->priv->screen,
+ pager->priv->layout_manager_token);
+ pager->priv->layout_manager_token = WNCK_NO_MANAGER_TOKEN;
+
+ wnck_pager_disconnect_screen (pager);
+ pager->priv->screen = NULL;
+
+ GTK_WIDGET_CLASS (wnck_pager_parent_class)->unrealize (widget);
+}
+
+static void
+_wnck_pager_get_padding (WnckPager *pager,
+ GtkBorder *padding)
+{
+ if (pager->priv->shadow_type != GTK_SHADOW_NONE)
+ {
+ GtkWidget *widget;
+ GtkStyleContext *context;
+ GtkStateFlags state;
+
+ widget = GTK_WIDGET (pager);
+ context = gtk_widget_get_style_context (widget);
+ state = gtk_style_context_get_state (context);
+ gtk_style_context_get_padding (context, state, padding);
+ }
+ else
+ {
+ GtkBorder empty_padding = { 0, 0, 0, 0 };
+ *padding = empty_padding;
+ }
+}
+
+static int
+_wnck_pager_get_workspace_width_for_height (WnckPager *pager,
+ int workspace_height)
+{
+ int workspace_width = 0;
+
+ if (pager->priv->display_mode == WNCK_PAGER_DISPLAY_CONTENT)
+ {
+ WnckWorkspace *space;
+ double screen_aspect;
+
+ space = wnck_screen_get_workspace (pager->priv->screen, 0);
+
+ if (space) {
+ screen_aspect =
+ (double) wnck_workspace_get_width (space) /
+ (double) wnck_workspace_get_height (space);
+ } else {
+ screen_aspect =
+ (double) wnck_screen_get_width (pager->priv->screen) /
+ (double) wnck_screen_get_height (pager->priv->screen);
+ }
+
+ workspace_width = screen_aspect * workspace_height;
+ }
+ else
+ {
+ PangoLayout *layout;
+ WnckScreen *screen;
+ int n_spaces;
+ int i, w;
+
+ layout = gtk_widget_create_pango_layout (GTK_WIDGET (pager), NULL);
+ screen = pager->priv->screen;
+ n_spaces = wnck_screen_get_workspace_count (pager->priv->screen);
+ workspace_width = 1;
+
+ for (i = 0; i < n_spaces; i++)
+ {
+ pango_layout_set_text (layout,
+ wnck_workspace_get_name (wnck_screen_get_workspace (screen, i)),
+ -1);
+ pango_layout_get_pixel_size (layout, &w, NULL);
+ workspace_width = MAX (workspace_width, w);
+ }
+
+ g_object_unref (layout);
+
+ workspace_width += 2;
+ }
+
+ return workspace_width;
+}
+
+static int
+_wnck_pager_get_workspace_height_for_width (WnckPager *pager,
+ int workspace_width)
+{
+ int workspace_height = 0;
+ WnckWorkspace *space;
+ double screen_aspect;
+
+ /* TODO: Handle WNCK_PAGER_DISPLAY_NAME for this case */
+
+ space = wnck_screen_get_workspace (pager->priv->screen, 0);
+
+ if (space) {
+ screen_aspect =
+ (double) wnck_workspace_get_height (space) /
+ (double) wnck_workspace_get_width (space);
+ } else {
+ screen_aspect =
+ (double) wnck_screen_get_height (pager->priv->screen) /
+ (double) wnck_screen_get_width (pager->priv->screen);
+ }
+
+ workspace_height = screen_aspect * workspace_width;
+
+ return workspace_height;
+}
+
+static void
+wnck_pager_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ WnckPager *pager;
+ int n_spaces;
+ int spaces_per_row;
+ int workspace_width, workspace_height;
+ int n_rows;
+ GtkBorder padding;
+
+ pager = WNCK_PAGER (widget);
+
+ /* if we're not realized, we don't know about our screen yet */
+ if (pager->priv->screen == NULL)
+ _wnck_pager_set_screen (pager);
+ g_assert (pager->priv->screen != NULL);
+
+ g_assert (pager->priv->n_rows > 0);
+
+ n_spaces = wnck_screen_get_workspace_count (pager->priv->screen);
+
+ if (pager->priv->show_all_workspaces)
+ {
+ n_rows = pager->priv->n_rows;
+ spaces_per_row = (n_spaces + n_rows - 1) / n_rows;
+ }
+ else
+ {
+ n_rows = 1;
+ spaces_per_row = 1;
+ }
+
+ if (pager->priv->orientation == GTK_ORIENTATION_VERTICAL)
+ {
+ workspace_width = pager->priv->workspace_size;
+ workspace_height = _wnck_pager_get_workspace_height_for_width (pager,
+ workspace_width);
+
+ requisition->width = workspace_width * n_rows + (n_rows - 1);
+ requisition->height = workspace_height * spaces_per_row + (spaces_per_row - 1);
+ }
+ else
+ {
+ workspace_height = pager->priv->workspace_size;
+ workspace_width = _wnck_pager_get_workspace_width_for_height (pager,
+ workspace_height);
+
+ requisition->width = workspace_width * spaces_per_row + (spaces_per_row - 1);
+ requisition->height = workspace_height * n_rows + (n_rows - 1);
+ }
+
+ _wnck_pager_get_padding (pager, &padding);
+ requisition->width += padding.left + padding.right;
+ requisition->height += padding.top + padding.bottom;
+}
+
+static GtkSizeRequestMode
+wnck_pager_get_request_mode (GtkWidget *widget)
+{
+ WnckPager *pager;
+
+ pager = WNCK_PAGER (widget);
+
+ if (pager->priv->orientation == GTK_ORIENTATION_VERTICAL)
+ return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+ else
+ return GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
+}
+
+static void
+wnck_pager_get_preferred_width (GtkWidget *widget,
+ int *minimum_width,
+ int *natural_width)
+{
+ GtkRequisition req;
+
+ wnck_pager_size_request (widget, &req);
+
+ *minimum_width = *natural_width = MAX (req.width, 0);
+}
+
+static void
+wnck_pager_get_preferred_width_for_height (GtkWidget *widget,
+ int height,
+ int *minimum_width,
+ int *natural_width)
+{
+ WnckPager *pager;
+ int n_spaces;
+ int n_rows;
+ int spaces_per_row;
+ int workspace_width, workspace_height;
+ GtkBorder padding;
+ int width = 0;
+
+ pager = WNCK_PAGER (widget);
+
+ /* if we're not realized, we don't know about our screen yet */
+ if (pager->priv->screen == NULL)
+ _wnck_pager_set_screen (pager);
+ g_assert (pager->priv->screen != NULL);
+
+ g_assert (pager->priv->n_rows > 0);
+
+ n_spaces = wnck_screen_get_workspace_count (pager->priv->screen);
+
+ if (pager->priv->show_all_workspaces)
+ {
+ n_rows = pager->priv->n_rows;
+ spaces_per_row = (n_spaces + n_rows - 1) / n_rows;
+ }
+ else
+ {
+ n_rows = 1;
+ spaces_per_row = 1;
+ }
+
+ _wnck_pager_get_padding (pager, &padding);
+ height -= padding.top + padding.bottom;
+ width += padding.left + padding.right;
+
+ height -= (n_rows - 1);
+ workspace_height = height / n_rows;
+
+ workspace_width = _wnck_pager_get_workspace_width_for_height (pager,
+ workspace_height);
+
+ width += workspace_width * spaces_per_row + (spaces_per_row - 1);
+ *natural_width = *minimum_width = MAX (width, 0);
+}
+
+static void
+wnck_pager_get_preferred_height (GtkWidget *widget,
+ int *minimum_height,
+ int *natural_height)
+{
+ GtkRequisition req;
+
+ wnck_pager_size_request (widget, &req);
+
+ *minimum_height = *natural_height = MAX (req.height, 0);
+}
+
+static void
+wnck_pager_get_preferred_height_for_width (GtkWidget *widget,
+ int width,
+ int *minimum_height,
+ int *natural_height)
+{
+ WnckPager *pager;
+ int n_spaces;
+ int n_rows;
+ int spaces_per_row;
+ int workspace_width, workspace_height;
+ GtkBorder padding;
+ int height = 0;
+
+ pager = WNCK_PAGER (widget);
+
+ /* if we're not realized, we don't know about our screen yet */
+ if (pager->priv->screen == NULL)
+ _wnck_pager_set_screen (pager);
+ g_assert (pager->priv->screen != NULL);
+
+ g_assert (pager->priv->n_rows > 0);
+
+ n_spaces = wnck_screen_get_workspace_count (pager->priv->screen);
+
+ if (pager->priv->show_all_workspaces)
+ {
+ n_rows = pager->priv->n_rows;
+ spaces_per_row = (n_spaces + n_rows - 1) / n_rows;
+ }
+ else
+ {
+ n_rows = 1;
+ spaces_per_row = 1;
+ }
+
+ _wnck_pager_get_padding (pager, &padding);
+ width -= padding.left + padding.right;
+ height += padding.top + padding.bottom;
+
+ width -= (n_rows - 1);
+ workspace_width = width / n_rows;
+
+ workspace_height = _wnck_pager_get_workspace_height_for_width (pager,
+ workspace_width);
+
+ height += workspace_height * spaces_per_row + (spaces_per_row - 1);
+ *natural_height = *minimum_height = MAX (height, 0);
+}
+
+static void
+get_workspace_rect (WnckPager *pager,
+ int space,
+ GdkRectangle *rect)
+{
+ int hsize, vsize;
+ int n_spaces;
+ int spaces_per_row;
+ GtkWidget *widget;
+ int col, row;
+ GtkAllocation allocation;
+ GtkBorder padding;
+
+ widget = GTK_WIDGET (pager);
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ if (allocation.x < 0 || allocation.y < 0 ||
+ allocation.width < 0 || allocation.height < 0)
+ {
+ rect->x = 0;
+ rect->y = 0;
+ rect->width = 0;
+ rect->height = 0;
+
+ return;
+ }
+
+ _wnck_pager_get_padding (pager, &padding);
+
+ if (!pager->priv->show_all_workspaces)
+ {
+ WnckWorkspace *active_space;
+
+ active_space = wnck_screen_get_active_workspace (pager->priv->screen);
+
+ if (active_space && space == wnck_workspace_get_number (active_space))
+ {
+ rect->x = padding.left;
+ rect->y = padding.top;
+ rect->width = allocation.width - padding.left - padding.right;
+ rect->height = allocation.height - padding.top - padding.bottom;
+ }
+ else
+ {
+ rect->x = 0;
+ rect->y = 0;
+ rect->width = 0;
+ rect->height = 0;
+ }
+
+ return;
+ }
+
+ hsize = allocation.width;
+ vsize = allocation.height;
+
+ if (pager->priv->shadow_type != GTK_SHADOW_NONE)
+ {
+ hsize -= padding.left + padding.right;
+ vsize -= padding.top + padding.bottom;
+ }
+
+ n_spaces = wnck_screen_get_workspace_count (pager->priv->screen);
+
+ g_assert (pager->priv->n_rows > 0);
+ spaces_per_row = (n_spaces + pager->priv->n_rows - 1) / pager->priv->n_rows;
+
+ if (pager->priv->orientation == GTK_ORIENTATION_VERTICAL)
+ {
+ rect->width = (hsize - (pager->priv->n_rows - 1)) / pager->priv->n_rows;
+ rect->height = (vsize - (spaces_per_row - 1)) / spaces_per_row;
+
+ col = space / spaces_per_row;
+ row = space % spaces_per_row;
+
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+ col = pager->priv->n_rows - col - 1;
+
+ rect->x = (rect->width + 1) * col;
+ rect->y = (rect->height + 1) * row;
+
+ if (col == pager->priv->n_rows - 1)
+ rect->width = hsize - rect->x;
+
+ if (row == spaces_per_row - 1)
+ rect->height = vsize - rect->y;
+ }
+ else
+ {
+ rect->width = (hsize - (spaces_per_row - 1)) / spaces_per_row;
+ rect->height = (vsize - (pager->priv->n_rows - 1)) / pager->priv->n_rows;
+
+ col = space % spaces_per_row;
+ row = space / spaces_per_row;
+
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+ col = spaces_per_row - col - 1;
+
+ rect->x = (rect->width + 1) * col;
+ rect->y = (rect->height + 1) * row;
+
+ if (col == spaces_per_row - 1)
+ rect->width = hsize - rect->x;
+
+ if (row == pager->priv->n_rows - 1)
+ rect->height = vsize - rect->y;
+ }
+
+ if (pager->priv->shadow_type != GTK_SHADOW_NONE)
+ {
+ rect->x += padding.left;
+ rect->y += padding.top;
+ }
+}
+
+static gboolean
+wnck_pager_window_state_is_relevant (int state)
+{
+ return (state & (WNCK_WINDOW_STATE_HIDDEN | WNCK_WINDOW_STATE_SKIP_PAGER)) ? FALSE : TRUE;
+}
+
+static gint
+wnck_pager_window_get_workspace (WnckWindow *window,
+ gboolean is_state_relevant)
+{
+ gint state;
+ WnckWorkspace *workspace;
+
+ state = wnck_window_get_state (window);
+ if (is_state_relevant && !wnck_pager_window_state_is_relevant (state))
+ return -1;
+ workspace = wnck_window_get_workspace (window);
+ if (workspace == NULL && wnck_window_is_pinned (window))
+ workspace = wnck_screen_get_active_workspace (wnck_window_get_screen (window));
+
+ return workspace ? wnck_workspace_get_number (workspace) : -1;
+}
+
+static GList*
+get_windows_for_workspace_in_bottom_to_top (WnckScreen *screen,
+ WnckWorkspace *workspace)
+{
+ GList *result;
+ GList *windows;
+ GList *tmp;
+ int workspace_num;
+
+ result = NULL;
+ workspace_num = wnck_workspace_get_number (workspace);
+
+ windows = wnck_screen_get_windows_stacked (screen);
+ for (tmp = windows; tmp != NULL; tmp = tmp->next)
+ {
+ WnckWindow *win = WNCK_WINDOW (tmp->data);
+ if (wnck_pager_window_get_workspace (win, TRUE) == workspace_num)
+ result = g_list_prepend (result, win);
+ }
+
+ result = g_list_reverse (result);
+
+ return result;
+}
+
+static void
+get_window_rect (WnckWindow *window,
+ const GdkRectangle *workspace_rect,
+ GdkRectangle *rect)
+{
+ double width_ratio, height_ratio;
+ int x, y, width, height;
+ WnckWorkspace *workspace;
+ GdkRectangle unclipped_win_rect;
+
+ workspace = wnck_window_get_workspace (window);
+ if (workspace == NULL)
+ workspace = wnck_screen_get_active_workspace (wnck_window_get_screen (window));
+
+ /* scale window down by same ratio we scaled workspace down */
+ width_ratio = (double) workspace_rect->width / (double) wnck_workspace_get_width (workspace);
+ height_ratio = (double) workspace_rect->height / (double) wnck_workspace_get_height (workspace);
+
+ wnck_window_get_geometry (window, &x, &y, &width, &height);
+
+ x += wnck_workspace_get_viewport_x (workspace);
+ y += wnck_workspace_get_viewport_y (workspace);
+ x = x * width_ratio + 0.5;
+ y = y * height_ratio + 0.5;
+ width = width * width_ratio + 0.5;
+ height = height * height_ratio + 0.5;
+
+ x += workspace_rect->x;
+ y += workspace_rect->y;
+
+ if (width < 3)
+ width = 3;
+ if (height < 3)
+ height = 3;
+
+ unclipped_win_rect.x = x;
+ unclipped_win_rect.y = y;
+ unclipped_win_rect.width = width;
+ unclipped_win_rect.height = height;
+
+ gdk_rectangle_intersect ((GdkRectangle *) workspace_rect, &unclipped_win_rect, rect);
+}
+
+static void
+draw_window (cairo_t *cr,
+ GtkWidget *widget,
+ WnckWindow *win,
+ const GdkRectangle *winrect,
+ GtkStateFlags state,
+ gboolean translucent)
+{
+ GtkStyleContext *context;
+ GdkPixbuf *icon;
+ int icon_x, icon_y, icon_w, icon_h;
+ gboolean is_active;
+ GdkRGBA fg;
+ gdouble translucency;
+
+ context = gtk_widget_get_style_context (widget);
+
+ is_active = wnck_window_is_active (win);
+ translucency = translucent ? 0.4 : 1.0;
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+
+ cairo_push_group (cr);
+
+ gtk_render_background (context, cr, winrect->x + 1, winrect->y + 1,
+ MAX (0, winrect->width - 2), MAX (0, winrect->height - 2));
+
+ if (is_active)
+ {
+ /* Sharpen the foreground color */
+ cairo_set_source_rgba (cr, 1.0f, 1.0f, 1.0f, 0.3f);
+ cairo_rectangle (cr, winrect->x + 1, winrect->y + 1,
+ MAX (0, winrect->width - 2), MAX (0, winrect->height - 2));
+ cairo_fill (cr);
+ }
+
+ cairo_pop_group_to_source (cr);
+ cairo_paint_with_alpha (cr, translucency);
+
+ icon = wnck_window_get_icon (win);
+
+ icon_w = icon_h = 0;
+
+ if (icon)
+ {
+ icon_w = gdk_pixbuf_get_width (icon);
+ icon_h = gdk_pixbuf_get_height (icon);
+
+ /* If the icon is too big, fall back to mini icon.
+ * We don't arbitrarily scale the icon, because it's
+ * just too slow on my Athlon 850.
+ */
+ if (icon_w > (winrect->width - 2) ||
+ icon_h > (winrect->height - 2))
+ {
+ icon = wnck_window_get_mini_icon (win);
+ if (icon)
+ {
+ icon_w = gdk_pixbuf_get_width (icon);
+ icon_h = gdk_pixbuf_get_height (icon);
+
+ /* Give up. */
+ if (icon_w > (winrect->width - 2) ||
+ icon_h > (winrect->height - 2))
+ icon = NULL;
+ }
+ }
+ }
+
+ if (icon)
+ {
+ icon_x = winrect->x + (winrect->width - icon_w) / 2;
+ icon_y = winrect->y + (winrect->height - icon_h) / 2;
+
+ cairo_push_group (cr);
+ gtk_render_icon (context, cr, icon, icon_x, icon_y);
+ cairo_pop_group_to_source (cr);
+ cairo_paint_with_alpha (cr, translucency);
+ }
+
+ cairo_push_group (cr);
+ gtk_render_frame (context, cr, winrect->x + 0.5, winrect->y + 0.5,
+ MAX (0, winrect->width - 1), MAX (0, winrect->height - 1));
+ cairo_pop_group_to_source (cr);
+ cairo_paint_with_alpha (cr, translucency);
+
+ gtk_style_context_get_color (context, state, &fg);
+ fg.alpha = translucency;
+ gdk_cairo_set_source_rgba (cr, &fg);
+ cairo_set_line_width (cr, 1.0);
+ cairo_rectangle (cr,
+ winrect->x + 0.5, winrect->y + 0.5,
+ MAX (0, winrect->width - 1), MAX (0, winrect->height - 1));
+ cairo_stroke (cr);
+
+ gtk_style_context_restore (context);
+}
+
+static WnckWindow *
+window_at_point (WnckPager *pager,
+ WnckWorkspace *space,
+ GdkRectangle *space_rect,
+ int x,
+ int y)
+{
+ WnckWindow *window;
+ GList *windows;
+ GList *tmp;
+
+ window = NULL;
+
+ windows = get_windows_for_workspace_in_bottom_to_top (pager->priv->screen,
+ space);
+
+ /* clicks on top windows first */
+ windows = g_list_reverse (windows);
+
+ for (tmp = windows; tmp != NULL; tmp = tmp->next)
+ {
+ WnckWindow *win = WNCK_WINDOW (tmp->data);
+ GdkRectangle winrect;
+
+ get_window_rect (win, space_rect, &winrect);
+
+ if (POINT_IN_RECT (x, y, winrect))
+ {
+ /* wnck_window_activate (win); */
+ window = win;
+ break;
+ }
+ }
+
+ g_list_free (windows);
+
+ return window;
+}
+
+static int
+workspace_at_point (WnckPager *pager,
+ int x,
+ int y,
+ int *viewport_x,
+ int *viewport_y)
+{
+ GtkWidget *widget;
+ int i;
+ int n_spaces;
+ GtkAllocation allocation;
+ GtkBorder padding;
+
+ widget = GTK_WIDGET (pager);
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ _wnck_pager_get_padding (pager, &padding);
+
+ n_spaces = wnck_screen_get_workspace_count (pager->priv->screen);
+
+ i = 0;
+ while (i < n_spaces)
+ {
+ GdkRectangle rect;
+
+ get_workspace_rect (pager, i, &rect);
+
+ /* If workspace is on the edge, pretend points on the frame belong to the
+ * workspace.
+ * Else, pretend the right/bottom line separating two workspaces belong
+ * to the workspace.
+ */
+
+ if (rect.x == padding.left)
+ {
+ rect.x = 0;
+ rect.width += padding.left;
+ }
+ if (rect.y == padding.top)
+ {
+ rect.y = 0;
+ rect.height += padding.top;
+ }
+ if (rect.y + rect.height == allocation.height - padding.bottom)
+ {
+ rect.height += padding.bottom;
+ }
+ else
+ {
+ rect.height += 1;
+ }
+ if (rect.x + rect.width == allocation.width - padding.right)
+ {
+ rect.width += padding.right;
+ }
+ else
+ {
+ rect.width += 1;
+ }
+
+ if (POINT_IN_RECT (x, y, rect))
+ {
+ double width_ratio, height_ratio;
+ WnckWorkspace *space;
+
+ space = wnck_screen_get_workspace (pager->priv->screen, i);
+ g_assert (space != NULL);
+
+ /* Scale x, y mouse coords to corresponding screenwide viewport coords */
+
+ width_ratio = (double) wnck_workspace_get_width (space) / (double) rect.width;
+ height_ratio = (double) wnck_workspace_get_height (space) / (double) rect.height;
+
+ if (viewport_x)
+ *viewport_x = width_ratio * (x - rect.x);
+ if (viewport_y)
+ *viewport_y = height_ratio * (y - rect.y);
+
+ return i;
+ }
+
+ ++i;
+ }
+
+ return -1;
+}
+
+static void
+draw_dark_rectangle (GtkStyleContext *context,
+ cairo_t *cr,
+ GtkStateFlags state,
+ int rx, int ry, int rw, int rh)
+{
+ gtk_style_context_save (context);
+
+ gtk_style_context_set_state (context, state);
+
+ cairo_push_group (cr);
+
+ gtk_render_background (context, cr, rx, ry, rw, rh);
+ cairo_set_source_rgba (cr, 0.0f, 0.0f, 0.0f, 0.3f);
+ cairo_rectangle (cr, rx, ry, rw, rh);
+ cairo_fill (cr);
+
+ cairo_pop_group_to_source (cr);
+ cairo_paint (cr);
+
+ gtk_style_context_restore (context);
+}
+
+static void
+wnck_pager_draw_workspace (WnckPager *pager,
+ cairo_t *cr,
+ int workspace,
+ GdkRectangle *rect,
+ GdkPixbuf *bg_pixbuf)
+{
+ GList *windows;
+ GList *tmp;
+ gboolean is_current;
+ WnckWorkspace *space;
+ GtkWidget *widget;
+ GtkStateFlags state;
+ GtkStyleContext *context;
+
+ space = wnck_screen_get_workspace (pager->priv->screen, workspace);
+ if (!space)
+ return;
+
+ widget = GTK_WIDGET (pager);
+ is_current = (space == wnck_screen_get_active_workspace (pager->priv->screen));
+
+ state = GTK_STATE_FLAG_NORMAL;
+ if (is_current)
+ state |= GTK_STATE_FLAG_SELECTED;
+ else if (workspace == pager->priv->prelight)
+ state |= GTK_STATE_FLAG_PRELIGHT;
+
+ context = gtk_widget_get_style_context (widget);
+
+ /* FIXME in names mode, should probably draw things like a button.
+ */
+
+ if (bg_pixbuf)
+ {
+ gdk_cairo_set_source_pixbuf (cr, bg_pixbuf, rect->x, rect->y);
+ cairo_paint (cr);
+ }
+ else
+ {
+ if (!wnck_workspace_is_virtual (space))
+ {
+ draw_dark_rectangle (context, cr, state,
+ rect->x, rect->y, rect->width, rect->height);
+ }
+ else
+ {
+ //FIXME prelight for dnd in the viewport?
+ int workspace_width, workspace_height;
+ int screen_width, screen_height;
+ double width_ratio, height_ratio;
+ double vx, vy, vw, vh; /* viewport */
+
+ workspace_width = wnck_workspace_get_width (space);
+ workspace_height = wnck_workspace_get_height (space);
+ screen_width = wnck_screen_get_width (pager->priv->screen);
+ screen_height = wnck_screen_get_height (pager->priv->screen);
+
+ if ((workspace_width % screen_width == 0) &&
+ (workspace_height % screen_height == 0))
+ {
+ int i, j;
+ int active_i, active_j;
+ int horiz_views;
+ int verti_views;
+
+ horiz_views = workspace_width / screen_width;
+ verti_views = workspace_height / screen_height;
+
+ /* do not forget thin lines to delimit "workspaces" */
+ width_ratio = (rect->width - (horiz_views - 1)) /
+ (double) workspace_width;
+ height_ratio = (rect->height - (verti_views - 1)) /
+ (double) workspace_height;
+
+ if (is_current)
+ {
+ active_i =
+ wnck_workspace_get_viewport_x (space) / screen_width;
+ active_j =
+ wnck_workspace_get_viewport_y (space) / screen_height;
+ }
+ else
+ {
+ active_i = -1;
+ active_j = -1;
+ }
+
+ for (i = 0; i < horiz_views; i++)
+ {
+ /* "+ i" is for the thin lines */
+ vx = rect->x + (width_ratio * screen_width) * i + i;
+
+ if (i == horiz_views - 1)
+ vw = rect->width + rect->x - vx;
+ else
+ vw = width_ratio * screen_width;
+
+ vh = height_ratio * screen_height;
+
+ for (j = 0; j < verti_views; j++)
+ {
+ GtkStateFlags rec_state = GTK_STATE_FLAG_NORMAL;
+
+ /* "+ j" is for the thin lines */
+ vy = rect->y + (height_ratio * screen_height) * j + j;
+
+ if (j == verti_views - 1)
+ vh = rect->height + rect->y - vy;
+
+ if (active_i == i && active_j == j)
+ rec_state = GTK_STATE_FLAG_SELECTED;
+
+ draw_dark_rectangle (context, cr, rec_state, vx, vy, vw, vh);
+ }
+ }
+ }
+ else
+ {
+ width_ratio = rect->width / (double) workspace_width;
+ height_ratio = rect->height / (double) workspace_height;
+
+ /* first draw non-active part of the viewport */
+ draw_dark_rectangle (context, cr, GTK_STATE_FLAG_NORMAL,
+ rect->x, rect->y, rect->width, rect->height);
+
+ if (is_current)
+ {
+ /* draw the active part of the viewport */
+ vx = rect->x +
+ width_ratio * wnck_workspace_get_viewport_x (space);
+ vy = rect->y +
+ height_ratio * wnck_workspace_get_viewport_y (space);
+ vw = width_ratio * screen_width;
+ vh = height_ratio * screen_height;
+
+ draw_dark_rectangle (context, cr, GTK_STATE_FLAG_SELECTED,
+ vx, vy, vw, vh);
+ }
+ }
+ }
+ }
+
+ if (pager->priv->display_mode == WNCK_PAGER_DISPLAY_CONTENT)
+ {
+ windows = get_windows_for_workspace_in_bottom_to_top (pager->priv->screen,
+ wnck_screen_get_workspace (pager->priv->screen,
+ workspace));
+
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ WnckWindow *win = tmp->data;
+ GdkRectangle winrect;
+ gboolean translucent;
+
+ get_window_rect (win, rect, &winrect);
+
+ translucent = (win == pager->priv->drag_window) && pager->priv->dragging;
+ draw_window (cr, widget, win, &winrect, state, translucent);
+
+ tmp = tmp->next;
+ }
+
+ g_list_free (windows);
+ }
+ else
+ {
+ /* Workspace name mode */
+ GtkStateFlags layout_state;
+ const char *workspace_name;
+ PangoLayout *layout;
+ WnckWorkspace *ws;
+ int w, h;
+
+ ws = wnck_screen_get_workspace (pager->priv->screen, workspace);
+ workspace_name = wnck_workspace_get_name (ws);
+ layout = gtk_widget_create_pango_layout (widget, workspace_name);
+
+ pango_layout_get_pixel_size (layout, &w, &h);
+
+ layout_state = (is_current) ? GTK_STATE_FLAG_SELECTED : GTK_STATE_FLAG_NORMAL;
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, layout_state);
+ gtk_render_layout (context, cr, rect->x + (rect->width - w) / 2,
+ rect->y + (rect->height - h) / 2, layout);
+
+ gtk_style_context_restore (context);
+ g_object_unref (layout);
+ }
+
+ if (workspace == pager->priv->prelight && pager->priv->prelight_dnd)
+ {
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
+ gtk_render_frame (context, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_style_context_restore (context);
+
+ cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
+ cairo_set_line_width (cr, 1.0);
+ cairo_rectangle (cr, rect->x + 0.5, rect->y + 0.5,
+ MAX (0, rect->width - 1), MAX (0, rect->height - 1));
+ cairo_stroke (cr);
+ }
+}
+
+static gboolean
+wnck_pager_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ WnckPager *pager;
+ int i;
+ int n_spaces;
+ WnckWorkspace *active_space;
+ GdkPixbuf *bg_pixbuf;
+ gboolean first;
+ GtkStyleContext *context;
+ GtkStateFlags state;
+
+ pager = WNCK_PAGER (widget);
+
+ n_spaces = wnck_screen_get_workspace_count (pager->priv->screen);
+ active_space = wnck_screen_get_active_workspace (pager->priv->screen);
+ bg_pixbuf = NULL;
+ first = TRUE;
+
+ state = gtk_widget_get_state_flags (widget);
+ context = gtk_widget_get_style_context (widget);
+
+ gtk_render_background (context, cr, 0, 0,
+ gtk_widget_get_allocated_width (widget),
+ gtk_widget_get_allocated_height (widget));
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+
+ if (gtk_widget_has_focus (widget))
+ {
+ cairo_save (cr);
+ gtk_render_focus (context, cr,
+ 0, 0,
+ gtk_widget_get_allocated_width (widget),
+ gtk_widget_get_allocated_height (widget));
+ cairo_restore (cr);
+ }
+
+ if (pager->priv->shadow_type != GTK_SHADOW_NONE)
+ {
+ cairo_save (cr);
+ gtk_render_frame (context, cr, 0, 0,
+ gtk_widget_get_allocated_width (widget),
+ gtk_widget_get_allocated_height (widget));
+ cairo_restore (cr);
+ }
+
+ gtk_style_context_restore (context);
+
+ i = 0;
+ while (i < n_spaces)
+ {
+ GdkRectangle rect;
+
+ if (pager->priv->show_all_workspaces ||
+ (active_space && i == wnck_workspace_get_number (active_space)))
+ {
+ get_workspace_rect (pager, i, &rect);
+
+ /* We only want to do this once, even if w/h change,
+ * for efficiency. width/height will only change by
+ * one pixel at most.
+ */
+ if (first &&
+ pager->priv->display_mode == WNCK_PAGER_DISPLAY_CONTENT)
+ {
+ bg_pixbuf = wnck_pager_get_background (pager,
+ rect.width,
+ rect.height);
+ first = FALSE;
+ }
+
+ wnck_pager_draw_workspace (pager, cr, i, &rect, bg_pixbuf);
+ }
+
+ ++i;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+wnck_pager_button_press (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ WnckPager *pager;
+ int space_number;
+ WnckWorkspace *space = NULL;
+ GdkRectangle workspace_rect;
+
+ if (event->button != 1)
+ return FALSE;
+
+ pager = WNCK_PAGER (widget);
+
+ space_number = workspace_at_point (pager, event->x, event->y, NULL, NULL);
+
+ if (space_number != -1)
+ {
+ get_workspace_rect (pager, space_number, &workspace_rect);
+ space = wnck_screen_get_workspace (pager->priv->screen, space_number);
+ }
+
+ if (space)
+ {
+ /* always save the start coordinates so we can know if we need to change
+ * workspace when the button is released (ie, start and end coordinates
+ * should be in the same workspace) */
+ pager->priv->drag_start_x = event->x;
+ pager->priv->drag_start_y = event->y;
+ }
+
+ if (space && (pager->priv->display_mode != WNCK_PAGER_DISPLAY_NAME))
+ {
+ pager->priv->drag_window = window_at_point (pager, space,
+ &workspace_rect,
+ event->x, event->y);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+wnck_pager_drag_motion_timeout (gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+ WnckWorkspace *active_workspace, *dnd_workspace;
+
+ pager->priv->dnd_activate = 0;
+ active_workspace = wnck_screen_get_active_workspace (pager->priv->screen);
+ dnd_workspace = wnck_screen_get_workspace (pager->priv->screen,
+ pager->priv->prelight);
+
+ if (dnd_workspace &&
+ (pager->priv->prelight != wnck_workspace_get_number (active_workspace)))
+ wnck_workspace_activate (dnd_workspace, pager->priv->dnd_time);
+
+ return FALSE;
+}
+
+static void
+wnck_pager_queue_draw_workspace (WnckPager *pager,
+ gint i)
+{
+ GdkRectangle rect;
+
+ if (i < 0)
+ return;
+
+ get_workspace_rect (pager, i, &rect);
+ gtk_widget_queue_draw_area (GTK_WIDGET (pager),
+ rect.x, rect.y,
+ rect.width, rect.height);
+}
+
+static void
+wnck_pager_queue_draw_window (WnckPager *pager,
+ WnckWindow *window)
+{
+ gint workspace;
+
+ workspace = wnck_pager_window_get_workspace (window, TRUE);
+ if (workspace == -1)
+ return;
+
+ wnck_pager_queue_draw_workspace (pager, workspace);
+}
+
+static void
+wnck_pager_check_prelight (WnckPager *pager,
+ gint x,
+ gint y,
+ gboolean prelight_dnd)
+{
+ gint id;
+
+ if (x < 0 || y < 0)
+ id = -1;
+ else
+ id = workspace_at_point (pager, x, y, NULL, NULL);
+
+ if (id != pager->priv->prelight)
+ {
+ wnck_pager_queue_draw_workspace (pager, pager->priv->prelight);
+ wnck_pager_queue_draw_workspace (pager, id);
+ pager->priv->prelight = id;
+ pager->priv->prelight_dnd = prelight_dnd;
+ }
+ else if (prelight_dnd != pager->priv->prelight_dnd)
+ {
+ wnck_pager_queue_draw_workspace (pager, pager->priv->prelight);
+ pager->priv->prelight_dnd = prelight_dnd;
+ }
+}
+
+static gboolean
+wnck_pager_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ WnckPager *pager;
+ gint previous_workspace;
+
+ pager = WNCK_PAGER (widget);
+
+ previous_workspace = pager->priv->prelight;
+ wnck_pager_check_prelight (pager, x, y, TRUE);
+
+ if (gtk_drag_dest_find_target (widget, context, NULL))
+ {
+ gdk_drag_status (context, gdk_drag_context_get_suggested_action (context), time);
+ }
+ else
+ {
+ gdk_drag_status (context, 0, time);
+
+ if (pager->priv->prelight != previous_workspace &&
+ pager->priv->dnd_activate != 0)
+ {
+ /* remove timeout, the window we hover over changed */
+ g_source_remove (pager->priv->dnd_activate);
+ pager->priv->dnd_activate = 0;
+ pager->priv->dnd_time = 0;
+ }
+
+ if (pager->priv->dnd_activate == 0 && pager->priv->prelight > -1)
+ {
+ pager->priv->dnd_activate = g_timeout_add_seconds (WNCK_ACTIVATE_TIMEOUT,
+ wnck_pager_drag_motion_timeout,
+ pager);
+ pager->priv->dnd_time = time;
+ }
+ }
+
+ return (pager->priv->prelight != -1);
+}
+
+static gboolean
+wnck_pager_drag_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ WnckPager *pager = WNCK_PAGER (widget);
+ GdkAtom target;
+
+ target = gtk_drag_dest_find_target (widget, context, NULL);
+
+ if (target != GDK_NONE)
+ gtk_drag_get_data (widget, context, target, time);
+ else
+ gtk_drag_finish (context, FALSE, FALSE, time);
+
+ wnck_pager_clear_drag (pager);
+ wnck_pager_check_prelight (pager, x, y, FALSE);
+
+ return TRUE;
+}
+
+static void
+wnck_pager_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time)
+{
+ WnckPager *pager = WNCK_PAGER (widget);
+ WnckWorkspace *space;
+ GList *tmp;
+ gint i;
+ gulong xid;
+
+ if ((gtk_selection_data_get_length (selection_data) != sizeof (gulong)) ||
+ (gtk_selection_data_get_format (selection_data) != 8))
+ {
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ return;
+ }
+
+ i = workspace_at_point (pager, x, y, NULL, NULL);
+ space = wnck_screen_get_workspace (pager->priv->screen, i);
+ if (!space)
+ {
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ return;
+ }
+
+ xid = *((gulong *) gtk_selection_data_get_data (selection_data));
+
+ for (tmp = wnck_screen_get_windows_stacked (pager->priv->screen); tmp != NULL; tmp = tmp->next)
+ {
+ if (wnck_window_get_xid (tmp->data) == xid)
+ {
+ WnckWindow *win = tmp->data;
+ wnck_window_move_to_workspace (win, space);
+ if (space == wnck_screen_get_active_workspace (pager->priv->screen))
+ wnck_window_activate (win, time);
+ gtk_drag_finish (context, TRUE, FALSE, time);
+ return;
+ }
+ }
+
+ gtk_drag_finish (context, FALSE, FALSE, time);
+}
+
+static void
+wnck_pager_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time)
+{
+ WnckPager *pager = WNCK_PAGER (widget);
+ gulong xid;
+
+ if (pager->priv->drag_window == NULL)
+ return;
+
+ xid = wnck_window_get_xid (pager->priv->drag_window);
+ gtk_selection_data_set (selection_data,
+ gtk_selection_data_get_target (selection_data),
+ 8, (guchar *)&xid, sizeof (gulong));
+}
+
+static void
+wnck_pager_drag_end (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ WnckPager *pager = WNCK_PAGER (widget);
+
+ wnck_pager_clear_drag (pager);
+}
+
+static void
+wnck_pager_drag_motion_leave (GtkWidget *widget,
+ GdkDragContext *context,
+ guint time)
+{
+ WnckPager *pager = WNCK_PAGER (widget);
+
+ if (pager->priv->dnd_activate != 0)
+ {
+ g_source_remove (pager->priv->dnd_activate);
+ pager->priv->dnd_activate = 0;
+ }
+ pager->priv->dnd_time = 0;
+ wnck_pager_check_prelight (pager, -1, -1, FALSE);
+}
+
+static void
+wnck_drag_clean_up (WnckWindow *window,
+ GdkDragContext *context,
+ gboolean clean_up_for_context_destroy,
+ gboolean clean_up_for_window_destroy);
+
+static void
+wnck_drag_context_destroyed (gpointer windowp,
+ GObject *context)
+{
+ wnck_drag_clean_up (windowp, (GdkDragContext *) context, TRUE, FALSE);
+}
+
+static void
+wnck_update_drag_icon (WnckWindow *window,
+ GdkDragContext *context)
+{
+ gint org_w, org_h, dnd_w, dnd_h;
+ WnckWorkspace *workspace;
+ GdkRectangle rect;
+ cairo_surface_t *surface;
+ GtkWidget *widget;
+ cairo_t *cr;
+
+ widget = g_object_get_data (G_OBJECT (context), "wnck-drag-source-widget");
+ if (!widget)
+ return;
+
+ if (!gtk_icon_size_lookup (GTK_ICON_SIZE_DND, &dnd_w, &dnd_h))
+ dnd_w = dnd_h = 32;
+ /* windows are huge, so let's make this huge */
+ dnd_w *= 3;
+
+ workspace = wnck_window_get_workspace (window);
+ if (workspace == NULL)
+ workspace = wnck_screen_get_active_workspace (wnck_window_get_screen (window));
+ if (workspace == NULL)
+ return;
+
+ wnck_window_get_geometry (window, NULL, NULL, &org_w, &org_h);
+
+ rect.x = rect.y = 0;
+ rect.width = 0.5 + ((double) (dnd_w * org_w) / (double) wnck_workspace_get_width (workspace));
+ rect.width = MIN (org_w, rect.width);
+ rect.height = 0.5 + ((double) (rect.width * org_h) / (double) org_w);
+
+ /* we need at least three pixels to draw the smallest window */
+ rect.width = MAX (rect.width, 3);
+ rect.height = MAX (rect.height, 3);
+
+ surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
+ CAIRO_CONTENT_COLOR,
+ rect.width, rect.height);
+ cr = cairo_create (surface);
+ draw_window (cr, widget, window, &rect, GTK_STATE_FLAG_NORMAL, FALSE);
+ cairo_destroy (cr);
+ cairo_surface_set_device_offset (surface, 2, 2);
+
+ gtk_drag_set_icon_surface (context, surface);
+
+ cairo_surface_destroy (surface);
+}
+
+static void
+wnck_drag_window_destroyed (gpointer contextp,
+ GObject *window)
+{
+ wnck_drag_clean_up ((WnckWindow *) window, GDK_DRAG_CONTEXT (contextp),
+ FALSE, TRUE);
+}
+
+static void
+wnck_drag_source_destroyed (gpointer contextp,
+ GObject *drag_source)
+{
+ g_object_steal_data (G_OBJECT (contextp), "wnck-drag-source-widget");
+}
+
+/* CAREFUL: This function is a bit brittle, because the pointers given may be
+ * finalized already */
+static void
+wnck_drag_clean_up (WnckWindow *window,
+ GdkDragContext *context,
+ gboolean clean_up_for_context_destroy,
+ gboolean clean_up_for_window_destroy)
+{
+ if (clean_up_for_context_destroy)
+ {
+ GtkWidget *drag_source;
+
+ drag_source = g_object_get_data (G_OBJECT (context),
+ "wnck-drag-source-widget");
+ if (drag_source)
+ g_object_weak_unref (G_OBJECT (drag_source),
+ wnck_drag_source_destroyed, context);
+
+ g_object_weak_unref (G_OBJECT (window),
+ wnck_drag_window_destroyed, context);
+ if (g_signal_handlers_disconnect_by_func (window,
+ wnck_update_drag_icon, context) != 2)
+ g_assert_not_reached ();
+ }
+
+ if (clean_up_for_window_destroy)
+ {
+ g_object_steal_data (G_OBJECT (context), "wnck-drag-source-widget");
+ g_object_weak_unref (G_OBJECT (context),
+ wnck_drag_context_destroyed, window);
+ }
+}
+
+/**
+ * wnck_window_set_as_drag_icon:
+ * @window: #WnckWindow to use as drag icon
+ * @context: #GdkDragContext to set the icon on
+ * @drag_source: #GtkWidget that started the drag, or one of its parent. This
+ * widget needs to stay alive as long as possible during the drag.
+ *
+ * Sets the given @window as the drag icon for @context.
+ **/
+void
+_wnck_window_set_as_drag_icon (WnckWindow *window,
+ GdkDragContext *context,
+ GtkWidget *drag_source)
+{
+ g_return_if_fail (WNCK_IS_WINDOW (window));
+ g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
+
+ g_object_weak_ref (G_OBJECT (window), wnck_drag_window_destroyed, context);
+ g_signal_connect (window, "geometry_changed",
+ G_CALLBACK (wnck_update_drag_icon), context);
+ g_signal_connect (window, "icon_changed",
+ G_CALLBACK (wnck_update_drag_icon), context);
+
+ g_object_set_data (G_OBJECT (context), "wnck-drag-source-widget", drag_source);
+ g_object_weak_ref (G_OBJECT (drag_source), wnck_drag_source_destroyed, context);
+
+ g_object_weak_ref (G_OBJECT (context), wnck_drag_context_destroyed, window);
+
+ wnck_update_drag_icon (window, context);
+}
+
+static gboolean
+wnck_pager_motion (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ WnckPager *pager;
+ GdkWindow *window;
+ GdkSeat *seat;
+ GdkDevice *pointer;
+ int x, y;
+
+ pager = WNCK_PAGER (widget);
+
+ seat = gdk_display_get_default_seat (gtk_widget_get_display (widget));
+ window = gtk_widget_get_window (widget);
+ pointer = gdk_seat_get_pointer (seat);
+ gdk_window_get_device_position (window, pointer, &x, &y, NULL);
+
+ if (!pager->priv->dragging &&
+ pager->priv->drag_window != NULL &&
+ gtk_drag_check_threshold (widget,
+ pager->priv->drag_start_x,
+ pager->priv->drag_start_y,
+ x, y))
+ {
+ GtkTargetList *target_list;
+ GdkDragContext *context;
+
+ target_list = gtk_drag_dest_get_target_list (widget);
+ context = gtk_drag_begin_with_coordinates (widget, target_list,
+ GDK_ACTION_MOVE,
+ 1, (GdkEvent *) event,
+ -1, -1);
+
+ pager->priv->dragging = TRUE;
+ pager->priv->prelight_dnd = TRUE;
+ _wnck_window_set_as_drag_icon (pager->priv->drag_window,
+ context,
+ GTK_WIDGET (pager));
+ }
+
+ wnck_pager_check_prelight (pager, x, y, pager->priv->prelight_dnd);
+
+ return TRUE;
+}
+
+static gboolean
+wnck_pager_leave_notify (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ WnckPager *pager;
+
+ pager = WNCK_PAGER (widget);
+
+ wnck_pager_check_prelight (pager, -1, -1, FALSE);
+
+ return FALSE;
+}
+
+static gboolean
+wnck_pager_button_release (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ WnckWorkspace *space;
+ WnckPager *pager;
+ int i;
+ int j;
+ int viewport_x;
+ int viewport_y;
+
+ if (event->button != 1)
+ return FALSE;
+
+ pager = WNCK_PAGER (widget);
+
+ if (!pager->priv->dragging)
+ {
+ i = workspace_at_point (pager,
+ event->x, event->y,
+ &viewport_x, &viewport_y);
+ j = workspace_at_point (pager,
+ pager->priv->drag_start_x,
+ pager->priv->drag_start_y,
+ NULL, NULL);
+
+ if (i == j && i >= 0 &&
+ (space = wnck_screen_get_workspace (pager->priv->screen, i)))
+ {
+ int screen_width, screen_height;
+
+ /* Don't switch the desktop if we're already there */
+ if (space != wnck_screen_get_active_workspace (pager->priv->screen))
+ wnck_workspace_activate (space, event->time);
+
+ /* EWMH only lets us move the viewport for the active workspace,
+ * but we just go ahead and hackily assume that the activate
+ * just above takes effect prior to moving the viewport
+ */
+
+ /* Transform the pointer location to viewport origin, assuming
+ * that we want the nearest "regular" viewport containing the
+ * pointer.
+ */
+ screen_width = wnck_screen_get_width (pager->priv->screen);
+ screen_height = wnck_screen_get_height (pager->priv->screen);
+ viewport_x = (viewport_x / screen_width) * screen_width;
+ viewport_y = (viewport_y / screen_height) * screen_height;
+
+ if (wnck_workspace_get_viewport_x (space) != viewport_x ||
+ wnck_workspace_get_viewport_y (space) != viewport_y)
+ wnck_screen_move_viewport (pager->priv->screen, viewport_x, viewport_y);
+ }
+
+ wnck_pager_clear_drag (pager);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+wnck_pager_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event)
+{
+ WnckPager *pager;
+ WnckWorkspace *space;
+ GdkScrollDirection absolute_direction;
+ int index;
+ int n_workspaces;
+ int n_columns;
+ int in_last_row;
+ gboolean wrap_workspaces;
+ gdouble smooth_x;
+ gdouble smooth_y;
+
+ pager = WNCK_PAGER (widget);
+
+ if (event->type != GDK_SCROLL)
+ return FALSE;
+ if (event->direction == GDK_SCROLL_SMOOTH)
+ return FALSE;
+ if (pager->priv->scroll_mode == WNCK_PAGER_SCROLL_NONE)
+ return FALSE;
+
+ absolute_direction = event->direction;
+
+ space = wnck_screen_get_active_workspace (pager->priv->screen);
+ index = wnck_workspace_get_number (space);
+
+ n_workspaces = wnck_screen_get_workspace_count (pager->priv->screen);
+ n_columns = n_workspaces / pager->priv->n_rows;
+ if (n_workspaces % pager->priv->n_rows != 0)
+ n_columns++;
+ in_last_row = n_workspaces % n_columns;
+ wrap_workspaces = pager->priv->wrap_on_scroll;
+
+ if (gtk_widget_get_direction (GTK_WIDGET (pager)) == GTK_TEXT_DIR_RTL)
+ {
+ switch (event->direction)
+ {
+ case GDK_SCROLL_DOWN:
+ case GDK_SCROLL_UP:
+ break;
+ case GDK_SCROLL_RIGHT:
+ absolute_direction = GDK_SCROLL_LEFT;
+ break;
+ case GDK_SCROLL_LEFT:
+ absolute_direction = GDK_SCROLL_RIGHT;
+ break;
+ case GDK_SCROLL_SMOOTH:
+ gdk_event_get_scroll_deltas ((GdkEvent*)event, &smooth_x, &smooth_y);
+ if (smooth_x > 5)
+ absolute_direction = GDK_SCROLL_RIGHT;
+ else if (smooth_x < -5)
+ absolute_direction = GDK_SCROLL_LEFT;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (pager->priv->scroll_mode == WNCK_PAGER_SCROLL_2D)
+ {
+ switch (absolute_direction)
+ {
+ case GDK_SCROLL_DOWN:
+ if (index + n_columns < n_workspaces)
+ {
+ index += n_columns;
+ }
+ else if (wrap_workspaces && index == n_workspaces - 1)
+ {
+ index = 0;
+ }
+ else if ((index < n_workspaces - 1 &&
+ index + in_last_row != n_workspaces - 1) ||
+ (index == n_workspaces - 1 &&
+ in_last_row != 0))
+ {
+ index = (index % n_columns) + 1;
+ }
+ break;
+ case GDK_SCROLL_RIGHT:
+ if (index < n_workspaces - 1)
+ {
+ index++;
+ }
+ else if (wrap_workspaces)
+ {
+ index = 0;
+ }
+ break;
+ case GDK_SCROLL_UP:
+ if (index - n_columns >= 0)
+ {
+ index -= n_columns;
+ }
+ else if (index > 0)
+ {
+ index = ((pager->priv->n_rows - 1) * n_columns) + (index % n_columns) - 1;
+ }
+ else if (wrap_workspaces)
+ {
+ index = n_workspaces - 1;
+ }
+ if (index >= n_workspaces)
+ {
+ index -= n_columns;
+ }
+ break;
+ case GDK_SCROLL_LEFT:
+ if (index > 0)
+ {
+ index--;
+ }
+ else if (wrap_workspaces)
+ {
+ index = n_workspaces - 1;
+ }
+ break;
+ case GDK_SCROLL_SMOOTH:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ else
+ {
+ switch (absolute_direction)
+ {
+ case GDK_SCROLL_UP:
+ case GDK_SCROLL_LEFT:
+ if (index > 0)
+ {
+ index--;
+ }
+ else if (wrap_workspaces)
+ {
+ index = n_workspaces - 1;
+ }
+ break;
+ case GDK_SCROLL_DOWN:
+ case GDK_SCROLL_RIGHT:
+ if (index < n_workspaces - 1)
+ {
+ index++;
+ }
+ else if (wrap_workspaces)
+ {
+ index = 0;
+ }
+ break;
+ case GDK_SCROLL_SMOOTH:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ space = wnck_screen_get_workspace (pager->priv->screen, index);
+ wnck_workspace_activate (space, event->time);
+
+ return TRUE;
+}
+
+static gboolean
+wnck_pager_query_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip)
+{
+ int i;
+ WnckPager *pager;
+ WnckScreen *screen;
+ WnckWorkspace *space;
+ char *name;
+
+ pager = WNCK_PAGER (widget);
+ screen = pager->priv->screen;
+
+ i = workspace_at_point (pager, x, y, NULL, NULL);
+ space = wnck_screen_get_workspace (screen, i);
+ if (!space)
+ return GTK_WIDGET_CLASS (wnck_pager_parent_class)->query_tooltip (widget,
+ x, y,
+ keyboard_tip,
+ tooltip);
+
+ if (wnck_screen_get_active_workspace (screen) == space)
+ {
+ WnckWindow *window;
+ GdkRectangle workspace_rect;
+
+ get_workspace_rect (pager, i, &workspace_rect);
+
+ window = window_at_point (pager, space, &workspace_rect, x, y);
+
+ if (window)
+ name = g_strdup_printf (_("Click to start dragging \"%s\""),
+ wnck_window_get_name (window));
+ else
+ name = g_strdup_printf (_("Current workspace: \"%s\""),
+ wnck_workspace_get_name (space));
+ }
+ else
+ {
+ name = g_strdup_printf (_("Click to switch to \"%s\""),
+ wnck_workspace_get_name (space));
+ }
+
+ gtk_tooltip_set_text (tooltip, name);
+
+ g_free (name);
+
+ return TRUE;
+}
+
+/**
+ * wnck_pager_new:
+ *
+ * Creates a new #WnckPager. The #WnckPager will show the #WnckWorkspace of the
+ * #WnckScreen it is on.
+ *
+ * Return value: a newly created #WnckPager.
+ */
+GtkWidget*
+wnck_pager_new (void)
+{
+ WnckPager *pager;
+
+ pager = g_object_new (WNCK_TYPE_PAGER, NULL);
+
+ return GTK_WIDGET (pager);
+}
+
+static gboolean
+wnck_pager_set_layout_hint (WnckPager *pager)
+{
+ int layout_rows;
+ int layout_cols;
+
+ /* if we're not realized, we don't know about our screen yet */
+ if (pager->priv->screen == NULL)
+ _wnck_pager_set_screen (pager);
+ /* can still happen if the pager was not added to a widget hierarchy */
+ if (pager->priv->screen == NULL)
+ return FALSE;
+
+ /* The visual representation of the pager doesn't
+ * correspond to the layout of the workspaces
+ * here. i.e. the user will not pay any attention
+ * to the n_rows setting on this pager.
+ */
+ if (!pager->priv->show_all_workspaces)
+ return FALSE;
+
+ if (pager->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ layout_rows = pager->priv->n_rows;
+ layout_cols = 0;
+ }
+ else
+ {
+ layout_rows = 0;
+ layout_cols = pager->priv->n_rows;
+ }
+
+ pager->priv->layout_manager_token =
+ wnck_screen_try_set_workspace_layout (pager->priv->screen,
+ pager->priv->layout_manager_token,
+ layout_rows,
+ layout_cols);
+
+ return (pager->priv->layout_manager_token != WNCK_NO_MANAGER_TOKEN);
+}
+
+/**
+ * wnck_pager_set_orientation:
+ * @pager: a #WnckPager.
+ * @orientation: orientation to use for the layout of #WnckWorkspace on the
+ * #WnckScreen @pager is watching.
+ *
+ * Tries to change the orientation of the layout of #WnckWorkspace on the
+ * #WnckScreen @pager is watching. Since no more than one application should
+ * set this property of a #WnckScreen at a time, setting the layout is not
+ * guaranteed to work.
+ *
+ * If @orientation is %GTK_ORIENTATION_HORIZONTAL, the #WnckWorkspace will be
+ * laid out in rows, with the first #WnckWorkspace in the top left corner.
+ *
+ * If @orientation is %GTK_ORIENTATION_VERTICAL, the #WnckWorkspace will be
+ * laid out in columns, with the first #WnckWorkspace in the top left corner.
+ *
+ * For example, if the layout contains one row, but the orientation of the
+ * layout is vertical, the #WnckPager will display a column of #WnckWorkspace.
+ *
+ * Note that setting the orientation will have an effect on the geometry
+ * management: if @orientation is %GTK_ORIENTATION_HORIZONTAL,
+ * %GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT will be used as request mode; if
+ * @orientation is %GTK_ORIENTATION_VERTICAL, GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH
+ * will be used instead.
+ *
+ * If @pager has not been added to a widget hierarchy, the call will fail
+ * because @pager can't know the screen on which to modify the orientation.
+ *
+ * Return value: %TRUE if the layout of #WnckWorkspace has been successfully
+ * changed or did not need to be changed, %FALSE otherwise.
+ */
+gboolean
+wnck_pager_set_orientation (WnckPager *pager,
+ GtkOrientation orientation)
+{
+ GtkOrientation old_orientation;
+ gboolean old_orientation_is_valid;
+
+ g_return_val_if_fail (WNCK_IS_PAGER (pager), FALSE);
+
+ if (pager->priv->orientation == orientation)
+ return TRUE;
+
+ old_orientation = pager->priv->orientation;
+ old_orientation_is_valid = pager->priv->screen != NULL;
+
+ pager->priv->orientation = orientation;
+
+ if (wnck_pager_set_layout_hint (pager))
+ {
+ gtk_widget_queue_resize (GTK_WIDGET (pager));
+ return TRUE;
+ }
+ else
+ {
+ if (old_orientation_is_valid)
+ pager->priv->orientation = old_orientation;
+ return FALSE;
+ }
+}
+
+/**
+ * wnck_pager_set_n_rows:
+ * @pager: a #WnckPager.
+ * @n_rows: the number of rows to use for the layout of #WnckWorkspace on the
+ * #WnckScreen @pager is watching.
+ *
+ * Tries to change the number of rows in the layout of #WnckWorkspace on the
+ * #WnckScreen @pager is watching. Since no more than one application should
+ * set this property of a #WnckScreen at a time, setting the layout is not
+ * guaranteed to work.
+ *
+ * If @pager has not been added to a widget hierarchy, the call will fail
+ * because @pager can't know the screen on which to modify the layout.
+ *
+ * Return value: %TRUE if the layout of #WnckWorkspace has been successfully
+ * changed or did not need to be changed, %FALSE otherwise.
+ */
+gboolean
+wnck_pager_set_n_rows (WnckPager *pager,
+ int n_rows)
+{
+ int old_n_rows;
+ gboolean old_n_rows_is_valid;
+
+ g_return_val_if_fail (WNCK_IS_PAGER (pager), FALSE);
+ g_return_val_if_fail (n_rows > 0, FALSE);
+
+ if (pager->priv->n_rows == n_rows)
+ return TRUE;
+
+ old_n_rows = pager->priv->n_rows;
+ old_n_rows_is_valid = pager->priv->screen != NULL;
+
+ pager->priv->n_rows = n_rows;
+
+ if (wnck_pager_set_layout_hint (pager))
+ {
+ gtk_widget_queue_resize (GTK_WIDGET (pager));
+ return TRUE;
+ }
+ else
+ {
+ if (old_n_rows_is_valid)
+ pager->priv->n_rows = old_n_rows;
+ return FALSE;
+ }
+}
+
+/**
+ * wnck_pager_set_display_mode:
+ * @pager: a #WnckPager.
+ * @mode: a display mode.
+ *
+ * Sets the display mode for @pager to @mode.
+ */
+void
+wnck_pager_set_display_mode (WnckPager *pager,
+ WnckPagerDisplayMode mode)
+{
+ g_return_if_fail (WNCK_IS_PAGER (pager));
+
+ if (pager->priv->display_mode == mode)
+ return;
+
+ g_object_set (pager, "has-tooltip", mode != WNCK_PAGER_DISPLAY_NAME, NULL);
+
+ pager->priv->display_mode = mode;
+ gtk_widget_queue_resize (GTK_WIDGET (pager));
+}
+
+/**
+ * wnck_pager_set_scroll_mode:
+ * @pager: a #WnckPager.
+ * @scroll_mode: a scroll mode.
+ *
+ * Sets @pager to react to input device scrolling in one of the
+ * available scroll modes.
+ *
+ * Since: 3.36
+ */
+void
+wnck_pager_set_scroll_mode (WnckPager *pager,
+ WnckPagerScrollMode scroll_mode)
+{
+ g_return_if_fail (WNCK_IS_PAGER (pager));
+
+ if (pager->priv->scroll_mode == scroll_mode)
+ return;
+
+ pager->priv->scroll_mode = scroll_mode;
+}
+
+/**
+ * wnck_pager_set_show_all:
+ * @pager: a #WnckPager.
+ * @show_all_workspaces: whether to display all #WnckWorkspace in @pager.
+ *
+ * Sets @pager to display all #WnckWorkspace or not, according to
+ * @show_all_workspaces.
+ */
+void
+wnck_pager_set_show_all (WnckPager *pager,
+ gboolean show_all_workspaces)
+{
+ g_return_if_fail (WNCK_IS_PAGER (pager));
+
+ show_all_workspaces = (show_all_workspaces != 0);
+
+ if (pager->priv->show_all_workspaces == show_all_workspaces)
+ return;
+
+ pager->priv->show_all_workspaces = show_all_workspaces;
+ gtk_widget_queue_resize (GTK_WIDGET (pager));
+}
+
+/**
+ * wnck_pager_set_shadow_type:
+ * @pager: a #WnckPager.
+ * @shadow_type: a shadow type.
+ *
+ * Sets the shadow type for @pager to @shadow_type. The main use of this
+ * function is proper integration of #WnckPager in panels with non-system
+ * backgrounds.
+ *
+ * Since: 2.2
+ */
+void
+wnck_pager_set_shadow_type (WnckPager * pager,
+ GtkShadowType shadow_type)
+{
+ g_return_if_fail (WNCK_IS_PAGER (pager));
+
+ if (pager->priv->shadow_type == shadow_type)
+ return;
+
+ pager->priv->shadow_type = shadow_type;
+ gtk_widget_queue_resize (GTK_WIDGET (pager));
+}
+
+/**
+ * wnck_pager_set_wrap_on_scroll:
+ * @pager: a #WnckPager.
+ * @wrap_on_scroll: a boolean.
+ *
+ * Sets the wrapping behavior of the @pager. Setting it to %TRUE will
+ * wrap arround to the start when scrolling over the end and vice
+ * versa. By default it is set to %FALSE.
+ *
+ * Since: 3.24.0
+ */
+void
+wnck_pager_set_wrap_on_scroll (WnckPager *pager,
+ gboolean wrap_on_scroll)
+{
+ g_return_if_fail (WNCK_IS_PAGER (pager));
+
+ pager->priv->wrap_on_scroll = wrap_on_scroll;
+}
+
+/**
+ * wnck_pager_get_wrap_on_scroll:
+ * @pager: a #WnckPager.
+ *
+ * Return value: %TRUE if the @pager wraps workspaces on a scroll event that
+ * hits a border, %FALSE otherwise.
+ *
+ * Since: 3.24.0
+ */
+gboolean
+wnck_pager_get_wrap_on_scroll (WnckPager *pager)
+{
+ g_return_val_if_fail (WNCK_IS_PAGER (pager), FALSE);
+
+ return pager->priv->wrap_on_scroll;
+}
+
+static void
+active_window_changed_callback (WnckScreen *screen,
+ WnckWindow *previous_window,
+ gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+ gtk_widget_queue_draw (GTK_WIDGET (pager));
+}
+
+static void
+active_workspace_changed_callback (WnckScreen *screen,
+ WnckWorkspace *previous_workspace,
+ gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+ gtk_widget_queue_draw (GTK_WIDGET (pager));
+}
+
+static void
+window_stacking_changed_callback (WnckScreen *screen,
+ gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+ gtk_widget_queue_draw (GTK_WIDGET (pager));
+}
+
+static void
+window_opened_callback (WnckScreen *screen,
+ WnckWindow *window,
+ gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+
+ wnck_pager_connect_window (pager, window);
+ wnck_pager_queue_draw_window (pager, window);
+}
+
+static void
+window_closed_callback (WnckScreen *screen,
+ WnckWindow *window,
+ gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+
+ if (pager->priv->drag_window == window)
+ wnck_pager_clear_drag (pager);
+
+ wnck_pager_queue_draw_window (pager, window);
+}
+
+static void
+workspace_created_callback (WnckScreen *screen,
+ WnckWorkspace *space,
+ gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+ g_signal_connect (space, "name_changed",
+ G_CALLBACK (workspace_name_changed_callback), pager);
+ gtk_widget_queue_resize (GTK_WIDGET (pager));
+}
+
+static void
+workspace_destroyed_callback (WnckScreen *screen,
+ WnckWorkspace *space,
+ gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+ g_signal_handlers_disconnect_by_func (space, G_CALLBACK (workspace_name_changed_callback), pager);
+ gtk_widget_queue_resize (GTK_WIDGET (pager));
+}
+
+static void
+application_opened_callback (WnckScreen *screen,
+ WnckApplication *app,
+ gpointer data)
+{
+ /* WnckPager *pager = WNCK_PAGER (data); */
+}
+
+static void
+application_closed_callback (WnckScreen *screen,
+ WnckApplication *app,
+ gpointer data)
+{
+ /* WnckPager *pager = WNCK_PAGER (data); */
+}
+
+static void
+window_name_changed_callback (WnckWindow *window,
+ gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+ wnck_pager_queue_draw_window (pager, window);
+}
+
+static void
+window_state_changed_callback (WnckWindow *window,
+ WnckWindowState changed,
+ WnckWindowState new,
+ gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+
+ /* if the changed state changes the visibility in the pager, we need to
+ * redraw the whole workspace. wnck_pager_queue_draw_window() might not be
+ * enough */
+ if (!wnck_pager_window_state_is_relevant (changed))
+ wnck_pager_queue_draw_workspace (pager,
+ wnck_pager_window_get_workspace (window,
+ FALSE));
+ else
+ wnck_pager_queue_draw_window (pager, window);
+}
+
+static void
+window_workspace_changed_callback (WnckWindow *window,
+ gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+ gtk_widget_queue_draw (GTK_WIDGET (pager));
+}
+
+static void
+window_icon_changed_callback (WnckWindow *window,
+ gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+ wnck_pager_queue_draw_window (pager, window);
+}
+
+static void
+window_geometry_changed_callback (WnckWindow *window,
+ gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+
+ wnck_pager_queue_draw_window (pager, window);
+}
+
+static void
+background_changed_callback (WnckWindow *window,
+ gpointer data)
+{
+ WnckPager *pager = WNCK_PAGER (data);
+
+ if (pager->priv->bg_cache)
+ {
+ g_object_unref (G_OBJECT (pager->priv->bg_cache));
+ pager->priv->bg_cache = NULL;
+ }
+
+ gtk_widget_queue_draw (GTK_WIDGET (pager));
+}
+
+static void
+workspace_name_changed_callback (WnckWorkspace *space,
+ gpointer data)
+{
+ gtk_widget_queue_resize (GTK_WIDGET (data));
+}
+
+static void
+viewports_changed_callback (WnckWorkspace *space,
+ gpointer data)
+{
+ gtk_widget_queue_resize (GTK_WIDGET (data));
+}
+
+static void
+wnck_pager_connect_screen (WnckPager *pager)
+{
+ int i;
+ guint *c;
+ GList *tmp;
+ WnckScreen *screen;
+
+ g_return_if_fail (pager->priv->screen != NULL);
+
+ screen = pager->priv->screen;
+
+ for (tmp = wnck_screen_get_windows (screen); tmp; tmp = tmp->next)
+ {
+ wnck_pager_connect_window (pager, WNCK_WINDOW (tmp->data));
+ }
+
+ i = 0;
+ c = pager->priv->screen_connections;
+
+ c[i] = g_signal_connect (G_OBJECT (screen), "active_window_changed",
+ G_CALLBACK (active_window_changed_callback),
+ pager);
+ ++i;
+
+ c[i] = g_signal_connect (G_OBJECT (screen), "active_workspace_changed",
+ G_CALLBACK (active_workspace_changed_callback),
+ pager);
+ ++i;
+
+ c[i] = g_signal_connect (G_OBJECT (screen), "window_stacking_changed",
+ G_CALLBACK (window_stacking_changed_callback),
+ pager);
+ ++i;
+
+ c[i] = g_signal_connect (G_OBJECT (screen), "window_opened",
+ G_CALLBACK (window_opened_callback),
+ pager);
+ ++i;
+
+ c[i] = g_signal_connect (G_OBJECT (screen), "window_closed",
+ G_CALLBACK (window_closed_callback),
+ pager);
+ ++i;
+
+ c[i] = g_signal_connect (G_OBJECT (screen), "workspace_created",
+ G_CALLBACK (workspace_created_callback),
+ pager);
+ ++i;
+
+ c[i] = g_signal_connect (G_OBJECT (screen), "workspace_destroyed",
+ G_CALLBACK (workspace_destroyed_callback),
+ pager);
+ ++i;
+
+ c[i] = g_signal_connect (G_OBJECT (screen), "application_opened",
+ G_CALLBACK (application_opened_callback),
+ pager);
+ ++i;
+
+ c[i] = g_signal_connect (G_OBJECT (screen), "application_closed",
+ G_CALLBACK (application_closed_callback),
+ pager);
+ ++i;
+
+ c[i] = g_signal_connect (G_OBJECT (screen), "background_changed",
+ G_CALLBACK (background_changed_callback),
+ pager);
+ ++i;
+
+ c[i] = g_signal_connect (G_OBJECT (screen), "viewports_changed",
+ G_CALLBACK (viewports_changed_callback),
+ pager);
+ ++i;
+
+ g_assert (i == N_SCREEN_CONNECTIONS);
+
+ /* connect to name_changed on each workspace */
+ for (i = 0; i < wnck_screen_get_workspace_count (pager->priv->screen); i++)
+ {
+ WnckWorkspace *space;
+ space = wnck_screen_get_workspace (pager->priv->screen, i);
+ g_signal_connect (space, "name_changed",
+ G_CALLBACK (workspace_name_changed_callback), pager);
+ }
+}
+
+static void
+wnck_pager_connect_window (WnckPager *pager,
+ WnckWindow *window)
+{
+ g_signal_connect (G_OBJECT (window), "name_changed",
+ G_CALLBACK (window_name_changed_callback),
+ pager);
+ g_signal_connect (G_OBJECT (window), "state_changed",
+ G_CALLBACK (window_state_changed_callback),
+ pager);
+ g_signal_connect (G_OBJECT (window), "workspace_changed",
+ G_CALLBACK (window_workspace_changed_callback),
+ pager);
+ g_signal_connect (G_OBJECT (window), "icon_changed",
+ G_CALLBACK (window_icon_changed_callback),
+ pager);
+ g_signal_connect (G_OBJECT (window), "geometry_changed",
+ G_CALLBACK (window_geometry_changed_callback),
+ pager);
+}
+
+static void
+wnck_pager_disconnect_screen (WnckPager *pager)
+{
+ int i;
+ GList *tmp;
+
+ if (pager->priv->screen == NULL)
+ return;
+
+ i = 0;
+ while (i < N_SCREEN_CONNECTIONS)
+ {
+ if (pager->priv->screen_connections[i] != 0)
+ g_signal_handler_disconnect (G_OBJECT (pager->priv->screen),
+ pager->priv->screen_connections[i]);
+
+ pager->priv->screen_connections[i] = 0;
+
+ ++i;
+ }
+
+ for (i = 0; i < wnck_screen_get_workspace_count (pager->priv->screen); i++)
+ {
+ WnckWorkspace *space;
+ space = wnck_screen_get_workspace (pager->priv->screen, i);
+ g_signal_handlers_disconnect_by_func (space, G_CALLBACK (workspace_name_changed_callback), pager);
+ }
+
+ for (tmp = wnck_screen_get_windows (pager->priv->screen); tmp; tmp = tmp->next)
+ {
+ wnck_pager_disconnect_window (pager, WNCK_WINDOW (tmp->data));
+ }
+}
+
+static void
+wnck_pager_disconnect_window (WnckPager *pager,
+ WnckWindow *window)
+{
+ g_signal_handlers_disconnect_by_func (G_OBJECT (window),
+ G_CALLBACK (window_name_changed_callback),
+ pager);
+ g_signal_handlers_disconnect_by_func (G_OBJECT (window),
+ G_CALLBACK (window_state_changed_callback),
+ pager);
+ g_signal_handlers_disconnect_by_func (G_OBJECT (window),
+ G_CALLBACK (window_workspace_changed_callback),
+ pager);
+ g_signal_handlers_disconnect_by_func (G_OBJECT (window),
+ G_CALLBACK (window_icon_changed_callback),
+ pager);
+ g_signal_handlers_disconnect_by_func (G_OBJECT (window),
+ G_CALLBACK (window_geometry_changed_callback),
+ pager);
+}
+
+static void
+wnck_pager_clear_drag (WnckPager *pager)
+{
+ if (pager->priv->dragging)
+ wnck_pager_queue_draw_window (pager, pager->priv->drag_window);
+
+ pager->priv->dragging = FALSE;
+ pager->priv->drag_window = NULL;
+ pager->priv->drag_start_x = -1;
+ pager->priv->drag_start_y = -1;
+}
+
+static GdkPixbuf*
+wnck_pager_get_background (WnckPager *pager,
+ int width,
+ int height)
+{
+ Pixmap p;
+ GdkPixbuf *pix = NULL;
+
+ /* We have to be careful not to keep alternating between
+ * width/height values, otherwise this would get really slow.
+ */
+ if (pager->priv->bg_cache &&
+ gdk_pixbuf_get_width (pager->priv->bg_cache) == width &&
+ gdk_pixbuf_get_height (pager->priv->bg_cache) == height)
+ return pager->priv->bg_cache;
+
+ if (pager->priv->bg_cache)
+ {
+ g_object_unref (G_OBJECT (pager->priv->bg_cache));
+ pager->priv->bg_cache = NULL;
+ }
+
+ if (pager->priv->screen == NULL)
+ return NULL;
+
+ /* FIXME this just globally disables the thumbnailing feature */
+ return NULL;
+
+#define MIN_BG_SIZE 10
+
+ if (width < MIN_BG_SIZE || height < MIN_BG_SIZE)
+ return NULL;
+
+ p = wnck_screen_get_background_pixmap (pager->priv->screen);
+
+ if (p != None)
+ {
+ GdkDisplay *display;
+ Screen *xscreen;
+
+ //xscreen = WNCK_SCREEN_XSCREEN (pager->priv->screen);
+
+ display = gdk_display_get_default ();
+ xscreen = gdk_x11_screen_get_xscreen (gdk_display_get_default_screen (display));
+ pix = _wnck_gdk_pixbuf_get_from_pixmap (xscreen, p);
+ }
+
+ if (pix)
+ {
+ pager->priv->bg_cache = gdk_pixbuf_scale_simple (pix,
+ width,
+ height,
+ GDK_INTERP_BILINEAR);
+
+ g_object_unref (G_OBJECT (pix));
+ }
+
+ return pager->priv->bg_cache;
+}
+
+/*
+ *This will return aobj_pager whose parent is wnck's atk object -Gail Container
+ */
+static AtkObject *
+wnck_pager_get_accessible (GtkWidget *widget)
+{
+ static gboolean first_time = TRUE;
+
+ if (first_time)
+ {
+ AtkObjectFactory *factory;
+ AtkRegistry *registry;
+ GType derived_type;
+ GType derived_atk_type;
+
+ /*
+ * Figure out whether accessibility is enabled by looking at the
+ * type of the accessible object which would be created for
+ * the parent type WnckPager.
+ */
+ derived_type = g_type_parent (WNCK_TYPE_PAGER);
+
+ registry = atk_get_default_registry ();
+ factory = atk_registry_get_factory (registry,
+ derived_type);
+ derived_atk_type = atk_object_factory_get_accessible_type (factory);
+
+ if (g_type_is_a (derived_atk_type, GTK_TYPE_ACCESSIBLE))
+ {
+ /*
+ * Specify what factory to use to create accessible
+ * objects
+ */
+ atk_registry_set_factory_type (registry,
+ WNCK_TYPE_PAGER,
+ WNCK_TYPE_PAGER_ACCESSIBLE_FACTORY);
+
+ atk_registry_set_factory_type (registry,
+ WNCK_TYPE_WORKSPACE,
+ WNCK_TYPE_WORKSPACE_ACCESSIBLE_FACTORY);
+ }
+ first_time = FALSE;
+ }
+ return GTK_WIDGET_CLASS (wnck_pager_parent_class)->get_accessible (widget);
+}
+
+int
+_wnck_pager_get_n_workspaces (WnckPager *pager)
+{
+ return wnck_screen_get_workspace_count (pager->priv->screen);
+}
+
+const char*
+_wnck_pager_get_workspace_name (WnckPager *pager,
+ int i)
+{
+ WnckWorkspace *space;
+
+ space = wnck_screen_get_workspace (pager->priv->screen, i);
+ if (space)
+ return wnck_workspace_get_name (space);
+ else
+ return NULL;
+}
+
+WnckWorkspace*
+_wnck_pager_get_active_workspace (WnckPager *pager)
+{
+ return wnck_screen_get_active_workspace (pager->priv->screen);
+}
+
+WnckWorkspace*
+_wnck_pager_get_workspace (WnckPager *pager,
+ int i)
+{
+ return wnck_screen_get_workspace (pager->priv->screen, i);
+}
+
+void
+_wnck_pager_activate_workspace (WnckWorkspace *wspace,
+ guint32 timestamp)
+{
+ wnck_workspace_activate (wspace, timestamp);
+}
+
+void
+_wnck_pager_get_workspace_rect (WnckPager *pager,
+ int i,
+ GdkRectangle *rect)
+{
+ get_workspace_rect (pager, i, rect);
+}
diff --git a/libwnck/widgets/pager.h b/libwnck/widgets/pager.h
new file mode 100644
index 0000000..9a92ac5
--- /dev/null
+++ b/libwnck/widgets/pager.h
@@ -0,0 +1,125 @@
+/* pager object */
+/* vim: set sw=2 et: */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2003, 2005-2007 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__LIBWNCK_H_INSIDE__) && !defined (WNCK_COMPILATION)
+#error "Only <libwnck/libwnck.h> can be included directly."
+#endif
+
+#ifndef WNCK_PAGER_H
+#define WNCK_PAGER_H
+
+#include <gtk/gtk.h>
+#include <libwnck/libwnck.h>
+
+G_BEGIN_DECLS
+
+#define WNCK_TYPE_PAGER (wnck_pager_get_type ())
+#define WNCK_PAGER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), WNCK_TYPE_PAGER, WnckPager))
+#define WNCK_PAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WNCK_TYPE_PAGER, WnckPagerClass))
+#define WNCK_IS_PAGER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), WNCK_TYPE_PAGER))
+#define WNCK_IS_PAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WNCK_TYPE_PAGER))
+#define WNCK_PAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WNCK_TYPE_PAGER, WnckPagerClass))
+
+typedef struct _WnckPager WnckPager;
+typedef struct _WnckPagerClass WnckPagerClass;
+typedef struct _WnckPagerPrivate WnckPagerPrivate;
+
+/**
+ * WnckPager:
+ *
+ * The #WnckPager struct contains only private fields and should not be
+ * directly accessed.
+ */
+struct _WnckPager
+{
+ GtkContainer parent_instance;
+
+ WnckPagerPrivate *priv;
+};
+
+struct _WnckPagerClass
+{
+ GtkContainerClass parent_class;
+
+ /* Padding for future expansion */
+ void (* pad1) (void);
+ void (* pad2) (void);
+ void (* pad3) (void);
+ void (* pad4) (void);
+};
+
+/**
+ * WnckPagerDisplayMode:
+ * @WNCK_PAGER_DISPLAY_NAME: the #WnckPager will only display the names of the
+ * workspaces.
+ * @WNCK_PAGER_DISPLAY_CONTENT: the #WnckPager will display a representation
+ * for each window in the workspaces.
+ *
+ * Mode defining what a #WnckPager will display.
+ */
+typedef enum {
+ WNCK_PAGER_DISPLAY_NAME,
+ WNCK_PAGER_DISPLAY_CONTENT
+} WnckPagerDisplayMode;
+
+/**
+ * WnckPagerScrollMode:
+ * @WNCK_PAGER_SCROLL_2D: given that the workspaces are set up in multiple rows,
+ * scrolling on the #WnckPager will cycle through the workspaces as if on a
+ * 2-dimensional map. Example cycling order with 2 rows and 4 workspaces: 1 3 2 4.
+ * @WNCK_PAGER_SCROLL_1D: the #WnckPager will always cycle workspaces in a linear
+ * manner, irrespective of how many rows are configured. (Hint: Better for mice)
+ * Example cycling order with 2 rows and 4 workspaces: 1 2 3 4.
+ * @WNCK_PAGER_SCROLL_NONE: the #WnckPager will not cycle workspaces. Since 3.40.
+ *
+ * Mode defining in which order scrolling on a #WnckPager will cycle through workspaces.
+ *
+ * Since: 3.36
+ */
+typedef enum {
+ WNCK_PAGER_SCROLL_2D,
+ WNCK_PAGER_SCROLL_1D,
+ WNCK_PAGER_SCROLL_NONE
+} WnckPagerScrollMode;
+
+GType wnck_pager_get_type (void) G_GNUC_CONST;
+
+GtkWidget* wnck_pager_new (void);
+
+gboolean wnck_pager_set_orientation (WnckPager *pager,
+ GtkOrientation orientation);
+gboolean wnck_pager_set_n_rows (WnckPager *pager,
+ int n_rows);
+void wnck_pager_set_display_mode (WnckPager *pager,
+ WnckPagerDisplayMode mode);
+void wnck_pager_set_scroll_mode (WnckPager *pager,
+ WnckPagerScrollMode scroll_mode);
+void wnck_pager_set_show_all (WnckPager *pager,
+ gboolean show_all_workspaces);
+void wnck_pager_set_shadow_type (WnckPager *pager,
+ GtkShadowType shadow_type);
+void wnck_pager_set_wrap_on_scroll (WnckPager *pager,
+ gboolean wrap_on_scroll);
+gboolean wnck_pager_get_wrap_on_scroll (WnckPager *pager);
+
+G_END_DECLS
+
+#endif /* WNCK_PAGER_H */
diff --git a/libwnck/widgets/private.h b/libwnck/widgets/private.h
new file mode 100644
index 0000000..e00c819
--- /dev/null
+++ b/libwnck/widgets/private.h
@@ -0,0 +1,60 @@
+/* Private stuff */
+/* vim: set sw=2 et: */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2006-2007 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WNCK_PRIVATE_H
+#define WNCK_PRIVATE_H
+
+#include <libwnck/libwnck.h>
+#include "pager.h"
+
+G_BEGIN_DECLS
+
+#define WNCK_ACTIVATE_TIMEOUT 1
+
+
+/* this one is in pager.c since it needs code from there to draw the icon */
+void _wnck_window_set_as_drag_icon (WnckWindow *window,
+ GdkDragContext *context,
+ GtkWidget *drag_source);
+
+void _make_gtk_label_bold (GtkLabel *label);
+void _make_gtk_label_normal (GtkLabel *label);
+
+void _wnck_pager_activate_workspace (WnckWorkspace *wspace,
+ guint32 timestamp);
+int _wnck_pager_get_n_workspaces (WnckPager *pager);
+const char* _wnck_pager_get_workspace_name (WnckPager *pager,
+ int i);
+WnckWorkspace* _wnck_pager_get_active_workspace (WnckPager *pager);
+WnckWorkspace* _wnck_pager_get_workspace (WnckPager *pager,
+ int i);
+void _wnck_pager_get_workspace_rect (WnckPager *pager,
+ int i,
+ GdkRectangle *rect);
+
+void _wnck_selector_set_window_icon (GtkWidget *image,
+ WnckWindow *window);
+
+void _wnck_ensure_fallback_style (void);
+
+G_END_DECLS
+
+#endif /* WNCK_PRIVATE_H */
diff --git a/libwnck/widgets/selector.c b/libwnck/widgets/selector.c
new file mode 100644
index 0000000..708b7ae
--- /dev/null
+++ b/libwnck/widgets/selector.c
@@ -0,0 +1,1278 @@
+/* selector */
+/* vim: set sw=2 et: */
+/*
+ * Copyright (C) 2003 Sun Microsystems, Inc.
+ * Copyright (C) 2001 Free Software Foundation, Inc.
+ * Copyright (C) 2000 Helix Code, Inc.
+ * Copyright (C) 2005-2007 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Mark McLoughlin <mark@skynet.ie>
+ * George Lebl <jirka@5z.com>
+ * Jacob Berkman <jacob@helixcode.com>
+ */
+
+#include <config.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+
+#include <glib/gi18n-lib.h>
+#include "selector.h"
+#include <libwnck/libwnck.h>
+#include "wnck-image-menu-item-private.h"
+#include "private.h"
+
+/**
+ * SECTION:selector
+ * @short_description: a window selector widget, showing the list of windows as
+ * a menu.
+ * @see_also: #WnckTasklist
+ * @stability: Unstable
+ *
+ * The #WnckSelector represents client windows on a screen as a menu, where
+ * menu items are labelled with the window titles and icons. Activating a menu
+ * item activates the represented window.
+ *
+ * The #WnckSelector will automatically detect the screen it is on, and will
+ * represent windows of this screen only.
+ */
+
+struct _WnckSelectorPrivate {
+ GtkWidget *image;
+ WnckWindow *icon_window;
+
+ /* those have the same lifecycle as the menu */
+ GtkWidget *menu;
+ GtkWidget *no_windows_item;
+ GHashTable *window_hash;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (WnckSelector, wnck_selector, GTK_TYPE_MENU_BAR);
+
+static GObject *wnck_selector_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties);
+static void wnck_selector_dispose (GObject *object);
+static void wnck_selector_finalize (GObject *object);
+static void wnck_selector_realize (GtkWidget *widget);
+static void wnck_selector_unrealize (GtkWidget *widget);
+static gboolean wnck_selector_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event);
+static void wnck_selector_connect_to_window (WnckSelector *selector,
+ WnckWindow *window);
+
+static void wnck_selector_insert_window (WnckSelector *selector,
+ WnckWindow *window);
+static void wnck_selector_append_window (WnckSelector *selector,
+ WnckWindow *window);
+
+static gint
+wnck_selector_windows_compare (gconstpointer a,
+ gconstpointer b)
+{
+ int posa;
+ int posb;
+
+ posa = wnck_window_get_sort_order (WNCK_WINDOW (a));
+ posb = wnck_window_get_sort_order (WNCK_WINDOW (b));
+
+ return (posa - posb);
+}
+
+static void
+wncklet_connect_while_alive (gpointer object,
+ const char *signal,
+ GCallback func,
+ gpointer func_data, gpointer alive_object)
+{
+ GClosure *closure;
+
+ closure = g_cclosure_new (func, func_data, NULL);
+ g_object_watch_closure (G_OBJECT (alive_object), closure);
+ g_signal_connect_closure_by_id (object,
+ g_signal_lookup (signal,
+ G_OBJECT_TYPE (object)), 0,
+ closure, FALSE);
+}
+
+static WnckScreen *
+wnck_selector_get_screen (WnckSelector *selector)
+{
+ GdkScreen *screen;
+
+ g_assert (gtk_widget_has_screen (GTK_WIDGET (selector)));
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (selector));
+
+ return wnck_screen_get (gdk_x11_screen_get_screen_number (screen));
+}
+
+static GdkPixbuf *
+wnck_selector_get_default_window_icon (void)
+{
+ static GdkPixbuf *retval = NULL;
+
+ if (retval)
+ return retval;
+
+ retval = gdk_pixbuf_new_from_resource ("/org/gnome/libwnck/default_icon.png", NULL);
+
+ g_assert (retval);
+
+ return retval;
+}
+
+static GdkPixbuf *
+wnck_selector_dimm_icon (GdkPixbuf *pixbuf)
+{
+ int x, y, pixel_stride, row_stride;
+ guchar *row, *pixels;
+ int w, h;
+ GdkPixbuf *dimmed;
+
+ w = gdk_pixbuf_get_width (pixbuf);
+ h = gdk_pixbuf_get_height (pixbuf);
+
+ if (gdk_pixbuf_get_has_alpha (pixbuf))
+ dimmed = gdk_pixbuf_copy (pixbuf);
+ else
+ dimmed = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
+
+ pixel_stride = 4;
+
+ row = gdk_pixbuf_get_pixels (dimmed);
+ row_stride = gdk_pixbuf_get_rowstride (dimmed);
+
+ for (y = 0; y < h; y++)
+ {
+ pixels = row;
+ for (x = 0; x < w; x++)
+ {
+ pixels[3] /= 2;
+ pixels += pixel_stride;
+ }
+ row += row_stride;
+ }
+
+ return dimmed;
+}
+
+void
+_wnck_selector_set_window_icon (GtkWidget *image,
+ WnckWindow *window)
+{
+ GdkPixbuf *pixbuf, *freeme, *freeme2;
+ int width, height;
+ int icon_size = -1;
+
+ pixbuf = NULL;
+ freeme = NULL;
+ freeme2 = NULL;
+
+ if (window)
+ pixbuf = wnck_window_get_mini_icon (window);
+
+ if (!pixbuf)
+ pixbuf = wnck_selector_get_default_window_icon ();
+
+ if (icon_size == -1)
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, NULL, &icon_size);
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ if (icon_size != -1 && (width > icon_size || height > icon_size))
+ {
+ double scale;
+
+ scale = ((double) icon_size) / MAX (width, height);
+
+ pixbuf = gdk_pixbuf_scale_simple (pixbuf, width * scale,
+ height * scale, GDK_INTERP_BILINEAR);
+ freeme = pixbuf;
+ }
+
+ if (window && wnck_window_is_minimized (window))
+ {
+ pixbuf = wnck_selector_dimm_icon (pixbuf);
+ freeme2 = pixbuf;
+ }
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
+
+ if (freeme)
+ g_object_unref (freeme);
+ if (freeme2)
+ g_object_unref (freeme2);
+}
+
+static void
+wnck_selector_set_active_window (WnckSelector *selector, WnckWindow *window)
+{
+ _wnck_selector_set_window_icon (selector->priv->image, window);
+ selector->priv->icon_window = window;
+}
+
+static void
+wnck_selector_make_menu_consistent (WnckSelector *selector)
+{
+ GList *l, *children;
+ int workspace_n;
+ GtkWidget *workspace_item;
+ GtkWidget *separator;
+ gboolean separator_is_first;
+ gboolean separator_is_last;
+ gboolean visible_window;
+
+ workspace_n = -1;
+ workspace_item = NULL;
+
+ separator = NULL;
+ separator_is_first = FALSE;
+ separator_is_last = FALSE;
+
+ visible_window = FALSE;
+
+ children = gtk_container_get_children (GTK_CONTAINER (selector->priv->menu));
+
+ for (l = children; l; l = l->next)
+ {
+ int i;
+
+ i = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (l->data),
+ "wnck-selector-workspace-n"));
+
+ if (i > 0)
+ {
+ workspace_n = i - 1;
+
+ /* we have two consecutive workspace items => hide the first */
+ if (workspace_item)
+ gtk_widget_hide (workspace_item);
+
+ workspace_item = GTK_WIDGET (l->data);
+ }
+ else if (GTK_IS_SEPARATOR_MENU_ITEM (l->data))
+ {
+ if (!visible_window)
+ separator_is_first = TRUE;
+ separator_is_last = TRUE;
+ separator = GTK_WIDGET (l->data);
+ }
+ else if (gtk_widget_get_visible (l->data) &&
+ l->data != selector->priv->no_windows_item)
+ {
+ separator_is_last = FALSE;
+ visible_window = TRUE;
+
+ /* if we know of a workspace item that was not shown */
+ if (workspace_item)
+ {
+ WnckWindow *window;
+ WnckWorkspace *workspace;
+
+ window = g_object_get_data (G_OBJECT (l->data),
+ "wnck-selector-window");
+
+ if (window)
+ {
+ workspace = wnck_window_get_workspace (window);
+ if (workspace &&
+ workspace_n == wnck_workspace_get_number (workspace))
+ {
+ gtk_widget_show (workspace_item);
+ workspace_n = -1;
+ workspace_item = NULL;
+ }
+ }
+ }
+ } /* end if (normal item) */
+ }
+
+ g_list_free (children);
+
+ /* do we have a trailing workspace item to be hidden? */
+ if (workspace_item)
+ gtk_widget_hide (workspace_item);
+
+ if (separator)
+ {
+ if (separator_is_first || separator_is_last)
+ gtk_widget_hide (separator);
+ else
+ gtk_widget_show (separator);
+ }
+
+ if (visible_window)
+ gtk_widget_hide (selector->priv->no_windows_item);
+ else
+ gtk_widget_show (selector->priv->no_windows_item);
+}
+
+static void
+wnck_selector_window_icon_changed (WnckWindow *window,
+ WnckSelector *selector)
+{
+ GtkWidget *item;
+
+ if (selector->priv->icon_window == window)
+ wnck_selector_set_active_window (selector, window);
+
+ if (!selector->priv->window_hash)
+ return;
+
+ item = g_hash_table_lookup (selector->priv->window_hash, window);
+ if (item != NULL)
+ {
+ wnck_image_menu_item_set_image_from_window (WNCK_IMAGE_MENU_ITEM (item),
+ window);
+ }
+}
+
+static void
+wnck_selector_window_name_changed (WnckWindow *window,
+ WnckSelector *selector)
+{
+ GtkWidget *item;
+ char *window_name;
+
+ if (!selector->priv->window_hash)
+ return;
+
+ item = g_hash_table_lookup (selector->priv->window_hash, window);
+ if (item != NULL)
+ {
+ window_name = wnck_window_get_name_for_display (window, FALSE, TRUE);
+ gtk_menu_item_set_label (GTK_MENU_ITEM (item), window_name);
+ g_free (window_name);
+ }
+}
+
+static void
+wnck_selector_window_state_changed (WnckWindow *window,
+ WnckWindowState changed_mask,
+ WnckWindowState new_state,
+ WnckSelector *selector)
+{
+ GtkWidget *item;
+ char *window_name;
+
+ if (!
+ (changed_mask &
+ (WNCK_WINDOW_STATE_MINIMIZED | WNCK_WINDOW_STATE_SHADED |
+ WNCK_WINDOW_STATE_SKIP_TASKLIST |
+ WNCK_WINDOW_STATE_DEMANDS_ATTENTION |
+ WNCK_WINDOW_STATE_URGENT)))
+ return;
+
+ if (!selector->priv->window_hash)
+ return;
+
+ item = g_hash_table_lookup (selector->priv->window_hash, window);
+ if (item == NULL)
+ return;
+
+ if (changed_mask & WNCK_WINDOW_STATE_SKIP_TASKLIST)
+ {
+ if (wnck_window_is_skip_tasklist (window))
+ gtk_widget_hide (item);
+ else
+ gtk_widget_show (item);
+
+ wnck_selector_make_menu_consistent (selector);
+
+ gtk_menu_reposition (GTK_MENU (selector->priv->menu));
+ }
+
+ if (changed_mask &
+ (WNCK_WINDOW_STATE_DEMANDS_ATTENTION | WNCK_WINDOW_STATE_URGENT))
+ {
+ if (wnck_window_or_transient_needs_attention (window))
+ wnck_image_menu_item_make_label_bold (WNCK_IMAGE_MENU_ITEM (item));
+ else
+ wnck_image_menu_item_make_label_normal (WNCK_IMAGE_MENU_ITEM (item));
+ }
+
+ if (changed_mask &
+ (WNCK_WINDOW_STATE_MINIMIZED | WNCK_WINDOW_STATE_SHADED))
+ {
+ window_name = wnck_window_get_name_for_display (window, FALSE, TRUE);
+ gtk_menu_item_set_label (GTK_MENU_ITEM (item), window_name);
+ g_free (window_name);
+ }
+}
+
+static void
+wnck_selector_window_workspace_changed (WnckWindow *window,
+ WnckSelector *selector)
+{
+ GtkWidget *item;
+
+ if (!selector->priv->menu || !gtk_widget_get_visible (selector->priv->menu))
+ return;
+
+ if (!selector->priv->window_hash)
+ return;
+
+ item = g_hash_table_lookup (selector->priv->window_hash, window);
+ if (!item)
+ return;
+
+ /* destroy the item and recreate one so it's at the right position */
+ gtk_widget_destroy (item);
+ g_hash_table_remove (selector->priv->window_hash, window);
+
+ wnck_selector_insert_window (selector, window);
+ wnck_selector_make_menu_consistent (selector);
+
+ gtk_menu_reposition (GTK_MENU (selector->priv->menu));
+}
+
+static void
+wnck_selector_active_window_changed (WnckScreen *screen,
+ WnckWindow *previous_window,
+ WnckSelector *selector)
+{
+ WnckWindow *window;
+
+ window = wnck_screen_get_active_window (screen);
+
+ if (selector->priv->icon_window != window)
+ wnck_selector_set_active_window (selector, window);
+}
+
+static void
+wnck_selector_activate_window (WnckWindow *window)
+{
+ WnckWorkspace *workspace;
+ guint32 timestamp;
+
+ /* We're in an activate callback, so gtk_get_current_time() works... */
+ timestamp = gtk_get_current_event_time ();
+
+ /* FIXME: THIS IS SICK AND WRONG AND BUGGY. See the end of
+ * http://mail.gnome.org/archives/wm-spec-list/2005-July/msg00032.html
+ * There should only be *one* activate call.
+ */
+ workspace = wnck_window_get_workspace (window);
+ if (workspace)
+ wnck_workspace_activate (workspace, timestamp);
+
+ wnck_window_activate (window, timestamp);
+}
+
+static void
+wnck_selector_drag_begin (GtkWidget *widget,
+ GdkDragContext *context,
+ WnckWindow *window)
+{
+ while (widget)
+ {
+ if (WNCK_IS_SELECTOR (widget))
+ break;
+
+ if (GTK_IS_MENU (widget))
+ widget = gtk_menu_get_attach_widget (GTK_MENU (widget));
+ else
+ widget = gtk_widget_get_parent (widget);
+ }
+
+ if (widget)
+ _wnck_window_set_as_drag_icon (window, context, widget);
+}
+
+static void
+wnck_selector_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time,
+ WnckWindow *window)
+{
+ gulong xid;
+
+ xid = wnck_window_get_xid (window);
+ gtk_selection_data_set (selection_data,
+ gtk_selection_data_get_target (selection_data),
+ 8, (guchar *)&xid, sizeof (gulong));
+}
+
+static GtkWidget *
+wnck_selector_item_new (WnckSelector *selector,
+ const gchar *label, WnckWindow *window)
+{
+ GtkWidget *item;
+ static const GtkTargetEntry targets[] = {
+ { (gchar *) "application/x-wnck-window-id", 0, 0 }
+ };
+
+ item = wnck_image_menu_item_new_with_label (label);
+
+ if (window != NULL)
+ {
+ /* if window demands attention, bold the label */
+ if (wnck_window_or_transient_needs_attention (window))
+ wnck_image_menu_item_make_label_bold (WNCK_IMAGE_MENU_ITEM (item));
+
+ g_hash_table_insert (selector->priv->window_hash, window, item);
+ }
+
+ if (window != NULL)
+ {
+ gtk_drag_source_set (item,
+ GDK_BUTTON1_MASK,
+ targets, 1,
+ GDK_ACTION_MOVE);
+
+ g_signal_connect_object (item, "drag_data_get",
+ G_CALLBACK (wnck_selector_drag_data_get),
+ G_OBJECT (window),
+ 0);
+
+ g_signal_connect_object (item, "drag_begin",
+ G_CALLBACK (wnck_selector_drag_begin),
+ G_OBJECT (window),
+ 0);
+ }
+
+ return item;
+}
+
+static void
+wnck_selector_workspace_name_changed (WnckWorkspace *workspace,
+ GtkLabel *label)
+{
+ GtkStyleContext *context;
+ GdkRGBA color;
+ char *name;
+ char *markup;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (label));
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, GTK_STATE_FLAG_INSENSITIVE);
+ gtk_style_context_get_color (context, GTK_STATE_FLAG_INSENSITIVE, &color);
+ gtk_style_context_restore (context);
+
+ name = g_markup_escape_text (wnck_workspace_get_name (workspace), -1);
+ markup = g_strdup_printf ("<span size=\"x-small\" style=\"italic\" foreground=\"#%.2x%.2x%.2x\">%s</span>",
+ (int)(color.red * 65535 + 0.5),
+ (int)(color.green * 65535 + 0.5),
+ (int)(color.blue * 65535 + 0.5), name);
+ g_free (name);
+
+ gtk_label_set_markup (label, markup);
+ g_free (markup);
+}
+
+static void
+wnck_selector_workspace_label_style_updated (GtkLabel *label,
+ WnckWorkspace *workspace)
+{
+ wnck_selector_workspace_name_changed (workspace, label);
+}
+
+static void
+wnck_selector_add_workspace (WnckSelector *selector,
+ WnckScreen *screen,
+ int workspace_n)
+{
+ WnckWorkspace *workspace;
+ GtkWidget *item;
+ GtkWidget *label;
+
+ workspace = wnck_screen_get_workspace (screen, workspace_n);
+
+ /* We use a separator in which we add a label. This makes the menu item not
+ * selectable without any hack. */
+ item = gtk_separator_menu_item_new ();
+
+ label = gtk_label_new ("");
+ gtk_label_set_xalign (GTK_LABEL (label), 1.0);
+ gtk_widget_show (label);
+ /* the handler will also take care of setting the name for the first time,
+ * and we'll be able to adapt to theme changes */
+ g_signal_connect (G_OBJECT (label), "style-updated",
+ G_CALLBACK (wnck_selector_workspace_label_style_updated),
+ workspace);
+ wncklet_connect_while_alive (workspace, "name_changed",
+ G_CALLBACK (wnck_selector_workspace_name_changed),
+ label, label);
+
+ gtk_container_add (GTK_CONTAINER (item), label);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (selector->priv->menu), item);
+
+ g_object_set_data (G_OBJECT (item), "wnck-selector-workspace-n",
+ GINT_TO_POINTER (workspace_n + 1));
+}
+
+static GtkWidget *
+wnck_selector_create_window (WnckSelector *selector, WnckWindow *window)
+{
+ GtkWidget *item;
+ char *name;
+
+ name = wnck_window_get_name_for_display (window, FALSE, TRUE);
+
+ item = wnck_selector_item_new (selector, name, window);
+ g_free (name);
+
+ wnck_image_menu_item_set_image_from_window (WNCK_IMAGE_MENU_ITEM (item),
+ window);
+
+ g_signal_connect_swapped (item, "activate",
+ G_CALLBACK (wnck_selector_activate_window),
+ window);
+
+ if (!wnck_window_is_skip_tasklist (window))
+ gtk_widget_show (item);
+
+ g_object_set_data (G_OBJECT (item), "wnck-selector-window", window);
+
+ return item;
+}
+
+static void
+wnck_selector_insert_window (WnckSelector *selector, WnckWindow *window)
+{
+ GtkWidget *item;
+ WnckScreen *screen;
+ WnckWorkspace *workspace;
+ int workspace_n;
+ int i;
+
+ screen = wnck_selector_get_screen (selector);
+ workspace = wnck_window_get_workspace (window);
+
+ if (!workspace && !wnck_window_is_pinned (window))
+ return;
+
+ item = wnck_selector_create_window (selector, window);
+
+ if (!workspace || workspace == wnck_screen_get_active_workspace (screen))
+ {
+ /* window is pinned or in the current workspace
+ * => insert before the separator */
+ GList *l, *children;
+
+ i = 0;
+
+ children = gtk_container_get_children (GTK_CONTAINER (selector->priv->menu));
+ for (l = children; l; l = l->next)
+ {
+ if (GTK_IS_SEPARATOR_MENU_ITEM (l->data))
+ break;
+ i++;
+ }
+ g_list_free (children);
+
+ gtk_menu_shell_insert (GTK_MENU_SHELL (selector->priv->menu),
+ item, i);
+ }
+ else
+ {
+ workspace_n = wnck_workspace_get_number (workspace);
+
+ if (workspace_n == wnck_screen_get_workspace_count (screen) - 1)
+ /* window is in last workspace => just append */
+ gtk_menu_shell_append (GTK_MENU_SHELL (selector->priv->menu), item);
+ else
+ {
+ /* insert just before the next workspace item */
+ GList *l, *children;
+
+ i = 0;
+
+ children = gtk_container_get_children (GTK_CONTAINER (selector->priv->menu));
+ for (l = children; l; l = l->next)
+ {
+ int j;
+ j = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (l->data),
+ "wnck-selector-workspace-n"));
+ if (j - 1 == workspace_n + 1)
+ break;
+ i++;
+ }
+ g_list_free (children);
+
+ gtk_menu_shell_insert (GTK_MENU_SHELL (selector->priv->menu),
+ item, i);
+ }
+ }
+}
+
+static void
+wnck_selector_append_window (WnckSelector *selector, WnckWindow *window)
+{
+ GtkWidget *item;
+
+ item = wnck_selector_create_window (selector, window);
+ gtk_menu_shell_append (GTK_MENU_SHELL (selector->priv->menu), item);
+}
+
+static void
+wnck_selector_window_opened (WnckScreen *screen,
+ WnckWindow *window, WnckSelector *selector)
+{
+ wnck_selector_connect_to_window (selector, window);
+
+ if (!selector->priv->menu || !gtk_widget_get_visible (selector->priv->menu))
+ return;
+
+ if (!selector->priv->window_hash)
+ return;
+
+ wnck_selector_insert_window (selector, window);
+ wnck_selector_make_menu_consistent (selector);
+
+ gtk_menu_reposition (GTK_MENU (selector->priv->menu));
+}
+
+static void
+wnck_selector_window_closed (WnckScreen *screen,
+ WnckWindow *window, WnckSelector *selector)
+{
+ GtkWidget *item;
+
+ if (window == selector->priv->icon_window)
+ wnck_selector_set_active_window (selector, NULL);
+
+ if (!selector->priv->menu || !gtk_widget_get_visible (selector->priv->menu))
+ return;
+
+ if (!selector->priv->window_hash)
+ return;
+
+ item = g_hash_table_lookup (selector->priv->window_hash, window);
+ if (!item)
+ return;
+
+ g_object_set_data (G_OBJECT (item), "wnck-selector-window", NULL);
+
+ gtk_widget_hide (item);
+ wnck_selector_make_menu_consistent (selector);
+
+ gtk_menu_reposition (GTK_MENU (selector->priv->menu));
+}
+
+static void
+wnck_selector_workspace_created (WnckScreen *screen,
+ WnckWorkspace *workspace,
+ WnckSelector *selector)
+{
+ if (!selector->priv->menu || !gtk_widget_get_visible (selector->priv->menu))
+ return;
+
+ /* this is assuming that the new workspace will have a higher number
+ * than all the old workspaces, which is okay since the old workspaces
+ * didn't disappear in the meantime */
+ wnck_selector_add_workspace (selector, screen,
+ wnck_workspace_get_number (workspace));
+
+ wnck_selector_make_menu_consistent (selector);
+
+ gtk_menu_reposition (GTK_MENU (selector->priv->menu));
+}
+
+static void
+wnck_selector_workspace_destroyed (WnckScreen *screen,
+ WnckWorkspace *workspace,
+ WnckSelector *selector)
+{
+ GList *l, *children;
+ GtkWidget *destroy;
+ int i;
+
+ if (!selector->priv->menu || !gtk_widget_get_visible (selector->priv->menu))
+ return;
+
+ destroy = NULL;
+
+ i = wnck_workspace_get_number (workspace);
+
+ /* search for the item of this workspace so that we destroy it */
+ children = gtk_container_get_children (GTK_CONTAINER (selector->priv->menu));
+
+ for (l = children; l; l = l->next)
+ {
+ int j;
+
+ j = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (l->data),
+ "wnck-selector-workspace-n"));
+
+
+ if (j - 1 == i)
+ destroy = GTK_WIDGET (l->data);
+ else if (j - 1 > i)
+ /* shift the following workspaces */
+ g_object_set_data (G_OBJECT (l->data), "wnck-selector-workspace-n",
+ GINT_TO_POINTER (j - 1));
+ }
+
+ g_list_free (children);
+
+ if (destroy)
+ gtk_widget_destroy (destroy);
+
+ wnck_selector_make_menu_consistent (selector);
+
+ gtk_menu_reposition (GTK_MENU (selector->priv->menu));
+}
+
+static void
+wnck_selector_connect_to_window (WnckSelector *selector, WnckWindow *window)
+{
+ wncklet_connect_while_alive (window, "icon_changed",
+ G_CALLBACK (wnck_selector_window_icon_changed),
+ selector, selector);
+ wncklet_connect_while_alive (window, "name_changed",
+ G_CALLBACK (wnck_selector_window_name_changed),
+ selector, selector);
+ wncklet_connect_while_alive (window, "state_changed",
+ G_CALLBACK (wnck_selector_window_state_changed),
+ selector, selector);
+ wncklet_connect_while_alive (window, "workspace_changed",
+ G_CALLBACK (wnck_selector_window_workspace_changed),
+ selector, selector);
+}
+
+static void
+wnck_selector_disconnect_from_window (WnckSelector *selector,
+ WnckWindow *window)
+{
+ g_signal_handlers_disconnect_by_func (window,
+ wnck_selector_window_icon_changed,
+ selector);
+ g_signal_handlers_disconnect_by_func (window,
+ wnck_selector_window_name_changed,
+ selector);
+ g_signal_handlers_disconnect_by_func (window,
+ wnck_selector_window_state_changed,
+ selector);
+ g_signal_handlers_disconnect_by_func (window,
+ wnck_selector_window_workspace_changed,
+ selector);
+}
+
+static void
+wnck_selector_connect_to_screen (WnckSelector *selector, WnckScreen *screen)
+{
+ wncklet_connect_while_alive (screen, "active_window_changed",
+ G_CALLBACK
+ (wnck_selector_active_window_changed),
+ selector, selector);
+
+ wncklet_connect_while_alive (screen, "window_opened",
+ G_CALLBACK (wnck_selector_window_opened),
+ selector, selector);
+
+ wncklet_connect_while_alive (screen, "window_closed",
+ G_CALLBACK (wnck_selector_window_closed),
+ selector, selector);
+
+ wncklet_connect_while_alive (screen, "workspace_created",
+ G_CALLBACK (wnck_selector_workspace_created),
+ selector, selector);
+
+ wncklet_connect_while_alive (screen, "workspace_destroyed",
+ G_CALLBACK (wnck_selector_workspace_destroyed),
+ selector, selector);
+}
+
+static void
+wnck_selector_disconnect_from_screen (WnckSelector *selector,
+ WnckScreen *screen)
+{
+ g_signal_handlers_disconnect_by_func (screen,
+ wnck_selector_active_window_changed,
+ selector);
+ g_signal_handlers_disconnect_by_func (screen,
+ wnck_selector_window_opened,
+ selector);
+ g_signal_handlers_disconnect_by_func (screen,
+ wnck_selector_window_closed,
+ selector);
+ g_signal_handlers_disconnect_by_func (screen,
+ wnck_selector_workspace_created,
+ selector);
+ g_signal_handlers_disconnect_by_func (screen,
+ wnck_selector_workspace_destroyed,
+ selector);
+}
+
+static void
+wnck_selector_destroy_menu (GtkWidget *widget, WnckSelector *selector)
+{
+ selector->priv->menu = NULL;
+
+ if (selector->priv->window_hash)
+ g_hash_table_destroy (selector->priv->window_hash);
+ selector->priv->window_hash = NULL;
+
+ selector->priv->no_windows_item = NULL;
+}
+
+static gboolean
+wnck_selector_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event)
+{
+ WnckSelector *selector;
+ WnckScreen *screen;
+ WnckWorkspace *workspace;
+ GList *windows_list;
+ GList *l;
+ WnckWindow *window;
+ WnckWindow *previous_window;
+ gboolean should_activate_next_window;
+
+ selector = WNCK_SELECTOR (widget);
+
+ screen = wnck_selector_get_screen (selector);
+ workspace = wnck_screen_get_active_workspace (screen);
+ windows_list = wnck_screen_get_windows (screen);
+ windows_list = g_list_sort (windows_list, wnck_selector_windows_compare);
+
+ /* Walk through the list of windows until we find the active one
+ * (considering only those windows on the same workspace).
+ * Then, depending on whether we're scrolling up or down, activate the next
+ * window in the list (if it exists), or the previous one.
+ */
+ previous_window = NULL;
+ should_activate_next_window = FALSE;
+ for (l = windows_list; l; l = l->next)
+ {
+ window = WNCK_WINDOW (l->data);
+
+ if (wnck_window_is_skip_tasklist (window))
+ continue;
+
+ if (workspace && !wnck_window_is_pinned (window) &&
+ wnck_window_get_workspace (window) != workspace)
+ continue;
+
+ if (should_activate_next_window)
+ {
+ wnck_window_activate_transient (window, event->time);
+ return TRUE;
+ }
+
+ if (wnck_window_is_active (window))
+ {
+ switch (event->direction)
+ {
+ case GDK_SCROLL_UP:
+ if (previous_window != NULL)
+ {
+ wnck_window_activate_transient (previous_window,
+ event->time);
+ return TRUE;
+ }
+ break;
+
+ case GDK_SCROLL_DOWN:
+ should_activate_next_window = TRUE;
+ break;
+
+ case GDK_SCROLL_LEFT:
+ case GDK_SCROLL_RIGHT:
+ /* We ignore LEFT and RIGHT scroll events. */
+ break;
+
+ case GDK_SCROLL_SMOOTH:
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+ }
+
+ previous_window = window;
+ }
+
+ return TRUE;
+}
+
+static void
+wnck_selector_menu_hidden (GtkWidget *menu, WnckSelector *selector)
+{
+ gtk_widget_set_state_flags (GTK_WIDGET (selector), GTK_STATE_FLAG_NORMAL, TRUE);
+}
+
+static void
+wnck_selector_on_show (GtkWidget *widget, WnckSelector *selector)
+{
+ GtkWidget *separator;
+ WnckScreen *screen;
+ WnckWorkspace *workspace;
+ int nb_workspace;
+ int i;
+ GList **windows_per_workspace;
+ GList *windows;
+ GList *l, *children;
+
+ /* Remove existing items */
+ children = gtk_container_get_children (GTK_CONTAINER (selector->priv->menu));
+ for (l = children; l; l = l->next)
+ gtk_container_remove (GTK_CONTAINER (selector->priv->menu), l->data);
+ g_list_free (children);
+
+ if (selector->priv->window_hash)
+ g_hash_table_destroy (selector->priv->window_hash);
+ selector->priv->window_hash = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL, NULL);
+
+ screen = wnck_selector_get_screen (selector);
+
+ nb_workspace = wnck_screen_get_workspace_count (screen);
+ windows_per_workspace = g_malloc0 (nb_workspace * sizeof (GList *));
+
+ /* Get windows ordered by workspaces */
+ windows = wnck_screen_get_windows (screen);
+ windows = g_list_sort (windows, wnck_selector_windows_compare);
+
+ for (l = windows; l; l = l->next)
+ {
+ workspace = wnck_window_get_workspace (l->data);
+ if (!workspace && wnck_window_is_pinned (l->data))
+ workspace = wnck_screen_get_active_workspace (screen);
+ if (!workspace)
+ continue;
+ i = wnck_workspace_get_number (workspace);
+ windows_per_workspace[i] = g_list_prepend (windows_per_workspace[i],
+ l->data);
+ }
+
+ /* Add windows from the current workspace */
+ workspace = wnck_screen_get_active_workspace (screen);
+ if (workspace)
+ {
+ i = wnck_workspace_get_number (workspace);
+
+ windows_per_workspace[i] = g_list_reverse (windows_per_workspace[i]);
+ for (l = windows_per_workspace[i]; l; l = l->next)
+ wnck_selector_append_window (selector, l->data);
+ g_list_free (windows_per_workspace[i]);
+ windows_per_workspace[i] = NULL;
+ }
+
+ /* Add separator */
+ separator = gtk_separator_menu_item_new ();
+ gtk_menu_shell_append (GTK_MENU_SHELL (selector->priv->menu), separator);
+
+ /* Add windows from other workspaces */
+ for (i = 0; i < nb_workspace; i++)
+ {
+ wnck_selector_add_workspace (selector, screen, i);
+ windows_per_workspace[i] = g_list_reverse (windows_per_workspace[i]);
+ for (l = windows_per_workspace[i]; l; l = l->next)
+ wnck_selector_append_window (selector, l->data);
+ g_list_free (windows_per_workspace[i]);
+ windows_per_workspace[i] = NULL;
+ }
+ g_free (windows_per_workspace);
+
+ selector->priv->no_windows_item = wnck_selector_item_new (selector,
+ _("No Windows Open"),
+ NULL);
+ gtk_widget_set_sensitive (selector->priv->no_windows_item, FALSE);
+ gtk_menu_shell_append (GTK_MENU_SHELL (selector->priv->menu),
+ selector->priv->no_windows_item);
+
+ wnck_selector_make_menu_consistent (selector);
+}
+
+static void
+wnck_selector_fill (WnckSelector *selector)
+{
+ GtkWidget *menu_item;
+ GtkCssProvider *provider;
+
+ menu_item = gtk_menu_item_new ();
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (selector), menu_item);
+
+ selector->priv->image = gtk_image_new ();
+ gtk_widget_show (selector->priv->image);
+ gtk_container_add (GTK_CONTAINER (menu_item), selector->priv->image);
+
+ selector->priv->menu = gtk_menu_new ();
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item),
+ selector->priv->menu);
+ g_signal_connect (selector->priv->menu, "hide",
+ G_CALLBACK (wnck_selector_menu_hidden), selector);
+ g_signal_connect (selector->priv->menu, "destroy",
+ G_CALLBACK (wnck_selector_destroy_menu), selector);
+ g_signal_connect (selector->priv->menu, "show",
+ G_CALLBACK (wnck_selector_on_show), selector);
+
+ gtk_widget_set_name (GTK_WIDGET (selector),
+ "gnome-panel-window-menu-menu-bar");
+
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_data (provider,
+ "#gnome-panel-window-menu-menu-bar {\n"
+ " border-width: 0px;\n"
+ "}",
+ -1, NULL);
+ gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (selector)),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ g_object_unref (provider);
+
+ gtk_widget_show (GTK_WIDGET (selector));
+}
+
+static void
+wnck_selector_init (WnckSelector *selector)
+{
+ AtkObject *atk_obj;
+
+ atk_obj = gtk_widget_get_accessible (GTK_WIDGET (selector));
+ atk_object_set_name (atk_obj, _("Window Selector"));
+ atk_object_set_description (atk_obj, _("Tool to switch between windows"));
+
+ selector->priv = wnck_selector_get_instance_private (selector);
+
+ gtk_widget_add_events (GTK_WIDGET (selector), GDK_SCROLL_MASK);
+}
+
+static void
+wnck_selector_class_init (WnckSelectorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructor = wnck_selector_constructor;
+ object_class->dispose = wnck_selector_dispose;
+ object_class->finalize = wnck_selector_finalize;
+
+ widget_class->realize = wnck_selector_realize;
+ widget_class->unrealize = wnck_selector_unrealize;
+ widget_class->scroll_event = wnck_selector_scroll_event;
+
+ gtk_widget_class_set_css_name (widget_class, "wnck-selector");
+}
+
+static GObject *
+wnck_selector_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GObject *obj;
+
+ obj = G_OBJECT_CLASS (wnck_selector_parent_class)->constructor (
+ type,
+ n_construct_properties,
+ construct_properties);
+
+ wnck_selector_fill (WNCK_SELECTOR (obj));
+
+ return obj;
+}
+
+static void
+wnck_selector_finalize (GObject *object)
+{
+ WnckSelector *selector;
+
+ selector = WNCK_SELECTOR (object);
+
+ if (selector->priv->window_hash)
+ g_hash_table_destroy (selector->priv->window_hash);
+ selector->priv->window_hash = NULL;
+
+ G_OBJECT_CLASS (wnck_selector_parent_class)->finalize (object);
+}
+
+static void
+wnck_selector_dispose (GObject *object)
+{
+ WnckSelector *selector;
+
+ selector = WNCK_SELECTOR (object);
+
+ if (selector->priv->menu)
+ gtk_widget_destroy (selector->priv->menu);
+ selector->priv->menu = NULL;
+
+ selector->priv->image = NULL;
+ selector->priv->icon_window = NULL;
+
+ G_OBJECT_CLASS (wnck_selector_parent_class)->dispose (object);
+}
+
+static void
+wnck_selector_realize (GtkWidget *widget)
+{
+ WnckSelector *selector;
+ WnckScreen *screen;
+ WnckWindow *window;
+ GList *l;
+
+ GTK_WIDGET_CLASS (wnck_selector_parent_class)->realize (widget);
+
+ selector = WNCK_SELECTOR (widget);
+ screen = wnck_selector_get_screen (selector);
+
+ window = wnck_screen_get_active_window (screen);
+ wnck_selector_set_active_window (selector, window);
+
+ for (l = wnck_screen_get_windows (screen); l; l = l->next)
+ wnck_selector_connect_to_window (selector, l->data);
+
+ wnck_selector_connect_to_screen (selector, screen);
+}
+
+static void
+wnck_selector_unrealize (GtkWidget *widget)
+{
+ WnckSelector *selector;
+ WnckScreen *screen;
+ GList *l;
+
+ selector = WNCK_SELECTOR (widget);
+ screen = wnck_selector_get_screen (selector);
+
+ wnck_selector_disconnect_from_screen (selector, screen);
+
+ for (l = wnck_screen_get_windows (screen); l; l = l->next)
+ wnck_selector_disconnect_from_window (selector, l->data);
+
+ GTK_WIDGET_CLASS (wnck_selector_parent_class)->unrealize (widget);
+}
+
+/**
+ * wnck_selector_new:
+ *
+ * Creates a new #WnckSelector. The #WnckSelector will list #WnckWindow of the
+ * #WnckScreen it is on.
+ *
+ * Return value: a newly created #WnckSelector.
+ *
+ * Since: 2.10
+ */
+GtkWidget *
+wnck_selector_new (void)
+{
+ WnckSelector *selector;
+
+ selector = g_object_new (WNCK_TYPE_SELECTOR, NULL);
+
+ return GTK_WIDGET (selector);
+}
diff --git a/libwnck/widgets/selector.h b/libwnck/widgets/selector.h
new file mode 100644
index 0000000..4783162
--- /dev/null
+++ b/libwnck/widgets/selector.h
@@ -0,0 +1,70 @@
+/* selector */
+/* vim: set sw=2 et: */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2005-2007 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__LIBWNCK_H_INSIDE__) && !defined (WNCK_COMPILATION)
+#error "Only <libwnck/libwnck.h> can be included directly."
+#endif
+
+#ifndef WNCK_SELECTOR_H
+#define WNCK_SELECTOR_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define WNCK_TYPE_SELECTOR (wnck_selector_get_type ())
+#define WNCK_SELECTOR(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), WNCK_TYPE_SELECTOR, WnckSelector))
+#define WNCK_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WNCK_TYPE_SELECTOR, WnckSelectorClass))
+#define WNCK_IS_SELECTOR(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), WNCK_TYPE_SELECTOR))
+#define WNCK_IS_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WNCK_TYPE_SELECTOR))
+#define WNCK_SELECTOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WNCK_TYPE_SELECTOR, WnckSelectorClass))
+typedef struct _WnckSelector WnckSelector;
+typedef struct _WnckSelectorClass WnckSelectorClass;
+typedef struct _WnckSelectorPrivate WnckSelectorPrivate;
+
+/**
+ * WnckSelector:
+ *
+ * The #WnckSelector struct contains only private fields and should not be
+ * directly accessed.
+ */
+struct _WnckSelector
+{
+ GtkMenuBar parent_instance;
+ WnckSelectorPrivate *priv;
+};
+
+struct _WnckSelectorClass
+{
+ GtkMenuBarClass parent_class;
+
+ /* Padding for future expansion */
+ void (* pad1) (void);
+ void (* pad2) (void);
+ void (* pad3) (void);
+ void (* pad4) (void);
+};
+
+GtkWidget *wnck_selector_new (void);
+GType wnck_selector_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* WNCK_SELECTOR_H */
diff --git a/libwnck/widgets/tasklist.c b/libwnck/widgets/tasklist.c
new file mode 100644
index 0000000..885cab5
--- /dev/null
+++ b/libwnck/widgets/tasklist.c
@@ -0,0 +1,4895 @@
+/* tasklist object */
+/* vim: set sw=2 et: */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2003 Kim Woelders
+ * Copyright (C) 2003 Red Hat, Inc.
+ * Copyright (C) 2003, 2005-2007 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+
+#include <math.h>
+#include <string.h>
+#include <stdio.h>
+#include <glib/gi18n-lib.h>
+#include "tasklist.h"
+#include "window-action-menu.h"
+#include "wnck-image-menu-item-private.h"
+#include "xutils.h"
+#include "private.h"
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+#include <libsn/sn.h>
+#endif
+
+/**
+ * SECTION:tasklist
+ * @short_description: a tasklist widget, showing the list of windows as a list
+ * of buttons.
+ * @see_also: #WnckScreen, #WnckSelector
+ * @stability: Unstable
+ *
+ * The #WnckTasklist represents client windows on a screen as a list of buttons
+ * labelled with the window titles and icons. Pressing a button can activate or
+ * minimize the represented window, and other typical actions are available
+ * through a popup menu. Windows needing attention can also be distinguished
+ * by a fade effect on the buttons representing them, to help attract the
+ * user's attention.
+ *
+ * The behavior of the #WnckTasklist can be customized in various ways, like
+ * grouping multiple windows of the same application in one button (see
+ * wnck_tasklist_set_grouping() and wnck_tasklist_set_grouping_limit()), or
+ * showing windows from all workspaces (see
+ * wnck_tasklist_set_include_all_workspaces()). The fade effect for windows
+ * needing attention can be controlled by various style properties like
+ * #WnckTasklist:fade-max-loops and #WnckTasklist:fade-opacity.
+ *
+ * The tasklist also acts as iconification destination. If there are multiple
+ * #WnckTasklist or other applications setting the iconification destination
+ * for windows, the iconification destinations might not be consistent among
+ * windows and it is not possible to determine which #WnckTasklist (or which
+ * other application) owns this propriety.
+ */
+
+/* TODO:
+ *
+ * Add total focused time to the grouping score function
+ * Fine tune the grouping scoring function
+ * Fix "changes" to icon for groups/applications
+ * Maybe fine tune size_allocate() some more...
+ * Better vertical layout handling
+ * prefs
+ * support for right-click menu merging w/ bonobo for the applet
+ *
+ */
+
+#define WNCK_TYPE_BUTTON (wnck_button_get_type ())
+G_DECLARE_FINAL_TYPE (WnckButton, wnck_button, WNCK, BUTTON, GtkToggleButton)
+
+#define WNCK_TYPE_TASK (wnck_task_get_type ())
+#define WNCK_TASK(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), WNCK_TYPE_TASK, WnckTask))
+#define WNCK_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WNCK_TYPE_TASK, WnckTaskClass))
+#define WNCK_IS_TASK(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), WNCK_TYPE_TASK))
+#define WNCK_IS_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WNCK_TYPE_TASK))
+#define WNCK_TASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WNCK_TYPE_TASK, WnckTaskClass))
+
+typedef struct _WnckTask WnckTask;
+typedef struct _WnckTaskClass WnckTaskClass;
+
+#define DEFAULT_GROUPING_LIMIT 80
+
+#define MINI_ICON_SIZE 16 /*_wnck_get_default_mini_icon_size ()*/
+#define TASKLIST_BUTTON_PADDING 4
+#define TASKLIST_TEXT_MAX_WIDTH 25 /* maximum width in characters */
+
+#define N_SCREEN_CONNECTIONS 5
+
+#define POINT_IN_RECT(xcoord, ycoord, rect) \
+ ((xcoord) >= (rect).x && \
+ (xcoord) < ((rect).x + (rect).width) && \
+ (ycoord) >= (rect).y && \
+ (ycoord) < ((rect).y + (rect).height))
+
+struct _WnckButton
+{
+ GtkToggleButton parent;
+
+ GtkWidget *image;
+ gboolean show_image;
+
+ GtkWidget *label;
+ gboolean show_label;
+
+ guint update_idle_id;
+};
+
+typedef enum
+{
+ WNCK_TASK_CLASS_GROUP,
+ WNCK_TASK_WINDOW,
+ WNCK_TASK_STARTUP_SEQUENCE
+} WnckTaskType;
+
+struct _WnckTask
+{
+ GObject parent_instance;
+
+ WnckTasklist *tasklist;
+
+ GtkWidget *button;
+
+ WnckTaskType type;
+
+ WnckClassGroup *class_group;
+ WnckWindow *window;
+#ifdef HAVE_STARTUP_NOTIFICATION
+ SnStartupSequence *startup_sequence;
+#endif
+
+ gdouble grouping_score;
+
+ GList *windows; /* List of the WnckTask for the window,
+ if this is a class group */
+ guint state_changed_tag;
+ guint icon_changed_tag;
+ guint name_changed_tag;
+ guint class_name_changed_tag;
+ guint class_icon_changed_tag;
+
+ /* task menu */
+ GtkWidget *menu;
+ /* ops menu */
+ GtkWidget *action_menu;
+
+ guint really_toggling : 1; /* Set when tasklist really wants
+ * to change the togglebutton state
+ */
+ guint was_active : 1; /* used to fixup activation behavior */
+
+ guint button_activate;
+
+ guint32 dnd_timestamp;
+
+ time_t start_needs_attention;
+ gdouble glow_start_time;
+ gdouble glow_factor;
+
+ guint button_glow;
+
+ gint row;
+ gint col;
+};
+
+struct _WnckTaskClass
+{
+ GObjectClass parent_class;
+};
+
+typedef struct _skipped_window
+{
+ WnckWindow *window;
+ gulong tag;
+} skipped_window;
+
+struct _WnckTasklistPrivate
+{
+ WnckScreen *screen;
+
+ WnckTask *active_task; /* NULL if active window not in tasklist */
+ WnckTask *active_class_group; /* NULL if active window not in tasklist */
+
+ gboolean include_all_workspaces;
+
+ /* Calculated by update_lists */
+ GList *class_groups;
+ GList *windows;
+ GList *windows_without_class_group;
+
+ /* Not handled by update_lists */
+ GList *startup_sequences;
+
+ /* windows with _NET_WM_STATE_SKIP_TASKBAR set; connected to
+ * "state_changed" signal, but excluded from tasklist.
+ */
+ GList *skipped_windows;
+
+ GHashTable *class_group_hash;
+ GHashTable *win_hash;
+
+ gboolean switch_workspace_on_unminimize;
+ gboolean middle_click_close;
+
+ WnckTasklistGroupingType grouping;
+ gint grouping_limit;
+
+ guint activate_timeout_id;
+ guint screen_connections [N_SCREEN_CONNECTIONS];
+
+ guint idle_callback_tag;
+
+ int *size_hints;
+ int size_hints_len;
+
+ WnckLoadIconFunction icon_loader;
+ void *icon_loader_data;
+ GDestroyNotify free_icon_loader_data;
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ SnDisplay *sn_display;
+#endif
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ SnMonitorContext *sn_context;
+ guint startup_sequence_timeout;
+#endif
+
+ GdkMonitor *monitor;
+ GdkRectangle monitor_geometry;
+ GtkReliefStyle relief;
+ GtkOrientation orientation;
+
+ guint drag_start_time;
+
+ gboolean scroll_enabled;
+};
+
+static GType wnck_task_get_type (void);
+
+G_DEFINE_TYPE (WnckButton, wnck_button, GTK_TYPE_TOGGLE_BUTTON)
+G_DEFINE_TYPE (WnckTask, wnck_task, G_TYPE_OBJECT);
+G_DEFINE_TYPE_WITH_PRIVATE (WnckTasklist, wnck_tasklist, GTK_TYPE_CONTAINER);
+
+enum
+{
+ TASK_ENTER_NOTIFY,
+ TASK_LEAVE_NOTIFY,
+ LAST_SIGNAL
+};
+
+static void wnck_task_finalize (GObject *object);
+
+static void wnck_task_stop_glow (WnckTask *task);
+
+static WnckTask *wnck_task_new_from_window (WnckTasklist *tasklist,
+ WnckWindow *window);
+static WnckTask *wnck_task_new_from_class_group (WnckTasklist *tasklist,
+ WnckClassGroup *class_group);
+#ifdef HAVE_STARTUP_NOTIFICATION
+static WnckTask *wnck_task_new_from_startup_sequence (WnckTasklist *tasklist,
+ SnStartupSequence *sequence);
+#endif
+static gboolean wnck_task_get_needs_attention (WnckTask *task);
+
+
+static char *wnck_task_get_text (WnckTask *task,
+ gboolean icon_text,
+ gboolean include_state);
+static GdkPixbuf *wnck_task_get_icon (WnckTask *task);
+static gint wnck_task_compare_alphabetically (gconstpointer a,
+ gconstpointer b);
+static gint wnck_task_compare (gconstpointer a,
+ gconstpointer b);
+static void wnck_task_update_visible_state (WnckTask *task);
+static void wnck_task_state_changed (WnckWindow *window,
+ WnckWindowState changed_mask,
+ WnckWindowState new_state,
+ gpointer data);
+
+static void wnck_task_drag_begin (GtkWidget *widget,
+ GdkDragContext *context,
+ WnckTask *task);
+static void wnck_task_drag_end (GtkWidget *widget,
+ GdkDragContext *context,
+ WnckTask *task);
+static void wnck_task_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time,
+ WnckTask *task);
+
+static void wnck_tasklist_finalize (GObject *object);
+
+static void wnck_tasklist_get_preferred_width (GtkWidget *widget,
+ int *minimum_width,
+ int *natural_width);
+static void wnck_tasklist_get_preferred_height_for_width (GtkWidget *widget,
+ int width,
+ int *minimum_height,
+ int *natural_height);
+static void wnck_tasklist_get_preferred_height (GtkWidget *widget,
+ int *minimum_height,
+ int *natural_height);
+static void wnck_tasklist_get_preferred_width_for_height (GtkWidget *widget,
+ int height,
+ int *minimum_width,
+ int *natural_width);
+static void wnck_tasklist_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static void wnck_tasklist_realize (GtkWidget *widget);
+static void wnck_tasklist_unrealize (GtkWidget *widget);
+static gboolean wnck_tasklist_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event);
+static void wnck_tasklist_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data);
+static void wnck_tasklist_remove (GtkContainer *container,
+ GtkWidget *widget);
+static void wnck_tasklist_free_tasks (WnckTasklist *tasklist);
+static void wnck_tasklist_update_lists (WnckTasklist *tasklist);
+static int wnck_tasklist_layout (GtkAllocation *allocation,
+ int max_width,
+ int max_height,
+ int n_buttons,
+ GtkOrientation orientation,
+ int *n_cols_out,
+ int *n_rows_out);
+
+static void wnck_tasklist_active_window_changed (WnckScreen *screen,
+ WnckWindow *previous_window,
+ WnckTasklist *tasklist);
+static void wnck_tasklist_active_workspace_changed (WnckScreen *screen,
+ WnckWorkspace *previous_workspace,
+ WnckTasklist *tasklist);
+static void wnck_tasklist_window_added (WnckScreen *screen,
+ WnckWindow *win,
+ WnckTasklist *tasklist);
+static void wnck_tasklist_window_removed (WnckScreen *screen,
+ WnckWindow *win,
+ WnckTasklist *tasklist);
+static void wnck_tasklist_viewports_changed (WnckScreen *screen,
+ WnckTasklist *tasklist);
+static void wnck_tasklist_connect_window (WnckTasklist *tasklist,
+ WnckWindow *window);
+static void wnck_tasklist_disconnect_window (WnckTasklist *tasklist,
+ WnckWindow *window);
+
+static void wnck_tasklist_change_active_task (WnckTasklist *tasklist,
+ WnckTask *active_task);
+static gboolean wnck_tasklist_change_active_timeout (gpointer data);
+static void wnck_tasklist_activate_task_window (WnckTask *task,
+ guint32 timestamp);
+
+static void wnck_tasklist_update_icon_geometries (WnckTasklist *tasklist,
+ GList *visible_tasks);
+static void wnck_tasklist_connect_screen (WnckTasklist *tasklist);
+static void wnck_tasklist_disconnect_screen (WnckTasklist *tasklist);
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+static void wnck_tasklist_sn_event (SnMonitorEvent *event,
+ void *user_data);
+static void wnck_tasklist_check_end_sequence (WnckTasklist *tasklist,
+ WnckWindow *window);
+#endif
+
+/*
+ * Keep track of all tasklist instances so we can decide
+ * whether to show windows from all monitors in the
+ * tasklist
+ */
+static GSList *tasklist_instances;
+
+static void
+wnck_button_dispose (GObject *object)
+{
+ WnckButton *self;
+
+ self = WNCK_BUTTON (object);
+
+ if (self->update_idle_id != 0)
+ {
+ g_source_remove (self->update_idle_id);
+ self->update_idle_id = 0;
+ }
+
+ G_OBJECT_CLASS (wnck_button_parent_class)->dispose (object);
+}
+
+static gboolean
+wnck_button_update_idle_cb (gpointer user_data)
+{
+ WnckButton *self;
+
+ self = WNCK_BUTTON (user_data);
+
+ gtk_widget_set_visible (self->image, self->show_image);
+ gtk_widget_set_visible (self->label, self->show_label);
+
+ self->update_idle_id = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+static int
+get_css_width (GtkWidget *widget)
+{
+ GtkStyleContext *context;
+ GtkStateFlags state;
+ GtkBorder margin;
+ GtkBorder border;
+ GtkBorder padding;
+ int min_width;
+
+ context = gtk_widget_get_style_context (widget);
+ state = gtk_style_context_get_state (context);
+
+ gtk_style_context_get_margin (context, state, &margin);
+ gtk_style_context_get_border (context, state, &border);
+ gtk_style_context_get_padding (context, state, &padding);
+
+ min_width = margin.left + margin.right;
+ min_width += border.left + border.right;
+ min_width += padding.left + padding.right;
+
+ return min_width;
+}
+
+static int
+get_char_width (GtkWidget *widget)
+{
+ PangoContext *context;
+ GtkStyleContext *style;
+ PangoFontDescription *description;
+ PangoFontMetrics *metrics;
+ int char_width;
+
+ context = gtk_widget_get_pango_context (widget);
+ style = gtk_widget_get_style_context (widget);
+
+ gtk_style_context_get (style,
+ gtk_style_context_get_state (style),
+ GTK_STYLE_PROPERTY_FONT,
+ &description,
+ NULL);
+
+ metrics = pango_context_get_metrics (context,
+ description,
+ pango_context_get_language (context));
+ pango_font_description_free (description);
+
+ char_width = pango_font_metrics_get_approximate_char_width (metrics);
+ pango_font_metrics_unref (metrics);
+
+ return PANGO_PIXELS (char_width);
+}
+
+static void
+wnck_button_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ WnckButton *self;
+ int min_width;
+ int min_image_width;
+
+ self = WNCK_BUTTON (widget);
+
+ GTK_WIDGET_CLASS (wnck_button_parent_class)->size_allocate (widget,
+ allocation);
+
+ min_width = get_css_width (widget);
+ min_width += get_css_width (gtk_bin_get_child (GTK_BIN (widget)));
+
+ min_image_width = MINI_ICON_SIZE +
+ min_width +
+ 2 * TASKLIST_BUTTON_PADDING;
+
+ if ((allocation->width < min_image_width + 2 * TASKLIST_BUTTON_PADDING) &&
+ (allocation->width >= min_image_width))
+ {
+ self->show_image = TRUE;
+ self->show_label = FALSE;
+ }
+ else if (allocation->width < min_image_width)
+ {
+ self->show_image = FALSE;
+ self->show_label = TRUE;
+ }
+ else
+ {
+ self->show_image = TRUE;
+ self->show_label = TRUE;
+ }
+
+ if (self->show_image != gtk_widget_get_visible (self->image) ||
+ self->show_label != gtk_widget_get_visible (self->label))
+ {
+ if (self->update_idle_id == 0)
+ {
+ self->update_idle_id = g_idle_add (wnck_button_update_idle_cb, self);
+ g_source_set_name_by_id (self->update_idle_id,
+ "[libwnck] wnck_button_update_idle_cb");
+ }
+ }
+ else if (self->update_idle_id != 0)
+ {
+ g_source_remove (self->update_idle_id);
+ self->update_idle_id = 0;
+ }
+}
+
+static void
+wnck_button_get_preferred_width (GtkWidget *widget,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ WnckButton *self;
+ int min_width;
+ int char_width;
+
+ self = WNCK_BUTTON (widget);
+
+ min_width = get_css_width (widget);
+ min_width += get_css_width (gtk_bin_get_child (GTK_BIN (widget)));
+
+ char_width = get_char_width (self->label);
+
+ /* Minimum width:
+ * - margin, border and padding that might be set on widget
+ * - margin, border and padding that might be set on box widget
+ * - TASKLIST_BUTTON_PADDING around image or label
+ * - character width
+ */
+ *minimum_width = min_width +
+ 2 * TASKLIST_BUTTON_PADDING +
+ char_width;
+
+ /* Natural width:
+ * - margin, border and padding that might be set on widget
+ * - margin, border and padding that might be set on box widget
+ * - TASKLIST_BUTTON_PADDING around image
+ * - TASKLIST_BUTTON_PADDING around label
+ * - needed size for TASKLIST_TEXT_MAX_WIDTH
+ */
+ *natural_width = min_width +
+ 2 * TASKLIST_BUTTON_PADDING +
+ 2 * TASKLIST_BUTTON_PADDING +
+ char_width * TASKLIST_TEXT_MAX_WIDTH;
+}
+
+static void
+wnck_button_class_init (WnckButtonClass *self_class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = G_OBJECT_CLASS (self_class);
+ widget_class = GTK_WIDGET_CLASS (self_class);
+
+ object_class->dispose = wnck_button_dispose;
+
+ widget_class->size_allocate = wnck_button_size_allocate;
+ widget_class->get_preferred_width = wnck_button_get_preferred_width;
+}
+
+static void
+wnck_button_init (WnckButton *self)
+{
+ GtkWidget *box;
+
+ gtk_widget_set_name (GTK_WIDGET (self), "tasklist-button");
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_container_add (GTK_CONTAINER (self), box);
+ gtk_widget_show (box);
+
+ self->image = gtk_image_new ();
+ gtk_box_pack_start (GTK_BOX (box),
+ self->image,
+ FALSE,
+ FALSE,
+ TASKLIST_BUTTON_PADDING);
+
+ self->label = gtk_label_new (NULL);
+ gtk_box_pack_start (GTK_BOX (box),
+ self->label,
+ TRUE,
+ TRUE,
+ TASKLIST_BUTTON_PADDING);
+
+ gtk_label_set_xalign (GTK_LABEL (self->label), 0.0);
+ gtk_label_set_ellipsize (GTK_LABEL (self->label), PANGO_ELLIPSIZE_END);
+
+ gtk_widget_show (self->image);
+ gtk_widget_show (self->label);
+}
+
+static GtkWidget *
+wnck_button_new (void)
+{
+ return g_object_new (WNCK_TYPE_BUTTON, NULL);
+}
+
+static void
+wnck_button_set_image_from_pixbuf (WnckButton *self,
+ GdkPixbuf *pixbuf)
+{
+ gtk_image_set_from_pixbuf (GTK_IMAGE (self->image), pixbuf);
+}
+
+static void
+wnck_button_set_text (WnckButton *self,
+ const char *text)
+{
+ gtk_label_set_text (GTK_LABEL (self->label), text);
+}
+
+static void
+wnck_button_set_bold (WnckButton *self,
+ gboolean bold)
+{
+ if (bold)
+ _make_gtk_label_bold ((GTK_LABEL (self->label)));
+ else
+ _make_gtk_label_normal ((GTK_LABEL (self->label)));
+}
+
+static void
+wnck_task_init (WnckTask *task)
+{
+ task->type = WNCK_TASK_WINDOW;
+}
+
+static void
+wnck_task_class_init (WnckTaskClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = wnck_task_finalize;
+}
+
+static gboolean
+wnck_task_button_glow (WnckTask *task)
+{
+ gdouble now;
+ gfloat fade_opacity, loop_time;
+ gint fade_max_loops;
+ gboolean stopped;
+
+ now = g_get_real_time () / G_USEC_PER_SEC;
+
+ if (task->glow_start_time <= G_MINDOUBLE)
+ task->glow_start_time = now;
+
+ gtk_widget_style_get (GTK_WIDGET (task->tasklist), "fade-opacity", &fade_opacity,
+ "fade-loop-time", &loop_time,
+ "fade-max-loops", &fade_max_loops,
+ NULL);
+
+ if (task->button_glow == 0)
+ {
+ /* we're in "has stopped glowing" mode */
+ task->glow_factor = (gdouble) fade_opacity * 0.5;
+ stopped = TRUE;
+ }
+ else
+ {
+ task->glow_factor =
+ (gdouble) fade_opacity * (0.5 -
+ 0.5 * cos ((now - task->glow_start_time) *
+ M_PI * 2.0 / (gdouble) loop_time));
+
+ if (now - task->start_needs_attention > (gdouble) loop_time * 1.0 * fade_max_loops)
+ stopped = ABS (task->glow_factor - (gdouble) fade_opacity * 0.5) < 0.05;
+ else
+ stopped = FALSE;
+ }
+
+ gtk_widget_queue_draw (task->button);
+
+ if (stopped)
+ wnck_task_stop_glow (task);
+
+ return !stopped;
+}
+
+static void
+wnck_task_clear_glow_start_timeout_id (WnckTask *task)
+{
+ task->button_glow = 0;
+}
+
+static void
+wnck_task_queue_glow (WnckTask *task)
+{
+ if (task->button_glow == 0)
+ {
+ task->glow_start_time = 0.0;
+
+ /* The animation doesn't speed up or slow down based on the
+ * timeout value, but instead will just appear smoother or
+ * choppier.
+ */
+ task->button_glow =
+ g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
+ 50,
+ (GSourceFunc) wnck_task_button_glow, task,
+ (GDestroyNotify) wnck_task_clear_glow_start_timeout_id);
+ }
+}
+
+static void
+wnck_task_stop_glow (WnckTask *task)
+{
+ /* We stop glowing, but we might still have the task colored,
+ * so we don't reset the glow factor */
+ if (task->button_glow != 0)
+ g_source_remove (task->button_glow);
+}
+
+static void
+wnck_task_reset_glow (WnckTask *task)
+{
+ wnck_task_stop_glow (task);
+ task->glow_factor = 0.0;
+}
+
+static void
+wnck_task_finalize (GObject *object)
+{
+ WnckTask *task;
+
+ task = WNCK_TASK (object);
+
+ if (task->tasklist->priv->active_task == task)
+ wnck_tasklist_change_active_task (task->tasklist, NULL);
+
+ if (task->button)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (task->button),
+ (void**) &task->button);
+ gtk_widget_destroy (task->button);
+ task->button = NULL;
+ }
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ if (task->startup_sequence)
+ {
+ sn_startup_sequence_unref (task->startup_sequence);
+ task->startup_sequence = NULL;
+ }
+#endif
+
+ g_list_free (task->windows);
+ task->windows = NULL;
+
+ if (task->state_changed_tag != 0)
+ {
+ g_signal_handler_disconnect (task->window,
+ task->state_changed_tag);
+ task->state_changed_tag = 0;
+ }
+
+ if (task->icon_changed_tag != 0)
+ {
+ g_signal_handler_disconnect (task->window,
+ task->icon_changed_tag);
+ task->icon_changed_tag = 0;
+ }
+
+ if (task->name_changed_tag != 0)
+ {
+ g_signal_handler_disconnect (task->window,
+ task->name_changed_tag);
+ task->name_changed_tag = 0;
+ }
+
+ if (task->class_name_changed_tag != 0)
+ {
+ g_signal_handler_disconnect (task->class_group,
+ task->class_name_changed_tag);
+ task->class_name_changed_tag = 0;
+ }
+
+ if (task->class_icon_changed_tag != 0)
+ {
+ g_signal_handler_disconnect (task->class_group,
+ task->class_icon_changed_tag);
+ task->class_icon_changed_tag = 0;
+ }
+
+ if (task->class_group)
+ {
+ g_object_unref (task->class_group);
+ task->class_group = NULL;
+ }
+
+ if (task->window)
+ {
+ g_object_unref (task->window);
+ task->window = NULL;
+ }
+
+ if (task->menu)
+ {
+ gtk_widget_destroy (task->menu);
+ task->menu = NULL;
+ }
+
+ if (task->action_menu)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (task->action_menu),
+ (void**) &task->action_menu);
+ gtk_widget_destroy (task->action_menu);
+ task->action_menu = NULL;
+ }
+
+ if (task->button_activate != 0)
+ {
+ g_source_remove (task->button_activate);
+ task->button_activate = 0;
+ }
+
+ wnck_task_stop_glow (task);
+
+ G_OBJECT_CLASS (wnck_task_parent_class)->finalize (object);
+}
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+wnck_tasklist_init (WnckTasklist *tasklist)
+{
+ GtkWidget *widget;
+ AtkObject *atk_obj;
+
+ widget = GTK_WIDGET (tasklist);
+
+ gtk_widget_set_has_window (widget, FALSE);
+
+ tasklist->priv = wnck_tasklist_get_instance_private (tasklist);
+
+ tasklist->priv->class_group_hash = g_hash_table_new (NULL, NULL);
+ tasklist->priv->win_hash = g_hash_table_new (NULL, NULL);
+
+ tasklist->priv->grouping = WNCK_TASKLIST_AUTO_GROUP;
+ tasklist->priv->grouping_limit = DEFAULT_GROUPING_LIMIT;
+
+ tasklist->priv->monitor = NULL;
+ tasklist->priv->monitor_geometry.width = -1; /* invalid value */
+ tasklist->priv->relief = GTK_RELIEF_NORMAL;
+ tasklist->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
+ tasklist->priv->scroll_enabled = TRUE;
+
+ atk_obj = gtk_widget_get_accessible (widget);
+ atk_object_set_name (atk_obj, _("Window List"));
+ atk_object_set_description (atk_obj, _("Tool to switch between visible windows"));
+
+#if 0
+ /* This doesn't work because, and I think this is because we have no window;
+ * therefore, we use the scroll events on task buttons instead */
+ gtk_widget_add_events (widget, GDK_SCROLL_MASK);
+#endif
+}
+
+static GtkSizeRequestMode
+wnck_tasklist_get_request_mode (GtkWidget *widget)
+{
+ WnckTasklist *self;
+
+ self = WNCK_TASKLIST (widget);
+
+ if (self->priv->orientation == GTK_ORIENTATION_VERTICAL)
+ return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+
+ return GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
+}
+
+static void
+wnck_tasklist_class_init (WnckTasklistClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+ object_class->finalize = wnck_tasklist_finalize;
+
+ widget_class->get_request_mode = wnck_tasklist_get_request_mode;
+ widget_class->get_preferred_width = wnck_tasklist_get_preferred_width;
+ widget_class->get_preferred_height_for_width = wnck_tasklist_get_preferred_height_for_width;
+ widget_class->get_preferred_height = wnck_tasklist_get_preferred_height;
+ widget_class->get_preferred_width_for_height = wnck_tasklist_get_preferred_width_for_height;
+ widget_class->size_allocate = wnck_tasklist_size_allocate;
+ widget_class->realize = wnck_tasklist_realize;
+ widget_class->unrealize = wnck_tasklist_unrealize;
+#if 0
+ /* See comment above gtk_widget_add_events() in wnck_tasklist_init() */
+ widget_class->scroll_event = wnck_tasklist_scroll_event;
+#endif
+
+ container_class->forall = wnck_tasklist_forall;
+ container_class->remove = wnck_tasklist_remove;
+
+ /**
+ * WnckTasklist:fade-loop-time:
+ *
+ * When a window needs attention, a fade effect is drawn on the button
+ * representing the window. This property controls the time one loop of this
+ * fade effect takes, in seconds.
+ *
+ * Since: 2.16
+ */
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_float ("fade-loop-time",
+ "Loop time",
+ "The time one loop takes when fading, in seconds. Default: 3.0",
+ 0.2, 10.0, 3.0,
+ G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB));
+
+ /**
+ * WnckTasklist:fade-max-loops:
+ *
+ * When a window needs attention, a fade effect is drawn on the button
+ * representing the window. This property controls the number of loops for
+ * this fade effect. 0 means the button will only fade to the final color.
+ *
+ * Since: 2.20
+ */
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_int ("fade-max-loops",
+ "Maximum number of loops",
+ "The number of fading loops. 0 means the button will only fade to the final color. Default: 5",
+ 0, 50, 5,
+ G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB));
+
+ /**
+ * WnckTasklist:fade-overlay-rect:
+ *
+ * When a window needs attention, a fade effect is drawn on the button
+ * representing the window. Set this property to %TRUE to enable a
+ * compatibility mode for pixbuf engine themes that cannot react to color
+ * changes. If enabled, a rectangle with the correct color will be drawn on
+ * top of the button.
+ *
+ * Since: 2.16
+ */
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boolean ("fade-overlay-rect",
+ "Overlay a rectangle, instead of modifying the background.",
+ "Compatibility mode for pixbuf engine themes that cannot react to color changes. If enabled, a rectangle with the correct color will be drawn on top of the button. Default: TRUE",
+ TRUE,
+ G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB));
+
+ /**
+ * WnckTasklist:fade-opacity:
+ *
+ * When a window needs attention, a fade effect is drawn on the button
+ * representing the window. This property controls the final opacity that
+ * will be reached by the fade effect.
+ *
+ * Since: 2.16
+ */
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_float ("fade-opacity",
+ "Final opacity",
+ "The final opacity that will be reached. Default: 0.8",
+ 0.0, 1.0, 0.8,
+ G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB));
+
+ gtk_widget_class_set_css_name (widget_class, "wnck-tasklist");
+
+ /**
+ * WnckTasklist::task-enter-notify:
+ * @tasklist: the #WnckTasklist which emitted the signal.
+ * @windows: the #GList with all the #WnckWindow belonging to the task.
+ *
+ * Emitted when the task is entered.
+ */
+ signals[TASK_ENTER_NOTIFY] =
+ g_signal_new ("task_enter_notify",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ /**
+ * WnckTasklist::task-leave-notify:
+ * @tasklist: the #WnckTasklist which emitted the signal.
+ * @windows: the #GList with all the #WnckWindow belonging to the task.
+ *
+ * Emitted when the task is entered.
+ */
+ signals[TASK_LEAVE_NOTIFY] =
+ g_signal_new ("task_leave_notify",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+}
+
+static void
+wnck_tasklist_free_skipped_windows (WnckTasklist *tasklist)
+{
+ GList *l;
+
+ l = tasklist->priv->skipped_windows;
+
+ while (l != NULL)
+ {
+ skipped_window *skipped = (skipped_window*) l->data;
+ g_signal_handler_disconnect (skipped->window, skipped->tag);
+ g_object_unref (skipped->window);
+ g_free (skipped);
+ l = l->next;
+ }
+
+ g_list_free (tasklist->priv->skipped_windows);
+}
+
+static void
+wnck_tasklist_finalize (GObject *object)
+{
+ WnckTasklist *tasklist;
+
+ tasklist = WNCK_TASKLIST (object);
+
+ /* Tasks should have gone away due to removing their
+ * buttons in container destruction
+ */
+ g_assert (tasklist->priv->class_groups == NULL);
+ g_assert (tasklist->priv->windows == NULL);
+ g_assert (tasklist->priv->windows_without_class_group == NULL);
+ g_assert (tasklist->priv->startup_sequences == NULL);
+ /* wnck_tasklist_free_tasks (tasklist); */
+
+ if (tasklist->priv->skipped_windows)
+ {
+ wnck_tasklist_free_skipped_windows (tasklist);
+ tasklist->priv->skipped_windows = NULL;
+ }
+
+ g_hash_table_destroy (tasklist->priv->class_group_hash);
+ tasklist->priv->class_group_hash = NULL;
+
+ g_hash_table_destroy (tasklist->priv->win_hash);
+ tasklist->priv->win_hash = NULL;
+
+ if (tasklist->priv->activate_timeout_id != 0)
+ {
+ g_source_remove (tasklist->priv->activate_timeout_id);
+ tasklist->priv->activate_timeout_id = 0;
+ }
+
+ if (tasklist->priv->idle_callback_tag != 0)
+ {
+ g_source_remove (tasklist->priv->idle_callback_tag);
+ tasklist->priv->idle_callback_tag = 0;
+ }
+
+ g_free (tasklist->priv->size_hints);
+ tasklist->priv->size_hints = NULL;
+ tasklist->priv->size_hints_len = 0;
+
+ if (tasklist->priv->free_icon_loader_data != NULL)
+ (* tasklist->priv->free_icon_loader_data) (tasklist->priv->icon_loader_data);
+ tasklist->priv->free_icon_loader_data = NULL;
+ tasklist->priv->icon_loader_data = NULL;
+
+ G_OBJECT_CLASS (wnck_tasklist_parent_class)->finalize (object);
+}
+
+/**
+ * wnck_tasklist_set_grouping:
+ * @tasklist: a #WnckTasklist.
+ * @grouping: a grouping policy.
+ *
+ * Sets the grouping policy for @tasklist to @grouping.
+ */
+void
+wnck_tasklist_set_grouping (WnckTasklist *tasklist,
+ WnckTasklistGroupingType grouping)
+{
+ g_return_if_fail (WNCK_IS_TASKLIST (tasklist));
+
+ if (tasklist->priv->grouping == grouping)
+ return;
+
+ tasklist->priv->grouping = grouping;
+ gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+}
+
+static void
+wnck_tasklist_set_relief_callback (WnckWindow *win,
+ WnckTask *task,
+ WnckTasklist *tasklist)
+{
+ gtk_button_set_relief (GTK_BUTTON (task->button), tasklist->priv->relief);
+}
+
+/**
+ * wnck_tasklist_set_button_relief:
+ * @tasklist: a #WnckTasklist.
+ * @relief: a relief type.
+ *
+ * Sets the relief type of the buttons in @tasklist to @relief. The main use of
+ * this function is proper integration of #WnckTasklist in panels with
+ * non-system backgrounds.
+ *
+ * Since: 2.12
+ */
+void
+wnck_tasklist_set_button_relief (WnckTasklist *tasklist, GtkReliefStyle relief)
+{
+ GList *walk;
+
+ g_return_if_fail (WNCK_IS_TASKLIST (tasklist));
+
+ if (relief == tasklist->priv->relief)
+ return;
+
+ tasklist->priv->relief = relief;
+
+ g_hash_table_foreach (tasklist->priv->win_hash,
+ (GHFunc) wnck_tasklist_set_relief_callback,
+ tasklist);
+ for (walk = tasklist->priv->class_groups; walk; walk = g_list_next (walk))
+ gtk_button_set_relief (GTK_BUTTON (WNCK_TASK (walk->data)->button), relief);
+}
+
+/**
+ * wnck_tasklist_set_middle_click_close:
+ * @tasklist: a #WnckTasklist.
+ * @middle_click_close: whether to close windows with middle click on
+ * button.
+ *
+ * Sets @tasklist to close windows with mouse middle click on button,
+ * according to @middle_click_close.
+ *
+ * Since: 3.4.6
+ */
+void
+wnck_tasklist_set_middle_click_close (WnckTasklist *tasklist,
+ gboolean middle_click_close)
+{
+ g_return_if_fail (WNCK_IS_TASKLIST (tasklist));
+
+ tasklist->priv->middle_click_close = middle_click_close;
+}
+
+/**
+ * wnck_tasklist_set_orientation:
+ * @tasklist: a #WnckTasklist.
+ * @orient: a GtkOrientation.
+ *
+ * Set the orientation of the @tasklist to match @orient.
+ * This function can be used to integrate a #WnckTasklist in vertical panels.
+ *
+ * Since: 3.4.6
+ */
+void wnck_tasklist_set_orientation (WnckTasklist *tasklist,
+ GtkOrientation orient)
+{
+ g_return_if_fail (WNCK_IS_TASKLIST (tasklist));
+
+ tasklist->priv->orientation = orient;
+}
+
+/**
+ * wnck_tasklist_set_scroll_enabled:
+ * @tasklist: a #WnckTasklist.
+ * @scroll_enabled: a boolean.
+ *
+ * Sets the scroll behavior of the @tasklist. When set to %TRUE, a scroll
+ * event over the tasklist will change the current window accordingly.
+ *
+ * Since: 3.24.0
+ */
+void
+wnck_tasklist_set_scroll_enabled (WnckTasklist *tasklist,
+ gboolean scroll_enabled)
+{
+ g_return_if_fail (WNCK_IS_TASKLIST (tasklist));
+
+ tasklist->priv->scroll_enabled = scroll_enabled;
+}
+
+/**
+ * wnck_tasklist_get_scroll_enabled:
+ * @tasklist: a #WnckTasklist.
+ *
+ * Gets the scroll behavior of the @tasklist.
+ *
+ * Since: 3.24.0
+ */
+gboolean
+wnck_tasklist_get_scroll_enabled (WnckTasklist *tasklist)
+{
+ g_return_val_if_fail (WNCK_IS_TASKLIST (tasklist), TRUE);
+
+ return tasklist->priv->scroll_enabled;
+}
+
+/**
+ * wnck_tasklist_set_switch_workspace_on_unminimize:
+ * @tasklist: a #WnckTasklist.
+ * @switch_workspace_on_unminimize: whether to activate the #WnckWorkspace a
+ * #WnckWindow is on when unminimizing it.
+ *
+ * Sets @tasklist to activate or not the #WnckWorkspace a #WnckWindow is on
+ * when unminimizing it, according to @switch_workspace_on_unminimize.
+ *
+ * FIXME: does it still work?
+ */
+void
+wnck_tasklist_set_switch_workspace_on_unminimize (WnckTasklist *tasklist,
+ gboolean switch_workspace_on_unminimize)
+{
+ g_return_if_fail (WNCK_IS_TASKLIST (tasklist));
+
+ tasklist->priv->switch_workspace_on_unminimize = switch_workspace_on_unminimize;
+}
+
+/**
+ * wnck_tasklist_set_include_all_workspaces:
+ * @tasklist: a #WnckTasklist.
+ * @include_all_workspaces: whether to display #WnckWindow from all
+ * #WnckWorkspace in @tasklist.
+ *
+ * Sets @tasklist to display #WnckWindow from all #WnckWorkspace or not,
+ * according to @include_all_workspaces.
+ *
+ * Note that if the active #WnckWorkspace has a viewport and if
+ * @include_all_workspaces is %FALSE, then only the #WnckWindow visible in the
+ * viewport are displayed in @tasklist. The rationale for this is that the
+ * viewport is generally used to implement workspace-like behavior. A
+ * side-effect of this is that, when using multiple #WnckWorkspace with
+ * viewport, it is not possible to show all #WnckWindow from a #WnckWorkspace
+ * (even those that are not visible in the viewport) in @tasklist without
+ * showing all #WnckWindow from all #WnckWorkspace.
+ */
+void
+wnck_tasklist_set_include_all_workspaces (WnckTasklist *tasklist,
+ gboolean include_all_workspaces)
+{
+ g_return_if_fail (WNCK_IS_TASKLIST (tasklist));
+
+ include_all_workspaces = (include_all_workspaces != 0);
+
+ if (tasklist->priv->include_all_workspaces == include_all_workspaces)
+ return;
+
+ tasklist->priv->include_all_workspaces = include_all_workspaces;
+ wnck_tasklist_update_lists (tasklist);
+ gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+}
+
+/**
+ * wnck_tasklist_set_grouping_limit:
+ * @tasklist: a #WnckTasklist.
+ * @limit: a size in pixels.
+ *
+ * Sets the maximum size of buttons in @tasklist before @tasklist tries to
+ * group #WnckWindow in the same #WnckApplication in only one button. This
+ * limit is valid only when the grouping policy of @tasklist is
+ * %WNCK_TASKLIST_AUTO_GROUP.
+ */
+void
+wnck_tasklist_set_grouping_limit (WnckTasklist *tasklist,
+ gint limit)
+{
+ g_return_if_fail (WNCK_IS_TASKLIST (tasklist));
+
+ if (tasklist->priv->grouping_limit == limit)
+ return;
+
+ tasklist->priv->grouping_limit = limit;
+ gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+}
+
+/**
+ * wnck_tasklist_set_icon_loader:
+ * @tasklist: a #WnckTasklist
+ * @load_icon_func: icon loader function
+ * @data: data for icon loader function
+ * @free_data_func: function to free the data
+ *
+ * Sets a function to be used for loading icons.
+ *
+ * Since: 2.2
+ **/
+void
+wnck_tasklist_set_icon_loader (WnckTasklist *tasklist,
+ WnckLoadIconFunction load_icon_func,
+ void *data,
+ GDestroyNotify free_data_func)
+{
+ g_return_if_fail (WNCK_IS_TASKLIST (tasklist));
+
+ if (tasklist->priv->free_icon_loader_data != NULL)
+ (* tasklist->priv->free_icon_loader_data) (tasklist->priv->icon_loader_data);
+
+ tasklist->priv->icon_loader = load_icon_func;
+ tasklist->priv->icon_loader_data = data;
+ tasklist->priv->free_icon_loader_data = free_data_func;
+}
+
+static void
+get_layout (GtkOrientation orientation,
+ int for_size,
+ int max_size,
+ int n_buttons,
+ int *n_cols_out,
+ int *n_rows_out)
+{
+ int n_cols;
+ int n_rows;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ /* How many rows fit in the allocation */
+ n_rows = for_size / max_size;
+
+ /* Don't have more rows than buttons */
+ n_rows = MIN (n_rows, n_buttons);
+
+ /* At least one row */
+ n_rows = MAX (n_rows, 1);
+
+ /* We want to use as many cols as possible to limit the width */
+ n_cols = (n_buttons + n_rows - 1) / n_rows;
+
+ /* At least one column */
+ n_cols = MAX (n_cols, 1);
+ }
+ else
+ {
+ /* How many cols fit in the allocation */
+ n_cols = for_size / max_size;
+
+ /* Don't have more cols than buttons */
+ n_cols = MIN (n_cols, n_buttons);
+
+ /* At least one col */
+ n_cols = MAX (n_cols, 1);
+
+ /* We want to use as many rows as possible to limit the height */
+ n_rows = (n_buttons + n_cols - 1) / n_cols;
+
+ /* At least one row */
+ n_rows = MAX (n_rows, 1);
+ }
+
+ if (n_cols_out != NULL)
+ *n_cols_out = n_cols;
+
+ if (n_rows_out != NULL)
+ *n_rows_out = n_rows;
+}
+
+/* returns the maximal possible button width (i.e. if you
+ * don't want to stretch the buttons to fill the alloctions
+ * the width can be smaller) */
+static int
+wnck_tasklist_layout (GtkAllocation *allocation,
+ int max_width,
+ int max_height,
+ int n_buttons,
+ GtkOrientation orientation,
+ int *n_cols_out,
+ int *n_rows_out)
+{
+ int n_cols, n_rows;
+
+ if (n_buttons == 0)
+ {
+ *n_cols_out = 0;
+ *n_rows_out = 0;
+ return 0;
+ }
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ get_layout (GTK_ORIENTATION_HORIZONTAL,
+ allocation->height,
+ max_height,
+ n_buttons,
+ &n_cols,
+ &n_rows);
+ }
+ else
+ {
+ get_layout (GTK_ORIENTATION_VERTICAL,
+ allocation->width,
+ max_width,
+ n_buttons,
+ &n_cols,
+ &n_rows);
+ }
+
+ *n_cols_out = n_cols;
+ *n_rows_out = n_rows;
+
+ return allocation->width / n_cols;
+}
+
+static void
+wnck_tasklist_score_groups (WnckTasklist *tasklist,
+ GList *ungrouped_class_groups)
+{
+ WnckTask *class_group_task;
+ WnckTask *win_task;
+ GList *l, *w;
+ const char *first_name = NULL;
+ int n_windows;
+ int n_same_title;
+ double same_window_ratio;
+
+ l = ungrouped_class_groups;
+ while (l != NULL)
+ {
+ class_group_task = WNCK_TASK (l->data);
+
+ n_windows = g_list_length (class_group_task->windows);
+
+ n_same_title = 0;
+ w = class_group_task->windows;
+ while (w != NULL)
+ {
+ win_task = WNCK_TASK (w->data);
+
+ if (first_name == NULL)
+ {
+ if (wnck_window_has_icon_name (win_task->window))
+ first_name = wnck_window_get_icon_name (win_task->window);
+ else
+ first_name = wnck_window_get_name (win_task->window);
+ n_same_title++;
+ }
+ else
+ {
+ const char *name;
+
+ if (wnck_window_has_icon_name (win_task->window))
+ name = wnck_window_get_icon_name (win_task->window);
+ else
+ name = wnck_window_get_name (win_task->window);
+
+ if (strcmp (name, first_name) == 0)
+ n_same_title++;
+ }
+
+ w = w->next;
+ }
+ same_window_ratio = (double)n_same_title/(double)n_windows;
+
+ /* FIXME: This is fairly bogus and should be researched more.
+ * XP groups by least used, so we probably want to add
+ * total focused time to this expression.
+ */
+ class_group_task->grouping_score = -same_window_ratio * 5 + n_windows;
+
+ l = l->next;
+ }
+}
+
+static GList *
+wnck_task_get_highest_scored (GList *ungrouped_class_groups,
+ WnckTask **class_group_task_out)
+{
+ WnckTask *class_group_task;
+ WnckTask *best_task = NULL;
+ double max_score = -1000000000.0; /* Large negative score */
+ GList *l;
+
+ l = ungrouped_class_groups;
+ while (l != NULL)
+ {
+ class_group_task = WNCK_TASK (l->data);
+
+ if (class_group_task->grouping_score >= max_score)
+ {
+ max_score = class_group_task->grouping_score;
+ best_task = class_group_task;
+ }
+
+ l = l->next;
+ }
+
+ *class_group_task_out = best_task;
+
+ return g_list_remove (ungrouped_class_groups, best_task);
+}
+
+static void
+calculate_max_button_size (WnckTasklist *self,
+ int *max_width_out,
+ int *max_height_out)
+{
+ int max_width;
+ int max_height;
+ GList *l;
+
+ max_width = 0;
+ max_height = 0;
+
+#define GET_MAX_WIDTH_HEIGHT_FROM_BUTTONS(list) \
+ l = list; \
+ \
+ while (l != NULL) \
+ { \
+ WnckTask *task; \
+ GtkRequisition child_min_req; \
+ GtkRequisition child_nat_req; \
+ \
+ task = WNCK_TASK (l->data); \
+ \
+ gtk_widget_get_preferred_size (task->button, \
+ &child_min_req, \
+ &child_nat_req); \
+ \
+ max_height = MAX (child_min_req.height, max_height); \
+ max_width = MAX (child_nat_req.width, max_width); \
+ \
+ l = l->next; \
+ }
+
+ GET_MAX_WIDTH_HEIGHT_FROM_BUTTONS (self->priv->windows)
+ GET_MAX_WIDTH_HEIGHT_FROM_BUTTONS (self->priv->class_groups)
+ GET_MAX_WIDTH_HEIGHT_FROM_BUTTONS (self->priv->startup_sequences)
+
+#undef GET_MAX_WIDTH_HEIGHT_FROM_BUTTONS
+
+ if (max_width_out != NULL)
+ *max_width_out = max_width;
+
+ if (max_height_out != NULL)
+ *max_height_out = max_height;
+}
+
+static void
+wnck_tasklist_update_size_hints (WnckTasklist *tasklist)
+{
+ GtkAllocation tasklist_allocation;
+ GtkAllocation fake_allocation;
+ int max_height = 1;
+ int max_width = 1;
+ GArray *array;
+ GList *ungrouped_class_groups;
+ int n_windows;
+ int n_startup_sequences;
+ int n_rows;
+ int n_cols, last_n_cols;
+ int n_grouped_buttons;
+ gboolean score_set;
+ int val;
+ WnckTask *class_group_task;
+ int lowest_range;
+ int grouping_limit;
+
+ /* Note that the fact that we nearly don't care about the width/height
+ * requested by the buttons makes it possible to hide/show the label/image
+ * in wnck_task_size_allocated(). If we really cared about those, this
+ * wouldn't work since our call to gtk_widget_size_request() does not take
+ * into account the hidden widgets.
+ */
+ calculate_max_button_size (tasklist, &max_width, &max_height);
+
+ gtk_widget_get_allocation (GTK_WIDGET (tasklist), &tasklist_allocation);
+
+ fake_allocation.width = tasklist_allocation.width;
+ fake_allocation.height = tasklist_allocation.height;
+
+ array = g_array_new (FALSE, FALSE, sizeof (int));
+
+ /* Calculate size_hints list */
+
+ n_windows = g_list_length (tasklist->priv->windows);
+ n_startup_sequences = g_list_length (tasklist->priv->startup_sequences);
+ n_grouped_buttons = 0;
+ ungrouped_class_groups = g_list_copy (tasklist->priv->class_groups);
+ score_set = FALSE;
+
+ grouping_limit = MIN (tasklist->priv->grouping_limit, max_width);
+
+ /* Try ungrouped mode */
+ wnck_tasklist_layout (&fake_allocation,
+ max_width,
+ max_height,
+ n_windows + n_startup_sequences,
+ tasklist->priv->orientation,
+ &n_cols, &n_rows);
+
+ last_n_cols = G_MAXINT;
+ lowest_range = G_MAXINT;
+ if (tasklist->priv->grouping != WNCK_TASKLIST_ALWAYS_GROUP)
+ {
+ if (tasklist->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ val = n_cols * max_width;
+ g_array_insert_val (array, array->len, val);
+ val = n_cols * grouping_limit;
+ g_array_insert_val (array, array->len, val);
+
+ last_n_cols = n_cols;
+ lowest_range = val;
+ }
+ else
+ {
+ val = n_rows * max_height;
+ g_array_insert_val (array, array->len, val);
+ val = n_rows * grouping_limit;
+ g_array_insert_val (array, array->len, val);
+
+ last_n_cols = n_rows;
+ lowest_range = val;
+ }
+ }
+
+ while (ungrouped_class_groups != NULL &&
+ tasklist->priv->grouping != WNCK_TASKLIST_NEVER_GROUP)
+ {
+ if (!score_set)
+ {
+ wnck_tasklist_score_groups (tasklist, ungrouped_class_groups);
+ score_set = TRUE;
+ }
+
+ ungrouped_class_groups = wnck_task_get_highest_scored (ungrouped_class_groups, &class_group_task);
+
+ n_grouped_buttons += g_list_length (class_group_task->windows) - 1;
+
+ wnck_tasklist_layout (&fake_allocation,
+ max_width,
+ max_height,
+ n_startup_sequences + n_windows - n_grouped_buttons,
+ tasklist->priv->orientation,
+ &n_cols, &n_rows);
+
+ if (tasklist->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (n_cols != last_n_cols &&
+ (tasklist->priv->grouping == WNCK_TASKLIST_AUTO_GROUP ||
+ ungrouped_class_groups == NULL))
+ {
+ val = n_cols * max_width;
+ if (val >= lowest_range)
+ {
+ /* Overlaps old range */
+ g_assert (array->len > 0);
+ lowest_range = n_cols * grouping_limit;
+ g_array_index(array, int, array->len-1) = lowest_range;
+ }
+ else
+ {
+ /* Full new range */
+ g_array_insert_val (array, array->len, val);
+ val = n_cols * grouping_limit;
+ g_array_insert_val (array, array->len, val);
+ lowest_range = val;
+ }
+
+ last_n_cols = n_cols;
+ }
+ }
+ else
+ {
+ if (n_rows != last_n_cols &&
+ (tasklist->priv->grouping == WNCK_TASKLIST_AUTO_GROUP ||
+ ungrouped_class_groups == NULL))
+ {
+ val = n_rows * max_height;
+ if (val >= lowest_range)
+ {
+ /* Overlaps old range */
+ g_assert (array->len > 0);
+ lowest_range = n_rows * grouping_limit;
+ g_array_index (array, int, array->len-1) = lowest_range;
+ }
+ else
+ {
+ /* Full new range */
+ g_array_insert_val (array, array->len, val);
+ val = n_rows * grouping_limit;
+ g_array_insert_val (array, array->len, val);
+ lowest_range = val;
+ }
+
+ last_n_cols = n_rows;
+ }
+ }
+ }
+
+ g_list_free (ungrouped_class_groups);
+
+ /* Always let you go down to a zero size: */
+ if (array->len > 0)
+ {
+ g_array_index(array, int, array->len-1) = 0;
+ }
+ else
+ {
+ val = 0;
+ g_array_insert_val (array, 0, val);
+ g_array_insert_val (array, 0, val);
+ }
+
+ if (tasklist->priv->size_hints)
+ g_free (tasklist->priv->size_hints);
+
+ tasklist->priv->size_hints_len = array->len;
+ tasklist->priv->size_hints = (int *)g_array_free (array, FALSE);
+}
+
+static int
+get_n_buttons (WnckTasklist *self)
+{
+ int n_windows;
+ int n_startup_sequences;
+ int n_buttons;
+
+ n_windows = g_list_length (self->priv->windows);
+ n_startup_sequences = g_list_length (self->priv->startup_sequences);
+
+ if (self->priv->grouping == WNCK_TASKLIST_ALWAYS_GROUP &&
+ self->priv->class_groups != NULL)
+ {
+ GList *ungrouped_class_groups;
+ int n_grouped_buttons;
+
+ ungrouped_class_groups = g_list_copy (self->priv->class_groups);
+ n_grouped_buttons = 0;
+
+ wnck_tasklist_score_groups (self, ungrouped_class_groups);
+
+ while (ungrouped_class_groups != NULL)
+ {
+ WnckTask *task;
+
+ ungrouped_class_groups = wnck_task_get_highest_scored (ungrouped_class_groups,
+ &task);
+
+ n_grouped_buttons += g_list_length (task->windows) - 1;
+ }
+
+ n_buttons = n_startup_sequences + n_windows - n_grouped_buttons;
+ g_list_free (ungrouped_class_groups);
+ }
+ else
+ {
+ n_buttons = n_windows + n_startup_sequences;
+ }
+
+ return n_buttons;
+}
+
+static void
+get_minimum_button_size (int *minimum_width,
+ int *minimum_height)
+{
+ GtkWidget *button;
+
+ button = wnck_button_new ();
+ gtk_widget_show (button);
+
+ if (minimum_width != NULL)
+ gtk_widget_get_preferred_width (button, minimum_width, NULL);
+
+ if (minimum_height != NULL)
+ gtk_widget_get_preferred_height (button, minimum_height, NULL);
+
+ g_object_ref_sink (button);
+ g_object_unref (button);
+}
+
+static void
+get_preferred_size (WnckTasklist *self,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural)
+{
+ int n_buttons;
+ int max_button_width;
+ int max_button_height;
+
+ *minimum = 0;
+ *natural = 0;
+
+ n_buttons = get_n_buttons (self);
+ if (n_buttons == 0)
+ return;
+
+ calculate_max_button_size (self, &max_button_width, &max_button_height);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ int min_button_width;
+
+ get_minimum_button_size (&min_button_width, NULL);
+
+ if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ int n_cols;
+
+ if (for_size < 0)
+ {
+ n_cols = n_buttons;
+ }
+ else
+ {
+ get_layout (GTK_ORIENTATION_HORIZONTAL,
+ for_size,
+ max_button_height,
+ n_buttons,
+ &n_cols,
+ NULL);
+ }
+
+ *minimum = min_button_width;
+ *natural = n_cols * max_button_width;
+ }
+ else
+ {
+ *minimum = *natural = min_button_width;
+ }
+ }
+ else
+ {
+ if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ *minimum = *natural = max_button_height;
+ }
+ else
+ {
+ int n_rows;
+
+ if (for_size < 0)
+ {
+ n_rows = n_buttons;
+ }
+ else
+ {
+ get_layout (GTK_ORIENTATION_VERTICAL,
+ for_size,
+ max_button_width,
+ n_buttons,
+ NULL,
+ &n_rows);
+ }
+
+ *minimum = max_button_height;
+ *natural = n_rows * max_button_height;
+ }
+ }
+}
+
+static void
+wnck_tasklist_get_preferred_width (GtkWidget *widget,
+ int *minimum_width,
+ int *natural_width)
+{
+ get_preferred_size (WNCK_TASKLIST (widget),
+ GTK_ORIENTATION_HORIZONTAL,
+ -1,
+ minimum_width,
+ natural_width);
+}
+
+static void
+wnck_tasklist_get_preferred_width_for_height (GtkWidget *widget,
+ int height,
+ int *minimum_width,
+ int *natural_width)
+{
+ get_preferred_size (WNCK_TASKLIST (widget),
+ GTK_ORIENTATION_HORIZONTAL,
+ height,
+ minimum_width,
+ natural_width);
+}
+
+static void
+wnck_tasklist_get_preferred_height (GtkWidget *widget,
+ int *minimum_height,
+ int *natural_height)
+{
+ get_preferred_size (WNCK_TASKLIST (widget),
+ GTK_ORIENTATION_VERTICAL,
+ -1,
+ minimum_height,
+ natural_height);
+}
+
+static void
+wnck_tasklist_get_preferred_height_for_width (GtkWidget *widget,
+ int width,
+ int *minimum_height,
+ int *natural_height)
+{
+ get_preferred_size (WNCK_TASKLIST (widget),
+ GTK_ORIENTATION_VERTICAL,
+ width,
+ minimum_height,
+ natural_height);
+}
+
+/**
+ * wnck_tasklist_get_size_hint_list:
+ * @tasklist: a #WnckTasklist.
+ * @n_elements: return location for the number of elements in the array
+ * returned by this function. This number should always be pair.
+ *
+ * Since a #WnckTasklist does not have a fixed size (#WnckWindow can be grouped
+ * when needed, for example), the standard size request mechanism in GTK+ is
+ * not enough to announce what sizes can be used by @tasklist. The size hints
+ * mechanism is a solution for this. See panel_applet_set_size_hints() for more
+ * information.
+ *
+ * Return value: a list of size hints that can be used to allocate an
+ * appropriate size for @tasklist.
+ *
+ * Deprecated: 3.42: Use minimum and natural size instead.
+ */
+const int *
+wnck_tasklist_get_size_hint_list (WnckTasklist *tasklist,
+ int *n_elements)
+{
+ g_return_val_if_fail (WNCK_IS_TASKLIST (tasklist), NULL);
+ g_return_val_if_fail (n_elements != NULL, NULL);
+
+ wnck_tasklist_update_size_hints (tasklist);
+
+ *n_elements = tasklist->priv->size_hints_len;
+ return tasklist->priv->size_hints;
+}
+
+static void
+wnck_tasklist_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkAllocation child_allocation;
+ WnckTasklist *tasklist;
+ WnckTask *class_group_task;
+ int n_windows;
+ int n_startup_sequences;
+ int max_height = 1;
+ int max_width = 1;
+ GList *l;
+ int button_width;
+ int total_width;
+ int n_rows;
+ int n_cols;
+ int n_grouped_buttons;
+ int i;
+ gboolean score_set;
+ GList *ungrouped_class_groups;
+ WnckTask *win_task;
+ GList *visible_tasks = NULL;
+ GList *windows_sorted = NULL;
+ int grouping_limit;
+
+ tasklist = WNCK_TASKLIST (widget);
+
+ n_windows = g_list_length (tasklist->priv->windows);
+ n_startup_sequences = g_list_length (tasklist->priv->startup_sequences);
+ n_grouped_buttons = 0;
+ ungrouped_class_groups = g_list_copy (tasklist->priv->class_groups);
+ score_set = FALSE;
+
+ calculate_max_button_size (tasklist, &max_width, &max_height);
+
+ grouping_limit = MIN (tasklist->priv->grouping_limit, max_width);
+
+ /* Try ungrouped mode */
+ button_width = wnck_tasklist_layout (allocation,
+ max_width,
+ max_height,
+ n_startup_sequences + n_windows,
+ tasklist->priv->orientation,
+ &n_cols, &n_rows);
+ while (ungrouped_class_groups != NULL &&
+ ((tasklist->priv->grouping == WNCK_TASKLIST_ALWAYS_GROUP) ||
+ ((tasklist->priv->grouping == WNCK_TASKLIST_AUTO_GROUP) &&
+ (button_width < grouping_limit))))
+ {
+ if (!score_set)
+ {
+ wnck_tasklist_score_groups (tasklist, ungrouped_class_groups);
+ score_set = TRUE;
+ }
+
+ ungrouped_class_groups = wnck_task_get_highest_scored (ungrouped_class_groups, &class_group_task);
+
+ n_grouped_buttons += g_list_length (class_group_task->windows) - 1;
+
+ if (g_list_length (class_group_task->windows) > 1)
+ {
+ visible_tasks = g_list_prepend (visible_tasks, class_group_task);
+
+ /* Sort */
+ class_group_task->windows = g_list_sort (class_group_task->windows,
+ wnck_task_compare_alphabetically);
+
+ /* Hide all this group's windows */
+ l = class_group_task->windows;
+ while (l != NULL)
+ {
+ win_task = WNCK_TASK (l->data);
+
+ gtk_widget_set_child_visible (GTK_WIDGET (win_task->button), FALSE);
+
+ l = l->next;
+ }
+ }
+ else
+ {
+ visible_tasks = g_list_prepend (visible_tasks, class_group_task->windows->data);
+ gtk_widget_set_child_visible (GTK_WIDGET (class_group_task->button), FALSE);
+ }
+
+ button_width = wnck_tasklist_layout (allocation,
+ max_width,
+ max_height,
+ n_startup_sequences + n_windows - n_grouped_buttons,
+ tasklist->priv->orientation,
+ &n_cols, &n_rows);
+ }
+
+ /* Add all ungrouped windows to visible_tasks, and hide their class groups */
+ l = ungrouped_class_groups;
+ while (l != NULL)
+ {
+ class_group_task = WNCK_TASK (l->data);
+
+ visible_tasks = g_list_concat (visible_tasks, g_list_copy (class_group_task->windows));
+ gtk_widget_set_child_visible (GTK_WIDGET (class_group_task->button), FALSE);
+
+ l = l->next;
+ }
+
+ /* Add all windows that are ungrouped because they don't belong to any class
+ * group */
+ l = tasklist->priv->windows_without_class_group;
+ while (l != NULL)
+ {
+ WnckTask *task;
+
+ task = WNCK_TASK (l->data);
+ visible_tasks = g_list_append (visible_tasks, task);
+
+ l = l->next;
+ }
+
+ /* Add all startup sequences */
+ visible_tasks = g_list_concat (visible_tasks, g_list_copy (tasklist->priv->startup_sequences));
+
+ /* Sort */
+ visible_tasks = g_list_sort (visible_tasks, wnck_task_compare);
+
+ /* Allocate children */
+ l = visible_tasks;
+ i = 0;
+ total_width = max_width * n_cols;
+ total_width = MIN (total_width, allocation->width);
+ /* FIXME: this is obviously wrong, but if we don't this, some space that the
+ * panel allocated to us won't have the panel popup menu, but the tasklist
+ * popup menu */
+ total_width = allocation->width;
+ while (l != NULL)
+ {
+ WnckTask *task = WNCK_TASK (l->data);
+ int row = i % n_rows;
+ int col = i / n_rows;
+
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+ col = n_cols - col - 1;
+
+ child_allocation.x = total_width*col / n_cols;
+ child_allocation.y = allocation->height*row / n_rows;
+ child_allocation.width = total_width*(col + 1) / n_cols - child_allocation.x;
+ child_allocation.height = allocation->height*(row + 1) / n_rows - child_allocation.y;
+ child_allocation.x += allocation->x;
+ child_allocation.y += allocation->y;
+
+ gtk_widget_size_allocate (task->button, &child_allocation);
+ gtk_widget_set_child_visible (GTK_WIDGET (task->button), TRUE);
+
+ if (task->type != WNCK_TASK_STARTUP_SEQUENCE)
+ {
+ GList *ll;
+
+ /* Build sorted windows list */
+ if (g_list_length (task->windows) > 1)
+ windows_sorted = g_list_concat (windows_sorted,
+ g_list_copy (task->windows));
+ else
+ windows_sorted = g_list_append (windows_sorted, task);
+ task->row = row;
+ task->col = col;
+ for (ll = task->windows; ll; ll = ll->next)
+ {
+ WNCK_TASK (ll->data)->row = row;
+ WNCK_TASK (ll->data)->col = col;
+ }
+ }
+ i++;
+ l = l->next;
+ }
+
+ /* Update icon geometries. */
+ wnck_tasklist_update_icon_geometries (tasklist, visible_tasks);
+
+ g_list_free (visible_tasks);
+ g_list_free (tasklist->priv->windows);
+ g_list_free (ungrouped_class_groups);
+ tasklist->priv->windows = windows_sorted;
+
+ GTK_WIDGET_CLASS (wnck_tasklist_parent_class)->size_allocate (widget,
+ allocation);
+}
+
+static void
+foreach_tasklist (WnckTasklist *tasklist,
+ gpointer user_data)
+{
+ wnck_tasklist_update_lists (tasklist);
+}
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+static void
+sn_error_trap_push (SnDisplay *display,
+ Display *xdisplay)
+{
+ _wnck_error_trap_push (xdisplay);
+}
+
+static void
+sn_error_trap_pop (SnDisplay *display,
+ Display *xdisplay)
+{
+ _wnck_error_trap_pop (xdisplay);
+}
+#endif /* HAVE_STARTUP_NOTIFICATION */
+
+static void
+wnck_tasklist_realize (GtkWidget *widget)
+{
+ WnckTasklist *tasklist;
+ GdkScreen *gdkscreen;
+ GdkDisplay *gdkdisplay;
+
+ tasklist = WNCK_TASKLIST (widget);
+
+ gdkscreen = gtk_widget_get_screen (widget);
+ gdkdisplay = gdk_screen_get_display (gdkscreen);
+ tasklist->priv->screen = wnck_screen_get (gdk_x11_screen_get_screen_number (gdkscreen));
+ g_assert (tasklist->priv->screen != NULL);
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ tasklist->priv->sn_display = sn_display_new (gdk_x11_display_get_xdisplay (gdkdisplay),
+ sn_error_trap_push,
+ sn_error_trap_pop);
+#endif
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ tasklist->priv->sn_context =
+ sn_monitor_context_new (tasklist->priv->sn_display,
+ wnck_screen_get_number (tasklist->priv->screen),
+ wnck_tasklist_sn_event,
+ tasklist,
+ NULL);
+#endif
+
+ (* GTK_WIDGET_CLASS (wnck_tasklist_parent_class)->realize) (widget);
+
+ tasklist_instances = g_slist_append (tasklist_instances, tasklist);
+ g_slist_foreach (tasklist_instances, (GFunc) foreach_tasklist, NULL);
+
+ wnck_tasklist_update_lists (tasklist);
+
+ wnck_tasklist_connect_screen (tasklist);
+}
+
+static void
+wnck_tasklist_unrealize (GtkWidget *widget)
+{
+ WnckTasklist *tasklist;
+
+ tasklist = WNCK_TASKLIST (widget);
+
+ wnck_tasklist_disconnect_screen (tasklist);
+ tasklist->priv->screen = NULL;
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ sn_monitor_context_unref (tasklist->priv->sn_context);
+ tasklist->priv->sn_context = NULL;
+#endif
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ sn_display_unref (tasklist->priv->sn_display);
+ tasklist->priv->sn_display = NULL;
+#endif
+
+ (* GTK_WIDGET_CLASS (wnck_tasklist_parent_class)->unrealize) (widget);
+
+ tasklist_instances = g_slist_remove (tasklist_instances, tasklist);
+ g_slist_foreach (tasklist_instances, (GFunc) foreach_tasklist, NULL);
+}
+
+static void
+wnck_tasklist_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data)
+{
+ WnckTasklist *tasklist;
+ GList *tmp;
+
+ tasklist = WNCK_TASKLIST (container);
+
+ tmp = tasklist->priv->windows;
+ while (tmp != NULL)
+ {
+ WnckTask *task = WNCK_TASK (tmp->data);
+ tmp = tmp->next;
+
+ (* callback) (task->button, callback_data);
+ }
+
+ tmp = tasklist->priv->class_groups;
+ while (tmp != NULL)
+ {
+ WnckTask *task = WNCK_TASK (tmp->data);
+ tmp = tmp->next;
+
+ (* callback) (task->button, callback_data);
+ }
+
+ tmp = tasklist->priv->startup_sequences;
+ while (tmp != NULL)
+ {
+ WnckTask *task = WNCK_TASK (tmp->data);
+ tmp = tmp->next;
+
+ (* callback) (task->button, callback_data);
+ }
+}
+
+static void
+wnck_tasklist_remove (GtkContainer *container,
+ GtkWidget *widget)
+{
+ WnckTasklist *tasklist;
+ GList *tmp;
+
+ g_return_if_fail (WNCK_IS_TASKLIST (container));
+ g_return_if_fail (widget != NULL);
+
+ tasklist = WNCK_TASKLIST (container);
+
+ /* it's safer to handle windows_without_class_group before windows */
+ tmp = tasklist->priv->windows_without_class_group;
+ while (tmp != NULL)
+ {
+ WnckTask *task = WNCK_TASK (tmp->data);
+ tmp = tmp->next;
+
+ if (task->button == widget)
+ {
+ tasklist->priv->windows_without_class_group =
+ g_list_remove (tasklist->priv->windows_without_class_group,
+ task);
+ g_object_unref (task);
+ break;
+ }
+ }
+
+ tmp = tasklist->priv->windows;
+ while (tmp != NULL)
+ {
+ WnckTask *task = WNCK_TASK (tmp->data);
+ tmp = tmp->next;
+
+ if (task->button == widget)
+ {
+ g_hash_table_remove (tasklist->priv->win_hash,
+ task->window);
+ tasklist->priv->windows =
+ g_list_remove (tasklist->priv->windows,
+ task);
+
+ gtk_widget_unparent (widget);
+ g_object_unref (task);
+ break;
+ }
+ }
+
+ tmp = tasklist->priv->class_groups;
+ while (tmp != NULL)
+ {
+ WnckTask *task = WNCK_TASK (tmp->data);
+ tmp = tmp->next;
+
+ if (task->button == widget)
+ {
+ g_hash_table_remove (tasklist->priv->class_group_hash,
+ task->class_group);
+ tasklist->priv->class_groups =
+ g_list_remove (tasklist->priv->class_groups,
+ task);
+
+ gtk_widget_unparent (widget);
+ g_object_unref (task);
+ break;
+ }
+ }
+
+ tmp = tasklist->priv->startup_sequences;
+ while (tmp != NULL)
+ {
+ WnckTask *task = WNCK_TASK (tmp->data);
+ tmp = tmp->next;
+
+ if (task->button == widget)
+ {
+ tasklist->priv->startup_sequences =
+ g_list_remove (tasklist->priv->startup_sequences,
+ task);
+
+ gtk_widget_unparent (widget);
+ g_object_unref (task);
+ break;
+ }
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (container));
+}
+
+static void
+wnck_tasklist_connect_screen (WnckTasklist *tasklist)
+{
+ GList *windows;
+ guint *c;
+ int i;
+ WnckScreen *screen;
+
+ g_return_if_fail (tasklist->priv->screen != NULL);
+
+ screen = tasklist->priv->screen;
+
+ i = 0;
+ c = tasklist->priv->screen_connections;
+
+ c [i++] = g_signal_connect_object (G_OBJECT (screen), "active_window_changed",
+ G_CALLBACK (wnck_tasklist_active_window_changed),
+ tasklist, 0);
+ c [i++] = g_signal_connect_object (G_OBJECT (screen), "active_workspace_changed",
+ G_CALLBACK (wnck_tasklist_active_workspace_changed),
+ tasklist, 0);
+ c [i++] = g_signal_connect_object (G_OBJECT (screen), "window_opened",
+ G_CALLBACK (wnck_tasklist_window_added),
+ tasklist, 0);
+ c [i++] = g_signal_connect_object (G_OBJECT (screen), "window_closed",
+ G_CALLBACK (wnck_tasklist_window_removed),
+ tasklist, 0);
+ c [i++] = g_signal_connect_object (G_OBJECT (screen), "viewports_changed",
+ G_CALLBACK (wnck_tasklist_viewports_changed),
+ tasklist, 0);
+
+
+ g_assert (i == N_SCREEN_CONNECTIONS);
+
+ windows = wnck_screen_get_windows (screen);
+ while (windows != NULL)
+ {
+ wnck_tasklist_connect_window (tasklist, windows->data);
+ windows = windows->next;
+ }
+}
+
+static void
+wnck_tasklist_disconnect_screen (WnckTasklist *tasklist)
+{
+ GList *windows;
+ int i;
+
+ windows = wnck_screen_get_windows (tasklist->priv->screen);
+ while (windows != NULL)
+ {
+ wnck_tasklist_disconnect_window (tasklist, windows->data);
+ windows = windows->next;
+ }
+
+ i = 0;
+ while (i < N_SCREEN_CONNECTIONS)
+ {
+ if (tasklist->priv->screen_connections [i] != 0)
+ g_signal_handler_disconnect (G_OBJECT (tasklist->priv->screen),
+ tasklist->priv->screen_connections [i]);
+
+ tasklist->priv->screen_connections [i] = 0;
+
+ ++i;
+ }
+
+ g_assert (i == N_SCREEN_CONNECTIONS);
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ if (tasklist->priv->startup_sequence_timeout != 0)
+ {
+ g_source_remove (tasklist->priv->startup_sequence_timeout);
+ tasklist->priv->startup_sequence_timeout = 0;
+ }
+#endif
+}
+
+static gboolean
+wnck_tasklist_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event)
+{
+ /* use the fact that tasklist->priv->windows is sorted
+ * see wnck_tasklist_size_allocate() */
+ WnckTasklist *tasklist;
+ GtkTextDirection ltr;
+ GList *window;
+ gint row = 0;
+ gint col = 0;
+
+ tasklist = WNCK_TASKLIST (widget);
+
+ if (!tasklist->priv->scroll_enabled)
+ return FALSE;
+
+ window = g_list_find (tasklist->priv->windows,
+ tasklist->priv->active_task);
+ if (window)
+ {
+ row = WNCK_TASK (window->data)->row;
+ col = WNCK_TASK (window->data)->col;
+ }
+ else
+ if (tasklist->priv->activate_timeout_id)
+ /* There is no active_task yet, but there will be one after the timeout.
+ * It occurs if we change the active task too fast. */
+ return TRUE;
+
+ ltr = (gtk_widget_get_direction (GTK_WIDGET (tasklist)) != GTK_TEXT_DIR_RTL);
+
+ switch (event->direction)
+ {
+ case GDK_SCROLL_UP:
+ if (!window)
+ window = g_list_last (tasklist->priv->windows);
+ else
+ window = window->prev;
+ break;
+
+ case GDK_SCROLL_DOWN:
+ if (!window)
+ window = tasklist->priv->windows;
+ else
+ window = window->next;
+ break;
+
+#define TASKLIST_GET_MOST_LEFT(ltr, window, tasklist) \
+ do \
+ { \
+ if (ltr) \
+ window = tasklist->priv->windows; \
+ else \
+ window = g_list_last (tasklist->priv->windows); \
+ } while (0)
+
+#define TASKLIST_GET_MOST_RIGHT(ltr, window, tasklist) \
+ do \
+ { \
+ if (ltr) \
+ window = g_list_last (tasklist->priv->windows); \
+ else \
+ window = tasklist->priv->windows; \
+ } while (0)
+
+ case GDK_SCROLL_LEFT:
+ if (!window)
+ TASKLIST_GET_MOST_RIGHT (ltr, window, tasklist);
+ else
+ {
+ /* Search the first window on the previous colomn at same row */
+ if (ltr)
+ {
+ while (window && (WNCK_TASK(window->data)->row != row ||
+ WNCK_TASK(window->data)->col != col-1))
+ window = window->prev;
+ }
+ else
+ {
+ while (window && (WNCK_TASK(window->data)->row != row ||
+ WNCK_TASK(window->data)->col != col-1))
+ window = window->next;
+ }
+ /* If no window found, select the top/bottom left one */
+ if (!window)
+ TASKLIST_GET_MOST_LEFT (ltr, window, tasklist);
+ }
+ break;
+
+ case GDK_SCROLL_RIGHT:
+ if (!window)
+ TASKLIST_GET_MOST_LEFT (ltr, window, tasklist);
+ else
+ {
+ /* Search the first window on the next colomn at same row */
+ if (ltr)
+ {
+ while (window && (WNCK_TASK(window->data)->row != row ||
+ WNCK_TASK(window->data)->col != col+1))
+ window = window->next;
+ }
+ else
+ {
+ while (window && (WNCK_TASK(window->data)->row != row ||
+ WNCK_TASK(window->data)->col != col+1))
+ window = window->prev;
+ }
+ /* If no window found, select the top/bottom right one */
+ if (!window)
+ TASKLIST_GET_MOST_RIGHT (ltr, window, tasklist);
+ }
+ break;
+
+ case GDK_SCROLL_SMOOTH:
+ window = NULL;
+ break;
+
+#undef TASKLIST_GET_MOST_LEFT
+#undef TASKLIST_GET_MOST_RIGHT
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (window)
+ wnck_tasklist_activate_task_window (window->data, event->time);
+
+ return TRUE;
+}
+
+/**
+ * wnck_tasklist_new:
+ *
+ * Creates a new #WnckTasklist. The #WnckTasklist will list #WnckWindow of the
+ * #WnckScreen it is on.
+ *
+ * Return value: a newly created #WnckTasklist.
+ */
+GtkWidget*
+wnck_tasklist_new (void)
+{
+ WnckTasklist *tasklist;
+
+ tasklist = g_object_new (WNCK_TYPE_TASKLIST, NULL);
+
+ return GTK_WIDGET (tasklist);
+}
+
+static void
+wnck_tasklist_free_tasks (WnckTasklist *tasklist)
+{
+ GList *l;
+
+ tasklist->priv->active_task = NULL;
+ tasklist->priv->active_class_group = NULL;
+
+ if (tasklist->priv->windows)
+ {
+ l = tasklist->priv->windows;
+ while (l != NULL)
+ {
+ WnckTask *task = WNCK_TASK (l->data);
+ l = l->next;
+ /* if we just unref the task it means we lose our ref to the
+ * task before we unparent the button, which breaks stuff.
+ */
+ gtk_widget_destroy (task->button);
+ }
+ }
+ g_assert (tasklist->priv->windows == NULL);
+ g_assert (tasklist->priv->windows_without_class_group == NULL);
+ g_assert (g_hash_table_size (tasklist->priv->win_hash) == 0);
+
+ if (tasklist->priv->class_groups)
+ {
+ l = tasklist->priv->class_groups;
+ while (l != NULL)
+ {
+ WnckTask *task = WNCK_TASK (l->data);
+ l = l->next;
+ /* if we just unref the task it means we lose our ref to the
+ * task before we unparent the button, which breaks stuff.
+ */
+ gtk_widget_destroy (task->button);
+ }
+ }
+
+ g_assert (tasklist->priv->class_groups == NULL);
+ g_assert (g_hash_table_size (tasklist->priv->class_group_hash) == 0);
+
+ if (tasklist->priv->skipped_windows)
+ {
+ wnck_tasklist_free_skipped_windows (tasklist);
+ tasklist->priv->skipped_windows = NULL;
+ }
+}
+
+
+/*
+ * This function determines if a window should be included in the tasklist.
+ */
+static gboolean
+tasklist_include_window_impl (WnckTasklist *tasklist,
+ WnckWindow *win,
+ gboolean check_for_skipped_list)
+{
+ WnckWorkspace *active_workspace;
+ int x, y, w, h;
+
+ if (!check_for_skipped_list &&
+ wnck_window_get_state (win) & WNCK_WINDOW_STATE_SKIP_TASKLIST)
+ return FALSE;
+
+ if (tasklist->priv->monitor != NULL)
+ {
+ int scale;
+ GdkDisplay *display;
+ GdkMonitor *monitor;
+
+ wnck_window_get_geometry (win, &x, &y, &w, &h);
+
+ scale = gtk_widget_get_scale_factor (GTK_WIDGET (tasklist));
+
+ x /= scale;
+ y /= scale;
+ w /= scale;
+ h /= scale;
+
+ /* Don't include the window if its center point is not on the same monitor */
+
+ display = gdk_display_get_default ();
+ monitor = gdk_display_get_monitor_at_point (display, x + w / 2, y + h / 2);
+
+ if (monitor != tasklist->priv->monitor)
+ return FALSE;
+ }
+
+ /* Remainder of checks aren't relevant for checking if the window should
+ * be in the skipped list.
+ */
+ if (check_for_skipped_list)
+ return TRUE;
+
+ if (tasklist->priv->include_all_workspaces)
+ return TRUE;
+
+ if (wnck_window_is_pinned (win))
+ return TRUE;
+
+ active_workspace = wnck_screen_get_active_workspace (tasklist->priv->screen);
+ if (active_workspace == NULL)
+ return TRUE;
+
+ if (wnck_window_or_transient_needs_attention (win))
+ return TRUE;
+
+ if (active_workspace != wnck_window_get_workspace (win))
+ return FALSE;
+
+ if (!wnck_workspace_is_virtual (active_workspace))
+ return TRUE;
+
+ return wnck_window_is_in_viewport (win, active_workspace);
+}
+
+static gboolean
+tasklist_include_in_skipped_list (WnckTasklist *tasklist, WnckWindow *win)
+{
+ return tasklist_include_window_impl (tasklist,
+ win,
+ TRUE /* check_for_skipped_list */);
+}
+
+static gboolean
+wnck_tasklist_include_window (WnckTasklist *tasklist, WnckWindow *win)
+{
+ return tasklist_include_window_impl (tasklist,
+ win,
+ FALSE /* check_for_skipped_list */);
+}
+
+static void
+wnck_tasklist_update_lists (WnckTasklist *tasklist)
+{
+ GdkWindow *tasklist_window;
+ GList *windows;
+ WnckWindow *win;
+ WnckClassGroup *class_group;
+ GList *l;
+ WnckTask *win_task;
+ WnckTask *class_group_task;
+
+ wnck_tasklist_free_tasks (tasklist);
+
+ /* wnck_tasklist_update_lists() will be called on realize */
+ if (!gtk_widget_get_realized (GTK_WIDGET (tasklist)))
+ return;
+
+ tasklist_window = gtk_widget_get_window (GTK_WIDGET (tasklist));
+
+ if (tasklist_window != NULL)
+ {
+ /*
+ * only show windows from this monitor if there is more than one tasklist running
+ */
+ if (tasklist_instances == NULL || tasklist_instances->next == NULL)
+ {
+ tasklist->priv->monitor = NULL;
+ }
+ else
+ {
+ GdkDisplay *display;
+ GdkMonitor *monitor;
+
+ display = gdk_display_get_default ();
+ monitor = gdk_display_get_monitor_at_window (display, tasklist_window);
+
+ if (monitor != tasklist->priv->monitor)
+ {
+ tasklist->priv->monitor = monitor;
+ gdk_monitor_get_geometry (monitor, &tasklist->priv->monitor_geometry);
+ }
+ }
+ }
+
+ l = windows = wnck_screen_get_windows (tasklist->priv->screen);
+ while (l != NULL)
+ {
+ win = WNCK_WINDOW (l->data);
+
+ if (wnck_tasklist_include_window (tasklist, win))
+ {
+ win_task = wnck_task_new_from_window (tasklist, win);
+ tasklist->priv->windows = g_list_prepend (tasklist->priv->windows, win_task);
+ g_hash_table_insert (tasklist->priv->win_hash, win, win_task);
+
+ gtk_widget_set_parent (win_task->button, GTK_WIDGET (tasklist));
+ gtk_widget_show (win_task->button);
+
+ /* Class group */
+
+ class_group = wnck_window_get_class_group (win);
+ /* don't group windows if they do not belong to any class */
+ if (strcmp (wnck_class_group_get_id (class_group), "") != 0)
+ {
+ class_group_task =
+ g_hash_table_lookup (tasklist->priv->class_group_hash,
+ class_group);
+
+ if (class_group_task == NULL)
+ {
+ class_group_task =
+ wnck_task_new_from_class_group (tasklist,
+ class_group);
+ gtk_widget_set_parent (class_group_task->button,
+ GTK_WIDGET (tasklist));
+ gtk_widget_show (class_group_task->button);
+
+ tasklist->priv->class_groups =
+ g_list_prepend (tasklist->priv->class_groups,
+ class_group_task);
+ g_hash_table_insert (tasklist->priv->class_group_hash,
+ class_group, class_group_task);
+ }
+
+ class_group_task->windows =
+ g_list_prepend (class_group_task->windows,
+ win_task);
+ }
+ else
+ {
+ g_object_ref (win_task);
+ tasklist->priv->windows_without_class_group =
+ g_list_prepend (tasklist->priv->windows_without_class_group,
+ win_task);
+ }
+ }
+ else if (tasklist_include_in_skipped_list (tasklist, win))
+ {
+ skipped_window *skipped = g_new0 (skipped_window, 1);
+ skipped->window = g_object_ref (win);
+ skipped->tag = g_signal_connect (G_OBJECT (win),
+ "state_changed",
+ G_CALLBACK (wnck_task_state_changed),
+ tasklist);
+ tasklist->priv->skipped_windows =
+ g_list_prepend (tasklist->priv->skipped_windows,
+ (gpointer) skipped);
+ }
+
+ l = l->next;
+ }
+
+ /* Sort the class group list */
+ l = tasklist->priv->class_groups;
+ while (l)
+ {
+ class_group_task = WNCK_TASK (l->data);
+
+ class_group_task->windows = g_list_sort (class_group_task->windows, wnck_task_compare);
+
+ /* so the number of windows in the task gets reset on the
+ * task label
+ */
+ wnck_task_update_visible_state (class_group_task);
+
+ l = l->next;
+ }
+
+ /* since we cleared active_window we need to reset it */
+ wnck_tasklist_active_window_changed (tasklist->priv->screen, NULL, tasklist);
+
+ gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+}
+
+static void
+wnck_tasklist_change_active_task (WnckTasklist *tasklist, WnckTask *active_task)
+{
+ if (active_task &&
+ active_task == tasklist->priv->active_task)
+ return;
+
+ g_assert (active_task == NULL ||
+ active_task->type != WNCK_TASK_STARTUP_SEQUENCE);
+
+ if (tasklist->priv->active_task)
+ {
+ tasklist->priv->active_task->really_toggling = TRUE;
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tasklist->priv->active_task->button),
+ FALSE);
+ tasklist->priv->active_task->really_toggling = FALSE;
+ }
+
+ tasklist->priv->active_task = active_task;
+
+ if (tasklist->priv->active_task)
+ {
+ tasklist->priv->active_task->really_toggling = TRUE;
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tasklist->priv->active_task->button),
+ TRUE);
+ tasklist->priv->active_task->really_toggling = FALSE;
+ }
+
+ if (active_task)
+ {
+ active_task = g_hash_table_lookup (tasklist->priv->class_group_hash,
+ active_task->class_group);
+
+ if (active_task &&
+ active_task == tasklist->priv->active_class_group)
+ return;
+
+ if (tasklist->priv->active_class_group)
+ {
+ tasklist->priv->active_class_group->really_toggling = TRUE;
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tasklist->priv->active_class_group->button),
+ FALSE);
+ tasklist->priv->active_class_group->really_toggling = FALSE;
+ }
+
+ tasklist->priv->active_class_group = active_task;
+
+ if (tasklist->priv->active_class_group)
+ {
+ tasklist->priv->active_class_group->really_toggling = TRUE;
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tasklist->priv->active_class_group->button),
+ TRUE);
+ tasklist->priv->active_class_group->really_toggling = FALSE;
+ }
+ }
+}
+
+static void
+wnck_tasklist_update_icon_geometries (WnckTasklist *tasklist,
+ GList *visible_tasks)
+{
+ int scale;
+ gint x, y, width, height;
+ GList *l1;
+
+ scale = gtk_widget_get_scale_factor (GTK_WIDGET (tasklist));
+
+ for (l1 = visible_tasks; l1; l1 = l1->next) {
+ WnckTask *task = WNCK_TASK (l1->data);
+ GtkAllocation allocation;
+
+ if (!gtk_widget_get_realized (task->button))
+ continue;
+
+ /* Let's cheat with some internal knowledge of GtkButton: in a
+ * GtkButton, the window is the same as the parent window. So
+ * to know the position of the widget, we should use the
+ * the position of the parent window and the allocation information. */
+
+ gtk_widget_get_allocation (task->button, &allocation);
+
+ gdk_window_get_origin (gtk_widget_get_parent_window (task->button),
+ &x, &y);
+
+ x += allocation.x;
+ y += allocation.y;
+ width = allocation.width;
+ height = allocation.height;
+
+ x *= scale;
+ y *= scale;
+ width *= scale;
+ height *= scale;
+
+ if (task->window)
+ wnck_window_set_icon_geometry (task->window,
+ x, y, width, height);
+ else {
+ GList *l2;
+
+ for (l2 = task->windows; l2; l2 = l2->next) {
+ WnckTask *win_task = WNCK_TASK (l2->data);
+
+ g_assert (win_task->window);
+
+ wnck_window_set_icon_geometry (win_task->window,
+ x, y, width, height);
+ }
+ }
+ }
+}
+
+static void
+wnck_tasklist_active_window_changed (WnckScreen *screen,
+ WnckWindow *previous_window,
+ WnckTasklist *tasklist)
+{
+ WnckWindow *active_window;
+ WnckWindow *initial_window;
+ WnckTask *active_task = NULL;
+
+ /* FIXME: check for group modal window */
+ initial_window = active_window = wnck_screen_get_active_window (screen);
+ active_task = g_hash_table_lookup (tasklist->priv->win_hash, active_window);
+ while (active_window && !active_task)
+ {
+ active_window = wnck_window_get_transient (active_window);
+ active_task = g_hash_table_lookup (tasklist->priv->win_hash,
+ active_window);
+ /* Check for transient cycles */
+ if (active_window == initial_window)
+ break;
+ }
+
+ wnck_tasklist_change_active_task (tasklist, active_task);
+}
+
+static void
+wnck_tasklist_active_workspace_changed (WnckScreen *screen,
+ WnckWorkspace *previous_workspace,
+ WnckTasklist *tasklist)
+{
+ wnck_tasklist_update_lists (tasklist);
+ gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+}
+
+static void
+wnck_tasklist_window_changed_workspace (WnckWindow *window,
+ WnckTasklist *tasklist)
+{
+ WnckWorkspace *active_ws;
+ WnckWorkspace *window_ws;
+ gboolean need_update;
+ GList *l;
+
+ active_ws = wnck_screen_get_active_workspace (tasklist->priv->screen);
+ window_ws = wnck_window_get_workspace (window);
+
+ if (!window_ws)
+ return;
+
+ need_update = FALSE;
+
+ if (active_ws == window_ws)
+ need_update = TRUE;
+
+ l = tasklist->priv->windows;
+ while (!need_update && l != NULL)
+ {
+ WnckTask *task = l->data;
+
+ if (task->type == WNCK_TASK_WINDOW &&
+ task->window == window)
+ need_update = TRUE;
+
+ l = l->next;
+ }
+
+ if (need_update)
+ {
+ wnck_tasklist_update_lists (tasklist);
+ gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+ }
+}
+
+static gboolean
+do_wnck_tasklist_update_lists (gpointer data)
+{
+ WnckTasklist *tasklist = WNCK_TASKLIST (data);
+
+ tasklist->priv->idle_callback_tag = 0;
+
+ wnck_tasklist_update_lists (tasklist);
+
+ return FALSE;
+}
+
+static void
+wnck_tasklist_window_changed_geometry (WnckWindow *window,
+ WnckTasklist *tasklist)
+{
+ GdkWindow *tasklist_window;
+ WnckTask *win_task;
+ gboolean show;
+ gboolean monitor_changed;
+ int x, y, w, h;
+
+ if (tasklist->priv->idle_callback_tag != 0)
+ return;
+
+ tasklist_window = gtk_widget_get_window (GTK_WIDGET (tasklist));
+
+ /*
+ * If the (parent of the) tasklist itself skips
+ * the tasklist, we need an extra check whether
+ * the tasklist itself possibly changed monitor.
+ */
+ monitor_changed = FALSE;
+ if (tasklist->priv->monitor != NULL &&
+ (wnck_window_get_state (window) & WNCK_WINDOW_STATE_SKIP_TASKLIST) &&
+ tasklist_window != NULL)
+ {
+ /* Do the extra check only if there is a suspect of a monitor change (= this window is off monitor) */
+ wnck_window_get_geometry (window, &x, &y, &w, &h);
+ if (!POINT_IN_RECT (x + w / 2, y + h / 2, tasklist->priv->monitor_geometry))
+ {
+ GdkDisplay *display;
+ GdkMonitor *monitor;
+
+ display = gdk_display_get_default ();
+ monitor = gdk_display_get_monitor_at_window (display, tasklist_window);
+
+ monitor_changed = (monitor != tasklist->priv->monitor);
+ }
+ }
+
+ /*
+ * We want to re-generate the task list if
+ * the window is shown but shouldn't be or
+ * the window isn't shown but should be or
+ * the tasklist itself changed monitor.
+ */
+ win_task = g_hash_table_lookup (tasklist->priv->win_hash, window);
+ show = wnck_tasklist_include_window (tasklist, window);
+ if (((win_task == NULL && !show) || (win_task != NULL && show)) &&
+ !monitor_changed)
+ return;
+
+ /* Don't keep any stale references */
+ gtk_widget_queue_draw (GTK_WIDGET (tasklist));
+
+ tasklist->priv->idle_callback_tag = g_idle_add (do_wnck_tasklist_update_lists, tasklist);
+}
+
+static void
+wnck_tasklist_connect_window (WnckTasklist *tasklist,
+ WnckWindow *window)
+{
+ g_signal_connect_object (window, "workspace_changed",
+ G_CALLBACK (wnck_tasklist_window_changed_workspace),
+ tasklist, 0);
+ g_signal_connect_object (window, "geometry_changed",
+ G_CALLBACK (wnck_tasklist_window_changed_geometry),
+ tasklist, 0);
+}
+
+static void
+wnck_tasklist_disconnect_window (WnckTasklist *tasklist,
+ WnckWindow *window)
+{
+ g_signal_handlers_disconnect_by_func (window,
+ wnck_tasklist_window_changed_workspace,
+ tasklist);
+ g_signal_handlers_disconnect_by_func (window,
+ wnck_tasklist_window_changed_geometry,
+ tasklist);
+}
+
+static void
+wnck_tasklist_window_added (WnckScreen *screen,
+ WnckWindow *win,
+ WnckTasklist *tasklist)
+{
+#ifdef HAVE_STARTUP_NOTIFICATION
+ wnck_tasklist_check_end_sequence (tasklist, win);
+#endif
+
+ wnck_tasklist_connect_window (tasklist, win);
+
+ wnck_tasklist_update_lists (tasklist);
+ gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+}
+
+static void
+wnck_tasklist_window_removed (WnckScreen *screen,
+ WnckWindow *win,
+ WnckTasklist *tasklist)
+{
+ wnck_tasklist_update_lists (tasklist);
+ gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+}
+
+static void
+wnck_tasklist_viewports_changed (WnckScreen *screen,
+ WnckTasklist *tasklist)
+{
+ wnck_tasklist_update_lists (tasklist);
+ gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+}
+
+static gboolean
+wnck_tasklist_change_active_timeout (gpointer data)
+{
+ WnckTasklist *tasklist = WNCK_TASKLIST (data);
+
+ tasklist->priv->activate_timeout_id = 0;
+
+ wnck_tasklist_active_window_changed (tasklist->priv->screen, NULL, tasklist);
+
+ return FALSE;
+}
+
+static void
+wnck_task_menu_activated (GtkMenuItem *menu_item,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+
+ /* This is an "activate" callback function so gtk_get_current_event_time()
+ * will suffice.
+ */
+ wnck_tasklist_activate_task_window (task, gtk_get_current_event_time ());
+}
+
+static void
+wnck_tasklist_activate_next_in_class_group (WnckTask *task,
+ guint32 timestamp)
+{
+ WnckTask *activate_task;
+ gboolean activate_next;
+ GList *l;
+
+ activate_task = NULL;
+ activate_next = FALSE;
+
+ l = task->windows;
+ while (l)
+ {
+ WnckTask *tmp;
+
+ tmp = WNCK_TASK (l->data);
+
+ if (wnck_window_is_most_recently_activated (tmp->window))
+ activate_next = TRUE;
+ else if (activate_next)
+ {
+ activate_task = tmp;
+ break;
+ }
+
+ l = l->next;
+ }
+
+ /* no task in this group is active, or only the last one => activate
+ * the first task */
+ if (!activate_task && task->windows)
+ activate_task = WNCK_TASK (task->windows->data);
+
+ if (activate_task)
+ {
+ task->was_active = FALSE;
+ wnck_tasklist_activate_task_window (activate_task, timestamp);
+ }
+}
+
+static void
+wnck_tasklist_activate_task_window (WnckTask *task,
+ guint32 timestamp)
+{
+ WnckTasklist *tasklist;
+ WnckWindowState state;
+ WnckWorkspace *active_ws;
+ WnckWorkspace *window_ws;
+
+ tasklist = task->tasklist;
+
+ if (task->window == NULL)
+ return;
+
+ state = wnck_window_get_state (task->window);
+
+ active_ws = wnck_screen_get_active_workspace (tasklist->priv->screen);
+ window_ws = wnck_window_get_workspace (task->window);
+
+ if (state & WNCK_WINDOW_STATE_MINIMIZED)
+ {
+ if (window_ws &&
+ active_ws != window_ws &&
+ !tasklist->priv->switch_workspace_on_unminimize)
+ wnck_workspace_activate (window_ws, timestamp);
+
+ wnck_window_activate_transient (task->window, timestamp);
+ }
+ else
+ {
+ if ((task->was_active ||
+ wnck_window_transient_is_most_recently_activated (task->window)) &&
+ (!window_ws || active_ws == window_ws))
+ {
+ task->was_active = FALSE;
+ wnck_window_minimize (task->window);
+ return;
+ }
+ else
+ {
+ /* FIXME: THIS IS SICK AND WRONG AND BUGGY. See the end of
+ * http://mail.gnome.org/archives/wm-spec-list/2005-July/msg00032.html
+ * There should only be *one* activate call.
+ */
+ if (window_ws)
+ wnck_workspace_activate (window_ws, timestamp);
+
+ wnck_window_activate_transient (task->window, timestamp);
+ }
+ }
+
+
+ if (tasklist->priv->activate_timeout_id)
+ g_source_remove (tasklist->priv->activate_timeout_id);
+
+ tasklist->priv->activate_timeout_id =
+ g_timeout_add (500, &wnck_tasklist_change_active_timeout, tasklist);
+
+ wnck_tasklist_change_active_task (tasklist, task);
+}
+
+static void
+wnck_task_close_all (GtkMenuItem *menu_item,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+ GList *l;
+
+ l = task->windows;
+ while (l)
+ {
+ WnckTask *child = WNCK_TASK (l->data);
+ wnck_window_close (child->window, gtk_get_current_event_time ());
+ l = l->next;
+ }
+}
+
+static void
+wnck_task_unminimize_all (GtkMenuItem *menu_item,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+ GList *l;
+
+ l = task->windows;
+ while (l)
+ {
+ WnckTask *child = WNCK_TASK (l->data);
+ /* This is inside an activate callback, so gtk_get_current_event_time()
+ * will work.
+ */
+ wnck_window_unminimize (child->window, gtk_get_current_event_time ());
+ l = l->next;
+ }
+}
+
+static void
+wnck_task_minimize_all (GtkMenuItem *menu_item,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+ GList *l;
+
+ l = task->windows;
+ while (l)
+ {
+ WnckTask *child = WNCK_TASK (l->data);
+ wnck_window_minimize (child->window);
+ l = l->next;
+ }
+}
+
+static void
+wnck_task_unmaximize_all (GtkMenuItem *menu_item,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+ GList *l;
+
+ l = task->windows;
+ while (l)
+ {
+ WnckTask *child = WNCK_TASK (l->data);
+ wnck_window_unmaximize (child->window);
+ l = l->next;
+ }
+}
+
+static void
+wnck_task_maximize_all (GtkMenuItem *menu_item,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+ GList *l;
+
+ l = task->windows;
+ while (l)
+ {
+ WnckTask *child = WNCK_TASK (l->data);
+ wnck_window_maximize (child->window);
+ l = l->next;
+ }
+}
+
+static void
+wnck_task_popup_menu (WnckTask *task,
+ gboolean action_submenu)
+{
+ GtkWidget *menu;
+ WnckTask *win_task;
+ char *text;
+ GdkPixbuf *pixbuf;
+ GtkWidget *menu_item;
+ GList *l, *list;
+
+ g_return_if_fail (task->type == WNCK_TASK_CLASS_GROUP);
+
+ if (task->class_group == NULL)
+ return;
+
+ if (task->menu == NULL)
+ {
+ task->menu = gtk_menu_new ();
+ g_object_ref_sink (task->menu);
+ }
+
+ menu = task->menu;
+
+ /* Remove old menu content */
+ list = gtk_container_get_children (GTK_CONTAINER (menu));
+ l = list;
+ while (l)
+ {
+ GtkWidget *child = GTK_WIDGET (l->data);
+ gtk_container_remove (GTK_CONTAINER (menu), child);
+ l = l->next;
+ }
+ g_list_free (list);
+
+ l = task->windows;
+ while (l)
+ {
+ win_task = WNCK_TASK (l->data);
+
+ text = wnck_task_get_text (win_task, TRUE, TRUE);
+ menu_item = wnck_image_menu_item_new_with_label (text);
+ g_free (text);
+
+ if (wnck_task_get_needs_attention (win_task))
+ _make_gtk_label_bold (GTK_LABEL (gtk_bin_get_child (GTK_BIN (menu_item))));
+
+ text = wnck_task_get_text (win_task, FALSE, FALSE);
+ gtk_widget_set_tooltip_text (menu_item, text);
+ g_free (text);
+
+ pixbuf = wnck_task_get_icon (win_task);
+ if (pixbuf)
+ {
+ WnckImageMenuItem *item;
+
+ item = WNCK_IMAGE_MENU_ITEM (menu_item);
+
+ wnck_image_menu_item_set_image_from_icon_pixbuf (item, pixbuf);
+ g_object_unref (pixbuf);
+ }
+
+ gtk_widget_show (menu_item);
+
+ if (action_submenu)
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item),
+ wnck_action_menu_new (win_task->window));
+ else
+ {
+ static const GtkTargetEntry targets[] = {
+ { (gchar *) "application/x-wnck-window-id", 0, 0 }
+ };
+
+ g_signal_connect_object (G_OBJECT (menu_item), "activate",
+ G_CALLBACK (wnck_task_menu_activated),
+ G_OBJECT (win_task),
+ 0);
+
+
+ gtk_drag_source_set (menu_item, GDK_BUTTON1_MASK,
+ targets, 1, GDK_ACTION_MOVE);
+ g_signal_connect_object (G_OBJECT(menu_item), "drag_begin",
+ G_CALLBACK (wnck_task_drag_begin),
+ G_OBJECT (win_task),
+ 0);
+ g_signal_connect_object (G_OBJECT(menu_item), "drag_end",
+ G_CALLBACK (wnck_task_drag_end),
+ G_OBJECT (win_task),
+ 0);
+ g_signal_connect_object (G_OBJECT(menu_item), "drag_data_get",
+ G_CALLBACK (wnck_task_drag_data_get),
+ G_OBJECT (win_task),
+ 0);
+ }
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+
+ l = l->next;
+ }
+
+ /* In case of Right click, show Minimize All, Unminimize All, Close All*/
+ if (action_submenu)
+ {
+ GtkWidget *separator;
+
+ separator = gtk_separator_menu_item_new ();
+ gtk_widget_show (separator);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), separator);
+
+ menu_item = gtk_menu_item_new_with_mnemonic (_("Mi_nimize All"));
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+ g_signal_connect_object (G_OBJECT (menu_item), "activate",
+ G_CALLBACK (wnck_task_minimize_all),
+ G_OBJECT (task),
+ 0);
+
+ menu_item = gtk_menu_item_new_with_mnemonic (_("Un_minimize All"));
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+ g_signal_connect_object (G_OBJECT (menu_item), "activate",
+ G_CALLBACK (wnck_task_unminimize_all),
+ G_OBJECT (task),
+ 0);
+
+ menu_item = gtk_menu_item_new_with_mnemonic (_("Ma_ximize All"));
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+ g_signal_connect_object (G_OBJECT (menu_item), "activate",
+ G_CALLBACK (wnck_task_maximize_all),
+ G_OBJECT (task),
+ 0);
+
+ menu_item = gtk_menu_item_new_with_mnemonic (_("_Unmaximize All"));
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+ g_signal_connect_object (G_OBJECT (menu_item), "activate",
+ G_CALLBACK (wnck_task_unmaximize_all),
+ G_OBJECT (task),
+ 0);
+
+ separator = gtk_separator_menu_item_new ();
+ gtk_widget_show (separator);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), separator);
+
+ menu_item = gtk_menu_item_new_with_mnemonic(_("_Close All"));
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+ g_signal_connect_object (G_OBJECT (menu_item), "activate",
+ G_CALLBACK (wnck_task_close_all),
+ G_OBJECT (task),
+ 0);
+ }
+
+ /*gtk_menu_set_screen (GTK_MENU (menu),
+ _wnck_screen_get_gdk_screen (task->tasklist->priv->screen));*/
+
+ gtk_widget_show (menu);
+ gtk_menu_popup_at_widget (GTK_MENU (menu), task->button,
+ GDK_GRAVITY_SOUTH_WEST,
+ GDK_GRAVITY_NORTH_WEST,
+ NULL);
+}
+
+static void
+wnck_task_button_toggled (GtkButton *button,
+ WnckTask *task)
+{
+ /* Did we really want to change the state of the togglebutton? */
+ if (task->really_toggling)
+ return;
+
+ /* Undo the toggle */
+ task->really_toggling = TRUE;
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+ !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
+ task->really_toggling = FALSE;
+
+ switch (task->type)
+ {
+ case WNCK_TASK_CLASS_GROUP:
+ wnck_task_popup_menu (task, FALSE);
+ break;
+ case WNCK_TASK_WINDOW:
+ if (task->window == NULL)
+ return;
+
+ /* This should only be called by clicking on the task button, so
+ * gtk_get_current_event_time() should be fine here...
+ */
+ wnck_tasklist_activate_task_window (task, gtk_get_current_event_time ());
+ break;
+ case WNCK_TASK_STARTUP_SEQUENCE:
+ break;
+ default:
+ break;
+ }
+}
+
+static char *
+wnck_task_get_text (WnckTask *task,
+ gboolean icon_text,
+ gboolean include_state)
+{
+ const char *name;
+
+ switch (task->type)
+ {
+ case WNCK_TASK_CLASS_GROUP:
+ name = wnck_class_group_get_name (task->class_group);
+ if (name[0] != 0)
+ return g_strdup_printf ("%s (%d)",
+ name,
+ g_list_length (task->windows));
+ else
+ return g_strdup_printf ("(%d)",
+ g_list_length (task->windows));
+
+ case WNCK_TASK_WINDOW:
+ return wnck_window_get_name_for_display (task->window,
+ icon_text, include_state);
+ break;
+
+ case WNCK_TASK_STARTUP_SEQUENCE:
+#ifdef HAVE_STARTUP_NOTIFICATION
+ name = sn_startup_sequence_get_description (task->startup_sequence);
+ if (name == NULL)
+ name = sn_startup_sequence_get_name (task->startup_sequence);
+ if (name == NULL)
+ name = sn_startup_sequence_get_binary_name (task->startup_sequence);
+
+ return g_strdup (name);
+#else
+ return NULL;
+#endif
+ break;
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static void
+wnck_dimm_icon (GdkPixbuf *pixbuf)
+{
+ int x, y, pixel_stride, row_stride;
+ guchar *row, *pixels;
+ int w, h;
+
+ g_assert (pixbuf != NULL);
+
+ w = gdk_pixbuf_get_width (pixbuf);
+ h = gdk_pixbuf_get_height (pixbuf);
+
+ g_assert (gdk_pixbuf_get_has_alpha (pixbuf));
+
+ pixel_stride = 4;
+
+ row = gdk_pixbuf_get_pixels (pixbuf);
+ row_stride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ for (y = 0; y < h; y++)
+ {
+ pixels = row;
+
+ for (x = 0; x < w; x++)
+ {
+ pixels[3] /= 2;
+
+ pixels += pixel_stride;
+ }
+
+ row += row_stride;
+ }
+}
+
+static GdkPixbuf *
+wnck_task_scale_icon (GdkPixbuf *orig, gboolean minimized)
+{
+ int w, h;
+ GdkPixbuf *pixbuf;
+
+ if (!orig)
+ return NULL;
+
+ w = gdk_pixbuf_get_width (orig);
+ h = gdk_pixbuf_get_height (orig);
+
+ if (h != (int) MINI_ICON_SIZE ||
+ !gdk_pixbuf_get_has_alpha (orig))
+ {
+ double scale;
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ TRUE,
+ 8,
+ MINI_ICON_SIZE * w / (double) h,
+ MINI_ICON_SIZE);
+
+ scale = MINI_ICON_SIZE / (double) gdk_pixbuf_get_height (orig);
+
+ gdk_pixbuf_scale (orig,
+ pixbuf,
+ 0, 0,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ 0, 0,
+ scale, scale,
+ GDK_INTERP_HYPER);
+ }
+ else
+ pixbuf = orig;
+
+ if (minimized)
+ {
+ if (orig == pixbuf)
+ pixbuf = gdk_pixbuf_copy (orig);
+
+ wnck_dimm_icon (pixbuf);
+ }
+
+ if (orig == pixbuf)
+ g_object_ref (pixbuf);
+
+ return pixbuf;
+}
+
+
+static GdkPixbuf *
+wnck_task_get_icon (WnckTask *task)
+{
+ WnckWindowState state;
+ GdkPixbuf *pixbuf;
+
+ pixbuf = NULL;
+
+ switch (task->type)
+ {
+ case WNCK_TASK_CLASS_GROUP:
+ pixbuf = wnck_task_scale_icon (wnck_class_group_get_mini_icon (task->class_group),
+ FALSE);
+ break;
+
+ case WNCK_TASK_WINDOW:
+ state = wnck_window_get_state (task->window);
+
+ pixbuf = wnck_task_scale_icon (wnck_window_get_mini_icon (task->window),
+ state & WNCK_WINDOW_STATE_MINIMIZED);
+ break;
+
+ case WNCK_TASK_STARTUP_SEQUENCE:
+#ifdef HAVE_STARTUP_NOTIFICATION
+ if (task->tasklist->priv->icon_loader != NULL)
+ {
+ const char *icon;
+
+ icon = sn_startup_sequence_get_icon_name (task->startup_sequence);
+ if (icon != NULL)
+ {
+ GdkPixbuf *loaded;
+
+ loaded = (* task->tasklist->priv->icon_loader) (icon,
+ MINI_ICON_SIZE,
+ 0,
+ task->tasklist->priv->icon_loader_data);
+
+ if (loaded != NULL)
+ {
+ pixbuf = wnck_task_scale_icon (loaded, FALSE);
+ g_object_unref (G_OBJECT (loaded));
+ }
+ }
+ }
+
+ if (pixbuf == NULL)
+ {
+ _wnck_get_fallback_icons (NULL, 0,
+ &pixbuf, MINI_ICON_SIZE);
+ }
+#endif
+ break;
+
+ default:
+ break;
+ }
+
+ return pixbuf;
+}
+
+static gboolean
+wnck_task_get_needs_attention (WnckTask *task)
+{
+ GList *l;
+ WnckTask *win_task;
+ gboolean needs_attention;
+
+ needs_attention = FALSE;
+
+ switch (task->type)
+ {
+ case WNCK_TASK_CLASS_GROUP:
+ task->start_needs_attention = 0;
+ l = task->windows;
+ while (l)
+ {
+ win_task = WNCK_TASK (l->data);
+
+ if (wnck_window_or_transient_needs_attention (win_task->window))
+ {
+ needs_attention = TRUE;
+ task->start_needs_attention = MAX (task->start_needs_attention, wnck_window_or_transient_get_needs_attention_time (win_task->window));
+ break;
+ }
+
+ l = l->next;
+ }
+ break;
+
+ case WNCK_TASK_WINDOW:
+ needs_attention =
+ wnck_window_or_transient_needs_attention (task->window);
+ task->start_needs_attention = wnck_window_or_transient_get_needs_attention_time (task->window);
+ break;
+
+ case WNCK_TASK_STARTUP_SEQUENCE:
+ default:
+ break;
+ }
+
+ return needs_attention != FALSE;
+}
+
+static void
+wnck_task_update_visible_state (WnckTask *task)
+{
+ GdkPixbuf *pixbuf;
+ char *text;
+
+ pixbuf = wnck_task_get_icon (task);
+ wnck_button_set_image_from_pixbuf (WNCK_BUTTON (task->button), pixbuf);
+ g_clear_object (&pixbuf);
+
+ text = wnck_task_get_text (task, TRUE, TRUE);
+ if (text != NULL)
+ {
+ wnck_button_set_text (WNCK_BUTTON (task->button), text);
+ g_free (text);
+
+ if (wnck_task_get_needs_attention (task))
+ {
+ wnck_button_set_bold (WNCK_BUTTON (task->button), TRUE);
+ wnck_task_queue_glow (task);
+ }
+ else
+ {
+ wnck_button_set_bold (WNCK_BUTTON (task->button), FALSE);
+ wnck_task_reset_glow (task);
+ }
+ }
+
+ text = wnck_task_get_text (task, FALSE, FALSE);
+ /* if text is NULL, this unsets the tooltip, which is probably what we'd want
+ * to do */
+ gtk_widget_set_tooltip_text (task->button, text);
+ g_free (text);
+
+ gtk_widget_queue_resize (GTK_WIDGET (task->tasklist));
+}
+
+static void
+wnck_task_state_changed (WnckWindow *window,
+ WnckWindowState changed_mask,
+ WnckWindowState new_state,
+ gpointer data)
+{
+ WnckTasklist *tasklist = WNCK_TASKLIST (data);
+
+ if (changed_mask & WNCK_WINDOW_STATE_SKIP_TASKLIST)
+ {
+ wnck_tasklist_update_lists (tasklist);
+ gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+ return;
+ }
+
+ if ((changed_mask & WNCK_WINDOW_STATE_DEMANDS_ATTENTION) ||
+ (changed_mask & WNCK_WINDOW_STATE_URGENT))
+ {
+ WnckWorkspace *active_workspace =
+ wnck_screen_get_active_workspace (tasklist->priv->screen);
+
+ if (active_workspace &&
+ (active_workspace != wnck_window_get_workspace (window) ||
+ (wnck_workspace_is_virtual (active_workspace) &&
+ !wnck_window_is_in_viewport (window, active_workspace))))
+ {
+ wnck_tasklist_update_lists (tasklist);
+ gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+ }
+ }
+
+ if ((changed_mask & WNCK_WINDOW_STATE_MINIMIZED) ||
+ (changed_mask & WNCK_WINDOW_STATE_DEMANDS_ATTENTION) ||
+ (changed_mask & WNCK_WINDOW_STATE_URGENT))
+ {
+ WnckTask *win_task = NULL;
+
+ /* FIXME: Handle group modal dialogs */
+ for (; window && !win_task; window = wnck_window_get_transient (window))
+ win_task = g_hash_table_lookup (tasklist->priv->win_hash, window);
+
+ if (win_task)
+ {
+ WnckTask *class_group_task;
+
+ wnck_task_update_visible_state (win_task);
+
+ class_group_task =
+ g_hash_table_lookup (tasklist->priv->class_group_hash,
+ win_task->class_group);
+
+ if (class_group_task)
+ wnck_task_update_visible_state (class_group_task);
+ }
+ }
+
+}
+
+static void
+wnck_task_icon_changed (WnckWindow *window,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+
+ if (task)
+ wnck_task_update_visible_state (task);
+}
+
+static void
+wnck_task_name_changed (WnckWindow *window,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+
+ if (task)
+ wnck_task_update_visible_state (task);
+}
+
+static void
+wnck_task_class_name_changed (WnckClassGroup *class_group,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+
+ if (task)
+ wnck_task_update_visible_state (task);
+}
+
+static void
+wnck_task_class_icon_changed (WnckClassGroup *class_group,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+
+ if (task)
+ wnck_task_update_visible_state (task);
+}
+
+static gboolean
+wnck_task_motion_timeout (gpointer data)
+{
+ WnckWorkspace *ws;
+ WnckTask *task = WNCK_TASK (data);
+
+ task->button_activate = 0;
+
+ /* FIXME: THIS IS SICK AND WRONG AND BUGGY. See the end of
+ * http://mail.gnome.org/archives/wm-spec-list/2005-July/msg00032.html
+ * There should only be *one* activate call.
+ */
+ ws = wnck_window_get_workspace (task->window);
+ if (ws && ws != wnck_screen_get_active_workspace (wnck_screen_get_default ()))
+ {
+ wnck_workspace_activate (ws, task->dnd_timestamp);
+ }
+ wnck_window_activate_transient (task->window, task->dnd_timestamp);
+
+ task->dnd_timestamp = 0;
+
+ return FALSE;
+}
+
+static void
+wnck_task_drag_leave (GtkWidget *widget,
+ GdkDragContext *context,
+ guint time,
+ WnckTask *task)
+{
+ if (task->button_activate != 0)
+ {
+ g_source_remove (task->button_activate);
+ task->button_activate = 0;
+ }
+
+ gtk_drag_unhighlight (widget);
+}
+
+static gboolean
+wnck_task_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time,
+ WnckTask *task)
+{
+ if (gtk_drag_dest_find_target (widget, context, NULL))
+ {
+ gtk_drag_highlight (widget);
+ gdk_drag_status (context, gdk_drag_context_get_suggested_action (context), time);
+ }
+ else
+ {
+ task->dnd_timestamp = time;
+
+ if (task->button_activate == 0 && task->type == WNCK_TASK_WINDOW)
+ {
+ task->button_activate = g_timeout_add_seconds (WNCK_ACTIVATE_TIMEOUT,
+ wnck_task_motion_timeout,
+ task);
+ }
+
+ gdk_drag_status (context, 0, time);
+ }
+ return TRUE;
+}
+
+static void
+wnck_task_drag_begin (GtkWidget *widget,
+ GdkDragContext *context,
+ WnckTask *task)
+{
+ _wnck_window_set_as_drag_icon (task->window, context,
+ GTK_WIDGET (task->tasklist));
+
+ task->tasklist->priv->drag_start_time = gtk_get_current_event_time ();
+}
+
+static void
+wnck_task_drag_end (GtkWidget *widget,
+ GdkDragContext *context,
+ WnckTask *task)
+{
+ task->tasklist->priv->drag_start_time = 0;
+}
+
+static void
+wnck_task_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time,
+ WnckTask *task)
+{
+ gulong xid;
+
+ xid = wnck_window_get_xid (task->window);
+ gtk_selection_data_set (selection_data,
+ gtk_selection_data_get_target (selection_data),
+ 8, (guchar *)&xid, sizeof (gulong));
+}
+
+static void
+wnck_task_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *data,
+ guint info,
+ guint time,
+ WnckTask *target_task)
+{
+ WnckTasklist *tasklist;
+ GList *l, *windows;
+ WnckWindow *window;
+ gulong *xid;
+ guint new_order, old_order, order;
+ WnckWindow *found_window;
+
+ if ((gtk_selection_data_get_length (data) != sizeof (gulong)) ||
+ (gtk_selection_data_get_format (data) != 8))
+ {
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ return;
+ }
+
+ tasklist = target_task->tasklist;
+ xid = (gulong *) gtk_selection_data_get_data (data);
+ found_window = NULL;
+ new_order = 0;
+ windows = wnck_screen_get_windows (tasklist->priv->screen);
+
+ for (l = windows; l; l = l->next)
+ {
+ window = WNCK_WINDOW (l->data);
+ if (wnck_window_get_xid (window) == *xid)
+ {
+ old_order = wnck_window_get_sort_order (window);
+ new_order = wnck_window_get_sort_order (target_task->window);
+ if (old_order < new_order)
+ new_order++;
+ found_window = window;
+ break;
+ }
+ }
+
+ if (target_task->window == found_window)
+ {
+ GtkSettings *settings;
+ guint double_click_time;
+
+ settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (tasklist)));
+ double_click_time = 0;
+ g_object_get (G_OBJECT (settings),
+ "gtk-double-click-time", &double_click_time,
+ NULL);
+
+ if ((time - tasklist->priv->drag_start_time) < double_click_time)
+ {
+ wnck_tasklist_activate_task_window (target_task, time);
+ gtk_drag_finish (context, TRUE, FALSE, time);
+ return;
+ }
+ }
+
+ if (found_window)
+ {
+ for (l = windows; l; l = l->next)
+ {
+ window = WNCK_WINDOW (l->data);
+ order = wnck_window_get_sort_order (window);
+ if (order >= new_order)
+ wnck_window_set_sort_order (window, order + 1);
+ }
+ wnck_window_set_sort_order (found_window, new_order);
+
+ if (!tasklist->priv->include_all_workspaces &&
+ !wnck_window_is_pinned (found_window))
+ {
+ WnckWorkspace *active_space;
+ active_space = wnck_screen_get_active_workspace (tasklist->priv->screen);
+ wnck_window_move_to_workspace (found_window, active_space);
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+ }
+
+ gtk_drag_finish (context, TRUE, FALSE, time);
+}
+
+static gboolean
+wnck_task_button_press_event (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+
+ switch (task->type)
+ {
+ case WNCK_TASK_CLASS_GROUP:
+ if (event->button == 2)
+ wnck_tasklist_activate_next_in_class_group (task, event->time);
+ else
+ wnck_task_popup_menu (task,
+ event->button == 3);
+ return TRUE;
+
+ case WNCK_TASK_WINDOW:
+ if (event->button == 1)
+ {
+ /* is_most_recently_activated == is_active for click &
+ * sloppy focus methods. We use the former here because
+ * 'mouse' focus provides a special case. In that case, no
+ * window will be active, but if a window was the most
+ * recently active one (i.e. user moves mouse straight from
+ * window to tasklist), then we should still minimize it.
+ */
+ if (wnck_window_is_most_recently_activated (task->window))
+ task->was_active = TRUE;
+ else
+ task->was_active = FALSE;
+
+ return FALSE;
+ }
+ else if (event->button == 2)
+ {
+ /* middle-click close window */
+ if (task->tasklist->priv->middle_click_close == TRUE)
+ {
+ wnck_window_close (task->window, gtk_get_current_event_time ());
+ return TRUE;
+ }
+ }
+ else if (event->button == 3)
+ {
+ if (task->action_menu)
+ gtk_widget_destroy (task->action_menu);
+
+ g_assert (task->action_menu == NULL);
+
+ task->action_menu = wnck_action_menu_new (task->window);
+
+ g_object_add_weak_pointer (G_OBJECT (task->action_menu),
+ (void**) &task->action_menu);
+
+ /*gtk_menu_set_screen (GTK_MENU (task->action_menu),
+ _wnck_screen_get_gdk_screen (task->tasklist->priv->screen));*/
+
+ gtk_widget_show (task->action_menu);
+ gtk_menu_popup_at_widget (GTK_MENU (task->action_menu), task->button,
+ GDK_GRAVITY_SOUTH_WEST,
+ GDK_GRAVITY_NORTH_WEST,
+ (GdkEvent *) event);
+
+ g_signal_connect (task->action_menu, "selection-done",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+
+ return TRUE;
+ }
+ break;
+
+ case WNCK_TASK_STARTUP_SEQUENCE:
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static GList*
+wnck_task_extract_windows (WnckTask *task)
+{
+ GList *windows = NULL;
+ GList *l;
+
+ /* Add the ungrouped window in the task */
+ if (task->window != NULL)
+ {
+ windows = g_list_prepend (windows, task->window);
+ }
+
+ /* Add any grouped windows available in the task */
+ for (l = task->windows; l != NULL; l = l->next)
+ {
+ WnckTask *t = WNCK_TASK (l->data);
+ windows = g_list_prepend (windows, t->window);
+ }
+
+ return g_list_reverse (windows);
+}
+
+static gboolean
+wnck_task_enter_notify_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+ GList *windows = wnck_task_extract_windows (task);
+
+ g_signal_emit (G_OBJECT (task->tasklist),
+ signals[TASK_ENTER_NOTIFY],
+ 0, windows);
+
+ g_list_free (windows);
+
+ return FALSE;
+}
+
+static gboolean
+wnck_task_leave_notify_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+ GList *windows = wnck_task_extract_windows (task);
+
+ g_signal_emit (G_OBJECT (task->tasklist),
+ signals[TASK_LEAVE_NOTIFY],
+ 0, windows);
+
+ g_list_free (windows);
+
+ return FALSE;
+}
+
+static gboolean
+wnck_task_scroll_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer data)
+{
+ WnckTask *task = WNCK_TASK (data);
+
+ return wnck_tasklist_scroll_event (GTK_WIDGET (task->tasklist), (GdkEventScroll *) event);
+}
+
+static gboolean
+wnck_task_draw (GtkWidget *widget,
+ cairo_t *cr,
+ gpointer data);
+
+static void
+wnck_task_create_widgets (WnckTask *task, GtkReliefStyle relief)
+{
+ GdkPixbuf *pixbuf;
+ char *text;
+ static const GtkTargetEntry targets[] = {
+ { (gchar *) "application/x-wnck-window-id", 0, 0 }
+ };
+
+ task->button = wnck_button_new ();
+
+ gtk_button_set_relief (GTK_BUTTON (task->button), relief);
+
+ task->button_activate = 0;
+ g_object_add_weak_pointer (G_OBJECT (task->button),
+ (void**) &task->button);
+
+ if (task->type == WNCK_TASK_WINDOW)
+ {
+ gtk_drag_source_set (GTK_WIDGET (task->button),
+ GDK_BUTTON1_MASK,
+ targets, 1,
+ GDK_ACTION_MOVE);
+ gtk_drag_dest_set (GTK_WIDGET (task->button), GTK_DEST_DEFAULT_DROP,
+ targets, 1, GDK_ACTION_MOVE);
+ }
+ else
+ gtk_drag_dest_set (GTK_WIDGET (task->button), 0,
+ NULL, 0, GDK_ACTION_DEFAULT);
+
+ pixbuf = wnck_task_get_icon (task);
+ wnck_button_set_image_from_pixbuf (WNCK_BUTTON (task->button), pixbuf);
+ g_clear_object (&pixbuf);
+
+ text = wnck_task_get_text (task, TRUE, TRUE);
+ wnck_button_set_text (WNCK_BUTTON (task->button), text);
+ g_free (text);
+
+ if (wnck_task_get_needs_attention (task))
+ {
+ wnck_button_set_bold (WNCK_BUTTON (task->button), TRUE);
+ wnck_task_queue_glow (task);
+ }
+
+ text = wnck_task_get_text (task, FALSE, FALSE);
+ gtk_widget_set_tooltip_text (task->button, text);
+ g_free (text);
+
+ /* Set up signals */
+ if (task->type != WNCK_TASK_STARTUP_SEQUENCE)
+ g_signal_connect_object (G_OBJECT (task->button), "toggled",
+ G_CALLBACK (wnck_task_button_toggled),
+ G_OBJECT (task),
+ 0);
+
+ g_signal_connect_object (G_OBJECT (task->button), "button_press_event",
+ G_CALLBACK (wnck_task_button_press_event),
+ G_OBJECT (task),
+ 0);
+
+ g_signal_connect_object (G_OBJECT (task->button), "enter_notify_event",
+ G_CALLBACK (wnck_task_enter_notify_event),
+ G_OBJECT (task),
+ 0);
+
+ g_signal_connect_object (G_OBJECT (task->button), "leave_notify_event",
+ G_CALLBACK (wnck_task_leave_notify_event),
+ G_OBJECT (task),
+ 0);
+
+ gtk_widget_add_events (task->button, GDK_SCROLL_MASK);
+ g_signal_connect_object (G_OBJECT (task->button), "scroll_event",
+ G_CALLBACK (wnck_task_scroll_event),
+ G_OBJECT (task),
+ 0);
+
+ g_signal_connect_object (G_OBJECT(task->button), "drag_motion",
+ G_CALLBACK (wnck_task_drag_motion),
+ G_OBJECT (task),
+ 0);
+
+ if (task->type == WNCK_TASK_WINDOW)
+ {
+ g_signal_connect_object (G_OBJECT (task->button), "drag_data_received",
+ G_CALLBACK (wnck_task_drag_data_received),
+ G_OBJECT (task),
+ 0);
+
+ }
+
+ g_signal_connect_object (G_OBJECT(task->button), "drag_leave",
+ G_CALLBACK (wnck_task_drag_leave),
+ G_OBJECT (task),
+ 0);
+
+ if (task->type == WNCK_TASK_WINDOW) {
+ g_signal_connect_object (G_OBJECT(task->button), "drag_data_get",
+ G_CALLBACK (wnck_task_drag_data_get),
+ G_OBJECT (task),
+ 0);
+
+ g_signal_connect_object (G_OBJECT(task->button), "drag_begin",
+ G_CALLBACK (wnck_task_drag_begin),
+ G_OBJECT (task),
+ 0);
+
+ g_signal_connect_object (G_OBJECT(task->button), "drag_end",
+ G_CALLBACK (wnck_task_drag_end),
+ G_OBJECT (task),
+ 0);
+ }
+
+ switch (task->type)
+ {
+ case WNCK_TASK_CLASS_GROUP:
+ task->class_name_changed_tag = g_signal_connect (G_OBJECT (task->class_group), "name_changed",
+ G_CALLBACK (wnck_task_class_name_changed), task);
+ task->class_icon_changed_tag = g_signal_connect (G_OBJECT (task->class_group), "icon_changed",
+ G_CALLBACK (wnck_task_class_icon_changed), task);
+ break;
+
+ case WNCK_TASK_WINDOW:
+ task->state_changed_tag = g_signal_connect (G_OBJECT (task->window), "state_changed",
+ G_CALLBACK (wnck_task_state_changed), task->tasklist);
+ task->icon_changed_tag = g_signal_connect (G_OBJECT (task->window), "icon_changed",
+ G_CALLBACK (wnck_task_icon_changed), task);
+ task->name_changed_tag = g_signal_connect (G_OBJECT (task->window), "name_changed",
+ G_CALLBACK (wnck_task_name_changed), task);
+ break;
+
+ case WNCK_TASK_STARTUP_SEQUENCE:
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_signal_connect_object (task->button, "draw",
+ G_CALLBACK (wnck_task_draw),
+ G_OBJECT (task),
+ G_CONNECT_AFTER);
+}
+
+#define ARROW_SPACE 4
+#define ARROW_SIZE 12
+#define INDICATOR_SIZE 7
+
+static gboolean
+wnck_task_draw (GtkWidget *widget,
+ cairo_t *cr,
+ gpointer data)
+{
+ int x, y;
+ WnckTask *task;
+ GtkStyleContext *context;
+ GtkStateFlags state;
+ GtkBorder padding;
+ GtkWidget *tasklist_widget;
+ gint width, height;
+ gboolean overlay_rect;
+ gint arrow_width;
+ gint arrow_height;
+ GdkRGBA color;
+
+ task = WNCK_TASK (data);
+
+ switch (task->type)
+ {
+ case WNCK_TASK_CLASS_GROUP:
+ context = gtk_widget_get_style_context (widget);
+
+ state = gtk_style_context_get_state (context);
+ gtk_style_context_get_padding (context, state, &padding);
+
+ state = (task->tasklist->priv->active_class_group == task) ?
+ GTK_STATE_FLAG_ACTIVE : GTK_STATE_FLAG_NORMAL;
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+ gtk_style_context_get_color (context, state, &color);
+ gtk_style_context_restore (context);
+
+ x = gtk_widget_get_allocated_width (widget) -
+ (gtk_container_get_border_width (GTK_CONTAINER (widget)) + padding.right + ARROW_SIZE);
+ y = gtk_widget_get_allocated_height (widget) / 2;
+
+ arrow_width = INDICATOR_SIZE + ((INDICATOR_SIZE % 2) - 1);
+ arrow_height = arrow_width / 2 + 1;
+ x += (ARROW_SIZE - arrow_width) / 2;
+ y -= (2 * arrow_height + ARROW_SPACE) / 2;
+
+ cairo_save (cr);
+ gdk_cairo_set_source_rgba (cr, &color);
+
+ /* Up arrow */
+ cairo_move_to (cr, x, y + arrow_height);
+ cairo_line_to (cr, x + arrow_width / 2., y);
+ cairo_line_to (cr, x + arrow_width, y + arrow_height);
+ cairo_close_path (cr);
+ cairo_fill (cr);
+
+ /* Down arrow */
+ y += arrow_height + ARROW_SPACE;
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x + arrow_width, y);
+ cairo_line_to (cr, x + arrow_width / 2., y + arrow_height);
+ cairo_close_path (cr);
+ cairo_fill (cr);
+
+ cairo_restore (cr);
+
+ break;
+
+ case WNCK_TASK_WINDOW:
+ case WNCK_TASK_STARTUP_SEQUENCE:
+ default:
+ break;
+ }
+
+ if (task->glow_factor == 0.0)
+ return FALSE;
+
+ /* push a translucent overlay to paint to, so we can blend later */
+ cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA);
+
+ width = gtk_widget_get_allocated_width (task->button);
+ height = gtk_widget_get_allocated_height (task->button);
+
+ tasklist_widget = GTK_WIDGET (task->tasklist);
+
+ context = gtk_widget_get_style_context (task->button);
+
+ /* first draw the button */
+ gtk_widget_style_get (tasklist_widget, "fade-overlay-rect", &overlay_rect, NULL);
+ if (overlay_rect)
+ {
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, GTK_STATE_FLAG_SELECTED);
+
+ /* Draw a rectangle with selected background color */
+ gtk_render_background (context, cr, 0, 0, width, height);
+
+ gtk_style_context_restore (context);
+ }
+ else
+ {
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, GTK_STATE_FLAG_SELECTED);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);
+
+ cairo_save (cr);
+ gtk_render_background (context, cr, 0, 0, width, height);
+ gtk_render_frame (context, cr, 0, 0, width, height);
+ cairo_restore (cr);
+
+ gtk_style_context_restore (context);
+ }
+
+ /* then the contents */
+ gtk_container_propagate_draw (GTK_CONTAINER (task->button),
+ gtk_bin_get_child (GTK_BIN (task->button)),
+ cr);
+ /* finally blend it */
+ cairo_pop_group_to_source (cr);
+ cairo_paint_with_alpha (cr, task->glow_factor);
+
+ return FALSE;
+}
+
+static gint
+wnck_task_compare_alphabetically (gconstpointer a,
+ gconstpointer b)
+{
+ char *text1;
+ char *text2;
+ gint result;
+
+ text1 = wnck_task_get_text (WNCK_TASK (a), TRUE, FALSE);
+ text2 = wnck_task_get_text (WNCK_TASK (b), TRUE, FALSE);
+
+ result= g_utf8_collate (text1, text2);
+
+ g_free (text1);
+ g_free (text2);
+
+ return result;
+}
+
+static gint
+compare_class_group_tasks (WnckTask *task1, WnckTask *task2)
+{
+ const char *name1, *name2;
+
+ name1 = wnck_class_group_get_name (task1->class_group);
+ name2 = wnck_class_group_get_name (task2->class_group);
+
+ return g_utf8_collate (name1, name2);
+}
+
+static gint
+wnck_task_compare (gconstpointer a,
+ gconstpointer b)
+{
+ WnckTask *task1 = WNCK_TASK (a);
+ WnckTask *task2 = WNCK_TASK (b);
+ gint pos1, pos2;
+
+ pos1 = pos2 = 0; /* silence the compiler */
+
+ switch (task1->type)
+ {
+ case WNCK_TASK_CLASS_GROUP:
+ if (task2->type == WNCK_TASK_CLASS_GROUP)
+ return compare_class_group_tasks (task1, task2);
+ else
+ return -1; /* Sort groups before everything else */
+
+ case WNCK_TASK_WINDOW:
+ pos1 = wnck_window_get_sort_order (task1->window);
+ break;
+ case WNCK_TASK_STARTUP_SEQUENCE:
+ pos1 = G_MAXINT; /* startup sequences are sorted at the end. */
+ break; /* Changing this will break scrolling. */
+
+ default:
+ break;
+ }
+
+ switch (task2->type)
+ {
+ case WNCK_TASK_CLASS_GROUP:
+ if (task1->type == WNCK_TASK_CLASS_GROUP)
+ return compare_class_group_tasks (task1, task2);
+ else
+ return 1; /* Sort groups before everything else */
+
+ case WNCK_TASK_WINDOW:
+ pos2 = wnck_window_get_sort_order (task2->window);
+ break;
+ case WNCK_TASK_STARTUP_SEQUENCE:
+ pos2 = G_MAXINT;
+ break;
+
+ default:
+ break;
+ }
+
+ if (pos1 < pos2)
+ return -1;
+ else if (pos1 > pos2)
+ return 1;
+ else
+ return 0; /* should only happen if there's multiple processes being
+ * started, and then who cares about sort order... */
+}
+
+static void
+remove_startup_sequences_for_window (WnckTasklist *tasklist,
+ WnckWindow *window)
+{
+#ifdef HAVE_STARTUP_NOTIFICATION
+ const char *win_id;
+ GList *tmp;
+
+ win_id = wnck_window_get_startup_id (window);
+ if (win_id == NULL)
+ return;
+
+ tmp = tasklist->priv->startup_sequences;
+ while (tmp != NULL)
+ {
+ WnckTask *task = tmp->data;
+ GList *next = tmp->next;
+ const char *task_id;
+
+ g_assert (task->type == WNCK_TASK_STARTUP_SEQUENCE);
+
+ task_id = sn_startup_sequence_get_id (task->startup_sequence);
+
+ if (task_id && strcmp (task_id, win_id) == 0)
+ gtk_widget_destroy (task->button);
+
+ tmp = next;
+ }
+#else
+ ; /* nothing */
+#endif
+}
+
+static WnckTask *
+wnck_task_new_from_window (WnckTasklist *tasklist,
+ WnckWindow *window)
+{
+ WnckTask *task;
+
+ task = g_object_new (WNCK_TYPE_TASK, NULL);
+ task->type = WNCK_TASK_WINDOW;
+ task->window = g_object_ref (window);
+ task->class_group = g_object_ref (wnck_window_get_class_group (window));
+ task->tasklist = tasklist;
+
+ wnck_task_create_widgets (task, tasklist->priv->relief);
+
+ remove_startup_sequences_for_window (tasklist, window);
+
+ return task;
+}
+
+static WnckTask *
+wnck_task_new_from_class_group (WnckTasklist *tasklist,
+ WnckClassGroup *class_group)
+{
+ WnckTask *task;
+
+ task = g_object_new (WNCK_TYPE_TASK, NULL);
+
+ task->type = WNCK_TASK_CLASS_GROUP;
+ task->window = NULL;
+ task->class_group = g_object_ref (class_group);
+ task->tasklist = tasklist;
+
+ wnck_task_create_widgets (task, tasklist->priv->relief);
+
+ return task;
+}
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+static WnckTask*
+wnck_task_new_from_startup_sequence (WnckTasklist *tasklist,
+ SnStartupSequence *sequence)
+{
+ WnckTask *task;
+
+ task = g_object_new (WNCK_TYPE_TASK, NULL);
+
+ task->type = WNCK_TASK_STARTUP_SEQUENCE;
+ task->window = NULL;
+ task->class_group = NULL;
+ task->startup_sequence = sequence;
+ sn_startup_sequence_ref (task->startup_sequence);
+ task->tasklist = tasklist;
+
+ wnck_task_create_widgets (task, tasklist->priv->relief);
+
+ return task;
+}
+
+/* This should be fairly long, as it should never be required unless
+ * apps or .desktop files are buggy, and it's confusing if
+ * OpenOffice or whatever seems to stop launching - people
+ * might decide they need to launch it again.
+ */
+#define STARTUP_TIMEOUT 15000
+
+static gboolean
+sequence_timeout_callback (void *user_data)
+{
+ WnckTasklist *tasklist = user_data;
+ GList *tmp;
+ gint64 now;
+ long tv_sec, tv_usec;
+ double elapsed;
+
+ now = g_get_real_time ();
+
+ restart:
+ tmp = tasklist->priv->startup_sequences;
+ while (tmp != NULL)
+ {
+ WnckTask *task = WNCK_TASK (tmp->data);
+
+ sn_startup_sequence_get_last_active_time (task->startup_sequence,
+ &tv_sec, &tv_usec);
+
+ elapsed = (now - (tv_sec * G_USEC_PER_SEC + tv_usec)) / 1000.0;
+
+ if (elapsed > STARTUP_TIMEOUT)
+ {
+ g_assert (task->button != NULL);
+ /* removes task from list as a side effect */
+ gtk_widget_destroy (task->button);
+
+ goto restart; /* don't iterate over changed list, just restart;
+ * not efficient but who cares here.
+ */
+ }
+
+ tmp = tmp->next;
+ }
+
+ if (tasklist->priv->startup_sequences == NULL)
+ {
+ tasklist->priv->startup_sequence_timeout = 0;
+ return FALSE;
+ }
+ else
+ return TRUE;
+}
+
+static void
+wnck_tasklist_sn_event (SnMonitorEvent *event,
+ void *user_data)
+{
+ WnckTasklist *tasklist;
+
+ tasklist = WNCK_TASKLIST (user_data);
+
+ switch (sn_monitor_event_get_type (event))
+ {
+ case SN_MONITOR_EVENT_INITIATED:
+ {
+ WnckTask *task;
+
+ task = wnck_task_new_from_startup_sequence (tasklist,
+ sn_monitor_event_get_startup_sequence (event));
+
+ gtk_widget_set_parent (task->button, GTK_WIDGET (tasklist));
+ gtk_widget_show (task->button);
+
+ tasklist->priv->startup_sequences =
+ g_list_prepend (tasklist->priv->startup_sequences,
+ task);
+
+ if (tasklist->priv->startup_sequence_timeout == 0)
+ {
+ tasklist->priv->startup_sequence_timeout =
+ g_timeout_add_seconds (1, sequence_timeout_callback,
+ tasklist);
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+ }
+ break;
+
+ case SN_MONITOR_EVENT_COMPLETED:
+ {
+ GList *tmp;
+ tmp = tasklist->priv->startup_sequences;
+ while (tmp != NULL)
+ {
+ WnckTask *task = WNCK_TASK (tmp->data);
+
+ if (task->startup_sequence ==
+ sn_monitor_event_get_startup_sequence (event))
+ {
+ g_assert (task->button != NULL);
+ /* removes task from list as a side effect */
+ gtk_widget_destroy (task->button);
+ break;
+ }
+
+ tmp = tmp->next;
+ }
+ }
+ break;
+
+ case SN_MONITOR_EVENT_CHANGED:
+ break;
+
+ case SN_MONITOR_EVENT_CANCELED:
+ break;
+
+ default:
+ break;
+ }
+
+ if (tasklist->priv->startup_sequences == NULL &&
+ tasklist->priv->startup_sequence_timeout != 0)
+ {
+ g_source_remove (tasklist->priv->startup_sequence_timeout);
+ tasklist->priv->startup_sequence_timeout = 0;
+ }
+}
+
+static void
+wnck_tasklist_check_end_sequence (WnckTasklist *tasklist,
+ WnckWindow *window)
+{
+ const char *res_class;
+ const char *res_name;
+ GList *tmp;
+
+ if (tasklist->priv->startup_sequences == NULL)
+ return;
+
+ res_class = wnck_window_get_class_group_name (window);
+ res_name = wnck_window_get_class_instance_name (window);
+
+ if (res_class == NULL && res_name == NULL)
+ return;
+
+ tmp = tasklist->priv->startup_sequences;
+ while (tmp != NULL)
+ {
+ WnckTask *task = WNCK_TASK (tmp->data);
+ const char *wmclass;
+
+ wmclass = sn_startup_sequence_get_wmclass (task->startup_sequence);
+
+ if (wmclass != NULL &&
+ ((res_class && strcmp (res_class, wmclass) == 0) ||
+ (res_name && strcmp (res_name, wmclass) == 0)))
+ {
+ sn_startup_sequence_complete (task->startup_sequence);
+
+ g_assert (task->button != NULL);
+ /* removes task from list as a side effect */
+ gtk_widget_destroy (task->button);
+
+ /* only match one */
+ return;
+ }
+
+ tmp = tmp->next;
+ }
+}
+
+#endif /* HAVE_STARTUP_NOTIFICATION */
diff --git a/libwnck/widgets/tasklist.h b/libwnck/widgets/tasklist.h
new file mode 100644
index 0000000..75c058a
--- /dev/null
+++ b/libwnck/widgets/tasklist.h
@@ -0,0 +1,143 @@
+/* tasklist object */
+/* vim: set sw=2 et: */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2003, 2005-2007 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__LIBWNCK_H_INSIDE__) && !defined (WNCK_COMPILATION)
+#error "Only <libwnck/libwnck.h> can be included directly."
+#endif
+
+#ifndef WNCK_TASKLIST_H
+#define WNCK_TASKLIST_H
+
+#include <gtk/gtk.h>
+#include <libwnck/libwnck.h>
+
+G_BEGIN_DECLS
+
+#define WNCK_TYPE_TASKLIST (wnck_tasklist_get_type ())
+#define WNCK_TASKLIST(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), WNCK_TYPE_TASKLIST, WnckTasklist))
+#define WNCK_TASKLIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WNCK_TYPE_TASKLIST, WnckTasklistClass))
+#define WNCK_IS_TASKLIST(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), WNCK_TYPE_TASKLIST))
+#define WNCK_IS_TASKLIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WNCK_TYPE_TASKLIST))
+#define WNCK_TASKLIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WNCK_TYPE_TASKLIST, WnckTasklistClass))
+
+typedef struct _WnckTasklist WnckTasklist;
+typedef struct _WnckTasklistClass WnckTasklistClass;
+typedef struct _WnckTasklistPrivate WnckTasklistPrivate;
+
+/**
+ * WnckTasklist:
+ *
+ * The #WnckTasklist struct contains only private fields and should not be
+ * directly accessed.
+ */
+struct _WnckTasklist
+{
+ GtkContainer parent_instance;
+
+ WnckTasklistPrivate *priv;
+};
+
+struct _WnckTasklistClass
+{
+ GtkContainerClass parent_class;
+
+ /* Padding for future expansion */
+ void (* pad1) (void);
+ void (* pad2) (void);
+ void (* pad3) (void);
+ void (* pad4) (void);
+};
+
+/**
+ * WnckTasklistGroupingType:
+ * @WNCK_TASKLIST_NEVER_GROUP: never group multiple #WnckWindow of the same
+ * #WnckApplication.
+ * @WNCK_TASKLIST_AUTO_GROUP: group multiple #WnckWindow of the same
+ * #WnckApplication for some #WnckApplication, when there is not enough place
+ * to have a good-looking list of all #WnckWindow.
+ * @WNCK_TASKLIST_ALWAYS_GROUP: always group multiple #WnckWindow of the same
+ * #WnckApplication, for all #WnckApplication.
+ *
+ * Type defining the policy of the #WnckTasklist for grouping multiple
+ * #WnckWindow of the same #WnckApplication.
+ */
+typedef enum {
+ WNCK_TASKLIST_NEVER_GROUP,
+ WNCK_TASKLIST_AUTO_GROUP,
+ WNCK_TASKLIST_ALWAYS_GROUP
+} WnckTasklistGroupingType;
+
+GType wnck_tasklist_get_type (void) G_GNUC_CONST;
+
+GtkWidget *wnck_tasklist_new (void);
+
+G_DEPRECATED
+const int *wnck_tasklist_get_size_hint_list (WnckTasklist *tasklist,
+ int *n_elements);
+
+void wnck_tasklist_set_grouping (WnckTasklist *tasklist,
+ WnckTasklistGroupingType grouping);
+void wnck_tasklist_set_switch_workspace_on_unminimize (WnckTasklist *tasklist,
+ gboolean switch_workspace_on_unminimize);
+void wnck_tasklist_set_middle_click_close (WnckTasklist *tasklist,
+ gboolean middle_click_close);
+void wnck_tasklist_set_grouping_limit (WnckTasklist *tasklist,
+ gint limit);
+void wnck_tasklist_set_include_all_workspaces (WnckTasklist *tasklist,
+ gboolean include_all_workspaces);
+void wnck_tasklist_set_button_relief (WnckTasklist *tasklist,
+ GtkReliefStyle relief);
+void wnck_tasklist_set_orientation (WnckTasklist *tasklist,
+ GtkOrientation orient);
+void wnck_tasklist_set_scroll_enabled (WnckTasklist *tasklist,
+ gboolean scroll_enabled);
+gboolean wnck_tasklist_get_scroll_enabled (WnckTasklist *tasklist);
+
+/**
+ * WnckLoadIconFunction:
+ * @icon_name: an icon name as in the Icon field in a .desktop file for the
+ * icon to load.
+ * @size: the desired icon size.
+ * @flags: not defined to do anything yet.
+ * @data: data passed to the function, set when the #WnckLoadIconFunction has
+ * been set for the #WnckTasklist.
+ *
+ * Specifies the type of function passed to wnck_tasklist_set_icon_loader().
+ *
+ * Returns: it should return a <classname>GdkPixbuf</classname> of @icon_name
+ * at size @size, or %NULL if no icon for @icon_name at size @size could be
+ * loaded.
+ *
+ * Since: 2.2
+ */
+typedef GdkPixbuf* (*WnckLoadIconFunction) (const char *icon_name,
+ int size,
+ unsigned int flags,
+ void *data);
+
+void wnck_tasklist_set_icon_loader (WnckTasklist *tasklist,
+ WnckLoadIconFunction load_icon_func,
+ void *data,
+ GDestroyNotify free_data_func);
+
+G_END_DECLS
+
+#endif /* WNCK_TASKLIST_H */
diff --git a/libwnck/widgets/test-pager.c b/libwnck/widgets/test-pager.c
new file mode 100644
index 0000000..03d2130
--- /dev/null
+++ b/libwnck/widgets/test-pager.c
@@ -0,0 +1,116 @@
+/* vim: set sw=2 et: */
+
+#include <libwnck/libwnck.h>
+#include <gtk/gtk.h>
+
+#include "pager.h"
+
+static int n_rows = 1;
+static gboolean only_current = FALSE;
+static gboolean rtl = FALSE;
+static gboolean show_name = FALSE;
+static gboolean simple_scrolling = FALSE;
+static gboolean vertical = FALSE;
+static gboolean wrap_on_scroll = FALSE;
+
+static GOptionEntry entries[] = {
+ {"n-rows", 'n', 0, G_OPTION_ARG_INT, &n_rows, "Use N_ROWS rows", "N_ROWS"},
+ {"only-current", 'c', 0, G_OPTION_ARG_NONE, &only_current, "Only show current workspace", NULL},
+ {"rtl", 'r', 0, G_OPTION_ARG_NONE, &rtl, "Use RTL as default direction", NULL},
+ {"show-name", 's', 0, G_OPTION_ARG_NONE, &show_name, "Show workspace names instead of workspace contents", NULL},
+ {"simple-scrolling", 'd', 0, G_OPTION_ARG_NONE, &simple_scrolling, "Use the simple 1d scroll mode", NULL},
+ {"vertical-orientation", 'v', 0, G_OPTION_ARG_NONE, &vertical, "Use a vertical orientation", NULL},
+ {"wrap-on-scroll", 'w', 0, G_OPTION_ARG_NONE, &wrap_on_scroll, "Wrap on scrolling over borders", NULL},
+ {NULL }
+};
+
+static void
+create_pager_window (GtkOrientation orientation,
+ gboolean show_all,
+ WnckPagerDisplayMode mode,
+ WnckPagerScrollMode scroll_mode,
+ int rows,
+ gboolean wrap)
+{
+ GtkWidget *win;
+ GtkWidget *pager;
+
+ win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+ gtk_window_stick (GTK_WINDOW (win));
+#if 0
+ wnck_gtk_window_set_dock_type (GTK_WINDOW (win));
+#endif
+
+ gtk_window_set_title (GTK_WINDOW (win), "Pager");
+
+ /* very very random */
+ gtk_window_move (GTK_WINDOW (win), 0, 0);
+
+ /* quit on window close */
+ g_signal_connect (G_OBJECT (win), "destroy",
+ G_CALLBACK (gtk_main_quit),
+ NULL);
+
+ pager = wnck_pager_new ();
+
+ wnck_pager_set_show_all (WNCK_PAGER (pager), show_all);
+ wnck_pager_set_display_mode (WNCK_PAGER (pager), mode);
+ wnck_pager_set_scroll_mode (WNCK_PAGER (pager), scroll_mode);
+ wnck_pager_set_orientation (WNCK_PAGER (pager), orientation);
+ wnck_pager_set_n_rows (WNCK_PAGER (pager), rows);
+ wnck_pager_set_shadow_type (WNCK_PAGER (pager), GTK_SHADOW_IN);
+ wnck_pager_set_wrap_on_scroll (WNCK_PAGER (pager), wrap);
+
+ gtk_container_add (GTK_CONTAINER (win), pager);
+
+ gtk_widget_show_all (win);
+}
+
+int
+main (int argc, char **argv)
+{
+ GOptionContext *ctxt;
+ GtkOrientation orientation;
+ WnckPagerDisplayMode mode;
+ WnckPagerScrollMode scroll_mode;
+ WnckScreen *screen;
+
+ ctxt = g_option_context_new ("");
+ g_option_context_add_main_entries (ctxt, entries, NULL);
+ g_option_context_add_group (ctxt, gtk_get_option_group (TRUE));
+ g_option_context_parse (ctxt, &argc, &argv, NULL);
+ g_option_context_free (ctxt);
+ ctxt = NULL;
+
+ gtk_init (&argc, &argv);
+
+ if (rtl)
+ gtk_widget_set_default_direction (GTK_TEXT_DIR_RTL);
+
+ screen = wnck_screen_get_default ();
+
+ /* because the pager doesn't respond to signals at the moment */
+ wnck_screen_force_update (screen);
+
+ if (vertical)
+ orientation = GTK_ORIENTATION_VERTICAL;
+ else
+ orientation = GTK_ORIENTATION_HORIZONTAL;
+
+ if (show_name)
+ mode = WNCK_PAGER_DISPLAY_NAME;
+ else
+ mode = WNCK_PAGER_DISPLAY_CONTENT;
+
+ if (simple_scrolling)
+ scroll_mode = WNCK_PAGER_SCROLL_1D;
+ else
+ scroll_mode = WNCK_PAGER_SCROLL_2D;
+
+ create_pager_window (orientation, !only_current, mode, scroll_mode, n_rows, wrap_on_scroll);
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/libwnck/widgets/test-selector.c b/libwnck/widgets/test-selector.c
new file mode 100644
index 0000000..ba67205
--- /dev/null
+++ b/libwnck/widgets/test-selector.c
@@ -0,0 +1,77 @@
+/* vim: set sw=2 et: */
+
+#include <libwnck/libwnck.h>
+#include <gtk/gtk.h>
+
+#include "selector.h"
+
+static gboolean skip_tasklist = FALSE;
+
+static GOptionEntry entries[] = {
+ /* Translators: "tasklist" is the list of running applications (the
+ * window list) */
+ {"skip-tasklist", 's', 0, G_OPTION_ARG_NONE, &skip_tasklist, "Don't show window in tasklist", NULL},
+ {NULL }
+};
+
+int
+main (int argc, char **argv)
+{
+ GOptionContext *ctxt;
+ WnckScreen *screen;
+ GtkWidget *win;
+ GtkWidget *frame;
+ GtkWidget *selector;
+
+ ctxt = g_option_context_new ("");
+ g_option_context_add_main_entries (ctxt, entries, NULL);
+ g_option_context_add_group (ctxt, gtk_get_option_group (TRUE));
+ g_option_context_parse (ctxt, &argc, &argv, NULL);
+ g_option_context_free (ctxt);
+ ctxt = NULL;
+
+ gtk_init (&argc, &argv);
+
+ screen = wnck_screen_get_default ();
+
+ /* because the pager doesn't respond to signals at the moment */
+ wnck_screen_force_update (screen);
+
+ win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_default_size (GTK_WINDOW (win), 200, 32);
+ gtk_window_stick (GTK_WINDOW (win));
+ /* wnck_gtk_window_set_dock_type (GTK_WINDOW (win)); */
+
+ gtk_window_set_title (GTK_WINDOW (win), "Window Selector");
+ gtk_window_set_resizable (GTK_WINDOW (win), TRUE);
+
+ /* quit on window close */
+ g_signal_connect (G_OBJECT (win), "destroy",
+ G_CALLBACK (gtk_main_quit),
+ NULL);
+
+ selector = wnck_selector_new ();
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (win), frame);
+
+ gtk_container_add (GTK_CONTAINER (frame), selector);
+
+ gtk_widget_show (selector);
+ gtk_widget_show (frame);
+
+ gtk_window_move (GTK_WINDOW (win), 0, 0);
+
+ if (skip_tasklist)
+ {
+ gtk_window_set_skip_taskbar_hint (GTK_WINDOW (win), TRUE);
+ gtk_window_set_keep_above (GTK_WINDOW (win), TRUE);
+ }
+
+ gtk_widget_show (win);
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/libwnck/widgets/test-tasklist.c b/libwnck/widgets/test-tasklist.c
new file mode 100644
index 0000000..9bb27ce
--- /dev/null
+++ b/libwnck/widgets/test-tasklist.c
@@ -0,0 +1,167 @@
+/* vim: set sw=2 et: */
+
+#include <libwnck/libwnck.h>
+#include <gtk/gtk.h>
+
+#include "tasklist.h"
+
+static gboolean display_all = FALSE;
+static gboolean never_group = FALSE;
+static gboolean always_group = FALSE;
+static gboolean rtl = FALSE;
+static gboolean skip_tasklist = FALSE;
+static gboolean transparent = FALSE;
+static gboolean vertical = FALSE;
+static gint icon_size = WNCK_DEFAULT_MINI_ICON_SIZE;
+static gboolean enable_scroll = TRUE;
+
+static GOptionEntry entries[] = {
+ {"always-group", 'g', 0, G_OPTION_ARG_NONE, &always_group, "Always group windows", NULL},
+ {"never-group", 'n', 0, G_OPTION_ARG_NONE, &never_group, "Never group windows", NULL},
+ {"display-all", 'a', 0, G_OPTION_ARG_NONE, &display_all, "Display windows from all workspaces", NULL},
+ {"icon-size", 'i', 0, G_OPTION_ARG_INT, &icon_size, "Icon size for tasklist", NULL},
+ {"rtl", 'r', 0, G_OPTION_ARG_NONE, &rtl, "Use RTL as default direction", NULL},
+ {"skip-tasklist", 's', 0, G_OPTION_ARG_NONE, &skip_tasklist, "Don't show window in tasklist", NULL},
+ {"vertical", 'v', 0, G_OPTION_ARG_NONE, &vertical, "Show in vertical mode", NULL},
+ {"transparent", 't', 0, G_OPTION_ARG_NONE, &transparent, "Enable Transparency", NULL},
+ {"disable-scroll", 'd', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &enable_scroll, "Disable scrolling", NULL},
+ {NULL }
+};
+
+static gboolean
+window_draw (GtkWidget *widget,
+ cairo_t *cr,
+ gpointer user_data)
+{
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_rgba (cr, 1., 1., 1., .5);
+ cairo_fill (cr);
+
+ return FALSE;
+}
+
+static void
+window_composited_changed (GtkWidget *widget,
+ gpointer user_data)
+{
+ GdkScreen *screen;
+ gboolean composited;
+
+ screen = gdk_screen_get_default ();
+ composited = gdk_screen_is_composited (screen);
+
+ gtk_widget_set_app_paintable (widget, composited);
+}
+
+int
+main (int argc, char **argv)
+{
+ GOptionContext *ctxt;
+ WnckScreen *screen;
+ GtkWidget *win;
+ GtkWidget *frame;
+ GtkWidget *tasklist;
+
+ ctxt = g_option_context_new ("");
+ g_option_context_add_main_entries (ctxt, entries, NULL);
+ g_option_context_add_group (ctxt, gtk_get_option_group (TRUE));
+ g_option_context_parse (ctxt, &argc, &argv, NULL);
+ g_option_context_free (ctxt);
+ ctxt = NULL;
+
+ gtk_init (&argc, &argv);
+
+ if (rtl)
+ gtk_widget_set_default_direction (GTK_TEXT_DIR_RTL);
+
+ wnck_set_default_mini_icon_size (icon_size);
+ screen = wnck_screen_get_default ();
+
+ /* because the pager doesn't respond to signals at the moment */
+ wnck_screen_force_update (screen);
+
+ win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_default_size (GTK_WINDOW (win), 200, 100);
+ gtk_window_stick (GTK_WINDOW (win));
+ /* wnck_gtk_window_set_dock_type (GTK_WINDOW (win)); */
+
+ gtk_window_set_title (GTK_WINDOW (win), "Task List");
+ gtk_window_set_resizable (GTK_WINDOW (win), TRUE);
+
+ /* quit on window close */
+ g_signal_connect (G_OBJECT (win), "destroy",
+ G_CALLBACK (gtk_main_quit),
+ NULL);
+
+ tasklist = wnck_tasklist_new ();
+
+ wnck_tasklist_set_include_all_workspaces (WNCK_TASKLIST (tasklist), display_all);
+ if (always_group)
+ wnck_tasklist_set_grouping (WNCK_TASKLIST (tasklist),
+ WNCK_TASKLIST_ALWAYS_GROUP);
+ else if (never_group)
+ wnck_tasklist_set_grouping (WNCK_TASKLIST (tasklist),
+ WNCK_TASKLIST_NEVER_GROUP);
+ else
+ wnck_tasklist_set_grouping (WNCK_TASKLIST (tasklist),
+ WNCK_TASKLIST_AUTO_GROUP);
+
+ wnck_tasklist_set_scroll_enabled (WNCK_TASKLIST (tasklist), enable_scroll);
+
+ wnck_tasklist_set_middle_click_close (WNCK_TASKLIST (tasklist), TRUE);
+
+ wnck_tasklist_set_orientation (WNCK_TASKLIST (tasklist),
+ (vertical ? GTK_ORIENTATION_VERTICAL :
+ GTK_ORIENTATION_HORIZONTAL));
+
+ if (transparent)
+ {
+ GdkVisual *visual;
+
+ visual = gdk_screen_get_rgba_visual (gtk_widget_get_screen (win));
+
+ if (visual != NULL)
+ {
+ gtk_widget_set_visual (win, visual);
+
+ g_signal_connect (win, "composited-changed",
+ G_CALLBACK (window_composited_changed),
+ NULL);
+
+ /* draw even if we are not app-painted.
+ * this just makes my life a lot easier :)
+ */
+ g_signal_connect (win, "draw",
+ G_CALLBACK (window_draw),
+ NULL);
+
+ window_composited_changed (win, NULL);
+ }
+
+ wnck_tasklist_set_button_relief (WNCK_TASKLIST (tasklist),
+ GTK_RELIEF_NONE);
+ }
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (win), frame);
+
+ gtk_container_add (GTK_CONTAINER (frame), tasklist);
+
+ gtk_widget_show (tasklist);
+ gtk_widget_show (frame);
+
+ gtk_window_move (GTK_WINDOW (win), 0, 0);
+
+ if (skip_tasklist)
+ {
+ gtk_window_set_skip_taskbar_hint (GTK_WINDOW (win), TRUE);
+ gtk_window_set_keep_above (GTK_WINDOW (win), TRUE);
+ }
+
+ gtk_widget_show (win);
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/libwnck/widgets/util.c b/libwnck/widgets/util.c
new file mode 100644
index 0000000..4af6eaf
--- /dev/null
+++ b/libwnck/widgets/util.c
@@ -0,0 +1,141 @@
+/* util header */
+/* vim: set sw=2 et: */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2006-2007 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <glib/gi18n-lib.h>
+#include "xutils.h"
+#include "private.h"
+#include <gdk/gdkx.h>
+#include <string.h>
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+#include <libsn/sn.h>
+#endif
+
+/**
+ * SECTION:misc
+ * @short_description: other additional features.
+ * @stability: Unstable
+ *
+ * These functions are utility functions providing some additional features to
+ * libwnck users.
+ */
+
+/**
+ * SECTION:icons
+ * @short_description: icons related functions.
+ * @stability: Unstable
+ *
+ * These functions are utility functions to manage icons for #WnckWindow and
+ * #WnckApplication.
+ */
+
+/**
+ * _make_gtk_label_bold:
+ * @label: The label.
+ *
+ * Switches the font of label to a bold equivalent.
+ **/
+void
+_make_gtk_label_bold (GtkLabel *label)
+{
+ GtkStyleContext *context;
+
+ _wnck_ensure_fallback_style ();
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (label));
+ gtk_style_context_add_class (context, "wnck-needs-attention");
+}
+
+void
+_make_gtk_label_normal (GtkLabel *label)
+{
+ GtkStyleContext *context;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (label));
+ gtk_style_context_remove_class (context, "wnck-needs-attention");
+}
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+static gboolean
+_wnck_util_sn_utf8_validator (const char *str,
+ int max_len)
+{
+ return g_utf8_validate (str, max_len, NULL);
+}
+#endif /* HAVE_STARTUP_NOTIFICATION */
+
+void
+_wnck_init (void)
+{
+ static gboolean done = FALSE;
+
+ if (!done)
+ {
+ bindtextdomain (GETTEXT_PACKAGE, WNCK_LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ sn_set_utf8_validator (_wnck_util_sn_utf8_validator);
+#endif /* HAVE_STARTUP_NOTIFICATION */
+
+ done = TRUE;
+ }
+}
+
+Display *
+_wnck_get_default_display (void)
+{
+ GdkDisplay *display = gdk_display_get_default ();
+ /* FIXME: when we fix libwnck to not use the GDK default display, we will
+ * need to fix wnckprop accordingly. */
+ if (!GDK_IS_X11_DISPLAY (display))
+ {
+ g_warning ("libwnck is designed to work in X11 only, no valid display found");
+ return NULL;
+ }
+
+ return GDK_DISPLAY_XDISPLAY (display);
+}
+
+void
+_wnck_ensure_fallback_style (void)
+{
+ static gboolean css_loaded = FALSE;
+ GtkCssProvider *provider;
+ guint priority;
+
+ if (css_loaded)
+ return;
+
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_resource (provider, "/org/gnome/libwnck/wnck.css");
+
+ priority = GTK_STYLE_PROVIDER_PRIORITY_FALLBACK;
+ gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ priority);
+
+ g_object_unref (provider);
+
+ css_loaded = TRUE;
+}
diff --git a/libwnck/widgets/window-action-menu.c b/libwnck/widgets/window-action-menu.c
new file mode 100644
index 0000000..bbd5efe
--- /dev/null
+++ b/libwnck/widgets/window-action-menu.c
@@ -0,0 +1,1172 @@
+/* window action menu (ops on a single window) */
+/* vim: set sw=2 et: */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2006-2007 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "window-action-menu.h"
+#include "private.h"
+
+/**
+ * SECTION:window-action-menu
+ * @short_description: a menu widget, used to manipulate a window.
+ * @see_also: #WnckWindow
+ * @stability: Unstable
+ *
+ * A #WnckActionMenu is a menu containing items to manipulate a window.
+ * Relevant actions are displayed in the menu, and updated if the window state
+ * changes. The content of this menu is synchronized with the similar menu
+ * available in Metacity.
+ *
+ * <note>
+ * <para>
+ * If there is only one workspace with a viewport, the #WnckActionMenu will
+ * contain items to move the window in the viewport as if the viewport feature
+ * was used to create workspaces. This is useful since viewport is generally
+ * used as an alternative way to create virtual desktops.
+ * </para>
+ * <para>
+ * The #WnckActionMenu does not support moving the window in the viewport if
+ * there are multiple workspaces on the screen: those two notions are so
+ * similar that having both at the same time would result in a menu which would
+ * be confusing to the user.
+ * </para>
+ * </note>
+ */
+
+typedef enum
+{
+ CLOSE,
+ MINIMIZE,
+ MAXIMIZE,
+ ABOVE,
+ MOVE,
+ RESIZE,
+ PIN,
+ UNPIN,
+ LEFT,
+ RIGHT,
+ UP,
+ DOWN,
+ MOVE_TO_WORKSPACE
+} WindowAction;
+
+struct _WnckActionMenuPrivate
+{
+ WnckWindow *window;
+ GtkWidget *minimize_item;
+ GtkWidget *maximize_item;
+ GtkWidget *above_item;
+ GtkWidget *move_item;
+ GtkWidget *resize_item;
+ GtkWidget *close_item;
+ GtkWidget *workspace_separator;
+ GtkWidget *pin_item;
+ GtkWidget *unpin_item;
+ GtkWidget *left_item;
+ GtkWidget *right_item;
+ GtkWidget *up_item;
+ GtkWidget *down_item;
+ GtkWidget *workspace_item;
+ guint idle_handler;
+};
+
+enum {
+ PROP_0,
+ PROP_WINDOW
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (WnckActionMenu, wnck_action_menu, GTK_TYPE_MENU);
+
+static void wnck_action_menu_dispose (GObject *object);
+
+static void window_weak_notify (gpointer data,
+ GObject *window);
+
+static void refill_submenu_workspace (WnckActionMenu *menu);
+static void refill_submenu_viewport (WnckActionMenu *menu);
+
+static void
+window_weak_notify (gpointer data,
+ GObject *window)
+{
+ WNCK_ACTION_MENU(data)->priv->window = NULL;
+ gtk_widget_destroy (GTK_WIDGET (data));
+}
+
+static WnckActionMenu*
+get_action_menu (GtkWidget *widget)
+{
+ while (widget) {
+ if (GTK_IS_MENU_ITEM (widget))
+ widget = gtk_widget_get_parent (widget);
+
+ if (WNCK_IS_ACTION_MENU (widget))
+ return WNCK_ACTION_MENU (widget);
+
+ widget = gtk_menu_get_attach_widget (GTK_MENU (widget));
+ if (widget == NULL)
+ break;
+ }
+
+ return NULL;
+}
+
+static void
+item_activated_callback (GtkWidget *menu_item,
+ gpointer data)
+{
+ WnckActionMenu *menu;
+ WnckWindow *window;
+ WindowAction action = GPOINTER_TO_INT (data);
+ WnckScreen *screen;
+ gboolean viewport_mode;
+
+ menu = get_action_menu (menu_item);
+ if (menu == NULL)
+ return;
+
+ window = menu->priv->window;
+
+ screen = wnck_window_get_screen (window);
+ viewport_mode = wnck_screen_get_workspace_count (screen) == 1 &&
+ wnck_workspace_is_virtual (wnck_screen_get_workspace (screen,
+ 0));
+
+ switch (action)
+ {
+ case CLOSE:
+ /* In an activate callback, so gtk_get_current_event_time() suffices */
+ wnck_window_close (window,
+ gtk_get_current_event_time ());
+ break;
+ case MINIMIZE:
+ if (wnck_window_is_minimized (window))
+ wnck_window_unminimize (window,
+ gtk_get_current_event_time ());
+ else
+ wnck_window_minimize (window);
+ break;
+ case MAXIMIZE:
+ if (wnck_window_is_maximized (window))
+ wnck_window_unmaximize (window);
+ else
+ wnck_window_maximize (window);
+ break;
+ case ABOVE:
+ if (wnck_window_is_above (window))
+ wnck_window_unmake_above (window);
+ else
+ wnck_window_make_above (window);
+ break;
+ case MOVE:
+ wnck_window_keyboard_move (window);
+ break;
+ case RESIZE:
+ wnck_window_keyboard_size (window);
+ break;
+ case PIN:
+ if (!viewport_mode)
+ wnck_window_pin (window);
+ else
+ wnck_window_stick (window);
+ break;
+ case UNPIN:
+ if (!viewport_mode)
+ wnck_window_unpin (window);
+ else
+ wnck_window_unstick (window);
+ break;
+ case LEFT:
+ if (!viewport_mode)
+ {
+ WnckWorkspace *workspace;
+ workspace = wnck_workspace_get_neighbor (wnck_window_get_workspace (window),
+ WNCK_MOTION_LEFT);
+ wnck_window_move_to_workspace (window, workspace);
+ }
+ else
+ {
+ int width, xw, yw, ww, hw;
+
+ width = wnck_screen_get_width (screen);
+ wnck_window_get_geometry (window, &xw, &yw, &ww, &hw);
+ wnck_window_unstick (window);
+ wnck_window_set_geometry (window, 0,
+ WNCK_WINDOW_CHANGE_X | WNCK_WINDOW_CHANGE_Y,
+ xw - width, yw,
+ ww, hw);
+ }
+ break;
+ case RIGHT:
+ if (!viewport_mode)
+ {
+ WnckWorkspace *workspace;
+ workspace = wnck_workspace_get_neighbor (wnck_window_get_workspace (window),
+ WNCK_MOTION_RIGHT);
+ wnck_window_move_to_workspace (window, workspace);
+ }
+ else
+ {
+ int width, xw, yw, ww, hw;
+
+ width = wnck_screen_get_width (screen);
+ wnck_window_get_geometry (window, &xw, &yw, &ww, &hw);
+ wnck_window_unstick (window);
+ wnck_window_set_geometry (window, 0,
+ WNCK_WINDOW_CHANGE_X | WNCK_WINDOW_CHANGE_Y,
+ xw + width, yw,
+ ww, hw);
+ }
+ break;
+ case UP:
+ if (!viewport_mode)
+ {
+ WnckWorkspace *workspace;
+ workspace = wnck_workspace_get_neighbor (wnck_window_get_workspace (window),
+ WNCK_MOTION_UP);
+ wnck_window_move_to_workspace (window, workspace);
+ }
+ else
+ {
+ int height, xw, yw, ww, hw;
+
+ height = wnck_screen_get_height (screen);
+ wnck_window_get_geometry (window, &xw, &yw, &ww, &hw);
+ wnck_window_unstick (window);
+ wnck_window_set_geometry (window, 0,
+ WNCK_WINDOW_CHANGE_X | WNCK_WINDOW_CHANGE_Y,
+ xw, yw - height,
+ ww, hw);
+ }
+ break;
+ case DOWN:
+ if (!viewport_mode)
+ {
+ WnckWorkspace *workspace;
+ workspace = wnck_workspace_get_neighbor (wnck_window_get_workspace (window),
+ WNCK_MOTION_DOWN);
+ wnck_window_move_to_workspace (window, workspace);
+ }
+ else
+ {
+ int height, xw, yw, ww, hw;
+
+ height = wnck_screen_get_height (screen);
+ wnck_window_get_geometry (window, &xw, &yw, &ww, &hw);
+ wnck_window_unstick (window);
+ wnck_window_set_geometry (window, 0,
+ WNCK_WINDOW_CHANGE_X | WNCK_WINDOW_CHANGE_Y,
+ xw, yw + height,
+ ww, hw);
+ }
+ break;
+ case MOVE_TO_WORKSPACE:
+ if (!viewport_mode)
+ {
+ int workspace_index;
+ WnckWorkspace *workspace;
+
+ workspace_index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item),
+ "workspace"));
+
+ workspace = wnck_screen_get_workspace (screen, workspace_index);
+ wnck_window_move_to_workspace (window, workspace);
+ }
+ else
+ {
+ WnckWorkspace *workspace;
+ int new_viewport_x, new_viewport_y;
+ int xw, yw, ww, hw;
+ int viewport_x, viewport_y;
+
+ new_viewport_x = GPOINTER_TO_INT (
+ g_object_get_data (G_OBJECT (menu_item),
+ "viewport_x"));
+ new_viewport_y = GPOINTER_TO_INT (
+ g_object_get_data (G_OBJECT (menu_item),
+ "viewport_y"));
+
+ workspace = wnck_screen_get_workspace (screen, 0);
+
+ wnck_window_get_geometry (window, &xw, &yw, &ww, &hw);
+
+ viewport_x = wnck_workspace_get_viewport_x (workspace);
+ viewport_y = wnck_workspace_get_viewport_y (workspace);
+
+ wnck_window_unstick (window);
+ wnck_window_set_geometry (window, 0,
+ WNCK_WINDOW_CHANGE_X | WNCK_WINDOW_CHANGE_Y,
+ xw + new_viewport_x - viewport_x,
+ yw + new_viewport_y - viewport_y,
+ ww, hw);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+set_item_text (GtkWidget *mi,
+ const char *text)
+{
+ GtkLabel *label;
+
+ label = GTK_LABEL (gtk_bin_get_child (GTK_BIN (mi)));
+ gtk_label_set_text_with_mnemonic (label, text);
+ gtk_label_set_use_underline (label, TRUE);
+}
+
+static gboolean
+update_menu_state (WnckActionMenu *menu)
+{
+ WnckActionMenuPrivate *priv;
+ WnckWindowActions actions;
+ WnckScreen *screen;
+ WnckWorkspace *workspace;
+ gboolean viewport_mode;
+ gboolean move_workspace_sensitive;
+
+ priv = menu->priv;
+
+ priv->idle_handler = 0;
+
+ actions = wnck_window_get_actions (priv->window);
+ screen = wnck_window_get_screen (priv->window);
+
+ viewport_mode = wnck_screen_get_workspace_count (screen) == 1 &&
+ wnck_workspace_is_virtual (wnck_screen_get_workspace (screen,
+ 0));
+ move_workspace_sensitive = viewport_mode ||
+ (actions & WNCK_WINDOW_ACTION_CHANGE_WORKSPACE) != 0;
+
+ if (wnck_window_is_minimized (priv->window))
+ {
+ set_item_text (priv->minimize_item, _("Unmi_nimize"));
+ gtk_widget_set_sensitive (priv->minimize_item,
+ (actions & WNCK_WINDOW_ACTION_UNMINIMIZE) != 0);
+ }
+ else
+ {
+ set_item_text (priv->minimize_item, _("Mi_nimize"));
+ gtk_widget_set_sensitive (priv->minimize_item,
+ (actions & WNCK_WINDOW_ACTION_MINIMIZE) != 0);
+ }
+
+ if (wnck_window_is_maximized (priv->window))
+ {
+ set_item_text (priv->maximize_item, _("Unma_ximize"));
+ gtk_widget_set_sensitive (priv->maximize_item,
+ (actions & WNCK_WINDOW_ACTION_UNMAXIMIZE) != 0);
+ }
+ else
+ {
+ set_item_text (priv->maximize_item, _("Ma_ximize"));
+ gtk_widget_set_sensitive (priv->maximize_item,
+ (actions & WNCK_WINDOW_ACTION_MAXIMIZE) != 0);
+ }
+
+ g_signal_handlers_block_by_func (G_OBJECT (priv->above_item),
+ item_activated_callback,
+ GINT_TO_POINTER (ABOVE));
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->above_item),
+ wnck_window_is_above (priv->window));
+ g_signal_handlers_unblock_by_func (G_OBJECT (priv->above_item),
+ item_activated_callback,
+ GINT_TO_POINTER (ABOVE));
+
+ gtk_widget_set_sensitive (priv->above_item,
+ (actions & WNCK_WINDOW_ACTION_ABOVE) != 0);
+
+ g_signal_handlers_block_by_func (G_OBJECT (priv->pin_item),
+ item_activated_callback,
+ GINT_TO_POINTER (PIN));
+ g_signal_handlers_block_by_func (G_OBJECT (priv->unpin_item),
+ item_activated_callback,
+ GINT_TO_POINTER (UNPIN));
+ if ((viewport_mode && wnck_window_is_sticky (priv->window)) ||
+ (!viewport_mode && wnck_window_is_pinned (priv->window)))
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->pin_item),
+ TRUE);
+ else
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->unpin_item),
+ TRUE);
+ g_signal_handlers_unblock_by_func (G_OBJECT (priv->pin_item),
+ item_activated_callback,
+ GINT_TO_POINTER (PIN));
+ g_signal_handlers_unblock_by_func (G_OBJECT (priv->unpin_item),
+ item_activated_callback,
+ GINT_TO_POINTER (UNPIN));
+
+ gtk_widget_set_sensitive (priv->pin_item,
+ move_workspace_sensitive);
+
+ gtk_widget_set_sensitive (priv->unpin_item,
+ move_workspace_sensitive);
+
+ gtk_widget_set_sensitive (priv->close_item,
+ (actions & WNCK_WINDOW_ACTION_CLOSE) != 0);
+
+ gtk_widget_set_sensitive (priv->move_item,
+ (actions & WNCK_WINDOW_ACTION_MOVE) != 0);
+
+ gtk_widget_set_sensitive (priv->resize_item,
+ (actions & WNCK_WINDOW_ACTION_RESIZE) != 0);
+
+ gtk_widget_set_sensitive (priv->workspace_item,
+ move_workspace_sensitive);
+
+ gtk_widget_set_sensitive (priv->left_item,
+ move_workspace_sensitive);
+ gtk_widget_set_sensitive (priv->right_item,
+ move_workspace_sensitive);
+ gtk_widget_set_sensitive (priv->up_item,
+ move_workspace_sensitive);
+ gtk_widget_set_sensitive (priv->down_item,
+ move_workspace_sensitive);
+
+ workspace = wnck_window_get_workspace (priv->window);
+
+ if (viewport_mode && !wnck_window_is_sticky (priv->window))
+ {
+ int window_x, window_y;
+ int viewport_x, viewport_y;
+ int viewport_width, viewport_height;
+ int screen_width, screen_height;
+
+ if (!workspace)
+ workspace = wnck_screen_get_workspace (screen, 0);
+
+ wnck_window_get_geometry (priv->window, &window_x, &window_y, NULL, NULL);
+
+ viewport_x = wnck_workspace_get_viewport_x (workspace);
+ viewport_y = wnck_workspace_get_viewport_y (workspace);
+
+ window_x += viewport_x;
+ window_y += viewport_y;
+
+ viewport_width = wnck_workspace_get_width (workspace);
+ viewport_height = wnck_workspace_get_height (workspace);
+
+ screen_width = wnck_screen_get_width (screen);
+ screen_height = wnck_screen_get_height (screen);
+
+ if (window_x >= screen_width)
+ gtk_widget_show (priv->left_item);
+ else
+ gtk_widget_hide (priv->left_item);
+
+ if (window_x < viewport_width - screen_width)
+ gtk_widget_show (priv->right_item);
+ else
+ gtk_widget_hide (priv->right_item);
+
+ if (window_y >= screen_height)
+ gtk_widget_show (priv->up_item);
+ else
+ gtk_widget_hide (priv->up_item);
+
+ if (window_y < viewport_height - screen_height)
+ gtk_widget_show (priv->down_item);
+ else
+ gtk_widget_hide (priv->down_item);
+ }
+ else if (!viewport_mode && workspace && !wnck_window_is_pinned (priv->window))
+ {
+ if (wnck_workspace_get_neighbor (workspace, WNCK_MOTION_LEFT))
+ gtk_widget_show (priv->left_item);
+ else
+ gtk_widget_hide (priv->left_item);
+
+ if (wnck_workspace_get_neighbor (workspace, WNCK_MOTION_RIGHT))
+ gtk_widget_show (priv->right_item);
+ else
+ gtk_widget_hide (priv->right_item);
+
+ if (wnck_workspace_get_neighbor (workspace, WNCK_MOTION_UP))
+ gtk_widget_show (priv->up_item);
+ else
+ gtk_widget_hide (priv->up_item);
+
+ if (wnck_workspace_get_neighbor (workspace, WNCK_MOTION_DOWN))
+ gtk_widget_show (priv->down_item);
+ else
+ gtk_widget_hide (priv->down_item);
+ }
+ else
+ {
+ gtk_widget_hide (priv->left_item);
+ gtk_widget_hide (priv->right_item);
+ gtk_widget_hide (priv->up_item);
+ gtk_widget_hide (priv->down_item);
+ }
+
+ if (viewport_mode)
+ {
+ int viewport_width, viewport_height;
+ int screen_width, screen_height;
+
+ viewport_width = wnck_workspace_get_width (workspace);
+ viewport_height = wnck_workspace_get_height (workspace);
+
+ screen_width = wnck_screen_get_width (screen);
+ screen_height = wnck_screen_get_height (screen);
+
+ gtk_widget_show (priv->workspace_separator);
+ gtk_widget_show (priv->pin_item);
+ gtk_widget_show (priv->unpin_item);
+ if (viewport_width >= 2 * screen_width ||
+ viewport_height >= 2 * screen_height)
+ {
+ gtk_widget_show (priv->workspace_item);
+ refill_submenu_viewport (menu);
+ }
+ else
+ {
+ gtk_widget_hide (priv->workspace_item);
+ gtk_menu_popdown (GTK_MENU (gtk_menu_item_get_submenu (GTK_MENU_ITEM (priv->workspace_item))));
+ }
+ }
+ else if (wnck_screen_get_workspace_count (screen) > 1)
+ {
+ gtk_widget_show (priv->workspace_separator);
+ gtk_widget_show (priv->pin_item);
+ gtk_widget_show (priv->unpin_item);
+ gtk_widget_show (priv->workspace_item);
+ refill_submenu_workspace (menu);
+ }
+ else
+ {
+ gtk_widget_hide (priv->workspace_separator);
+ gtk_widget_hide (priv->pin_item);
+ gtk_widget_hide (priv->unpin_item);
+ gtk_widget_hide (priv->workspace_item);
+ gtk_menu_popdown (GTK_MENU (gtk_menu_item_get_submenu (GTK_MENU_ITEM (priv->workspace_item))));
+ }
+
+ gtk_menu_reposition (GTK_MENU (menu));
+
+ return FALSE;
+}
+
+static void
+queue_update (WnckActionMenu *menu)
+{
+ if (menu->priv->idle_handler == 0)
+ menu->priv->idle_handler = g_idle_add ((GSourceFunc)update_menu_state,
+ menu);
+}
+
+static void
+state_changed_callback (WnckWindow *window,
+ WnckWindowState changed_mask,
+ WnckWindowState new_state,
+ gpointer data)
+{
+ queue_update (WNCK_ACTION_MENU (data));
+}
+
+static void
+actions_changed_callback (WnckWindow *window,
+ WnckWindowActions changed_mask,
+ WnckWindowActions new_actions,
+ gpointer data)
+{
+ queue_update (WNCK_ACTION_MENU (data));
+}
+
+static void
+workspace_changed_callback (WnckWindow *window,
+ gpointer data)
+{
+ queue_update (WNCK_ACTION_MENU (data));
+}
+
+static void
+screen_workspace_callback (WnckWindow *window,
+ WnckWorkspace *space,
+ gpointer data)
+{
+ queue_update (WNCK_ACTION_MENU (data));
+}
+
+static void
+viewports_changed_callback (WnckWindow *window,
+ gpointer data)
+{
+ queue_update (WNCK_ACTION_MENU (data));
+}
+
+static GtkWidget*
+make_radio_menu_item (WindowAction action,
+ GSList **group,
+ const gchar *mnemonic_text)
+{
+ GtkWidget *mi;
+
+ mi = gtk_radio_menu_item_new_with_mnemonic (*group, mnemonic_text);
+ *group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (mi));
+
+ g_signal_connect (G_OBJECT (mi), "activate",
+ G_CALLBACK (item_activated_callback),
+ GINT_TO_POINTER (action));
+
+ gtk_widget_show (mi);
+
+ return mi;
+}
+
+static GtkWidget*
+make_check_menu_item (WindowAction action,
+ const gchar *mnemonic_text)
+{
+ GtkWidget *mi;
+
+ mi = gtk_check_menu_item_new_with_mnemonic (mnemonic_text);
+
+ g_signal_connect (G_OBJECT (mi), "activate",
+ G_CALLBACK (item_activated_callback),
+ GINT_TO_POINTER (action));
+
+ gtk_widget_show (mi);
+
+ return mi;
+}
+
+static GtkWidget*
+make_menu_item (WindowAction action)
+{
+ GtkWidget *mi;
+
+ mi = gtk_menu_item_new_with_label ("");
+
+ g_signal_connect (G_OBJECT (mi), "activate",
+ G_CALLBACK (item_activated_callback),
+ GINT_TO_POINTER (action));
+
+ gtk_widget_show (mi);
+
+ return mi;
+}
+
+static char *
+get_workspace_name_with_accel (WnckWindow *window,
+ int index)
+{
+ const char *name;
+ int number;
+
+ name = wnck_workspace_get_name (wnck_screen_get_workspace (wnck_window_get_screen (window),
+ index));
+
+ g_assert (name != NULL);
+
+ /*
+ * If the name is of the form "Workspace x" where x is an unsigned
+ * integer, insert a '_' before the number if it is less than 10 and
+ * return it
+ */
+ number = 0;
+ if (sscanf (name, _("Workspace %d"), &number) == 1) {
+ /* Keep this in sync with what is in refill_submenu_viewport() */
+ char *new_name;
+
+ /*
+ * Above name is a pointer into the Workspace struct. Here we make
+ * a copy copy so we can have our wicked way with it.
+ */
+ if (number == 10)
+ new_name = g_strdup_printf (_("Workspace 1_0"));
+ else
+ new_name = g_strdup_printf (_("Workspace %s%d"),
+ number < 10 ? "_" : "",
+ number);
+ return new_name;
+ }
+ else {
+ /*
+ * Otherwise this is just a normal name. Escape any _ characters so that
+ * the user's workspace names do not get mangled. If the number is less
+ * than 10 we provide an accelerator.
+ */
+ char *new_name;
+ const char *source;
+ char *dest;
+
+ /*
+ * Assume the worst case, that every character is a _. We also
+ * provide memory for " (_#)"
+ */
+ new_name = g_malloc0 (strlen (name) * 2 + 6 + 1);
+
+ /*
+ * Now iterate down the strings, adding '_' to escape as we go
+ */
+ dest = new_name;
+ source = name;
+ while (*source != '\0') {
+ if (*source == '_')
+ *dest++ = '_';
+ *dest++ = *source++;
+ }
+
+ /* People don't start at workstation 0, but workstation 1 */
+ if (index < 9) {
+ g_snprintf (dest, 6, " (_%d)", index + 1);
+ }
+ else if (index == 9) {
+ g_snprintf (dest, 6, " (_0)");
+ }
+
+ return new_name;
+ }
+}
+
+static void
+refill_submenu_workspace (WnckActionMenu *menu)
+{
+ GtkWidget *submenu;
+ GList *children;
+ GList *l;
+ int num_workspaces, window_space, i;
+ WnckWorkspace *workspace;
+
+ submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu->priv->workspace_item));
+
+ /* Remove existing items */
+ children = gtk_container_get_children (GTK_CONTAINER (submenu));
+ for (l = children; l; l = l->next)
+ gtk_container_remove (GTK_CONTAINER (submenu), l->data);
+ g_list_free (children);
+
+ workspace = wnck_window_get_workspace (menu->priv->window);
+
+ num_workspaces = wnck_screen_get_workspace_count (wnck_window_get_screen (menu->priv->window));
+
+ if (workspace)
+ window_space = wnck_workspace_get_number (workspace);
+ else
+ window_space = -1;
+
+ for (i = 0; i < num_workspaces; i++)
+ {
+ char *name;
+ GtkWidget *item;
+
+ name = get_workspace_name_with_accel (menu->priv->window, i);
+
+ item = make_menu_item (MOVE_TO_WORKSPACE);
+ g_object_set_data (G_OBJECT (item), "workspace", GINT_TO_POINTER (i));
+
+ if (i == window_space)
+ gtk_widget_set_sensitive (item, FALSE);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
+ set_item_text (item, name);
+
+ g_free (name);
+ }
+
+ gtk_menu_reposition (GTK_MENU (submenu));
+}
+
+static void
+refill_submenu_viewport (WnckActionMenu *menu)
+{
+ GtkWidget *submenu;
+ GList *children;
+ GList *l;
+ WnckScreen *screen;
+ WnckWorkspace *workspace;
+ int window_x, window_y;
+ int viewport_x, viewport_y;
+ int viewport_width, viewport_height;
+ int screen_width, screen_height;
+ int x, y;
+ int number;
+
+ submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu->priv->workspace_item));
+
+ /* Remove existing items */
+ children = gtk_container_get_children (GTK_CONTAINER (submenu));
+ for (l = children; l; l = l->next)
+ gtk_container_remove (GTK_CONTAINER (submenu), l->data);
+ g_list_free (children);
+
+ screen = wnck_window_get_screen (menu->priv->window);
+ workspace = wnck_screen_get_workspace (screen, 0);
+
+ wnck_window_get_geometry (menu->priv->window,
+ &window_x, &window_y, NULL, NULL);
+
+ viewport_x = wnck_workspace_get_viewport_x (workspace);
+ viewport_y = wnck_workspace_get_viewport_y (workspace);
+
+ window_x += viewport_x;
+ window_y += viewport_y;
+
+ viewport_width = wnck_workspace_get_width (workspace);
+ viewport_height = wnck_workspace_get_height (workspace);
+
+ screen_width = wnck_screen_get_width (screen);
+ screen_height = wnck_screen_get_height (screen);
+
+ number = 1;
+ for (y = 0; y < viewport_height; y += screen_height)
+ {
+ char *label;
+ GtkWidget *item;
+
+ for (x = 0; x < viewport_width; x += screen_width)
+ {
+ /* Keep this in sync with what is in get_workspace_name_with_accel()
+ */
+ if (number == 10)
+ label = g_strdup_printf (_("Workspace 1_0"));
+ else
+ label = g_strdup_printf (_("Workspace %s%d"),
+ number < 10 ? "_" : "",
+ number);
+ number++;
+
+ item = make_menu_item (MOVE_TO_WORKSPACE);
+ g_object_set_data (G_OBJECT (item), "viewport_x",
+ GINT_TO_POINTER (x));
+ g_object_set_data (G_OBJECT (item), "viewport_y",
+ GINT_TO_POINTER (y));
+
+ if (window_x >= x && window_x < x + screen_width &&
+ window_y >= y && window_y < y + screen_height)
+ gtk_widget_set_sensitive (item, FALSE);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
+ set_item_text (item, label);
+
+ g_free (label);
+ }
+ }
+
+ gtk_menu_reposition (GTK_MENU (submenu));
+}
+
+static void
+wnck_action_menu_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WnckActionMenu *menu;
+
+ g_return_if_fail (WNCK_IS_ACTION_MENU (object));
+
+ menu = WNCK_ACTION_MENU (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_value_set_pointer (value, menu->priv->window);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+wnck_action_menu_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WnckActionMenu *menu;
+
+ g_return_if_fail (WNCK_IS_ACTION_MENU (object));
+
+ menu = WNCK_ACTION_MENU (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_return_if_fail (WNCK_IS_WINDOW (g_value_get_pointer (value)));
+
+ menu->priv->window = g_value_get_pointer (value);
+ g_object_notify (G_OBJECT (menu), "window");
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+wnck_action_menu_init (WnckActionMenu *menu)
+{
+ menu->priv = wnck_action_menu_get_instance_private (menu);
+
+ menu->priv->window = NULL;
+ menu->priv->minimize_item = NULL;
+ menu->priv->maximize_item = NULL;
+ menu->priv->above_item = NULL;
+ menu->priv->move_item = NULL;
+ menu->priv->resize_item = NULL;
+ menu->priv->close_item = NULL;
+ menu->priv->workspace_separator = NULL;
+ menu->priv->pin_item = NULL;
+ menu->priv->unpin_item = NULL;
+ menu->priv->left_item = NULL;
+ menu->priv->right_item = NULL;
+ menu->priv->up_item = NULL;
+ menu->priv->down_item = NULL;
+ menu->priv->workspace_item = NULL;
+ menu->priv->idle_handler = 0;
+}
+
+static GObject *
+wnck_action_menu_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GObject *obj;
+ WnckActionMenu *menu;
+ WnckActionMenuPrivate *priv;
+ GtkWidget *submenu;
+ GtkWidget *separator;
+ GSList *pin_group;
+ WnckScreen *screen;
+
+
+ obj = G_OBJECT_CLASS (wnck_action_menu_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_properties);
+
+ menu = WNCK_ACTION_MENU (obj);
+ priv = menu->priv;
+
+ if (priv->window == NULL)
+ {
+ g_warning ("No window specified during creation of the action menu");
+ return obj;
+ }
+
+ g_object_weak_ref (G_OBJECT (priv->window), window_weak_notify, menu);
+
+ priv->minimize_item = make_menu_item (MINIMIZE);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ priv->minimize_item);
+
+ priv->maximize_item = make_menu_item (MAXIMIZE);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ priv->maximize_item);
+
+ priv->move_item = make_menu_item (MOVE);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ priv->move_item);
+
+ set_item_text (priv->move_item, _("_Move"));
+
+ priv->resize_item = make_menu_item (RESIZE);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ priv->resize_item);
+
+ set_item_text (priv->resize_item, _("_Resize"));
+
+ priv->workspace_separator = separator = gtk_separator_menu_item_new ();
+ gtk_widget_show (separator);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ separator);
+
+ priv->above_item = make_check_menu_item (ABOVE,
+ _("Always On _Top"));
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ priv->above_item);
+
+ pin_group = NULL;
+
+ priv->pin_item = make_radio_menu_item (PIN, &pin_group,
+ _("_Always on Visible Workspace"));
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ priv->pin_item);
+
+ priv->unpin_item = make_radio_menu_item (UNPIN, &pin_group,
+ _("_Only on This Workspace"));
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ priv->unpin_item);
+
+ priv->left_item = make_menu_item (LEFT);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ priv->left_item);
+ set_item_text (priv->left_item, _("Move to Workspace _Left"));
+
+ priv->right_item = make_menu_item (RIGHT);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ priv->right_item);
+ set_item_text (priv->right_item, _("Move to Workspace R_ight"));
+
+ priv->up_item = make_menu_item (UP);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ priv->up_item);
+ set_item_text (priv->up_item, _("Move to Workspace _Up"));
+
+ priv->down_item = make_menu_item (DOWN);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ priv->down_item);
+ set_item_text (priv->down_item, _("Move to Workspace _Down"));
+
+ priv->workspace_item = gtk_menu_item_new_with_mnemonic (_("Move to Another _Workspace"));
+ gtk_widget_show (priv->workspace_item);
+
+ submenu = gtk_menu_new ();
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (priv->workspace_item),
+ submenu);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ priv->workspace_item);
+
+ separator = gtk_separator_menu_item_new ();
+ gtk_widget_show (separator);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ separator);
+
+ priv->close_item = make_menu_item (CLOSE);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ priv->close_item);
+
+ set_item_text (priv->close_item, _("_Close"));
+
+ g_signal_connect_object (G_OBJECT (priv->window),
+ "state_changed",
+ G_CALLBACK (state_changed_callback),
+ G_OBJECT (menu),
+ 0);
+
+ g_signal_connect_object (G_OBJECT (priv->window),
+ "actions_changed",
+ G_CALLBACK (actions_changed_callback),
+ G_OBJECT (menu),
+ 0);
+
+ g_signal_connect_object (G_OBJECT (priv->window),
+ "workspace_changed",
+ G_CALLBACK (workspace_changed_callback),
+ G_OBJECT (menu),
+ 0);
+
+ screen = wnck_window_get_screen (priv->window);
+
+ g_signal_connect_object (G_OBJECT (screen),
+ "workspace_created",
+ G_CALLBACK (screen_workspace_callback),
+ G_OBJECT (menu),
+ 0);
+
+ g_signal_connect_object (G_OBJECT (screen),
+ "workspace_destroyed",
+ G_CALLBACK (screen_workspace_callback),
+ G_OBJECT (menu),
+ 0);
+
+ g_signal_connect_object (G_OBJECT (screen),
+ "viewports_changed",
+ G_CALLBACK (viewports_changed_callback),
+ G_OBJECT (menu),
+ 0);
+
+ update_menu_state (menu);
+
+ return obj;
+}
+
+static void
+wnck_action_menu_class_init (WnckActionMenuClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructor = wnck_action_menu_constructor;
+ object_class->get_property = wnck_action_menu_get_property;
+ object_class->set_property = wnck_action_menu_set_property;
+ object_class->dispose = wnck_action_menu_dispose;
+
+ g_object_class_install_property (object_class,
+ PROP_WINDOW,
+ g_param_spec_pointer ("window",
+ "Window",
+ "The window that will be manipulated through this menu",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+wnck_action_menu_dispose (GObject *object)
+{
+ WnckActionMenu *menu;
+
+ menu = WNCK_ACTION_MENU (object);
+
+ if (menu->priv->idle_handler)
+ {
+ g_source_remove (menu->priv->idle_handler);
+ menu->priv->idle_handler = 0;
+ }
+
+ if (WNCK_IS_WINDOW (menu->priv->window))
+ {
+ WnckScreen *screen;
+
+ g_object_weak_unref (G_OBJECT (menu->priv->window), window_weak_notify, menu);
+ g_signal_handlers_disconnect_by_data (menu->priv->window, menu);
+
+ screen = wnck_window_get_screen (menu->priv->window);
+ g_signal_handlers_disconnect_by_data (screen, menu);
+
+ menu->priv->window = NULL;
+ }
+
+ G_OBJECT_CLASS (wnck_action_menu_parent_class)->dispose (object);
+}
+
+/**
+ * wnck_action_menu_new:
+ * @window: the #WnckWindow for which a menu will be created.
+ *
+ * Creates a new #WnckActionMenu. The #WnckActionMenu will be filled with menu
+ * items for window operations on @window.
+ *
+ * Return value: a newly created #WnckActionMenu.
+ *
+ * Since: 2.22
+ **/
+GtkWidget*
+wnck_action_menu_new (WnckWindow *window)
+{
+ g_return_val_if_fail (WNCK_IS_WINDOW (window), NULL);
+
+ return g_object_new (WNCK_TYPE_ACTION_MENU,
+ "window", window,
+ NULL);
+}
diff --git a/libwnck/widgets/window-action-menu.h b/libwnck/widgets/window-action-menu.h
new file mode 100644
index 0000000..73847e1
--- /dev/null
+++ b/libwnck/widgets/window-action-menu.h
@@ -0,0 +1,75 @@
+/* window action menu (ops on a single window) */
+/* vim: set sw=2 et: */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2006-2007 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__LIBWNCK_H_INSIDE__) && !defined (WNCK_COMPILATION)
+#error "Only <libwnck/libwnck.h> can be included directly."
+#endif
+
+#ifndef WNCK_WINDOW_ACTION_MENU_H
+#define WNCK_WINDOW_ACTION_MENU_H
+
+#include <libwnck/libwnck.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define WNCK_TYPE_ACTION_MENU (wnck_action_menu_get_type ())
+#define WNCK_ACTION_MENU(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), WNCK_TYPE_ACTION_MENU, WnckActionMenu))
+#define WNCK_ACTION_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WNCK_TYPE_ACTION_MENU, WnckActionMenuClass))
+#define WNCK_IS_ACTION_MENU(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), WNCK_TYPE_ACTION_MENU))
+#define WNCK_IS_ACTION_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WNCK_TYPE_ACTION_MENU))
+#define WNCK_ACTION_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WNCK_TYPE_ACTION_MENU, WnckActionMenuClass))
+
+typedef struct _WnckActionMenu WnckActionMenu;
+typedef struct _WnckActionMenuClass WnckActionMenuClass;
+typedef struct _WnckActionMenuPrivate WnckActionMenuPrivate;
+
+/**
+ * WnckActionMenu:
+ *
+ * The #WnckActionMenu struct contains only private fields and should not be
+ * directly accessed.
+ */
+struct _WnckActionMenu
+{
+ GtkMenu parent_instance;
+
+ WnckActionMenuPrivate *priv;
+};
+
+struct _WnckActionMenuClass
+{
+ GtkMenuClass parent_class;
+
+ /* Padding for future expansion */
+ void (* pad1) (void);
+ void (* pad2) (void);
+ void (* pad3) (void);
+ void (* pad4) (void);
+};
+
+GType wnck_action_menu_get_type (void) G_GNUC_CONST;
+
+GtkWidget* wnck_action_menu_new (WnckWindow *window);
+
+G_END_DECLS
+
+#endif /* WNCK_WINDOW_MENU_H */
diff --git a/libwnck/widgets/wnck-image-menu-item-private.h b/libwnck/widgets/wnck-image-menu-item-private.h
new file mode 100644
index 0000000..5d33378
--- /dev/null
+++ b/libwnck/widgets/wnck-image-menu-item-private.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WNCK_IMAGE_MENU_ITEM_PRIVATE_H
+#define WNCK_IMAGE_MENU_ITEM_PRIVATE_H
+
+#include <gtk/gtk.h>
+#include <libwnck/libwnck.h>
+
+G_BEGIN_DECLS
+
+#define WNCK_TYPE_IMAGE_MENU_ITEM wnck_image_menu_item_get_type ()
+G_DECLARE_FINAL_TYPE (WnckImageMenuItem, wnck_image_menu_item,
+ WNCK, IMAGE_MENU_ITEM, GtkMenuItem)
+
+GtkWidget *wnck_image_menu_item_new (void);
+
+GtkWidget *wnck_image_menu_item_new_with_label (const gchar *label);
+
+void wnck_image_menu_item_set_image_from_icon_pixbuf (WnckImageMenuItem *item,
+ GdkPixbuf *pixbuf);
+
+void wnck_image_menu_item_set_image_from_window (WnckImageMenuItem *item,
+ WnckWindow *window);
+
+void wnck_image_menu_item_make_label_bold (WnckImageMenuItem *item);
+
+void wnck_image_menu_item_make_label_normal (WnckImageMenuItem *item);
+
+G_END_DECLS
+
+#endif
diff --git a/libwnck/widgets/wnck-image-menu-item.c b/libwnck/widgets/wnck-image-menu-item.c
new file mode 100644
index 0000000..e8e6d87
--- /dev/null
+++ b/libwnck/widgets/wnck-image-menu-item.c
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "wnck-image-menu-item-private.h"
+#include "private.h"
+
+#define SPACING 6
+
+struct _WnckImageMenuItem
+{
+ GtkMenuItem parent;
+
+ GtkWidget *box;
+ GtkWidget *image;
+ GtkWidget *accel_label;
+
+ gchar *label;
+};
+
+G_DEFINE_TYPE (WnckImageMenuItem, wnck_image_menu_item, GTK_TYPE_MENU_ITEM)
+
+static void
+wnck_image_menu_item_finalize (GObject *object)
+{
+ WnckImageMenuItem *item;
+
+ item = WNCK_IMAGE_MENU_ITEM (object);
+
+ g_clear_pointer (&item->label, g_free);
+
+ G_OBJECT_CLASS (wnck_image_menu_item_parent_class)->finalize (object);
+}
+
+static void
+wnck_image_menu_item_get_preferred_width (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ GtkWidgetClass *widget_class;
+ WnckImageMenuItem *item;
+ GtkRequisition image_requisition;
+
+ widget_class = GTK_WIDGET_CLASS (wnck_image_menu_item_parent_class);
+ item = WNCK_IMAGE_MENU_ITEM (widget);
+
+ widget_class->get_preferred_width (widget, minimum, natural);
+
+ if (!gtk_widget_get_visible (item->image))
+ return;
+
+ gtk_widget_get_preferred_size (item->image, &image_requisition, NULL);
+
+ if (image_requisition.width > 0)
+ {
+ *minimum -= image_requisition.width + SPACING;
+ *natural -= image_requisition.width + SPACING;
+ }
+}
+
+static void
+wnck_image_menu_item_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkWidgetClass *widget_class;
+ WnckImageMenuItem *item;
+ GtkRequisition image_requisition;
+ GtkAllocation box_allocation;
+
+ widget_class = GTK_WIDGET_CLASS (wnck_image_menu_item_parent_class);
+ item = WNCK_IMAGE_MENU_ITEM (widget);
+
+ widget_class->size_allocate (widget, allocation);
+
+ if (!gtk_widget_get_visible (item->image))
+ return;
+
+ gtk_widget_get_preferred_size (item->image, &image_requisition, NULL);
+ gtk_widget_get_allocation (item->box, &box_allocation);
+
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
+ {
+ if (image_requisition.width > 0)
+ box_allocation.x -= image_requisition.width + SPACING;
+ }
+ else
+ {
+ if (image_requisition.width > 0)
+ box_allocation.x += image_requisition.width + SPACING;
+ }
+
+ gtk_widget_size_allocate (item->box, &box_allocation);
+}
+
+static const gchar *
+wnck_image_menu_item_get_label (GtkMenuItem *menu_item)
+{
+ WnckImageMenuItem *item;
+
+ item = WNCK_IMAGE_MENU_ITEM (menu_item);
+
+ return item->label;
+}
+
+static void
+wnck_image_menu_item_toggle_size_request (GtkMenuItem *menu_item,
+ gint *requisition)
+{
+ WnckImageMenuItem *item;
+ GtkRequisition image_requisition;
+
+ item = WNCK_IMAGE_MENU_ITEM (menu_item);
+
+ *requisition = 0;
+
+ if (!gtk_widget_get_visible (item->image))
+ return;
+
+ gtk_widget_get_preferred_size (item->image, &image_requisition, NULL);
+
+ if (image_requisition.width > 0)
+ *requisition = image_requisition.width + SPACING;
+}
+
+static void
+wnck_image_menu_item_set_label (GtkMenuItem *menu_item,
+ const gchar *label)
+{
+ WnckImageMenuItem *item;
+
+ item = WNCK_IMAGE_MENU_ITEM (menu_item);
+
+ if (g_strcmp0 (item->label, label) != 0)
+ {
+ g_free (item->label);
+ item->label = g_strdup (label);
+
+ gtk_label_set_text_with_mnemonic (GTK_LABEL (item->accel_label), label);
+ g_object_notify (G_OBJECT (menu_item), "label");
+ }
+}
+
+static void
+wnck_image_menu_item_class_init (WnckImageMenuItemClass *item_class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkMenuItemClass *menu_item_class;
+
+ object_class = G_OBJECT_CLASS (item_class);
+ widget_class = GTK_WIDGET_CLASS (item_class);
+ menu_item_class = GTK_MENU_ITEM_CLASS (item_class);
+
+ object_class->finalize = wnck_image_menu_item_finalize;
+
+ widget_class->get_preferred_width = wnck_image_menu_item_get_preferred_width;
+ widget_class->size_allocate = wnck_image_menu_item_size_allocate;
+
+ menu_item_class->get_label = wnck_image_menu_item_get_label;
+ menu_item_class->toggle_size_request = wnck_image_menu_item_toggle_size_request;
+ menu_item_class->set_label = wnck_image_menu_item_set_label;
+}
+
+static void
+wnck_image_menu_item_init (WnckImageMenuItem *item)
+{
+ GtkAccelLabel *accel_label;
+
+ item->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, SPACING);
+ gtk_container_add (GTK_CONTAINER (item), item->box);
+ gtk_widget_show (item->box);
+
+ item->image = gtk_image_new ();
+ gtk_box_pack_start (GTK_BOX (item->box), item->image, FALSE, FALSE, 0);
+
+ item->accel_label = gtk_accel_label_new ("");
+ gtk_box_pack_end (GTK_BOX (item->box), item->accel_label, TRUE, TRUE, 0);
+ gtk_label_set_xalign (GTK_LABEL (item->accel_label), 0.0);
+ gtk_widget_show (item->accel_label);
+
+ accel_label = GTK_ACCEL_LABEL (item->accel_label);
+ gtk_accel_label_set_accel_widget (accel_label, GTK_WIDGET (item));
+ gtk_label_set_ellipsize (GTK_LABEL (accel_label), PANGO_ELLIPSIZE_END);
+ gtk_label_set_use_underline (GTK_LABEL (accel_label), TRUE);
+}
+
+GtkWidget *
+wnck_image_menu_item_new (void)
+{
+ return g_object_new (WNCK_TYPE_IMAGE_MENU_ITEM, NULL);
+}
+
+GtkWidget *
+wnck_image_menu_item_new_with_label (const gchar *label)
+{
+ return g_object_new (WNCK_TYPE_IMAGE_MENU_ITEM, "label", label, NULL);
+}
+
+void
+wnck_image_menu_item_set_image_from_icon_pixbuf (WnckImageMenuItem *item,
+ GdkPixbuf *pixbuf)
+{
+ gtk_image_set_from_pixbuf (GTK_IMAGE (item->image), pixbuf);
+ gtk_widget_show (item->image);
+}
+
+void
+wnck_image_menu_item_set_image_from_window (WnckImageMenuItem *item,
+ WnckWindow *window)
+{
+ _wnck_selector_set_window_icon (item->image, window);
+ gtk_widget_show (item->image);
+}
+
+void
+wnck_image_menu_item_make_label_bold (WnckImageMenuItem *item)
+{
+ _make_gtk_label_bold (GTK_LABEL (item->accel_label));
+}
+
+void
+wnck_image_menu_item_make_label_normal (WnckImageMenuItem *item)
+{
+ _make_gtk_label_normal (GTK_LABEL (item->accel_label));
+}
diff --git a/libwnck/widgets/wnck.css b/libwnck/widgets/wnck.css
new file mode 100644
index 0000000..9e658b1
--- /dev/null
+++ b/libwnck/widgets/wnck.css
@@ -0,0 +1,3 @@
+.wnck-needs-attention {
+ font-weight: bold;
+}
diff --git a/libwnck/widgets/wnck.gresource.xml b/libwnck/widgets/wnck.gresource.xml
new file mode 100644
index 0000000..2bef3c5
--- /dev/null
+++ b/libwnck/widgets/wnck.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/libwnck">
+ <file>wnck.css</file>
+ </gresource>
+</gresources>
diff --git a/libwnck/widgets/workspace-accessible-factory.c b/libwnck/widgets/workspace-accessible-factory.c
new file mode 100644
index 0000000..2aabc5c
--- /dev/null
+++ b/libwnck/widgets/workspace-accessible-factory.c
@@ -0,0 +1,62 @@
+/* vim: set sw=2 et: */
+/*
+ * Copyright 2002 Sun Microsystems Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtk/gtk.h>
+#include "workspace-accessible-factory.h"
+#include "workspace-accessible.h"
+
+G_DEFINE_TYPE (WnckWorkspaceAccessibleFactory,
+ wnck_workspace_accessible_factory, ATK_TYPE_OBJECT_FACTORY);
+
+static AtkObject* wnck_workspace_accessible_factory_create_accessible (GObject *obj);
+
+static GType wnck_workspace_accessible_factory_get_accessible_type (void);
+
+static void
+wnck_workspace_accessible_factory_class_init (WnckWorkspaceAccessibleFactoryClass *klass)
+{
+ AtkObjectFactoryClass *class = ATK_OBJECT_FACTORY_CLASS (klass);
+
+ class->create_accessible = wnck_workspace_accessible_factory_create_accessible;
+ class->get_accessible_type = wnck_workspace_accessible_factory_get_accessible_type;
+}
+
+static void
+wnck_workspace_accessible_factory_init (WnckWorkspaceAccessibleFactory *factory)
+{
+}
+
+AtkObjectFactory*
+wnck_workspace_accessible_factory_new (void)
+{
+ GObject *factory;
+ factory = g_object_new (WNCK_TYPE_WORKSPACE_ACCESSIBLE_FACTORY, NULL);
+ return ATK_OBJECT_FACTORY (factory);
+}
+
+static AtkObject*
+wnck_workspace_accessible_factory_create_accessible (GObject *obj)
+{
+ return wnck_workspace_accessible_new (obj);
+}
+
+static GType
+wnck_workspace_accessible_factory_get_accessible_type (void)
+{
+ return WNCK_WORKSPACE_TYPE_ACCESSIBLE;
+}
diff --git a/libwnck/widgets/workspace-accessible-factory.h b/libwnck/widgets/workspace-accessible-factory.h
new file mode 100644
index 0000000..d2d4a84
--- /dev/null
+++ b/libwnck/widgets/workspace-accessible-factory.h
@@ -0,0 +1,52 @@
+/* vim: set sw=2 et: */
+/*
+ * Copyright 2002 Sun Microsystems Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __WNCK_WORKSPACE_ACCESSIBLE_FACTORY_H__
+#define __WBCK_WORKSPACE_ACCESSIBLE_FACTORY_H__
+
+#include <atk/atk.h>
+
+G_BEGIN_DECLS
+
+#define WNCK_TYPE_WORKSPACE_ACCESSIBLE_FACTORY (wnck_workspace_accessible_factory_get_type())
+#define WNCK_WORKSPACE_ACCESSIBLE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WNCK_TYPE_WORKSPACE_ACCESSIBLE_FACTORY, WnckWorkspaceAccessibleFactory))
+#define WNCK_WORKSPACE_ACCESSIBLE_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WNCK_TYPE_WORKSPACE_ACCESSIBLE_FACTORY, WnckWorkspaceAccessibleFactoryClass))
+#define WNCK_IS_WORKSPACE_ACCESSIBLE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WNCK_TYPE_WORKSPACE_ACCESSIBLE_FACTORY))
+#define WNCK_IS_WORKSPACE_ACCESSIBLE_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WNCK_TYPE_WORKSPACE_ACCESSIBLE_FACTORY))
+#define WNCK_WORKSPACE_ACCESSIBLE_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WNCK_TYPE_WORKSPACE_ACCESSIBLE_FACTORY, WnckWorkspaceAccessibleFactoryClass))
+
+typedef struct _WnckWorkspaceAccessibleFactory WnckWorkspaceAccessibleFactory;
+typedef struct _WnckWorkspaceAccessibleFactoryClass WnckWorkspaceAccessibleFactoryClass;
+
+struct _WnckWorkspaceAccessibleFactory
+{
+ AtkObjectFactory parent;
+};
+
+struct _WnckWorkspaceAccessibleFactoryClass
+{
+ AtkObjectFactoryClass parent_class;
+};
+
+GType wnck_workspace_accessible_factory_get_type (void) G_GNUC_CONST;
+
+AtkObjectFactory* wnck_workspace_accessible_factory_new (void);
+
+G_END_DECLS
+
+#endif /* __WNCK_WORKSPACE_ACCESSIBLE_FACTORY_H__ */
diff --git a/libwnck/widgets/workspace-accessible.c b/libwnck/widgets/workspace-accessible.c
new file mode 100644
index 0000000..50c4f8e
--- /dev/null
+++ b/libwnck/widgets/workspace-accessible.c
@@ -0,0 +1,225 @@
+/* vim: set sw=2 et: */
+/*
+ * Copyright 2002 Sun Microsystems Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libwnck/libwnck.h>
+#include <gtk/gtk.h>
+#include <errno.h>
+#include <unistd.h>
+#include "workspace-accessible.h"
+#include "private.h"
+
+static const char* wnck_workspace_accessible_get_name (AtkObject *obj);
+static const char* wnck_workspace_accessible_get_description (AtkObject *obj);
+static int wnck_workspace_accessible_get_index_in_parent (AtkObject *obj);
+static void atk_component_interface_init (AtkComponentIface *iface);
+static void wnck_workspace_accessible_get_extents (AtkComponent *component,
+ int *x,
+ int *y,
+ int *width,
+ int *height,
+ AtkCoordType coords);
+static void wnck_workspace_accessible_get_position (AtkComponent *component,
+ int *x,
+ int *y,
+ AtkCoordType coords);
+static gboolean wnck_workspace_accessible_contains (AtkComponent *component,
+ int x,
+ int y,
+ AtkCoordType coords);
+static void wnck_workspace_accessible_get_size (AtkComponent *component,
+ int *width,
+ int *height);
+
+G_DEFINE_TYPE_WITH_CODE (WnckWorkspaceAccessible,
+ wnck_workspace_accessible,
+ ATK_TYPE_GOBJECT_ACCESSIBLE,
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT,
+ atk_component_interface_init))
+
+static void
+atk_component_interface_init (AtkComponentIface *iface)
+{
+ g_return_if_fail (iface != NULL);
+
+ iface->get_extents = wnck_workspace_accessible_get_extents;
+ iface->get_size = wnck_workspace_accessible_get_size;
+ iface->get_position = wnck_workspace_accessible_get_position;
+ iface->contains = wnck_workspace_accessible_contains;
+}
+
+static void
+wnck_workspace_accessible_get_extents (AtkComponent *component,
+ int *x,
+ int *y,
+ int *width,
+ int *height,
+ AtkCoordType coords)
+{
+ AtkGObjectAccessible *atk_gobj;
+ WnckPager *pager;
+ GdkRectangle rect;
+ GtkWidget *widget;
+ AtkObject *parent;
+ GObject *g_obj;
+ int px, py;
+
+ g_return_if_fail (WNCK_IS_WORKSPACE_ACCESSIBLE (component));
+
+ atk_gobj = ATK_GOBJECT_ACCESSIBLE (component);
+ g_obj = atk_gobject_accessible_get_object (atk_gobj);
+ if (g_obj == NULL)
+ return;
+
+ g_return_if_fail (WNCK_IS_WORKSPACE (g_obj));
+
+ parent = atk_object_get_parent (ATK_OBJECT(component));
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (parent));
+
+ if (widget == NULL)
+ {
+ /*
+ *State is defunct
+ */
+ return;
+ }
+
+ g_return_if_fail (WNCK_IS_PAGER (widget));
+ pager = WNCK_PAGER (widget);
+
+ g_return_if_fail (WNCK_IS_PAGER (pager));
+
+ atk_component_get_extents (ATK_COMPONENT (parent), &px, &py, NULL, NULL, coords);
+
+ _wnck_pager_get_workspace_rect (pager, WNCK_WORKSPACE_ACCESSIBLE (component)->index, &rect);
+
+ *x = rect.x + px;
+ *y = rect.y + py;
+ *height = rect.height;
+ *width = rect.width;
+}
+
+static void
+wnck_workspace_accessible_get_size (AtkComponent *component,
+ int *width,
+ int *height)
+{
+ AtkCoordType coords = ATK_XY_SCREEN;
+ int x, y;
+
+ /* FIXME: Value for initialization of coords picked randomly to please gcc */
+
+ wnck_workspace_accessible_get_extents (component, &x, &y, width, height, coords);
+}
+
+static void
+wnck_workspace_accessible_get_position (AtkComponent *component,
+ int *x,
+ int *y,
+ AtkCoordType coords)
+{
+ int width, height;
+ wnck_workspace_accessible_get_extents (component, x, y, &width, &height, coords);
+}
+
+static gboolean
+wnck_workspace_accessible_contains (AtkComponent *component,
+ int x,
+ int y,
+ AtkCoordType coords)
+{
+ int lx, ly, width, height;
+
+ wnck_workspace_accessible_get_extents (component, &lx, &ly, &width, &height, coords);
+
+ /*
+ * Check if the specified co-ordinates fall within the workspace.
+ */
+ if ( (x > lx) && ((lx + width) >= x) && (y > ly) && ((ly + height) >= ly) )
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void
+wnck_workspace_accessible_class_init (WnckWorkspaceAccessibleClass *klass)
+{
+ AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
+
+ class->get_name = wnck_workspace_accessible_get_name;
+ class->get_description = wnck_workspace_accessible_get_description;
+ class->get_index_in_parent = wnck_workspace_accessible_get_index_in_parent;
+}
+
+static void
+wnck_workspace_accessible_init (WnckWorkspaceAccessible *accessible)
+{
+}
+
+AtkObject*
+wnck_workspace_accessible_new (GObject *obj)
+{
+ GObject *object;
+ AtkObject *atk_object;
+
+ g_return_val_if_fail (WNCK_IS_WORKSPACE (obj), NULL);
+
+ object = g_object_new (WNCK_WORKSPACE_TYPE_ACCESSIBLE, NULL);
+ atk_object = ATK_OBJECT (object);
+ atk_object_initialize (atk_object, obj);
+
+ g_return_val_if_fail (ATK_IS_OBJECT (atk_object), NULL);
+
+ WNCK_WORKSPACE_ACCESSIBLE (atk_object)->index =
+ wnck_workspace_get_number (WNCK_WORKSPACE (obj));
+
+ return atk_object;
+}
+
+static const char*
+wnck_workspace_accessible_get_name (AtkObject *obj)
+{
+ g_return_val_if_fail (WNCK_IS_WORKSPACE_ACCESSIBLE (obj), NULL);
+
+ if (obj->name != NULL)
+ {
+ return obj->name;
+ }
+ else
+ return NULL;
+}
+
+static const char*
+wnck_workspace_accessible_get_description (AtkObject *obj)
+{
+ g_return_val_if_fail (WNCK_IS_WORKSPACE_ACCESSIBLE (obj), NULL);
+
+ if (obj->description != NULL)
+ {
+ return obj->description;
+ }
+ else
+ return NULL;
+}
+
+static gint
+wnck_workspace_accessible_get_index_in_parent (AtkObject *obj)
+{
+ g_return_val_if_fail (WNCK_IS_WORKSPACE_ACCESSIBLE (obj), -1);
+
+ return WNCK_WORKSPACE_ACCESSIBLE (obj)->index;
+}
diff --git a/libwnck/widgets/workspace-accessible.h b/libwnck/widgets/workspace-accessible.h
new file mode 100644
index 0000000..d20cee4
--- /dev/null
+++ b/libwnck/widgets/workspace-accessible.h
@@ -0,0 +1,55 @@
+/* vim: set sw=2 et: */
+/*
+ * Copyright 2002 Sun Microsystems Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __WNCK_WORKSPACE_ACCESSIBLE_H__
+#define __WNCK_WORKSPACE_ACCESSIBLE_H__
+
+#include <gtk/gtk.h>
+#include <atk/atk.h>
+
+G_BEGIN_DECLS
+
+#define WNCK_WORKSPACE_TYPE_ACCESSIBLE (wnck_workspace_accessible_get_type ())
+#define WNCK_WORKSPACE_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WNCK_WORKSPACE_TYPE_ACCESSIBLE, WnckWorkspaceAccessible))
+#define WNCK_WORKSPACE_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WNCK_WORKSPACE_TYPE_ACCESSIBLE, WnckWorkspaceAccessibleClass))
+#define WNCK_IS_WORKSPACE_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WNCK_WORKSPACE_TYPE_ACCESSIBLE))
+#define WNCK_IS_WORKSPACE_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WnckWorkspaceAccessible))
+#define WNCK_WORKSPACE_ACCESSIBLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WNCK_WORKSPACE_TYPE_ACCESSIBLE, WnckWorkspaceAccessibleClass))
+
+typedef struct _WnckWorkspaceAccessible WnckWorkspaceAccessible;
+typedef struct _WnckWorkspaceAccessibleClass WnckWorkspaceAccessibleClass;
+
+struct _WnckWorkspaceAccessible
+{
+ AtkGObjectAccessible parent;
+
+ int index;
+};
+
+struct _WnckWorkspaceAccessibleClass
+{
+ AtkGObjectAccessibleClass parent_class;
+};
+
+GType wnck_workspace_accessible_get_type (void) G_GNUC_CONST;
+
+AtkObject* wnck_workspace_accessible_new (GObject *obj);
+
+G_END_DECLS
+
+#endif /* __WNCK_WORKSPACE_ACCESSIBLE_H__ */
diff --git a/libwnck/widgets/xutils.c b/libwnck/widgets/xutils.c
new file mode 100644
index 0000000..96e686c
--- /dev/null
+++ b/libwnck/widgets/xutils.c
@@ -0,0 +1,394 @@
+/* Xlib utils */
+/* vim: set sw=2 et: */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2005-2007 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include "xutils.h"
+#include <string.h>
+#include <stdio.h>
+#include <cairo-xlib.h>
+#if HAVE_CAIRO_XLIB_XRENDER
+#include <cairo-xlib-xrender.h>
+#endif
+#include "private.h"
+
+gboolean
+_wnck_get_window (Screen *screen,
+ Window xwindow,
+ Atom atom,
+ Window *val)
+{
+ Display *display;
+ Atom type;
+ int format;
+ gulong nitems;
+ gulong bytes_after;
+ Window *w;
+ int err, result;
+
+ display = DisplayOfScreen (screen);
+
+ *val = 0;
+
+ _wnck_error_trap_push (display);
+ type = None;
+ result = XGetWindowProperty (display,
+ xwindow,
+ atom,
+ 0, G_MAXLONG,
+ False, XA_WINDOW, &type, &format, &nitems,
+ &bytes_after, (void*)&w);
+ err = _wnck_error_trap_pop (display);
+ if (err != Success ||
+ result != Success)
+ return FALSE;
+
+ if (type != XA_WINDOW)
+ {
+ XFree (w);
+ return FALSE;
+ }
+
+ *val = *w;
+
+ XFree (w);
+
+ return TRUE;
+}
+
+gboolean
+_wnck_get_atom (Screen *screen,
+ Window xwindow,
+ Atom atom,
+ Atom *val)
+{
+ Display *display;
+ Atom type;
+ int format;
+ gulong nitems;
+ gulong bytes_after;
+ Atom *a;
+ int err, result;
+
+ display = DisplayOfScreen (screen);
+
+ *val = 0;
+
+ _wnck_error_trap_push (display);
+ type = None;
+ result = XGetWindowProperty (display,
+ xwindow,
+ atom,
+ 0, G_MAXLONG,
+ False, XA_ATOM, &type, &format, &nitems,
+ &bytes_after, (void*)&a);
+ err = _wnck_error_trap_pop (display);
+ if (err != Success ||
+ result != Success)
+ return FALSE;
+
+ if (type != XA_ATOM)
+ {
+ XFree (a);
+ return FALSE;
+ }
+
+ *val = *a;
+
+ XFree (a);
+
+ return TRUE;
+}
+
+void
+_wnck_error_trap_push (Display *display)
+{
+ GdkDisplay *gdk_display;
+
+ gdk_display = gdk_x11_lookup_xdisplay (display);
+ g_assert (gdk_display != NULL);
+
+ gdk_x11_display_error_trap_push (gdk_display);
+}
+
+int
+_wnck_error_trap_pop (Display *display)
+{
+ GdkDisplay *gdk_display;
+
+ gdk_display = gdk_x11_lookup_xdisplay (display);
+ g_assert (gdk_display != NULL);
+
+ gdk_display_flush (gdk_display);
+ return gdk_x11_display_error_trap_pop (gdk_display);
+}
+
+cairo_surface_t *
+_wnck_cairo_surface_get_from_pixmap (Screen *screen,
+ Pixmap xpixmap)
+{
+ cairo_surface_t *surface;
+ Display *display;
+ Window root_return;
+ int x_ret, y_ret;
+ unsigned int w_ret, h_ret, bw_ret, depth_ret;
+ XWindowAttributes attrs;
+
+ surface = NULL;
+ display = DisplayOfScreen (screen);
+
+ _wnck_error_trap_push (display);
+
+ if (!XGetGeometry (display, xpixmap, &root_return,
+ &x_ret, &y_ret, &w_ret, &h_ret, &bw_ret, &depth_ret))
+ goto TRAP_POP;
+
+ if (depth_ret == 1)
+ {
+ surface = cairo_xlib_surface_create_for_bitmap (display,
+ xpixmap,
+ screen,
+ w_ret,
+ h_ret);
+ }
+ else
+ {
+ if (!XGetWindowAttributes (display, root_return, &attrs))
+ goto TRAP_POP;
+
+ if (depth_ret == (unsigned int) attrs.depth)
+ {
+ surface = cairo_xlib_surface_create (display,
+ xpixmap,
+ attrs.visual,
+ w_ret, h_ret);
+ }
+ else
+ {
+#if HAVE_CAIRO_XLIB_XRENDER
+ int std;
+
+ switch (depth_ret) {
+ case 1: std = PictStandardA1; break;
+ case 4: std = PictStandardA4; break;
+ case 8: std = PictStandardA8; break;
+ case 24: std = PictStandardRGB24; break;
+ case 32: std = PictStandardARGB32; break;
+ default: goto TRAP_POP;
+ }
+
+ surface = cairo_xlib_surface_create_with_xrender_format (display,
+ xpixmap,
+ attrs.screen,
+ XRenderFindStandardFormat (display, std),
+ w_ret, h_ret);
+#endif
+ }
+ }
+
+TRAP_POP:
+ _wnck_error_trap_pop (display);
+
+ return surface;
+}
+
+GdkPixbuf*
+_wnck_gdk_pixbuf_get_from_pixmap (Screen *screen,
+ Pixmap xpixmap)
+{
+ cairo_surface_t *surface;
+ GdkPixbuf *retval;
+
+ surface = _wnck_cairo_surface_get_from_pixmap (screen, xpixmap);
+
+ if (surface == NULL)
+ return NULL;
+
+ retval = gdk_pixbuf_get_from_surface (surface,
+ 0,
+ 0,
+ cairo_xlib_surface_get_width (surface),
+ cairo_xlib_surface_get_height (surface));
+ cairo_surface_destroy (surface);
+
+ return retval;
+}
+
+static GdkPixbuf*
+default_icon_at_size (int size)
+{
+ GdkPixbuf *base;
+
+ base = gdk_pixbuf_new_from_resource ("/org/gnome/libwnck/default_icon.png", NULL);
+
+ g_assert (base);
+
+ if (gdk_pixbuf_get_width (base) == size &&
+ gdk_pixbuf_get_height (base) == size)
+ {
+ return base;
+ }
+ else
+ {
+ GdkPixbuf *scaled;
+
+ scaled = gdk_pixbuf_scale_simple (base, size, size, GDK_INTERP_BILINEAR);
+ g_object_unref (G_OBJECT (base));
+
+ return scaled;
+ }
+}
+
+void
+_wnck_get_fallback_icons (GdkPixbuf **iconp,
+ int ideal_size,
+ GdkPixbuf **mini_iconp,
+ int ideal_mini_size)
+{
+ if (iconp)
+ *iconp = default_icon_at_size (ideal_size);
+
+ if (mini_iconp)
+ *mini_iconp = default_icon_at_size (ideal_mini_size);
+}
+
+void
+_wnck_get_window_geometry (Screen *screen,
+ Window xwindow,
+ int *xp,
+ int *yp,
+ int *widthp,
+ int *heightp)
+{
+ Display *display;
+ int x, y;
+ unsigned int width, height, bw, depth;
+ Window root_window;
+
+ width = 1;
+ height = 1;
+
+ display = DisplayOfScreen (screen);
+
+ _wnck_error_trap_push (display);
+
+ XGetGeometry (display,
+ xwindow,
+ &root_window,
+ &x, &y, &width, &height, &bw, &depth);
+
+ _wnck_error_trap_pop (display);
+
+ _wnck_get_window_position (screen, xwindow, xp, yp);
+
+ if (widthp)
+ *widthp = width;
+ if (heightp)
+ *heightp = height;
+}
+
+void
+_wnck_get_window_position (Screen *screen,
+ Window xwindow,
+ int *xp,
+ int *yp)
+{
+ Display *display;
+ Window root;
+ int x, y;
+ Window child;
+
+ x = 0;
+ y = 0;
+
+ display = DisplayOfScreen (screen);
+ root = RootWindowOfScreen (screen);
+
+ _wnck_error_trap_push (display);
+ XTranslateCoordinates (display,
+ xwindow,
+ root,
+ 0, 0,
+ &x, &y, &child);
+ _wnck_error_trap_pop (display);
+
+ if (xp)
+ *xp = x;
+ if (yp)
+ *yp = y;
+}
+
+void
+_wnck_set_icon_geometry (Screen *screen,
+ Window xwindow,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ Display *display;
+ gulong data[4];
+
+ display = DisplayOfScreen (screen);
+
+ data[0] = x;
+ data[1] = y;
+ data[2] = width;
+ data[3] = height;
+
+ _wnck_error_trap_push (display);
+
+ XChangeProperty (display,
+ xwindow,
+ _wnck_atom_get ("_NET_WM_ICON_GEOMETRY"),
+ XA_CARDINAL, 32, PropModeReplace,
+ (guchar *)&data, 4);
+
+ _wnck_error_trap_pop (display);
+}
+
+GdkDisplay*
+_wnck_gdk_display_lookup_from_display (Display *display)
+{
+ GdkDisplay *gdkdisplay = NULL;
+
+ gdkdisplay = gdk_x11_lookup_xdisplay (display);
+
+ if (!gdkdisplay)
+ g_warning ("No GdkDisplay matching Display \"%s\" was found.\n",
+ DisplayString (display));
+
+ return gdkdisplay;
+}
+
+GdkWindow*
+_wnck_gdk_window_lookup_from_window (Screen *screen,
+ Window xwindow)
+{
+ Display *display;
+ GdkDisplay *gdkdisplay;
+
+ display = DisplayOfScreen (screen);
+ gdkdisplay = _wnck_gdk_display_lookup_from_display (display);
+ if (!gdkdisplay)
+ return NULL;
+
+ return gdk_x11_window_lookup_for_display (gdkdisplay, xwindow);
+}
diff --git a/libwnck/widgets/xutils.h b/libwnck/widgets/xutils.h
new file mode 100644
index 0000000..01acaf9
--- /dev/null
+++ b/libwnck/widgets/xutils.h
@@ -0,0 +1,87 @@
+/* Xlib utilities */
+/* vim: set sw=2 et: */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2005-2007 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WNCK_XUTILS_H
+#define WNCK_XUTILS_H
+
+#include <glib.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <libwnck/libwnck.h>
+
+G_BEGIN_DECLS
+
+#define WNCK_APP_WINDOW_EVENT_MASK (PropertyChangeMask | StructureNotifyMask)
+
+gboolean _wnck_get_window (Screen *screen,
+ Window xwindow,
+ Atom atom,
+ Window *val);
+gboolean _wnck_get_atom (Screen *screen,
+ Window xwindow,
+ Atom atom,
+ Atom *val);
+void _wnck_error_trap_push (Display *display);
+int _wnck_error_trap_pop (Display *display);
+
+#define _wnck_atom_get(atom_name) gdk_x11_get_xatom_by_name (atom_name)
+#define _wnck_atom_name(atom) gdk_x11_get_xatom_name (atom)
+
+void _wnck_get_fallback_icons (GdkPixbuf **iconp,
+ int ideal_size,
+ GdkPixbuf **mini_iconp,
+ int ideal_mini_size);
+
+void _wnck_get_window_geometry (Screen *screen,
+ Window xwindow,
+ int *xp,
+ int *yp,
+ int *widthp,
+ int *heightp);
+
+void _wnck_get_window_position (Screen *screen,
+ Window xwindow,
+ int *xp,
+ int *yp);
+
+void _wnck_set_icon_geometry (Screen *screen,
+ Window xwindow,
+ int x,
+ int y,
+ int width,
+ int height);
+
+cairo_surface_t *_wnck_cairo_surface_get_from_pixmap (Screen *screen,
+ Pixmap xpixmap);
+
+GdkPixbuf* _wnck_gdk_pixbuf_get_from_pixmap (Screen *screen,
+ Pixmap xpixmap);
+
+GdkDisplay* _wnck_gdk_display_lookup_from_display (Display *display);
+
+GdkWindow* _wnck_gdk_window_lookup_from_window (Screen *screen,
+ Window xwindow);
+
+G_END_DECLS
+
+#endif /* WNCK_XUTILS_H */