/*
* 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 .
*
* Authored by Jason Smith
*
*/
#include "task-item.h"
#include "task-list.h"
#include
#include
#include
G_DEFINE_TYPE (TaskItem, task_item, GTK_TYPE_EVENT_BOX);
#define TASK_ITEM_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
TASK_TYPE_ITEM, \
TaskItemPrivate))
#define DEFAULT_TASK_ITEM_HEIGHT 24;
#define DEFAULT_TASK_ITEM_WIDTH 28
struct _TaskItemPrivate
{
WnckWindow *window;
WnckScreen *screen;
GdkPixbuf *pixbuf;
GdkRectangle area;
GTimeVal urgent_time;
guint timer;
gboolean mouse_over;
};
enum {
TASK_ITEM_CLOSED_SIGNAL,
LAST_SIGNAL
};
/* D&D stuff */
static const GtkTargetEntry drop_types[] =
{
{ "STRING", 0, 0 },
{ "text/plain", 0, 0},
{ "text/uri-list", 0, 0}
};
static const gint n_drop_types = G_N_ELEMENTS(drop_types);
static guint task_item_signals[LAST_SIGNAL] = { 0 };
static void
update_hints (TaskItem *item)
{
GtkWidget *parent;
GtkWidget *widget;
GtkAllocation allocation_parent;
GtkAllocation allocation_widget;
WnckWindow *window;
GdkWindow *gdkwindow;
gint x, y, x1, y1;
widget = GTK_WIDGET (item);
window = item->priv->window;
/* Skip problems */
if (!WNCK_IS_WINDOW (window)) return;
if (!GTK_IS_WIDGET (widget)) return;
/* Skip invisible windows */
if (!gtk_widget_get_visible (widget)) return;
x = y = 0;
/* Recursively compute the button's coordinates */
for (parent = widget; parent; parent = gtk_widget_get_parent (parent))
{
if (gtk_widget_get_parent (parent))
{
gtk_widget_get_allocation (parent, &allocation_parent);
x += allocation_parent.x;
y += allocation_parent.y;
}
else
{
x1 = y1 = 0;
gdkwindow = gtk_widget_get_window (parent);
#if !GTK_CHECK_VERSION (3, 0, 0)
if (GDK_IS_WINDOW (gdkwindow))
#endif
gdk_window_get_origin (gdkwindow, &x1, &y1);
x += x1; y += y1;
break;
}
}
/* Set the minimize hint for the window */
gtk_widget_get_allocation (widget, &allocation_widget);
wnck_window_set_icon_geometry (window, x, y,
allocation_widget.width,
allocation_widget.height);
}
static gboolean
on_task_item_button_released (GtkWidget *widget,
GdkEventButton *event,
TaskItem *item)
{
WnckWindow *window;
WnckScreen *screen;
WnckWorkspace *workspace;
TaskItemPrivate *priv;
g_return_val_if_fail (TASK_IS_ITEM (item), TRUE);
priv = item->priv;
window = priv->window;
g_return_val_if_fail (WNCK_IS_WINDOW (window), TRUE);
screen = priv->screen;
workspace = wnck_window_get_workspace (window);
if (event->button == 1)
{
if (WNCK_IS_WORKSPACE (workspace) && workspace != wnck_screen_get_active_workspace (screen))
{
wnck_workspace_activate (workspace, event->time);
}
if (wnck_window_is_active (window))
{
wnck_window_minimize (window);
}
else
{
wnck_window_activate (window, event->time);
}
}
return TRUE;
}
static void
task_item_set_visibility (TaskItem *item)
{
WnckScreen *screen;
WnckWindow *window;
WnckWorkspace *workspace;
g_return_if_fail (TASK_IS_ITEM (item));
TaskItemPrivate *priv = item->priv;
if (!WNCK_IS_WINDOW (priv->window))
{
gtk_widget_hide (GTK_WIDGET (item));
return;
}
window = priv->window;
screen = priv->screen;
workspace = wnck_screen_get_active_workspace (screen);
gboolean show_all = task_list_get_show_all_windows (TASK_LIST (task_list_get_default ()));
gboolean show_window = FALSE;
if (!wnck_window_is_skip_tasklist (window))
{
if (wnck_workspace_is_virtual (workspace))
{
show_window = wnck_window_is_in_viewport (window, workspace);
}
else
{
show_window = wnck_window_is_on_workspace (window, workspace);
}
show_window = show_window || show_all;
}
if (show_window)
{
gtk_widget_show (GTK_WIDGET (item));
}
else
{
gtk_widget_hide (GTK_WIDGET (item));
}
}
#if GTK_CHECK_VERSION (3, 0, 0)
static void
task_item_get_preferred_width (GtkWidget *widget,
gint *minimal_width,
gint *natural_width)
{
*minimal_width = *natural_width = DEFAULT_TASK_ITEM_WIDTH;
}
static void
task_item_get_preferred_height (GtkWidget *widget,
gint *minimal_height,
gint *natural_height)
{
*minimal_height = *natural_height = DEFAULT_TASK_ITEM_HEIGHT;
}
#else
static void
task_item_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
/* Candidate for terrible hack of the year award */
requisition->width = DEFAULT_TASK_ITEM_WIDTH;
requisition->height = DEFAULT_TASK_ITEM_HEIGHT;
}
#endif
static GdkPixbuf *
task_item_sized_pixbuf_for_window (TaskItem *item,
WnckWindow *window,
gint size)
{
GdkPixbuf *pbuf = NULL;
g_return_val_if_fail (WNCK_IS_WINDOW (window), NULL);
if (wnck_window_has_icon_name (window))
{
const gchar *icon_name = wnck_window_get_icon_name (window);
GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
if (gtk_icon_theme_has_icon (icon_theme, icon_name))
{
GdkPixbuf *internal = gtk_icon_theme_load_icon (icon_theme,
icon_name,
size,
GTK_ICON_LOOKUP_FORCE_SIZE,
NULL);
pbuf = gdk_pixbuf_copy (internal);
g_object_unref (internal);
}
}
if (!pbuf)
{
pbuf = gdk_pixbuf_copy (wnck_window_get_icon (item->priv->window));
}
gint width = gdk_pixbuf_get_width (pbuf);
gint height = gdk_pixbuf_get_height (pbuf);
if (MAX (width, height) != size)
{
gdouble scale = (gdouble) size / (gdouble) MAX (width, height);
GdkPixbuf *tmp = pbuf;
pbuf = gdk_pixbuf_scale_simple (tmp, (gint) (width * scale), (gint) (height * scale), GDK_INTERP_HYPER);
g_object_unref (tmp);
}
return pbuf;
}
static gboolean
#if GTK_CHECK_VERSION (3, 0, 0)
task_item_draw (GtkWidget *widget,
cairo_t *unused)
#else
task_item_expose_event (GtkWidget *widget,
GdkEventExpose *event)
#endif
{
cairo_t *cr;
TaskItem *item;
GdkRectangle area;
TaskItemPrivate *priv;
GdkPixbuf *pbuf;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (TASK_IS_ITEM (widget), FALSE);
#if !GTK_CHECK_VERSION (3, 0, 0)
g_return_val_if_fail (event != NULL, FALSE);
#endif
item = TASK_ITEM (widget);
priv = item->priv;
g_return_val_if_fail (WNCK_IS_WINDOW (priv->window), FALSE);
area = priv->area;
#if GTK_CHECK_VERSION (3, 0, 0)
cr = gdk_cairo_create (gtk_widget_get_window (widget));
#else
cr = gdk_cairo_create (event->window);
#endif
pbuf = priv->pixbuf;
gint size = MIN (area.height, area.width);
gboolean active = wnck_window_is_active (priv->window);
gboolean attention = wnck_window_or_transient_needs_attention (priv->window);
if (GDK_IS_PIXBUF (pbuf) &&
gdk_pixbuf_get_width (pbuf) != size &&
gdk_pixbuf_get_height (pbuf) != size)
{
g_object_unref (pbuf);
pbuf = NULL;
}
if (active)
{
cairo_rectangle (cr, area.x + 1, area.y - 1, area.width - 2, area.height - 2);
cairo_set_source_rgba (cr, .8, .8, .8, .2);
cairo_fill_preserve (cr);
if (priv->mouse_over)
{
cairo_set_source_rgba (cr, .9, .9, 1, 0.45);
cairo_stroke (cr);
}
else
{
cairo_set_line_width (cr, 1);
cairo_set_source_rgba (cr, .8, .8, .8, .4);
cairo_stroke (cr);
}
}
else if (priv->mouse_over)
{
int glow_x, glow_y;
cairo_pattern_t *glow_pattern;
glow_x = area.width / 2;
glow_y = area.height / 2;
glow_pattern = cairo_pattern_create_radial (
area.x + glow_x, area.y + glow_y, glow_x * 0.3,
area.x + glow_x, area.y + glow_y, glow_x * 1.4
);
cairo_pattern_add_color_stop_rgba (glow_pattern, 0, 1, 1, 1, 1);
cairo_pattern_add_color_stop_rgba (glow_pattern, 0.6, 1, 1, 1, 0);
cairo_set_source (cr, glow_pattern);
cairo_paint (cr);
}
if (!pbuf)
{
pbuf = priv->pixbuf = task_item_sized_pixbuf_for_window (item, priv->window, size);
}
if (active || priv->mouse_over || attention)
{
gdk_cairo_set_source_pixbuf (cr,
pbuf,
(area.x + (area.width - gdk_pixbuf_get_width (pbuf)) / 2),
(area.y + (area.height - gdk_pixbuf_get_height (pbuf)) / 2));
}
else
{
GdkPixbuf *desat = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
TRUE,
gdk_pixbuf_get_bits_per_sample (pbuf),
gdk_pixbuf_get_width (pbuf),
gdk_pixbuf_get_height (pbuf));
if (desat)
{
gdk_pixbuf_saturate_and_pixelate (pbuf,
desat,
0,
FALSE);
}
else /* just paint the colored version as a fallback */
{
desat = g_object_ref (pbuf);
}
gdk_cairo_set_source_pixbuf (cr,
desat,
(area.x + (area.width - gdk_pixbuf_get_width (desat)) / 2),
(area.y + (area.height - gdk_pixbuf_get_height (desat)) / 2));
g_object_unref (desat);
}
if (!priv->mouse_over && attention) /* urgent */
{
GTimeVal current_time;
g_get_current_time (¤t_time);
gdouble ms = (current_time.tv_sec - priv->urgent_time.tv_sec) * 1000 +
(current_time.tv_usec - priv->urgent_time.tv_usec) / 1000;
gdouble alpha = .66 + (cos (3.15 * ms / 600) / 3);
cairo_paint_with_alpha (cr, alpha);
}
else if (priv->mouse_over || active) /* focused */
{
cairo_paint (cr);
}
else /* not focused */
{
cairo_paint_with_alpha (cr, .65);
}
cairo_destroy (cr);
return FALSE;
}
static void
on_size_allocate (GtkWidget *widget,
GtkAllocation *allocation,
TaskItem *item)
{
TaskItemPrivate *priv;
if (allocation->width != allocation->height + 6)
gtk_widget_set_size_request (widget, allocation->height + 6, -1);
g_return_if_fail (TASK_IS_ITEM (item));
priv = item->priv;
priv->area.x = allocation->x;
priv->area.y = allocation->y;
priv->area.width = allocation->width;
priv->area.height = allocation->height;
update_hints (item);
}
static gboolean
on_button_pressed (GtkWidget *button,
GdkEventButton *event,
TaskItem *item)
{
WnckWindow *window;
g_return_val_if_fail (TASK_IS_ITEM (item), FALSE);
window = item->priv->window;
g_return_val_if_fail (WNCK_IS_WINDOW (window), FALSE);
if (event->button == 3)
{
GtkWidget *menu = wnck_action_menu_new (window);
gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
event->button, event->time);
return TRUE;
}
return FALSE;
}
static gboolean
on_query_tooltip (GtkWidget *widget,
gint x, gint y,
gboolean keyboard_mode,
GtkTooltip *tooltip,
TaskItem *item)
{
WnckWindow *window = item->priv->window;
g_return_val_if_fail (WNCK_IS_WINDOW (window), FALSE);
gtk_tooltip_set_text (tooltip, wnck_window_get_name(window));
gtk_tooltip_set_icon (tooltip, wnck_window_get_icon (window));
return TRUE;
}
static gboolean
on_enter_notify (GtkWidget *widget,
GdkEventCrossing *event,
TaskItem *item)
{
g_return_val_if_fail (TASK_IS_ITEM (item), FALSE);
item->priv->mouse_over = TRUE;
gtk_widget_queue_draw (widget);
return FALSE;
}
static gboolean
on_leave_notify (GtkWidget *widget,
GdkEventCrossing *event,
TaskItem *item)
{
g_return_val_if_fail (TASK_IS_ITEM (item), FALSE);
item->priv->mouse_over = FALSE;
gtk_widget_queue_draw (widget);
return FALSE;
}
static gboolean
on_blink (TaskItem *item)
{
g_return_val_if_fail (TASK_IS_ITEM (item), FALSE);
gtk_widget_queue_draw (GTK_WIDGET (item));
if (wnck_window_or_transient_needs_attention (item->priv->window))
{
return TRUE;
}
else
{
item->priv->timer = 0;
return FALSE;
}
}
static void
on_window_state_changed (WnckWindow *window,
WnckWindowState changed_mask,
WnckWindowState new_state,
TaskItem *item)
{
g_return_if_fail (WNCK_IS_WINDOW (window));
g_return_if_fail (TASK_IS_ITEM (item));
TaskItemPrivate *priv = item->priv;
if (new_state & WNCK_WINDOW_STATE_URGENT && !priv->timer)
{
priv->timer = g_timeout_add (30, (GSourceFunc)on_blink, item);
g_get_current_time (&priv->urgent_time);
}
task_item_set_visibility (item);
}
static void
on_window_workspace_changed (WnckWindow *window, TaskItem *item)
{
g_return_if_fail (TASK_IS_ITEM (item));
task_item_set_visibility (item);
}
static void
on_window_icon_changed (WnckWindow *window, TaskItem *item)
{
TaskItemPrivate *priv;
g_return_if_fail (TASK_IS_ITEM (item));
priv = item->priv;
if (GDK_IS_PIXBUF (priv->pixbuf))
{
g_object_unref (priv->pixbuf);
priv->pixbuf = NULL;
}
gtk_widget_queue_draw (GTK_WIDGET (item));
}
static void
on_screen_active_window_changed (WnckScreen *screen,
WnckWindow *old_window,
TaskItem *item)
{
WnckWindow *window;
TaskItemPrivate *priv;
g_return_if_fail (TASK_IS_ITEM (item));
priv = item->priv;
window = priv->window;
g_return_if_fail (WNCK_IS_WINDOW (window));
if ((WNCK_IS_WINDOW (old_window) && window == old_window) ||
window == wnck_screen_get_active_window (screen))
{
/* queue a draw to reflect that we are [no longer] the active window */
gtk_widget_queue_draw (GTK_WIDGET (item));
}
}
static void
on_screen_active_workspace_changed (WnckScreen *screen,
WnckWorkspace *old_workspace,
TaskItem *item)
{
g_return_if_fail (TASK_IS_ITEM (item));
task_item_set_visibility (item);
}
static void
on_screen_active_viewport_changed (WnckScreen *screen,
TaskItem *item)
{
g_return_if_fail (TASK_IS_ITEM (item));
task_item_set_visibility (item);
}
static void
on_screen_window_closed (WnckScreen *screen,
WnckWindow *window,
TaskItem *item)
{
TaskItemPrivate *priv;
g_return_if_fail (TASK_IS_ITEM (item));
priv = item->priv;
g_return_if_fail (WNCK_IS_WINDOW (priv->window));
if (priv->window == window)
{
g_signal_handlers_disconnect_by_func (screen, G_CALLBACK (on_screen_active_viewport_changed), item);
g_signal_handlers_disconnect_by_func (screen, G_CALLBACK (on_screen_active_window_changed), item);
g_signal_handlers_disconnect_by_func (screen, G_CALLBACK (on_screen_active_workspace_changed), item);
g_signal_handlers_disconnect_by_func (screen, G_CALLBACK (on_screen_window_closed), item);
g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_window_workspace_changed), item);
g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_window_state_changed), item);
g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_window_icon_changed), item);
g_signal_emit (G_OBJECT (item), task_item_signals[TASK_ITEM_CLOSED_SIGNAL], 0);
}
}
static gboolean
activate_window (GtkWidget *widget)
{
gint active;
TaskItemPrivate *priv;
g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
g_return_val_if_fail (TASK_IS_ITEM (widget), FALSE);
priv = TASK_ITEM (widget)->priv;
g_return_val_if_fail (WNCK_IS_WINDOW (priv->window), FALSE);
active = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "drag-true"));
if (active)
{
WnckWindow *window;
window = priv->window;
if (WNCK_IS_WINDOW (window))
wnck_window_activate (window, time (NULL));
}
g_object_set_data (G_OBJECT (widget), "drag-true", GINT_TO_POINTER (0));
return FALSE;
}
static void
on_drag_leave (GtkWidget *item,
GdkDragContext *context,
guint time)
{
g_object_set_data (G_OBJECT (item), "drag-true", GINT_TO_POINTER (0));
}
static gboolean
on_drag_motion (GtkWidget *item,
GdkDragContext *context,
gint x,
gint y,
guint t)
{
gint active;
active = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "drag-true"));
if (!active)
{
g_object_set_data (G_OBJECT (item), "drag-true", GINT_TO_POINTER (1));
g_timeout_add (1000, (GSourceFunc)activate_window, item);
}
return FALSE;
}
static void
task_item_setup_atk (TaskItem *item)
{
TaskItemPrivate *priv;
GtkWidget *widget;
AtkObject *atk;
WnckWindow *window;
g_return_if_fail (TASK_IS_ITEM (item));
widget = GTK_WIDGET (item);
priv = item->priv;
window = priv->window;
g_return_if_fail (WNCK_IS_WINDOW (window));
atk = gtk_widget_get_accessible (widget);
atk_object_set_name (atk, _("Window Task Button"));
atk_object_set_description (atk, wnck_window_get_name (window));
atk_object_set_role (atk, ATK_ROLE_PUSH_BUTTON);
}
static void
task_item_finalize (GObject *object)
{
TaskItemPrivate *priv;
priv = TASK_ITEM_GET_PRIVATE (object);
/* remove timer */
if (priv->timer)
{
g_source_remove (priv->timer);
}
if (GDK_IS_PIXBUF (priv->pixbuf))
{
g_object_unref (priv->pixbuf);
}
G_OBJECT_CLASS (task_item_parent_class)->finalize (object);
}
static void
task_item_class_init (TaskItemClass *klass)
{
GObjectClass *obj_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
obj_class->finalize = task_item_finalize;
#if GTK_CHECK_VERSION (3, 0, 0)
widget_class->draw = task_item_draw;
widget_class->get_preferred_width = task_item_get_preferred_width;
widget_class->get_preferred_height = task_item_get_preferred_height;
#else
widget_class->expose_event = task_item_expose_event;
widget_class->size_request = task_item_size_request;
#endif
g_type_class_add_private (obj_class, sizeof (TaskItemPrivate));
task_item_signals [TASK_ITEM_CLOSED_SIGNAL] =
g_signal_new ("task-item-closed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (TaskItemClass, itemclosed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
}
static void
task_item_init (TaskItem *item)
{
TaskItemPrivate *priv;
priv = item->priv = TASK_ITEM_GET_PRIVATE (item);
priv->timer = 0;
}
GtkWidget *
task_item_new (WnckWindow *window)
{
GtkWidget *item = NULL;
TaskItem *task;
TaskItemPrivate *priv;
WnckScreen *screen;
g_return_val_if_fail (WNCK_IS_WINDOW (window), item);
item = g_object_new (TASK_TYPE_ITEM,
"has-tooltip", TRUE,
"visible-window", FALSE,
"above-child", TRUE,
NULL);
gtk_widget_add_events (item, GDK_ALL_EVENTS_MASK);
gtk_container_set_border_width (GTK_CONTAINER (item), 0);
task = TASK_ITEM (item);
priv = task->priv;
priv->window = window;
screen = wnck_window_get_screen (window);
priv->screen = screen;
gtk_drag_dest_set (item,
GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
drop_types, n_drop_types,
GDK_ACTION_COPY);
gtk_drag_dest_add_uri_targets (item);
gtk_drag_dest_add_text_targets (item);
g_signal_connect (item, "drag-motion",
G_CALLBACK (on_drag_motion), NULL);
g_signal_connect (item, "drag-leave",
G_CALLBACK (on_drag_leave), NULL);
g_signal_connect (screen, "viewports-changed",
G_CALLBACK (on_screen_active_viewport_changed), item);
g_signal_connect (screen, "active-window-changed",
G_CALLBACK (on_screen_active_window_changed), item);
g_signal_connect (screen, "active-workspace-changed",
G_CALLBACK (on_screen_active_workspace_changed), item);
g_signal_connect (screen, "window-closed",
G_CALLBACK (on_screen_window_closed), item);
g_signal_connect (window, "workspace-changed",
G_CALLBACK (on_window_workspace_changed), item);
g_signal_connect (window, "state-changed",
G_CALLBACK (on_window_state_changed), item);
g_signal_connect (window, "icon-changed",
G_CALLBACK (on_window_icon_changed), item);
g_signal_connect (item, "button-release-event",
G_CALLBACK (on_task_item_button_released), item);
g_signal_connect (item, "button-press-event",
G_CALLBACK (on_button_pressed), item);
g_signal_connect (item, "size-allocate",
G_CALLBACK (on_size_allocate), item);
g_signal_connect (item, "query-tooltip",
G_CALLBACK (on_query_tooltip), item);
g_signal_connect (item, "enter-notify-event",
G_CALLBACK (on_enter_notify), item);
g_signal_connect (item, "leave-notify-event",
G_CALLBACK (on_leave_notify), item);
g_signal_connect (item, "drag-motion",
G_CALLBACK (on_drag_motion), item);
g_signal_connect (item, "drag-leave",
G_CALLBACK (on_drag_leave), item);
task_item_set_visibility (task);
task_item_setup_atk (task);
return item;
}