/* * 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 #define WNCK_I_KNOW_THIS_IS_UNSTABLE 1 #include #include "../config.h" #include "mdock-widget.h" #include "mdock-item-window-selector.h" #include "mdock-item-menu.h" #include "mdock-item.h" #include "matcher.h" #ifdef HAVE_ZEITGEIST #include #endif #define POPUP_MONITOR_EDGE_MARGIN 8 #define POPUP_MOVE_TIMEOUT 100 #define POPUP_SHOW_TIMEOUT 200 #define POPUP_HIDE_TIMEOUT 500 struct _MDockWidgetPrivate { GSettings *settings; WnckScreen *wnck_screen; GSequence *items; GHashTable *appid_to_item; GHashTable *desktopid_to_item; GHashTable *window_to_item; MDockItem *current_pointed_item; MDockItem *current_popup_item; guint popup_timer; gboolean loading_settings; gboolean primary_button_down; }; G_DEFINE_TYPE(MDockWidget, mdock_widget, GTK_TYPE_BOX) #define MDOCK_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), MDOCK_TYPE_WIDGET, MDockWidgetPrivate)) enum { PROP_0, PROP_SETTINGS, N_PROPERTIES }; static GParamSpec *obj_properties[N_PROPERTIES] = { NULL }; G_DEFINE_QUARK(mdock-widget-item-iter, mdock_widget_item_iter) G_DEFINE_QUARK(mdock-widget-item-menu, mdock_widget_item_menu) G_DEFINE_QUARK(mdock-widget-item-window-selector, mdock_widget_item_window_selector) enum { DRAG_TYPE_ITEM, DRAG_TYPE_URILIST }; static const GtkTargetEntry drag_types[] = { {"application/x-mdock-item", 0, DRAG_TYPE_ITEM}, {"text/uri-list", 0, DRAG_TYPE_URILIST} }; static void connect_item(MDockWidget *self, MDockItem *item, GSequenceIter *position); static inline MDockItemWindowSelector * selector_from_item(MDockItem *item) { return g_object_get_qdata((GObject*)item, mdock_widget_item_window_selector_quark()); } static void save_items_to_settings(MDockWidget *self) { g_return_if_fail(self->priv->settings); GSequenceIter *seq_iter = g_sequence_get_begin_iter(self->priv->items); GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE("as")); while (!g_sequence_iter_is_end(seq_iter)) { MDockItem *item = MDOCK_ITEM(g_sequence_get(seq_iter)); if (mdock_item_get_pinned(item)) { GDesktopAppInfo *info = mdock_item_get_desktop_app_info(item); if (info) { g_variant_builder_add(&builder, "s", g_desktop_app_info_get_filename(info)); } } seq_iter = g_sequence_iter_next(seq_iter); } self->priv->loading_settings = TRUE; g_settings_set(self->priv->settings, "items", "as", &builder); self->priv->loading_settings = FALSE; } static void move_item_to_position(MDockWidget *self, GSequenceIter *iter, GSequenceIter *position) { gtk_box_reorder_child(GTK_BOX(self), GTK_WIDGET(g_sequence_get(iter)), g_sequence_iter_get_position(position)); g_sequence_move(iter, position); } static void hide_item_popup(MDockWidget *self) { g_return_if_fail(self->priv->current_popup_item); MDockItemWindowSelector *selector = MDOCK_ITEM_WINDOW_SELECTOR(g_object_get_qdata(G_OBJECT(self->priv->current_popup_item), mdock_widget_item_window_selector_quark())); gtk_widget_hide(GTK_WIDGET(selector)); self->priv->current_popup_item = NULL; } static void show_item_popup(MDockWidget *self, MDockItem *item) { g_warn_if_fail(!self->priv->current_popup_item); MDockItemWindowSelector *selector = MDOCK_ITEM_WINDOW_SELECTOR(g_object_get_qdata(G_OBJECT(item), mdock_widget_item_window_selector_quark())); // Position the popup GtkOrientation orientation = gtk_orientable_get_orientation(GTK_ORIENTABLE(self)); GdkWindow *item_window = gtk_widget_get_window(GTK_WIDGET(item)); GdkScreen *screen = gdk_window_get_screen(item_window); gint monitor_num = gdk_screen_get_monitor_at_window(screen, item_window); GdkRectangle monitor; GtkRequisition requisition; gint item_x, item_y, item_w, item_h; gint x, y; gdk_window_get_origin(item_window, &item_x, &item_y); gdk_window_get_size(item_window, &item_w, &item_h); gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor); gtk_widget_size_request(GTK_WIDGET(selector), &requisition); if (requisition.width > 0 || requisition.height > 0) { if (orientation == GTK_ORIENTATION_HORIZONTAL) { x = item_x + item_w / 2 - requisition.width / 2; if (item_y > (monitor.y + monitor.height / 2)) { y = item_y - requisition.height; } else { y = item_y + item_h; } } else { y = item_y + item_h / 2 - requisition.height / 2; if (item_x > (monitor.x + monitor.width / 2)) { x = item_x - requisition.width; } else { x = item_x + item_w; } } if (x + requisition.width > monitor.x + monitor.width) { x -= x - (monitor.x + monitor.width) + requisition.width; } else if (x < monitor.x + POPUP_MONITOR_EDGE_MARGIN) { x = monitor.x + POPUP_MONITOR_EDGE_MARGIN; } if (y + requisition.height > monitor.y + monitor.height) { y -= y - (monitor.y + monitor.height) + requisition.height; } else if (y < monitor.y + POPUP_MONITOR_EDGE_MARGIN) { y = monitor.y + POPUP_MONITOR_EDGE_MARGIN; } gtk_window_move(GTK_WINDOW(selector), x, y); gtk_widget_show(GTK_WIDGET(selector)); } self->priv->current_popup_item = item; } static gboolean filter_deleted_group(gpointer key, gpointer value, gpointer user_data) { return value == user_data; } static void remove_item(MDockWidget *self, GSequenceIter *iter) { MDockItem *item = MDOCK_ITEM(g_sequence_get(iter)); g_hash_table_foreach_remove(self->priv->appid_to_item, filter_deleted_group, iter); g_hash_table_foreach_remove(self->priv->desktopid_to_item, filter_deleted_group, iter); g_sequence_remove(iter); if (self->priv->current_popup_item == item) { hide_item_popup(self); } gtk_widget_destroy(GTK_WIDGET(item)); } static void handle_item_pinned_changed(MDockWidget *self, GParamSpec *spec, MDockItem *item) { if (!self->priv->loading_settings) { save_items_to_settings(self); if (!mdock_item_get_pinned(item) && mdock_item_get_num_windows(item) == 0) { // Not pinned and without windows? GSequenceIter *iter = g_object_get_qdata(G_OBJECT(item), mdock_widget_item_iter_quark()); remove_item(self, iter); } } } static gboolean handle_item_button_press(MDockWidget *self, GdkEventButton *event, MDockItem *item) { if (event->button != 1) { if (self->priv->popup_timer) { g_source_remove(self->priv->popup_timer); self->priv->popup_timer = 0; } if (self->priv->current_popup_item) { hide_item_popup(self); } } switch (event->button) { case 1: self->priv->primary_button_down = TRUE; break; case 2: return FALSE; // Let the panel handle a middle button press. break; case 3: gtk_menu_popup(GTK_MENU(g_object_get_qdata(G_OBJECT(item), mdock_widget_item_menu_quark())), NULL, NULL, NULL, NULL, event->button, event->time); break; } return TRUE; } static gboolean handle_item_button_release(MDockWidget *self, GdkEventButton *event, MDockItem *item) { switch (event->button) { case 1: if (self->priv->primary_button_down) { mdock_item_activate(item); self->priv->primary_button_down = FALSE; break; } break; } return TRUE; } static void handle_item_drag_begin(MDockWidget *self, GdkDragContext *context, MDockItem *item) { gtk_drag_set_icon_pixbuf(context, mdock_item_get_icon_pixbuf(item), 0, 0); self->priv->primary_button_down = FALSE; self->priv->current_pointed_item = NULL; } static void handle_item_drag_end(MDockWidget *self, GdkDragContext *context, MDockItem *item) { // Nothing to do. } static void handle_item_drag_data_get(MDockWidget *self, GdkDragContext *context, GtkSelectionData *selection_data, guint target_type, guint timestamp, MDockItem *item) { GSequenceIter *iter = g_object_get_qdata(G_OBJECT(item), mdock_widget_item_iter_quark()); GDesktopAppInfo *appinfo = mdock_item_get_desktop_app_info(item); gint32 position = g_sequence_iter_get_position(iter); switch (target_type) { case DRAG_TYPE_ITEM: gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data), sizeof(gint32)*8, (guchar*)&position, sizeof(gint32)); break; case DRAG_TYPE_URILIST: if (appinfo) { const gchar *strv[2] = { g_desktop_app_info_get_filename(appinfo), NULL }; gtk_selection_data_set_uris(selection_data, (gchar**) strv); } break; } } static void handle_item_drag_data_delete(MDockWidget *self, GdkDragContext *context, MDockItem *item) { // Ignored, as this only happens when moving an applet item. } static GSequenceIter * find_drop_position_at_coordinates(MDockWidget *self, gint x, gint y, gboolean moving) { GtkOrientation orientation = gtk_orientable_get_orientation(GTK_ORIENTABLE(self)); gint nitems = g_sequence_get_length(self->priv->items) - (moving ? 1 : 0); GtkAllocation allocation; gtk_widget_get_allocation(GTK_WIDGET(self), &allocation); const gint major_length = orientation == GTK_ORIENTATION_HORIZONTAL ? allocation.width : allocation.height; if (nitems == 0) { return g_sequence_get_begin_iter(self->priv->items); } const gint item_size = major_length / nitems; const guint offset = item_size / 3; if (orientation == GTK_ORIENTATION_HORIZONTAL) { return g_sequence_get_iter_at_pos(self->priv->items, (x + offset) / item_size); } else { return g_sequence_get_iter_at_pos(self->priv->items, (y + offset) / item_size); } } static void mdock_widget_drag_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint timestamp) { MDockWidget *self = MDOCK_WIDGET(widget); const guchar *data = gtk_selection_data_get_data(selection_data); gchar **uris; switch (info) { case DRAG_TYPE_ITEM: if (data) { gint32 position = *(gint32*)data; GSequenceIter *iter = g_sequence_get_iter_at_pos(self->priv->items, position); g_warn_if_fail(iter); GSequenceIter *pos_iter = find_drop_position_at_coordinates(self, x, y, TRUE); move_item_to_position(self, iter, pos_iter); if (mdock_item_get_pinned(MDOCK_ITEM(g_sequence_get(iter)))) { save_items_to_settings(self); } } gtk_drag_finish(context, TRUE, TRUE, timestamp); break; case DRAG_TYPE_URILIST: uris = gtk_selection_data_get_uris(selection_data); if (uris) { GSequenceIter *pos_iter = find_drop_position_at_coordinates(self, x, y, TRUE); gint position = g_sequence_iter_get_position(pos_iter); for (int i = 0; uris[i]; i++) { const gchar *uri = uris[i]; gchar *filename = g_filename_from_uri(uri, NULL, NULL); if (!filename) { g_message("dropped uri is not a local filename: %s", uri); continue; } GDesktopAppInfo *appinfo = g_desktop_app_info_new_from_filename(filename); if (appinfo) { const gchar *desktopid = g_app_info_get_id(G_APP_INFO(appinfo)); GSequenceIter *iter = g_hash_table_lookup(self->priv->desktopid_to_item, desktopid); if (!iter) { // This desktopid is not yet already on the dock. g_debug("creating launcher for dropped item"); MDockItem *item = mdock_item_new(); mdock_item_set_desktop_app_info(item, appinfo); mdock_item_set_pinned(item, TRUE); iter = g_sequence_insert_before(pos_iter, item); g_hash_table_insert(self->priv->desktopid_to_item, g_strdup(desktopid), iter); connect_item(self, item, iter); gtk_widget_show(GTK_WIDGET(item)); gtk_box_pack_start(GTK_BOX(self), GTK_WIDGET(item), TRUE, TRUE, 0); gtk_box_reorder_child(GTK_BOX(self), GTK_WIDGET(item), position); } g_object_unref(appinfo); } else { g_message("dropped uri is not a desktop file: %s", filename); } g_free(filename); } g_strfreev(uris); save_items_to_settings(self); } gtk_drag_finish(context, TRUE, FALSE, timestamp); break; default: g_warning("drag target type not found"); gtk_drag_finish(context, FALSE, FALSE, timestamp); break; } } static gboolean mdock_widget_drag_drop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint timestamp) { GtkTargetList *target_list = gtk_drag_dest_get_target_list(widget); GdkAtom target = gtk_drag_dest_find_target(widget, context, target_list); guint info; gboolean drag_ok = FALSE; if (target != GDK_NONE && gtk_target_list_find(target_list, target, &info)) { switch (info) { case DRAG_TYPE_ITEM: case DRAG_TYPE_URILIST: drag_ok = TRUE; break; default: break; } } else { g_warning("drag target type not found"); } if (drag_ok) { gtk_drag_get_data(widget, context, target, timestamp); } else { gtk_drag_finish(context, FALSE, FALSE, timestamp); } return TRUE; } static gboolean mdock_widget_drag_motion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint timestamp) { GtkTargetList *target_list = gtk_drag_dest_get_target_list(widget); GdkAtom target = gtk_drag_dest_find_target(widget, context, target_list); guint info; gboolean drag_ok = FALSE; if (target != GDK_NONE && gtk_target_list_find(target_list, target, &info)) { switch (info) { case DRAG_TYPE_ITEM: gdk_drag_status(context, GDK_ACTION_MOVE, timestamp); drag_ok = TRUE; break; case DRAG_TYPE_URILIST: gdk_drag_status(context, GDK_ACTION_COPY, timestamp); drag_ok = TRUE; break; default: break; } } else { g_warning("drag target type not found"); } if (drag_ok) { // TODO Draw placeholder } else { gdk_drag_status(context, 0, timestamp); } return TRUE; } static gboolean handle_popup_timer(gpointer user_data) { MDockWidget *self = MDOCK_WIDGET(user_data); if (self->priv->current_popup_item) { hide_item_popup(self); } if (self->priv->current_pointed_item) { show_item_popup(self, self->priv->current_pointed_item); } self->priv->popup_timer = 0; return G_SOURCE_REMOVE; } static gboolean handle_item_enter(MDockWidget *self, GdkEventCrossing *event, MDockItem *item) { if (self->priv->primary_button_down) { // Do nothing if dragging return FALSE; } g_warn_if_fail(!self->priv->current_pointed_item || self->priv->current_pointed_item == item); self->priv->current_pointed_item = item; if (self->priv->current_popup_item) { if (self->priv->popup_timer) { g_source_remove(self->priv->popup_timer); self->priv->popup_timer = 0; } self->priv->popup_timer = g_timeout_add(POPUP_MOVE_TIMEOUT, handle_popup_timer, self); } else if (!self->priv->popup_timer) { self->priv->popup_timer = g_timeout_add(POPUP_SHOW_TIMEOUT, handle_popup_timer, self); } return FALSE; } static gboolean handle_item_leave(MDockWidget *self, GdkEventCrossing *event, MDockItem *item) { if (self->priv->primary_button_down) { // Do nothing if dragging return FALSE; } g_warn_if_fail(!self->priv->current_pointed_item || self->priv->current_pointed_item == item); self->priv->current_pointed_item = NULL; if (self->priv->current_popup_item && !self->priv->popup_timer) { g_warn_if_fail(!self->priv->current_popup_item || self->priv->current_popup_item == item); self->priv->popup_timer = g_timeout_add(POPUP_HIDE_TIMEOUT, handle_popup_timer, self); } return FALSE; } static gboolean handle_item_selector_enter(MDockWidget *self, GdkEventCrossing *event, MDockItemWindowSelector *selector) { if (event->detail == GDK_NOTIFY_INFERIOR) return FALSE; g_warn_if_fail(self->priv->current_popup_item); if (self->priv->popup_timer) { g_source_remove(self->priv->popup_timer); self->priv->popup_timer = 0; } return FALSE; } static gboolean handle_item_selector_leave(MDockWidget *self, GdkEventCrossing *event, MDockItemWindowSelector *selector) { if (event->detail == GDK_NOTIFY_INFERIOR) return FALSE; g_warn_if_fail(self->priv->current_popup_item); if (!self->priv->popup_timer) { self->priv->popup_timer = g_timeout_add(POPUP_HIDE_TIMEOUT, handle_popup_timer, self); } return FALSE; } static void connect_item(MDockWidget *self, MDockItem *item, GSequenceIter *position) { g_object_set_qdata(G_OBJECT(item), mdock_widget_item_iter_quark(), position);; g_signal_connect_object(item, "notify::pinned", G_CALLBACK(handle_item_pinned_changed), self, G_CONNECT_SWAPPED); gtk_drag_source_set(GTK_WIDGET(item), GDK_BUTTON1_MASK, drag_types, G_N_ELEMENTS(drag_types), GDK_ACTION_MOVE); g_signal_connect_object(item, "button-press-event", G_CALLBACK(handle_item_button_press), self, G_CONNECT_SWAPPED); g_signal_connect_object(item, "button-release-event", G_CALLBACK(handle_item_button_release), self, G_CONNECT_SWAPPED); g_signal_connect_object(item, "drag-begin", G_CALLBACK(handle_item_drag_begin), self, G_CONNECT_SWAPPED); g_signal_connect_object(item, "drag-end", G_CALLBACK(handle_item_drag_end), self, G_CONNECT_SWAPPED); g_signal_connect_object(item, "drag-data-get", G_CALLBACK(handle_item_drag_data_get), self, G_CONNECT_SWAPPED); g_signal_connect_object(item, "drag-data-delete", G_CALLBACK(handle_item_drag_data_delete), self, G_CONNECT_SWAPPED); g_signal_connect_object(item, "enter-notify-event", G_CALLBACK(handle_item_enter), self, G_CONNECT_SWAPPED); g_signal_connect_object(item, "leave-notify-event", G_CALLBACK(handle_item_leave), self, G_CONNECT_SWAPPED); MDockItemMenu *menu = mdock_item_menu_new(item); g_object_set_qdata_full(G_OBJECT(item), mdock_widget_item_menu_quark(), menu, (GDestroyNotify)gtk_widget_destroy); MDockItemWindowSelector *selector = mdock_item_window_selector_new(item); g_object_set_qdata_full(G_OBJECT(item), mdock_widget_item_window_selector_quark(), selector, (GDestroyNotify)gtk_widget_destroy); g_object_bind_property(self, "orientation", selector, "orientation", G_BINDING_SYNC_CREATE); g_signal_connect_object(selector, "enter-notify-event", G_CALLBACK(handle_item_selector_enter), self, G_CONNECT_SWAPPED); g_signal_connect_object(selector, "leave-notify-event", G_CALLBACK(handle_item_selector_leave), self, G_CONNECT_SWAPPED); } static void reload_items_from_settings(MDockWidget *self) { self->priv->loading_settings = TRUE; GSequenceIter *seq_iter = g_sequence_get_begin_iter(self->priv->items); if (self->priv->settings) { GVariantIter *set_iter; g_settings_get(self->priv->settings, "items", "as", &set_iter); gchar *value; while (g_variant_iter_next(set_iter, "s", &value)) { GDesktopAppInfo *set_appinfo = g_desktop_app_info_new_from_filename(value); const gchar *set_desktopid = g_app_info_get_id(G_APP_INFO(set_appinfo)); GSequenceIter *iter = g_hash_table_lookup(self->priv->desktopid_to_item, set_desktopid); MDockItem *item; if (iter) { // This item is already on the dock, let's move it to the front and pin it. item = MDOCK_ITEM(g_sequence_get(iter)); mdock_item_set_pinned(item, TRUE); g_sequence_move(iter, seq_iter); } else { // Item not already in the dock. item = mdock_item_new(); mdock_item_set_desktop_app_info(item, set_appinfo); mdock_item_set_pinned(item, TRUE); iter = g_sequence_insert_before(seq_iter, item); g_hash_table_insert(self->priv->desktopid_to_item, g_strdup(set_desktopid), iter); connect_item(self, item, iter); gtk_widget_show(GTK_WIDGET(item)); gtk_box_pack_start(GTK_BOX(self), GTK_WIDGET(item), TRUE, TRUE, 0); } // Fix the box order of the item... gtk_box_reorder_child(GTK_BOX(self), GTK_WIDGET(item), g_sequence_iter_get_position(iter)); g_object_unref(set_appinfo); g_free(value); } g_variant_iter_free(set_iter); } while (!g_sequence_iter_is_end(seq_iter)) { MDockItem *item = g_sequence_get(seq_iter); if (mdock_item_get_pinned(item)) { mdock_item_set_pinned(item, FALSE); if (mdock_item_get_num_windows(item) == 0) { // Remove this item // The iterator will be destroyed, so take care. GSequenceIter *iter = seq_iter; seq_iter = g_sequence_iter_next(iter); remove_item(self, iter); continue; } } seq_iter = g_sequence_iter_next(seq_iter); } self->priv->loading_settings = FALSE; } static void handle_active_window_changed(MDockWidget *self, WnckWindow *previous_window, WnckScreen *screen) { WnckWindow *window = wnck_screen_get_active_window(screen); GSequenceIter *iter = g_hash_table_lookup(self->priv->window_to_item, window); if (iter) { MDockItem *item = MDOCK_ITEM(g_sequence_get(iter)); mdock_item_set_last_active_window(item, window); mdock_item_window_selector_set_active_window(selector_from_item(item), window); } } static void handle_window_closed(MDockWidget *self, WnckWindow *window, WnckScreen *screen) { GSequenceIter *iter = g_hash_table_lookup(self->priv->window_to_item, window); if (!iter) { // We never managed this window return; } MDockItem *item = MDOCK_ITEM(g_sequence_get(iter)); mdock_item_remove_window(item, window); mdock_item_window_selector_remove_window(selector_from_item(item), window); g_hash_table_remove(self->priv->window_to_item, window); if (!mdock_item_has_windows(item) && !mdock_item_get_pinned(item)) { // Removing item from dock remove_item(self, iter); } } static void handle_window_opened(MDockWidget *self, WnckWindow *window, WnckScreen *screen) { if (wnck_window_is_skip_tasklist(window)) { return; } AppId *appid = app_id_from_window(window); GSequenceIter *iter = g_hash_table_lookup(self->priv->appid_to_item, appid); if (iter) { MDockItem *item = MDOCK_ITEM(g_sequence_get(iter)); mdock_item_add_window(item, window); mdock_item_window_selector_add_window(selector_from_item(item), window); app_id_destroy(appid); g_hash_table_insert(self->priv->window_to_item, window, iter); return; } const gchar *desktopid = match_appid_to_desktopid(appid); if (desktopid) { iter = g_hash_table_lookup(self->priv->desktopid_to_item, desktopid); if (iter) { MDockItem *item = MDOCK_ITEM(g_sequence_get(iter)); mdock_item_add_window(item, window); mdock_item_window_selector_add_window(selector_from_item(item), window); g_hash_table_insert(self->priv->appid_to_item, appid, iter); // takes ownership of appid g_hash_table_insert(self->priv->window_to_item, window, iter); return; } } MDockItem *item = mdock_item_new(); iter = g_sequence_append(self->priv->items, item); if (desktopid) { GDesktopAppInfo *app_info = g_desktop_app_info_new(desktopid); mdock_item_set_desktop_app_info(item, app_info); g_object_unref(app_info); } connect_item(self, item, iter); mdock_item_add_window(item, window); mdock_item_window_selector_add_window(selector_from_item(item), window); g_hash_table_insert(self->priv->appid_to_item, appid, iter); // takes ownership of appid if (desktopid) { g_hash_table_insert(self->priv->desktopid_to_item, g_strdup(desktopid), iter); } g_hash_table_insert(self->priv->window_to_item, window, iter); gtk_widget_show(GTK_WIDGET(item)); gtk_box_pack_start(GTK_BOX(self), GTK_WIDGET(item), TRUE, TRUE, 0); } static void handle_settings_items_changed(MDockWidget *self, gchar *key, GSettings *settings) { if (!self->priv->loading_settings) { reload_items_from_settings(self); } } static void mdock_widget_dispose(GObject *obj) { MDockWidget *self = MDOCK_WIDGET(obj); g_clear_object(&self->priv->settings); if (self->priv->wnck_screen) { g_signal_handlers_disconnect_by_data(self->priv->wnck_screen, self); self->priv->wnck_screen = NULL; } G_OBJECT_CLASS(mdock_widget_parent_class)->dispose(obj); } static void mdock_widget_finalize(GObject *obj) { MDockWidget *self = MDOCK_WIDGET(obj); g_sequence_free(self->priv->items); g_hash_table_destroy(self->priv->appid_to_item); g_hash_table_destroy(self->priv->desktopid_to_item); g_hash_table_destroy(self->priv->window_to_item); G_OBJECT_CLASS(mdock_widget_parent_class)->finalize(obj); } static void mdock_widget_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { MDockWidget *self = MDOCK_WIDGET(object); switch (property_id) { case PROP_SETTINGS: if (self->priv->settings) { g_signal_handlers_disconnect_by_data(self->priv->settings, self); g_object_unref(self->priv->settings); } self->priv->settings = g_value_dup_object(value); if (self->priv->settings) { g_signal_connect_object(self->priv->settings, "changed::items", G_CALLBACK(handle_settings_items_changed), self, G_CONNECT_SWAPPED); } reload_items_from_settings(self); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void mdock_widget_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { MDockWidget *self = MDOCK_WIDGET(object); switch (property_id) { case PROP_SETTINGS: g_value_set_object(value, self->priv->settings); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void mdock_widget_init(MDockWidget *self) { self->priv = MDOCK_WIDGET_GET_PRIVATE(self); #ifdef HAVE_ZEITGEIST // Initialize zeitgeist now, // since hilarity ensues if you try to do it on a Wnck callback... zeitgeist_log_get_default(); #endif gtk_drag_dest_set(GTK_WIDGET(self), 0, drag_types, G_N_ELEMENTS(drag_types), GDK_ACTION_MOVE); self->priv->wnck_screen = wnck_screen_get_default(); g_signal_connect_object(self->priv->wnck_screen, "active-window-changed", G_CALLBACK(handle_active_window_changed), self, G_CONNECT_SWAPPED); g_signal_connect_object(self->priv->wnck_screen, "window-closed", G_CALLBACK(handle_window_closed), self, G_CONNECT_SWAPPED); g_signal_connect_object(self->priv->wnck_screen, "window-opened", G_CALLBACK(handle_window_opened), self, G_CONNECT_SWAPPED); self->priv->items = g_sequence_new(NULL); self->priv->appid_to_item = g_hash_table_new_full(app_id_hash, app_id_equal, (GDestroyNotify)app_id_destroy, NULL); self->priv->desktopid_to_item = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); self->priv->window_to_item = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL); GList *windows = wnck_screen_get_windows_stacked(self->priv->wnck_screen); for (GList *l = windows; l; l = g_list_next(l)) { WnckWindow *window = l->data; handle_window_opened(self, window, self->priv->wnck_screen); } } static void mdock_widget_class_init(MDockWidgetClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); widget_class->drag_data_received = mdock_widget_drag_data_received; widget_class->drag_drop = mdock_widget_drag_drop; widget_class->drag_motion = mdock_widget_drag_motion; GObjectClass *obj_class = G_OBJECT_CLASS(klass); obj_class->dispose = mdock_widget_dispose; obj_class->finalize = mdock_widget_finalize; obj_class->set_property = mdock_widget_set_property; obj_class->get_property = mdock_widget_get_property; obj_properties[PROP_SETTINGS] = g_param_spec_object("settings", "GSettings object this widget will use", "Set the path to GSettings for this widget", G_TYPE_SETTINGS, G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(obj_class, N_PROPERTIES, obj_properties); g_type_class_add_private(klass, sizeof(MDockWidgetPrivate)); } gint mdock_widget_get_n_items(MDockWidget *self) { return g_sequence_get_length(self->priv->items); } void mdock_widget_update_background(MDockWidget *self) { g_sequence_foreach(self->priv->items, (GFunc)mdock_item_update_background, NULL); } GtkWidget *mdock_widget_new(void) { return GTK_WIDGET(g_object_new(MDOCK_TYPE_WIDGET, NULL)); } GtkWidget *mdock_widget_new_with_settings(GSettings *settings) { return GTK_WIDGET(g_object_new(MDOCK_TYPE_WIDGET, "settings", settings, NULL)); } GtkWidget *mdock_widget_new_with_settings_path(const gchar *path) { GSettings *settings = g_settings_new_with_path(MDOCK_WIDGET_GSETTINGS_SCHEMA, path); GtkWidget *self = mdock_widget_new_with_settings(settings); g_object_unref(settings); return self; }