/* * 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 "../config.h" #include "mdock-item-menu.h" #ifdef HAVE_ZEITGEIST #include #endif #define NUM_RECENT_FILES 10u #define MDOCK_ITEM_MENU_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), MDOCK_TYPE_ITEM_MENU, MDockItemMenuPrivate)) struct _MDockItemMenuPrivate { GtkSeparatorMenuItem *menu_sep1; GtkImageMenuItem *menu_close_all; GtkImageMenuItem *menu_pin; GtkImageMenuItem *menu_launch; GPtrArray *menu_recent; GCancellable *recent_fetch; time_t recent_fetch_time; }; G_DEFINE_TYPE (MDockItemMenu, mdock_item_menu, GTK_TYPE_MENU) enum { PROP_0, PROP_ITEM, N_PROPERTIES }; static GParamSpec *obj_properties[N_PROPERTIES] = { NULL }; #ifdef HAVE_ZEITGEIST #define INITIAL_REFRESH_DELAY 3 #define RECENT_FETCH_MIN_INTERVAL 15 G_DEFINE_QUARK(mdock-item-menu-file, mdock_item_menu_file) static void clear_recent_menu(MDockItemMenu *self) { g_ptr_array_set_size(self->priv->menu_recent, 0); gtk_widget_hide(GTK_WIDGET(self->priv->menu_sep1)); } static void handle_recent_activate(MDockItemMenu *self, GtkImageMenuItem *menu_item) { GFile *file = g_object_get_qdata(G_OBJECT(menu_item), mdock_item_menu_file_quark()); g_return_if_fail(file); mdock_item_launch_file(self->item, file); } static void recents_icon_ready(GObject *source, GAsyncResult *res, gpointer user_data) { GtkImageMenuItem *menu_item = GTK_IMAGE_MENU_ITEM(user_data); GFile *file = G_FILE(source); GError *error = NULL; GFileInfo *info = g_file_query_info_finish(file, res, &error); if (info) { GIcon *icon = g_file_info_get_icon(info); if (icon) { GtkImage *image = GTK_IMAGE(gtk_image_menu_item_get_image(menu_item)); gtk_image_set_from_gicon(image, icon, GTK_ICON_SIZE_MENU); } g_object_unref(info); } else { gchar *uri = g_file_get_uri(file); g_debug("Could not get icon for file '%s': %s", uri, error->message); g_free(uri); g_error_free(error); } } static void recents_result_ready(GObject *source, GAsyncResult *res, gpointer user_data) { MDockItemMenu *self = MDOCK_ITEM_MENU(user_data); ZeitgeistLog *log = ZEITGEIST_LOG(source); GError *error = NULL; ZeitgeistResultSet *results = zeitgeist_log_find_events_finish(log, res, &error); if (error) { g_warning("Could not get recents from Zeitgeist: %s", error->message); g_error_free(error); return; } g_return_if_fail(results); guint nelems = zeitgeist_result_set_size(results); g_return_if_fail(nelems <= NUM_RECENT_FILES); g_ptr_array_set_size(self->priv->menu_recent, nelems); guint i = 0; while (zeitgeist_result_set_has_next(results)) { ZeitgeistEvent *event = zeitgeist_result_set_next_value(results); g_warn_if_fail(event); g_warn_if_fail(zeitgeist_event_num_subjects(event) == 1); ZeitgeistSubject *subject = zeitgeist_event_get_subject(event, 0); g_warn_if_fail(subject); GFile *file = g_file_new_for_uri(zeitgeist_subject_get_uri(subject)); GtkImageMenuItem *menu_item = self->priv->menu_recent->pdata[i]; if (!menu_item) { menu_item = self->priv->menu_recent->pdata[i] = gtk_image_menu_item_new_with_label(zeitgeist_subject_get_text(subject)); gtk_image_menu_item_set_image(menu_item, gtk_image_new()); gtk_image_menu_item_set_always_show_image(menu_item, TRUE); gtk_menu_shell_append(GTK_MENU_SHELL(self), GTK_WIDGET(menu_item)); gtk_widget_show(GTK_WIDGET(menu_item)); g_signal_connect_object(menu_item, "activate", G_CALLBACK(handle_recent_activate), self, G_CONNECT_SWAPPED); } else { gtk_menu_item_set_label(GTK_MENU_ITEM(menu_item), zeitgeist_subject_get_text(subject)); gtk_image_clear(GTK_IMAGE(gtk_image_menu_item_get_image(menu_item))); } // Takes ownership of "file" g_object_set_qdata_full(G_OBJECT(menu_item), mdock_item_menu_file_quark(), file, (GDestroyNotify)g_object_unref); g_file_query_info_async(file, G_FILE_ATTRIBUTE_STANDARD_ICON, G_FILE_QUERY_INFO_NONE, G_PRIORITY_LOW, self->priv->recent_fetch, recents_icon_ready, menu_item); g_object_unref(event); g_object_unref(subject); i++; } g_object_unref(results); g_ptr_array_set_size(self->priv->menu_recent, i); gtk_widget_set_visible(GTK_WIDGET(self->priv->menu_sep1), i > 0); } static void refresh_recent_menu(MDockItemMenu *self) { ZeitgeistLog *log = zeitgeist_log_get_default(); g_return_if_fail(log); if (self->priv->recent_fetch) { g_cancellable_cancel(self->priv->recent_fetch); g_clear_object(&self->priv->recent_fetch); } self->priv->recent_fetch = g_cancellable_new(); time(&self->priv->recent_fetch_time); GDesktopAppInfo *appinfo = mdock_item_get_desktop_app_info(self->item); if (!appinfo) { clear_recent_menu(self); return; } g_debug("refreshing recent menu"); ZeitgeistTimeRange *range = zeitgeist_time_range_new_anytime(); GPtrArray *templs = g_ptr_array_new_full(1, (GDestroyNotify)g_object_unref); ZeitgeistEvent *templ = zeitgeist_event_new(); zeitgeist_event_set_actor_from_app_info(templ, G_APP_INFO(appinfo)); g_ptr_array_add(templs, templ); zeitgeist_log_find_events(log, range, templs, ZEITGEIST_STORAGE_STATE_ANY, NUM_RECENT_FILES, ZEITGEIST_RESULT_TYPE_MOST_RECENT_SUBJECTS, NULL, recents_result_ready, self); g_object_unref(range); g_ptr_array_unref(templs); } static gboolean handle_menu_map(MDockItemMenu *self, GdkEventAny *event) { time_t now; time(&now); if (now - self->priv->recent_fetch_time > RECENT_FETCH_MIN_INTERVAL) { refresh_recent_menu(self); } return FALSE; } static gboolean initial_menu_refresh(gpointer user_data) { MDockItemMenu *self = MDOCK_ITEM_MENU(user_data); // If we have never refreshed the menu so far, do an initial one now. if (self->priv->recent_fetch_time == 0) { refresh_recent_menu(self); } } #endif static void handle_item_pinned(MDockItemMenu *self, GParamSpec *spec, MDockItem *item) { const gboolean pinned = mdock_item_get_pinned(item); gtk_menu_item_set_label(GTK_MENU_ITEM(self->priv->menu_pin), pinned ? _("Un_pin") : _("_Pin")); } static void handle_item_desktop_app_info(MDockItemMenu *self, GParamSpec *spec, MDockItem *item) { GDesktopAppInfo *appinfo = mdock_item_get_desktop_app_info(item); gtk_widget_set_visible(GTK_WIDGET(self->priv->menu_pin), appinfo != NULL); gtk_widget_set_visible(GTK_WIDGET(self->priv->menu_launch), appinfo != NULL); if (!gtk_image_menu_item_get_image(self->priv->menu_launch) && appinfo) { GIcon *icon = g_app_info_get_icon(G_APP_INFO(appinfo)); GtkWidget *image = gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_MENU); gtk_image_menu_item_set_image(self->priv->menu_launch, image); } } static void handle_item_n_windows(MDockItemMenu *self, GParamSpec *spec, MDockItem *item) { gtk_widget_set_visible(GTK_WIDGET(self->priv->menu_close_all), mdock_item_has_windows(item)); } static void mdock_item_menu_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { MDockItemMenu *self = MDOCK_ITEM_MENU(object); switch (property_id) { case PROP_ITEM: g_clear_object(&self->item); self->item = g_value_dup_object(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void mdock_item_menu_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { MDockItemMenu *self = MDOCK_ITEM_MENU(object); switch (property_id) { case PROP_ITEM: g_value_set_object(value, self->item); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void mdock_item_menu_constructed(GObject *object) { MDockItemMenu *self = MDOCK_ITEM_MENU(object); G_OBJECT_CLASS(mdock_item_menu_parent_class)->constructed(object); g_signal_connect_object(self->priv->menu_close_all, "activate", G_CALLBACK(mdock_item_close_all_windows), self->item, G_CONNECT_SWAPPED); g_signal_connect_object(self->priv->menu_pin, "activate", G_CALLBACK(mdock_item_toggle_pinned), self->item, G_CONNECT_SWAPPED); g_signal_connect_object(self->priv->menu_launch, "activate", G_CALLBACK(mdock_item_launch), self->item, G_CONNECT_SWAPPED); g_signal_connect_object(self->item, "notify::pinned", G_CALLBACK(handle_item_pinned), self, G_CONNECT_SWAPPED); g_signal_connect_object(self->item, "notify::desktop-app-info", G_CALLBACK(handle_item_desktop_app_info), self, G_CONNECT_SWAPPED); g_signal_connect_object(self->item, "notify::n-windows", G_CALLBACK(handle_item_n_windows), self, G_CONNECT_SWAPPED); handle_item_desktop_app_info(self, NULL, self->item); handle_item_pinned(self, NULL, self->item); #ifdef HAVE_ZEITGEIST g_signal_connect(self, "map-event", G_CALLBACK(handle_menu_map), self); g_timeout_add_seconds(INITIAL_REFRESH_DELAY, initial_menu_refresh, self); #endif } static void mdock_item_menu_dispose(GObject *object) { MDockItemMenu *self = MDOCK_ITEM_MENU(object); g_cancellable_cancel(self->priv->recent_fetch); g_clear_object(&self->item); g_ptr_array_free(self->priv->menu_recent, TRUE); g_clear_object(&self->priv->recent_fetch); G_OBJECT_CLASS (mdock_item_menu_parent_class)->dispose(object); } static void mdock_item_menu_finalize (GObject *object) { MDockItemMenu *self = MDOCK_ITEM_MENU(object); G_OBJECT_CLASS (mdock_item_menu_parent_class)->finalize (object); } static void mdock_item_menu_init (MDockItemMenu *self) { self->priv = MDOCK_ITEM_MENU_GET_PRIVATE (self); self->priv->menu_close_all = GTK_IMAGE_MENU_ITEM(gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE, NULL)); gtk_menu_shell_append(GTK_MENU_SHELL(self), GTK_WIDGET(self->priv->menu_close_all)); self->priv->menu_pin = GTK_IMAGE_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(_("_Pin"))); gtk_menu_shell_append(GTK_MENU_SHELL(self), GTK_WIDGET(self->priv->menu_pin)); self->priv->menu_launch = GTK_IMAGE_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(_("_New instance"))); gtk_menu_shell_append(GTK_MENU_SHELL(self), GTK_WIDGET(self->priv->menu_launch)); self->priv->menu_sep1 = GTK_SEPARATOR_MENU_ITEM(gtk_separator_menu_item_new()); gtk_menu_shell_append(GTK_MENU_SHELL(self), GTK_WIDGET(self->priv->menu_sep1)); self->priv->menu_recent = g_ptr_array_new_with_free_func((GDestroyNotify)gtk_widget_destroy); } static void mdock_item_menu_class_init (MDockItemMenuClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS (klass); obj_class->set_property = mdock_item_menu_set_property; obj_class->get_property = mdock_item_menu_get_property; obj_class->constructed = mdock_item_menu_constructed; obj_class->dispose = mdock_item_menu_dispose; obj_class->finalize = mdock_item_menu_finalize; g_type_class_add_private(obj_class, sizeof(MDockItemMenuPrivate)); obj_properties[PROP_ITEM] = g_param_spec_object("item", "The MDockItem the options in this menu apply to", "Set the MDockItem the options in this menu apply to", MDOCK_TYPE_ITEM, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(obj_class, N_PROPERTIES, obj_properties); } MDockItemMenu * mdock_item_menu_new(MDockItem *item) { return g_object_new(MDOCK_TYPE_ITEM_MENU, "item", item, NULL); }