diff options
Diffstat (limited to 'libwnck/widgets')
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 */ |