/* * 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-item.h" #define MDOCK_ITEM_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), MDOCK_TYPE_ITEM, MDockItemPrivate)) #define REQUISITION_ICON_SIZE 32 struct _MDockItemPrivate { gboolean pinned; GDesktopAppInfo *appinfo; guint icon_size; GdkPixbuf *icon; GList *windows; WnckWindow *last_active; }; G_DEFINE_TYPE (MDockItem, mdock_item, GTK_TYPE_WIDGET) enum { PROP_0, PROP_PINNED, PROP_DESKTOP_APP_INFO, PROP_N_WINDOWS, PROP_DISPLAY_NAME, 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 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_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_back_pixmap(widget->window, NULL, TRUE); gdk_window_set_user_data(widget->window, widget); gtk_widget_set_realized(widget, TRUE); widget->style = gtk_style_attach(widget->style, widget->window); } static void mdock_item_size_request(GtkWidget *widget, GtkRequisition *requisition) { requisition->width = requisition->height = REQUISITION_ICON_SIZE; } static void mdock_item_size_allocate(GtkWidget *widget, GtkAllocation *allocation) { MDockItem *self = MDOCK_ITEM(widget); self->priv->icon_size = MIN(allocation->width, allocation->height); GTK_WIDGET_CLASS(mdock_item_parent_class)->size_allocate(widget, allocation); mdock_item_update_icon(self); } 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) { cairo_save(cr); gdk_cairo_set_source_pixbuf(cr, self->priv->icon, 0, 0); cairo_paint(cr); cairo_restore(cr); } if (self->priv->windows) { double circle_radius = self->priv->icon_size / 8.0; cairo_save(cr); cairo_set_source_rgba(cr, 0.0, 0.0, 0.2, 0.6); cairo_translate(cr, self->priv->icon_size / 2.0, self->priv->icon_size - circle_radius / 2.0); cairo_arc(cr, 0, 0, circle_radius, 0, 2 * M_PI); cairo_fill(cr); cairo_restore(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); break; case PROP_DESKTOP_APP_INFO: self->priv->appinfo = g_value_dup_object(value); 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_N_WINDOWS: g_value_set_uint(value, g_list_length(self->priv->windows)); break; case PROP_DISPLAY_NAME: g_value_set_string(value, mdock_item_get_display_name(self)); 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_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 = REQUISITION_ICON_SIZE; gtk_widget_add_events(GTK_WIDGET(self), GDK_BUTTON_PRESS_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); } 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->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_N_WINDOWS] = g_param_spec_uint("n-windows", "Number of windows", "Reads the current number of windows under this item", 0, G_MAXINT32, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_DISPLAY_NAME] = g_param_spec_string("display-name", "Name of this item", "Reads the current name of this item", "", G_PARAM_READABLE | 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 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_files(MDockItem *self, GList *files) { 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), files, 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_launch_file(MDockItem *self, GFile *file) { GList *files = g_list_append(NULL, file); mdock_item_launch_files(self, files); g_list_free(files); } void mdock_item_launch(MDockItem *self) { mdock_item_launch_files(self, NULL); } 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; } } GdkPixbuf *mdock_item_get_icon_pixbuf(MDockItem *self) { return self->priv->icon; } void mdock_item_add_window(MDockItem *self, WnckWindow *window) { if (!self->priv->windows) { // If there were no windows before adding this one, // we want to redraw in order to get the "active" indicator. gtk_widget_queue_draw(GTK_WIDGET(self)); } self->priv->windows = g_list_append(self->priv->windows, window); if (!self->priv->icon) { mdock_item_update_icon(self); } g_object_notify_by_pspec(G_OBJECT(self), obj_properties[PROP_N_WINDOWS]); } 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; } if (self->priv->windows) { // If there are no windows left, // we want to redraw in order to remove the "active" indicator. gtk_widget_queue_draw(GTK_WIDGET(self)); } g_object_notify_by_pspec(G_OBJECT(self), obj_properties[PROP_N_WINDOWS]); } 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; } gint mdock_item_get_num_windows(MDockItem *self) { return g_list_length(self->priv->windows); } gboolean mdock_item_has_windows(MDockItem *self) { return self->priv->windows ? TRUE : FALSE; } 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); } } void mdock_item_activate(MDockItem *self) { 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, gtk_get_current_event_time()); found_window = TRUE; break; } } if (!found_window) { WnckWindow *window = WNCK_WINDOW(self->priv->windows->data); wnck_window_activate_transient(window, gtk_get_current_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, gtk_get_current_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, gtk_get_current_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"); } }