/* * Copyright (C) 2008 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Authored by Neil Jagdish Patel <neil.patel@canonical.com> * */ #include "task-title.h" #include <libwnck/libwnck.h> #include <panel-applet.h> #include <panel-applet-gconf.h> #include <gconf/gconf.h> #include <gconf/gconf-client.h> #include "task-list.h" G_DEFINE_TYPE (TaskTitle, task_title, GTK_TYPE_EVENT_BOX); #define TASK_TITLE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\ TASK_TYPE_TITLE, \ TaskTitlePrivate)) #define LOGOUT "gnome-session-save --kill --gui" #define SHOW_HOME_TITLE_KEY "/apps/window-picker-applet/show_home_title" struct _TaskTitlePrivate { WnckScreen *screen; WnckWindow *top_maximized_window; GtkWidget *align; GtkWidget *box; GtkWidget *image; GtkWidget *label; GtkWidget *button; GtkWidget *button_image; gboolean show_home_title; }; /* Circular dependency through on_window_state_changed and * on_window_workspace_changed, but only through signals, so it's okay. */ static void hook_top_maximized_window (TaskTitle *title); static void on_top_icon_changed (WnckWindow *window, TaskTitle *title) { /* Update title icon when top maximized window's icon changes. */ TaskTitlePrivate *priv; g_return_if_fail (TASK_IS_TITLE (title)); priv = title->priv; gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), wnck_window_get_mini_icon (window)); gtk_widget_queue_draw (GTK_WIDGET (title)); } static void on_top_name_changed (WnckWindow *window, TaskTitle *title) { /* Update title label and tooltip when top maxed window's name changes. */ TaskTitlePrivate *priv; const char *window_name; g_return_if_fail (TASK_IS_TITLE (title)); priv = title->priv; window_name = wnck_window_get_name (window); gtk_label_set_text (GTK_LABEL (priv->label), window_name); gtk_widget_set_tooltip_text (GTK_WIDGET (title), window_name); gtk_widget_queue_draw (GTK_WIDGET (title)); } static void on_window_state_changed (WnckWindow *window, WnckWindowState changed_mask, WnckWindowState new_state, TaskTitle *title) { /* Window may have maximized/unmaximized. * Re-call check/hook top maximized window. */ g_return_if_fail (TASK_IS_TITLE (title)); hook_top_maximized_window (title); } static void on_window_workspace_changed (WnckWindow *window, TaskTitle *title) { /* Switching workspaces changes the visible windows. * Re-call check/hook top maximized window. */ g_return_if_fail (TASK_IS_TITLE (title)); hook_top_maximized_window (title); } static WnckWindow * find_top_maximized_window (TaskTitle *title) { /* Find the top-most maximized "normal" window. * Return NULL if we couldn't find one. */ TaskTitlePrivate *priv; WnckWindow *found = NULL; WnckWorkspace *current_workspace; GList *windows; WnckWindow *windows_i; WnckWindowType windows_i_type = WNCK_WINDOW_NORMAL; g_return_val_if_fail (TASK_IS_TITLE (title), NULL); priv = title->priv; current_workspace = wnck_screen_get_active_workspace (priv->screen); if (current_workspace == NULL) return NULL; windows = wnck_screen_get_windows_stacked (priv->screen); if (windows == NULL) return NULL; for ( ; windows; windows = g_list_next (windows)) { windows_i = windows->data; windows_i_type = wnck_window_get_window_type (windows_i); if (windows_i_type != WNCK_WINDOW_DESKTOP && windows_i_type != WNCK_WINDOW_DOCK && windows_i_type != WNCK_WINDOW_MENU && windows_i_type != WNCK_WINDOW_SPLASHSCREEN && wnck_window_is_visible_on_workspace (windows_i, current_workspace) && wnck_window_is_in_viewport (windows_i, current_workspace) && wnck_window_is_maximized (windows_i)) { found = windows_i; } } return found; } static void set_top_maximized_window_hooks (TaskTitle *title, WnckWindow *window) { /* Set up the callbacks that the top maximized window should have. */ g_return_if_fail (TASK_IS_TITLE (title)); g_signal_connect (window, "icon-changed", G_CALLBACK (on_top_icon_changed), title); g_signal_connect (window, "name-changed", G_CALLBACK (on_top_name_changed), title); } static void unset_top_maximized_window_hooks (TaskTitle *title, WnckWindow *window) { /* Remove callbacks set for the top maximized window. */ g_return_if_fail (TASK_IS_TITLE (title)); g_signal_handlers_disconnect_by_func (window, on_top_icon_changed, title); g_signal_handlers_disconnect_by_func (window, on_top_name_changed, title); } static void set_normal_window_hooks (TaskTitle *title, WnckWindow *window) { /* Set up the callback(s) that all windows should have. */ g_return_if_fail (TASK_IS_TITLE (title)); g_signal_connect (window, "state-changed", G_CALLBACK (on_window_state_changed), title); g_signal_connect (window, "workspace-changed", G_CALLBACK (on_window_workspace_changed), title); } static void set_title_to_window (TaskTitle *title, WnckWindow *window) { /* Set the title's graphics, label and tooltips to the window. */ TaskTitlePrivate *priv; g_return_if_fail (TASK_IS_TITLE (title)); priv = title->priv; gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), wnck_window_get_mini_icon (window)); gtk_label_set_text (GTK_LABEL (priv->label), wnck_window_get_name (window)); gtk_image_set_from_stock (GTK_IMAGE (priv->button_image), GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU); gtk_widget_set_tooltip_text (GTK_WIDGET (title), wnck_window_get_name (window)); gtk_widget_set_tooltip_text (priv->button, _("Close window")); gtk_widget_set_state (GTK_WIDGET (title), GTK_STATE_ACTIVE); gtk_widget_show (priv->box); } static void set_title_to_home (TaskTitle *title) { /* Set the title's graphics, label and tooltips to home. */ TaskTitlePrivate *priv; g_return_if_fail (TASK_IS_TITLE (title)); priv = title->priv; gtk_image_set_from_stock (GTK_IMAGE (priv->image), GTK_STOCK_HOME, GTK_ICON_SIZE_MENU); gtk_label_set_text (GTK_LABEL (priv->label), _("Home")); gtk_image_set_from_stock (GTK_IMAGE (priv->button_image), GTK_STOCK_QUIT, GTK_ICON_SIZE_MENU); gtk_widget_set_tooltip_text (GTK_WIDGET (title), _("Home")); gtk_widget_set_tooltip_text (priv->button, _("Log off, switch user, lock screen or power " "down the computer")); gtk_widget_set_state (GTK_WIDGET (title), GTK_STATE_ACTIVE); gtk_widget_show (priv->box); } static void set_title_to_hidden (TaskTitle *title) { /* Hide the title's graphics and label, and clear the tooltips. */ TaskTitlePrivate *priv; g_return_if_fail (TASK_IS_TITLE (title)); priv = title->priv; gtk_widget_set_tooltip_text (GTK_WIDGET (title), NULL); gtk_widget_set_tooltip_text (priv->button, NULL); gtk_widget_set_state (GTK_WIDGET (title), GTK_STATE_NORMAL); gtk_widget_hide (priv->box); } static void hook_top_maximized_window (TaskTitle *title) { /* Check for top-most maximized window and hook its events. */ TaskTitlePrivate *priv; WnckWindow *top_window; g_return_if_fail (TASK_IS_TITLE (title)); priv = title->priv; top_window = find_top_maximized_window (title); if (top_window == priv->top_maximized_window) return; if (priv->top_maximized_window) unset_top_maximized_window_hooks (title, priv->top_maximized_window); if (top_window && !task_list_get_desktop_visible (TASK_LIST (task_list_get_default ()))) { set_top_maximized_window_hooks (title, top_window); set_title_to_window (title, top_window); priv->top_maximized_window = top_window; } else { if (priv->show_home_title) set_title_to_home (title); else set_title_to_hidden (title); priv->top_maximized_window = NULL; } } static gboolean on_close_clicked (GtkButton *button, GdkEventButton *event, TaskTitle *title) { /* Close the top-most maximized window if it exists, * otherwise log the user out. */ TaskTitlePrivate *priv; g_return_val_if_fail (TASK_IS_TITLE (title), FALSE); priv = title->priv; if (event->button != 1) return FALSE; if (priv->top_maximized_window) wnck_window_close (priv->top_maximized_window, GDK_CURRENT_TIME); else gdk_spawn_command_line_on_screen (gdk_screen_get_default (), LOGOUT, NULL); gtk_widget_queue_draw (GTK_WIDGET (title)); return TRUE; } static gboolean on_title_clicked (GtkWidget *title, GdkEventButton *event) { /* Show the top-most maximized window's action menu on right click. */ TaskTitlePrivate *priv; GtkWidget *action_menu; g_return_val_if_fail (TASK_IS_TITLE (title), FALSE); priv = TASK_TITLE_GET_PRIVATE (title); if (event->button != 3) return FALSE; if (priv->top_maximized_window == NULL) return FALSE; action_menu = wnck_action_menu_new (priv->top_maximized_window); gtk_menu_popup (GTK_MENU (action_menu), NULL, NULL, NULL, NULL, event->button, event->time); return TRUE; } static void on_active_workspace_changed (WnckScreen *screen, WnckWorkspace *previously_active_workspace, TaskTitle *title) { /* On a new workspace, the windows will be completely different. * Re-call check/hook top maximized window. */ g_return_if_fail (TASK_IS_TITLE (title)); hook_top_maximized_window (title); } static void on_showing_desktop_mode_changed (WnckScreen *screen, TaskTitle *title) { /* All windows have either hidden or re-revealed themselves. * Re-call check/hook top maximized window. */ g_return_if_fail (TASK_IS_TITLE (title)); hook_top_maximized_window (title); } static void on_any_window_closed (WnckScreen *screen, WnckWindow *window, TaskTitle *title) { /* Re-call check/hook top maximized window. */ g_return_if_fail (TASK_IS_TITLE (title)); hook_top_maximized_window (title); } static void on_viewports_changed (WnckScreen *screen, TaskTitle *title) { /* Viewport in a workspace has changed/added/removed, for Compiz users. * Re-call check/hook top maximized window. */ g_return_if_fail (TASK_IS_TITLE (title)); hook_top_maximized_window (title); } static void on_window_manager_changed (WnckScreen *screen, TaskTitle *title) { /* Changing window managers is rare, but it still has to be handled. * Re-call check/hook top maximized window. */ g_return_if_fail (TASK_IS_TITLE (title)); hook_top_maximized_window (title); } static void on_window_order_changed (WnckScreen *screen, TaskTitle *title) { /* Re-call check/hook top maximized window. */ g_return_if_fail (TASK_IS_TITLE (title)); hook_top_maximized_window (title); } static void on_workspace_destroyed (WnckScreen *screen, WnckWorkspace *space, TaskTitle *title) { /* If a workspace dies, its windows may flow into the current one. * Re-call check/hook top maximized window. */ g_return_if_fail (TASK_IS_TITLE (title)); hook_top_maximized_window (title); } static void on_window_opened (WnckScreen *screen, WnckWindow *window, TaskTitle *title) { /* Set up normal window hook(s), then * re-call check/hook top maximized window. */ g_return_if_fail (TASK_IS_TITLE (title)); set_normal_window_hooks (title, window); hook_top_maximized_window (title); } static gboolean on_expose (GtkWidget *eb, GdkEventExpose *event) { if (eb->state == GTK_STATE_ACTIVE) gtk_paint_box (eb->style, eb->window, eb->state, GTK_SHADOW_NONE, NULL, eb, "button", eb->allocation.x, eb->allocation.y, eb->allocation.width, eb->allocation.height); gtk_container_propagate_expose (GTK_CONTAINER (eb), gtk_bin_get_child (GTK_BIN (eb)), event); return TRUE; } /* GObject stuff */ static void task_title_finalize (GObject *object) { TaskTitlePrivate *priv; priv = TASK_TITLE_GET_PRIVATE (object); G_OBJECT_CLASS (task_title_parent_class)->finalize (object); } static void task_title_class_init (TaskTitleClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS (klass); GtkWidgetClass *wid_class = GTK_WIDGET_CLASS (klass); obj_class->finalize = task_title_finalize; wid_class->expose_event = on_expose; g_type_class_add_private (obj_class, sizeof (TaskTitlePrivate)); } static void task_title_init (TaskTitle *title) { TaskTitlePrivate *priv; GConfClient *client; priv = title->priv = TASK_TITLE_GET_PRIVATE (title); priv->screen = wnck_screen_get_default (); /* NULL == no windows are represented by the title... yet */ priv->top_maximized_window = NULL; client = gconf_client_get_default (); priv->show_home_title = gconf_client_get_bool (client, SHOW_HOME_TITLE_KEY, NULL); g_object_unref (client); gtk_widget_add_events (GTK_WIDGET (title), GDK_ALL_EVENTS_MASK); priv->align = gtk_alignment_new (0.0, 0.5, 1.0, 1.0); gtk_alignment_set_padding (GTK_ALIGNMENT (priv->align), 0, 0, 6, 6); gtk_container_add (GTK_CONTAINER (title), priv->align); priv->box = gtk_hbox_new (FALSE, 2); gtk_container_add (GTK_CONTAINER (priv->align), priv->box); gtk_widget_set_no_show_all (priv->box, TRUE); gtk_widget_show (priv->box); priv->image = gtk_image_new_from_stock (GTK_STOCK_HOME, GTK_ICON_SIZE_MENU); gtk_box_pack_start (GTK_BOX (priv->box), priv->image, FALSE, FALSE, 0); gtk_widget_show (priv->image); priv->label = gtk_label_new (_("Home")); gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END); gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (priv->box), priv->label, TRUE, TRUE, 0); gtk_widget_show (priv->label); priv->button = g_object_new (GTK_TYPE_EVENT_BOX, "visible-window", FALSE, "above-child", TRUE, NULL); gtk_box_pack_start (GTK_BOX (priv->box), priv->button, FALSE, FALSE, 0); gtk_widget_show (priv->button); g_signal_connect (priv->button, "button-release-event", G_CALLBACK (on_close_clicked), title); priv->button_image = gtk_image_new_from_stock (GTK_STOCK_QUIT, GTK_ICON_SIZE_MENU); gtk_container_add (GTK_CONTAINER (priv->button), priv->button_image); gtk_widget_show (priv->button_image); gtk_widget_set_tooltip_text (priv->button, _("Log off, switch user, lock screen or power " "down the computer")); gtk_widget_set_tooltip_text (GTK_WIDGET (title), _("Home")); if (priv->show_home_title) gtk_widget_set_state (GTK_WIDGET (title), GTK_STATE_ACTIVE); else gtk_widget_hide (priv->box); /* Show the top maximized window's action menu when right clicked. */ g_signal_connect (title, "button-press-event", G_CALLBACK (on_title_clicked), NULL); /* Detect when the title should be changed to another window/nothing. */ g_signal_connect (priv->screen, "active-workspace-changed", G_CALLBACK (on_active_workspace_changed), title); g_signal_connect (priv->screen, "showing-desktop-changed", G_CALLBACK (on_showing_desktop_mode_changed), title); g_signal_connect (priv->screen, "viewports-changed", G_CALLBACK (on_viewports_changed), title); g_signal_connect (priv->screen, "window-closed", G_CALLBACK (on_any_window_closed), title); g_signal_connect (priv->screen, "window-manager-changed", G_CALLBACK (on_window_manager_changed), title); g_signal_connect (priv->screen, "window-stacking-changed", G_CALLBACK (on_window_order_changed), title); g_signal_connect (priv->screen, "workspace-destroyed", G_CALLBACK (on_workspace_destroyed), title); /* This sets up the normal window state hook, as well as the above check. */ g_signal_connect (priv->screen, "window-opened", G_CALLBACK (on_window_opened), title); } GtkWidget * task_title_new (void) { GtkWidget *title = NULL; title = g_object_new (TASK_TYPE_TITLE, "border-width", 0, "name", "tasklist-button", "visible-window", FALSE, NULL); return title; } /* vim: set expandtab:shiftwidth=2 */