/* * 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; gboolean needs_attention; }; G_DEFINE_TYPE (MDockItem, mdock_item, GTK_TYPE_WIDGET) enum { PROP_0, PROP_PINNED, PROP_DESKTOP_APP_INFO, PROP_N_WINDOWS, PROP_DISPLAY_NAME, PROP_NEEDS_ATTENTION, 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_update_icon_geometry_for_window(MDockItem *self, WnckWindow *window) { if (!gtk_widget_get_realized(GTK_WIDGET(self))) return; GdkWindow *self_window = gtk_widget_get_window(GTK_WIDGET(self)); gint x, y, width, height; gdk_window_get_origin(self_window, &x, &y); gdk_window_get_size(self_window, &width, &height); wnck_window_set_icon_geometry(window, x, y, width, height); } static void mdock_item_update_icon_geometry(MDockItem *self) { if (!gtk_widget_get_realized(GTK_WIDGET(self))) return; if (!self->priv->windows) return; GdkWindow *self_window = gtk_widget_get_window(GTK_WIDGET(self)); gint x, y, width, height; gdk_window_get_origin(self_window, &x, &y); gdk_window_get_size(self_window, &width, &height); for (GList *l = self->priv->windows; l; l = g_list_next(l)) { WnckWindow *window = WNCK_WINDOW(l->data); wnck_window_set_icon_geometry(window, x, y, width, height); } } static void mdock_item_update_needs_attention(MDockItem *self) { gboolean needs_it = FALSE; for (GList *l = self->priv->windows; l; l = g_list_next(l)) { WnckWindow *window = WNCK_WINDOW(l->data); needs_it |= wnck_window_or_transient_needs_attention(window); } if (needs_it != self->priv->needs_attention) { self->priv->needs_attention = needs_it; gtk_widget_queue_draw(GTK_WIDGET(self)); g_object_notify_by_pspec(G_OBJECT(self), obj_properties[PROP_NEEDS_ATTENTION]); } } static void handle_window_state_changed(WnckWindow *window, WnckWindowState changed_mask, WnckWindowState new_state, MDockItem *self) { if (changed_mask & (WNCK_WINDOW_STATE_DEMANDS_ATTENTION | WNCK_WINDOW_STATE_URGENT)) { mdock_item_update_needs_attention(self); } } static void mdock_item_realize(GtkWidget *widget) { MDockItem *self = MDOCK_ITEM(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); mdock_item_update_icon_geometry(self); } 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); mdock_item_update_icon_geometry(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); if (self->priv->needs_attention) { cairo_set_source_rgba(cr, 0.9, 0.3, 0.3, 1.0); } else { cairo_set_source_rgba(cr, 0.4, 0.4, 0.6, 0.8); } cairo_translate(cr, self->priv->icon_size / 2.0, self->priv->icon_size - circle_radius / 2.0 - 1.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; case PROP_NEEDS_ATTENTION: g_value_set_boolean(value, self->priv->needs_attention); 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; self->priv->needs_attention = FALSE; 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); obj_properties[PROP_NEEDS_ATTENTION] = g_param_spec_boolean("needs-attention", "Needs attention", "Whether any of the windows in this item requires attention", FALSE, 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); GAppInfo *appinfo = G_APP_INFO(self->priv->appinfo); GdkAppLaunchContext *launch_context = gdk_app_launch_context_new(); GError *error = NULL; gdk_app_launch_context_set_display(launch_context, gtk_widget_get_display(GTK_WIDGET(self))); gdk_app_launch_context_set_timestamp(launch_context, gtk_get_current_event_time()); g_debug("Launching '%s'", g_app_info_get_commandline(appinfo)); if (!g_app_info_launch(appinfo, files, G_APP_LAUNCH_CONTEXT(launch_context), &error)) { GtkWidget *msg; g_warning("Cannot launch '%s': %s", g_app_info_get_display_name(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(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; } gboolean mdock_item_needs_attention(MDockItem *self) { return self->priv->needs_attention; } 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); } mdock_item_update_icon_geometry_for_window(self, window); if (wnck_window_or_transient_needs_attention(window)) { mdock_item_update_needs_attention(self); } g_signal_connect_object(window, "state-changed", G_CALLBACK(handle_window_state_changed), self, 0); 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->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)); } if (wnck_window_or_transient_needs_attention(window)) { mdock_item_update_needs_attention(self); } g_signal_handlers_disconnect_by_data(window, 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(self->priv->windows); if (self->priv->windows->data == window) { return; // Nothing to do } // Move the last active window to the head of the window list. GList *l = g_list_find(self->priv->windows, window); g_return_if_fail(l); self->priv->windows = g_list_remove_link(self->priv->windows, l); self->priv->windows = g_list_concat(l, self->priv->windows); } 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_update_background(MDockItem *self) { GtkWidget *widget = GTK_WIDGET(self); GdkWindow *window = gtk_widget_get_window(widget); g_return_if_fail(window); gdk_window_set_back_pixmap(window, NULL, TRUE); gtk_widget_queue_draw(widget); } 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"); } }