task-title.c replacement for window-picker-applet 0.4.21

tung's picture
/*
 * 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 */