/* * Copyright 2015 Javier S. Pedro * * This file is part of MDock. * * MDock is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MDock 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 General Public License * along with MDock. If not, see . */ #include #include "mdock-enums.h" #include "mdock-item.h" #include "mdock-item-menu.h" #include "thumbnailer.h" #define MDOCK_ITEM_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), MDOCK_TYPE_ITEM, MDockItemPrivate)) struct _MDockItemPrivate { gboolean pinned; GDesktopAppInfo *appinfo; guint icon_size; GdkPixbuf *icon; GList *windows; WnckWindow *last_active; MDockItemMenu *menu; GtkMenu *right_menu; GtkImageMenuItem *menu_close_all; GtkImageMenuItem *menu_pin; GtkImageMenuItem *menu_launch; }; G_DEFINE_TYPE (MDockItem, mdock_item, GTK_TYPE_WIDGET) enum { PROP_0, PROP_PINNED, PROP_DESKTOP_APP_INFO, PROP_ICON_SIZE, N_PROPERTIES }; static GParamSpec *obj_properties[N_PROPERTIES] = { NULL }; static GdkPixbuf *mdock_item_load_icon(MDockItem *self, guint icon_size) { if (self->priv->appinfo) { GtkIconTheme *theme = gtk_icon_theme_get_default(); GIcon *icon = g_app_info_get_icon(G_APP_INFO(self->priv->appinfo)); if (icon) { GtkIconInfo *info = gtk_icon_theme_lookup_by_gicon(theme, icon, icon_size, GTK_ICON_LOOKUP_GENERIC_FALLBACK | GTK_ICON_LOOKUP_FORCE_SIZE); GdkPixbuf *pixbuf = gtk_icon_info_load_icon(info, NULL); gtk_icon_info_free(info); if (pixbuf) { return pixbuf; } } } if (self->priv->windows) { GdkPixbuf *pixbuf = wnck_window_get_icon(self->priv->windows->data); if (pixbuf) { return gdk_pixbuf_scale_simple(pixbuf, self->priv->icon_size, icon_size, GDK_INTERP_BILINEAR); } } return NULL; } static const gchar *mdock_item_get_display_name(MDockItem *self) { if (self->priv->appinfo) { return g_app_info_get_display_name(G_APP_INFO(self->priv->appinfo)); } else if (self->priv->windows) { WnckApplication *app = wnck_window_get_application(self->priv->windows->data); return wnck_application_get_name(app); } else { return NULL; } } static void mdock_item_update_icon(MDockItem *self) { g_clear_object(&self->priv->icon); self->priv->icon = mdock_item_load_icon(self, self->priv->icon_size); gtk_widget_queue_draw(GTK_WIDGET(self)); } static void mdock_item_update_tooltip(MDockItem *self) { // TODO gtk_widget_set_tooltip_text(GTK_WIDGET(self), mdock_item_get_display_name(self)); } static void mdock_item_update_menu(MDockItem *self) { gtk_widget_set_visible(GTK_WIDGET(self->priv->menu_close_all), self->priv->windows != NULL); gtk_widget_set_visible(GTK_WIDGET(self->priv->menu_pin), self->priv->appinfo != NULL); if (self->priv->appinfo) { gtk_menu_item_set_label(GTK_MENU_ITEM(self->priv->menu_pin), self->priv->pinned ? _("Un_pin") : _("_Pin")); } gtk_widget_set_visible(GTK_WIDGET(self->priv->menu_launch), self->priv->appinfo != NULL); if (!gtk_image_menu_item_get_image(self->priv->menu_launch) && self->priv->appinfo && self->priv->icon) { gint width, height; if (gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height)) { GdkPixbuf *pixbuf = mdock_item_load_icon(self, MIN(width, height)); if (pixbuf) { GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf); gtk_image_menu_item_set_image(self->priv->menu_launch, image); g_object_unref(pixbuf); } } } } static void mdock_item_realize(GtkWidget *widget) { GdkWindowAttr attributes; guint attributes_mask; attributes.window_type = GDK_WINDOW_CHILD; attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK; attributes_mask = GDK_WA_X | GDK_WA_Y; widget->window = gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask); gdk_window_set_user_data(widget->window, widget); gtk_widget_set_realized(widget, TRUE); widget->style = gtk_style_attach(widget->style, widget->window); gtk_style_set_background(widget->style, widget->window, GTK_STATE_NORMAL); } static void mdock_item_size_request(GtkWidget *widget, GtkRequisition *requisition) { MDockItem *self = MDOCK_ITEM(widget); requisition->width = requisition->height = self->priv->icon_size; } static void mdock_item_size_allocate(GtkWidget *widget, GtkAllocation *allocation) { MDockItem *self = MDOCK_ITEM(widget); allocation->width = allocation->height = self->priv->icon_size; GTK_WIDGET_CLASS(mdock_item_parent_class)->size_allocate(widget, allocation); } static gboolean mdock_item_button_press(GtkWidget *widget, GdkEventButton *event) { MDockItem *self = MDOCK_ITEM(widget); switch (event->button) { case 1: if (self->priv->windows) { // This item has one window at least if (self->priv->windows->next) { // There is more than one window gboolean found_window = FALSE; for (GList *l = self->priv->windows; l; l = g_list_next(l)) { WnckWindow *window = WNCK_WINDOW(l->data); if (wnck_window_is_most_recently_activated(window) || wnck_window_transient_is_most_recently_activated(window)) { // This is the currently focused window, let's focus the next in the list GList *n = l->next ? g_list_next(l) : self->priv->windows; window = WNCK_WINDOW(n->data); wnck_window_activate_transient(window, event->time); found_window = TRUE; break; } } if (!found_window) { WnckWindow *window = WNCK_WINDOW(self->priv->windows->data); wnck_window_activate_transient(window, event->time); } } else { // There is only one window in this group WnckWindow *window = WNCK_WINDOW(self->priv->windows->data); if (wnck_window_is_minimized(window)) { wnck_window_unminimize(window, event->time); } else if (wnck_window_is_most_recently_activated(window) || wnck_window_transient_is_most_recently_activated(window)) { wnck_window_minimize(window); } else { wnck_window_activate_transient(window, event->time); } } } else if (self->priv->appinfo) { // No windows, but we have an application to launch! mdock_item_launch(self); } else { g_warning("A MDockItem has no windows and no appinfo to launch"); } break; case 3: gtk_menu_popup(self->priv->right_menu, NULL, NULL, NULL, NULL, event->button, event->time); break; } return TRUE; } static gboolean mdock_item_expose(GtkWidget *widget, GdkEventExpose *event) { MDockItem *self = MDOCK_ITEM(widget); cairo_t *cr = gdk_cairo_create(widget->window); if (self->priv->icon) { gdk_cairo_set_source_pixbuf(cr, self->priv->icon, 0, 0); cairo_paint(cr); } cairo_destroy(cr); return FALSE; } static void mdock_item_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { MDockItem *self = MDOCK_ITEM(object); switch (property_id) { case PROP_PINNED: self->priv->pinned = g_value_get_boolean(value); mdock_item_update_menu(self); break; case PROP_DESKTOP_APP_INFO: self->priv->appinfo = g_value_dup_object(value); mdock_item_update_icon(self); mdock_item_update_tooltip(self); mdock_item_update_menu(self); break; case PROP_ICON_SIZE: self->priv->icon_size = g_value_get_uint(value); gtk_widget_queue_resize(GTK_WIDGET(self)); mdock_item_update_icon(self); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void mdock_item_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { MDockItem *self = MDOCK_ITEM(object); switch (property_id) { case PROP_PINNED: g_value_set_boolean(value, self->priv->pinned); break; case PROP_DESKTOP_APP_INFO: g_value_set_object(value, self->priv->appinfo); break; case PROP_ICON_SIZE: g_value_set_uint(value, self->priv->icon_size); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void mdock_item_dispose(GObject *object) { MDockItem *self = MDOCK_ITEM(object); g_clear_object(&self->priv->appinfo); g_clear_object(&self->priv->icon); g_clear_object(&self->priv->menu); g_clear_object(&self->priv->right_menu); g_clear_object(&self->priv->menu_close_all); g_clear_object(&self->priv->menu_pin); g_clear_object(&self->priv->menu_launch); G_OBJECT_CLASS(mdock_item_parent_class)->dispose(object); } static void mdock_item_finalize(GObject *object) { MDockItem *self = MDOCK_ITEM(object); g_list_free(self->priv->windows); G_OBJECT_CLASS(mdock_item_parent_class)->finalize(object); } static void mdock_item_init(MDockItem *self) { self->priv = MDOCK_ITEM_GET_PRIVATE (self); self->priv->pinned = FALSE; self->priv->icon_size = 48; gtk_widget_add_events(GTK_WIDGET(self), GDK_BUTTON_PRESS_MASK); self->priv->menu = g_object_ref_sink(mdock_item_menu_new()); gtk_widget_set_tooltip_window(GTK_WIDGET(self), GTK_WINDOW(self->priv->menu)); self->priv->right_menu = g_object_ref_sink(gtk_menu_new()); self->priv->menu_close_all = g_object_ref_sink(gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE, NULL)); gtk_menu_shell_append(GTK_MENU_SHELL(self->priv->right_menu), GTK_WIDGET(self->priv->menu_close_all)); g_signal_connect_swapped(self->priv->menu_close_all, "activate", G_CALLBACK(mdock_item_close_all_windows), self); self->priv->menu_pin = g_object_ref_sink(gtk_image_menu_item_new_with_mnemonic(_("_Pin"))); gtk_menu_shell_append(GTK_MENU_SHELL(self->priv->right_menu), GTK_WIDGET(self->priv->menu_pin)); g_signal_connect_swapped(self->priv->menu_pin, "activate", G_CALLBACK(mdock_item_toggle_pinned), self); self->priv->menu_launch = g_object_ref_sink(gtk_image_menu_item_new_with_mnemonic(_("_New instance"))); gtk_menu_shell_append(GTK_MENU_SHELL(self->priv->right_menu), GTK_WIDGET(self->priv->menu_launch)); g_signal_connect_swapped(self->priv->menu_launch, "activate", G_CALLBACK(mdock_item_launch), self); } static void mdock_item_class_init(MDockItemClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS (klass); obj_class->set_property = mdock_item_set_property; obj_class->get_property = mdock_item_get_property; obj_class->dispose = mdock_item_dispose; obj_class->finalize = mdock_item_finalize; GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); widget_class->realize = mdock_item_realize; widget_class->size_request = mdock_item_size_request; widget_class->size_allocate = mdock_item_size_allocate; widget_class->button_press_event = mdock_item_button_press; widget_class->expose_event = mdock_item_expose; obj_properties[PROP_PINNED] = g_param_spec_boolean("pinned", "Pinned launcher", "Set whether this launcher is pinned", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_DESKTOP_APP_INFO] = g_param_spec_object("desktop-app-info", "The GDesktopAppInfo this application or launcher refers to", "Set the GDesktopAppInfo this application or launcher refers to", G_TYPE_DESKTOP_APP_INFO, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_ICON_SIZE] = g_param_spec_uint("icon-size", "Icon size", "Set the icon size", 16, 256, 48, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(obj_class, N_PROPERTIES, obj_properties); g_type_class_add_private (obj_class, sizeof (MDockItemPrivate)); } MDockItem *mdock_item_new() { return g_object_new(MDOCK_TYPE_ITEM, NULL); } gboolean mdock_item_get_pinned(MDockItem *self) { return self->priv->pinned; } void mdock_item_set_pinned(MDockItem *self, gboolean pinned) { g_object_set(self, "pinned", pinned, NULL); } void mdock_item_toggle_pinned(MDockItem *self) { mdock_item_set_pinned(self, !self->priv->pinned); } GDesktopAppInfo *mdock_item_get_desktop_app_info(MDockItem *self) { return g_object_ref(self->priv->appinfo); } void mdock_item_set_desktop_app_info(MDockItem *self, GDesktopAppInfo *app_info) { g_object_set(self, "desktop-app-info", app_info, NULL); } void mdock_item_launch(MDockItem *self) { g_return_if_fail(self->priv->appinfo); GError *error = NULL; g_debug("Launching '%s'", g_app_info_get_commandline(G_APP_INFO(self->priv->appinfo))); if (!g_app_info_launch(G_APP_INFO(self->priv->appinfo), NULL, NULL, &error)) { GtkWidget *msg; g_warning("Cannot launch '%s': %s", g_app_info_get_display_name(G_APP_INFO(self->priv->appinfo)), error->message); msg = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(self))), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Cannot launch '%s': %s", g_app_info_get_display_name(G_APP_INFO(self->priv->appinfo)), error->message); g_signal_connect_swapped(msg, "response", G_CALLBACK(gtk_widget_destroy), msg); gtk_widget_show_all(msg); g_error_free(error); } } void mdock_item_add_window(MDockItem *self, WnckWindow *window) { thumbnailer_enable_for_window(window); self->priv->windows = g_list_append(self->priv->windows, window); if (!self->priv->icon) { mdock_item_update_icon(self); } if (!gtk_widget_get_has_tooltip(GTK_WIDGET(self))) { mdock_item_update_tooltip(self); } mdock_item_update_menu(self); } void mdock_item_remove_window(MDockItem *self, WnckWindow *window) { self->priv->windows = g_list_remove(self->priv->windows, window); if (self->priv->last_active == window) { self->priv->last_active = NULL; } mdock_item_update_menu(self); } void mdock_item_set_last_active_window(MDockItem *self, WnckWindow *window) { g_return_if_fail(g_list_find(self->priv->windows, window)); self->priv->last_active = window; thumbnailer_update_thumbnail(window); } gint mdock_item_get_num_windows(MDockItem *self) { return g_list_length(self->priv->windows); } void mdock_item_close_all_windows(MDockItem *self) { for (GList *l = self->priv->windows; l; l = g_list_next(l)) { WnckWindow *window = WNCK_WINDOW(l->data); wnck_window_close(window, GDK_CURRENT_TIME); } }