Adding upstream version 0.9.0.
[debian/lxpanel.git] / plugins / dirmenu.c
CommitLineData
0688b017
AG
1/*
2 * Copyright (C) 2006-2008 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
3 * 2006 Jim Huang <jserv.tw@gmail.com>
4 * 2008 Fred Chien <fred@lxde.org>
5 * 2009-2010 Marty Jack <martyj19@comcast.net>
6 * 2010 Julien Lavergne <julien.lavergne@gmail.com>
7 * 2013 Henry Gebhardt <hsggebhardt@gmail.com>
7a1c5048 8 * 2014-2016 Andriy Grytsenko <andrej@rep.kiev.ua>
0688b017
AG
9 *
10 * This file is a part of LXPanel project.
6cc5e1a6
DB
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software Foundation,
24 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 */
26
27#include <stdlib.h>
28#include <unistd.h>
29
6cc5e1a6 30#include <glib/gi18n.h>
6b775dbb 31#include <libfm/fm-gtk.h>
6cc5e1a6
DB
32#include <string.h>
33
6cc5e1a6
DB
34#include "misc.h"
35#include "plugin.h"
6cc5e1a6 36
4652f59b
DB
37/* Temporary for sort of directory names. */
38typedef struct _directory_name {
39 struct _directory_name * flink;
40 char * directory_name;
41 char * directory_name_collate_key;
42} DirectoryName;
43
2ba86315 44/* Private context for directory menu plugin. */
6cc5e1a6 45typedef struct {
6b775dbb
AG
46 LXPanel * panel; /* The panel and settings are required to apply config */
47 config_setting_t * settings;
2ba86315
DB
48 char * image; /* Icon for top level widget */
49 char * path; /* Top level path for widget */
50 char * name; /* User's label for widget */
51 GdkPixbuf * folder_icon; /* Icon for folders */
52} DirMenuPlugin;
53
6b775dbb
AG
54static GtkWidget * dirmenu_create_menu(DirMenuPlugin * dm, const char * path, gboolean open_at_top);
55static void dirmenu_destructor(gpointer user_data);
56static gboolean dirmenu_apply_configuration(gpointer user_data);
6cc5e1a6 57
6cc5e1a6 58
2ba86315 59/* Handler for activate event on popup Open menu item. */
6b775dbb 60static void dirmenu_menuitem_open_directory(GtkWidget * item, DirMenuPlugin * dm)
6cc5e1a6 61{
6b775dbb
AG
62 FmPath *path = fm_path_new_for_str(g_object_get_data(G_OBJECT(gtk_widget_get_parent(item)), "path"));
63 lxpanel_launch_path(dm->panel, path);
64 fm_path_unref(path);
6cc5e1a6
DB
65}
66
2ba86315 67/* Handler for activate event on popup Open In Terminal menu item. */
6b775dbb 68static void dirmenu_menuitem_open_in_terminal(GtkWidget * item, DirMenuPlugin * dm)
6cc5e1a6 69{
6b775dbb 70 fm_terminal_launch(g_object_get_data(G_OBJECT(gtk_widget_get_parent(item)), "path"), NULL);
6cc5e1a6
DB
71}
72
2ba86315 73/* Handler for select event on popup menu item. */
6b775dbb 74static void dirmenu_menuitem_select(GtkMenuItem * item, DirMenuPlugin * dm)
6cc5e1a6 75{
2ba86315
DB
76 GtkWidget * sub = gtk_menu_item_get_submenu(item);
77 if (sub != NULL)
78 {
79 /* On first reference, populate the submenu using the parent directory and the item directory name. */
80 GtkMenu * parent = GTK_MENU(gtk_widget_get_parent(GTK_WIDGET(item)));
81 char * path = (char *) g_object_get_data(G_OBJECT(sub), "path");
82 if (path == NULL)
83 {
84 path = g_build_filename(
85 (char *) g_object_get_data(G_OBJECT(parent), "path"),
86 (char *) g_object_get_data(G_OBJECT(item), "name"),
87 NULL);
6b775dbb 88 sub = dirmenu_create_menu(dm, path, TRUE);
2ba86315
DB
89 g_free(path);
90 gtk_menu_item_set_submenu(item, sub);
91 }
6cc5e1a6 92 }
6cc5e1a6
DB
93}
94
2ba86315 95/* Handler for deselect event on popup menu item. */
6b775dbb 96static void dirmenu_menuitem_deselect(GtkMenuItem * item, DirMenuPlugin * dm)
6cc5e1a6 97{
2ba86315
DB
98 /* Delete old menu on deselect to save resource. */
99 gtk_menu_item_set_submenu(item, gtk_menu_new());
6cc5e1a6
DB
100}
101
2ba86315 102/* Handler for selection-done event on popup menu. */
6b775dbb 103static void dirmenu_menu_selection_done(GtkWidget * menu, DirMenuPlugin * dm)
6cc5e1a6 104{
2ba86315 105 gtk_widget_destroy(menu);
6cc5e1a6 106}
6cc5e1a6 107
2ba86315 108/* Position-calculation callback for popup menu. */
6b775dbb 109static void dirmenu_popup_set_position(GtkWidget * menu, gint * px, gint * py, gboolean * push_in, GtkWidget * p)
6cc5e1a6 110{
6b775dbb 111 DirMenuPlugin * dm = lxpanel_plugin_get_data(p);
2ba86315
DB
112
113 /* Determine the coordinates. */
6b775dbb 114 lxpanel_plugin_popup_set_position_helper(dm->panel, p, menu, px, py);
2ba86315 115 *push_in = TRUE;
6cc5e1a6
DB
116}
117
2ba86315 118/* Create a menu populated with all subdirectories. */
6b775dbb 119static GtkWidget * dirmenu_create_menu(DirMenuPlugin * dm, const char * path, gboolean open_at_top)
6cc5e1a6 120{
2ba86315
DB
121 /* Create a menu. */
122 GtkWidget * menu = gtk_menu_new();
123
124 if (dm->folder_icon == NULL)
6cc5e1a6 125 {
2ba86315
DB
126 int w;
127 int h;
128 gtk_icon_size_lookup_for_settings(gtk_widget_get_settings(menu), GTK_ICON_SIZE_MENU, &w, &h);
129 dm->folder_icon = gtk_icon_theme_load_icon(
6b775dbb 130 panel_get_icon_theme(dm->panel),
2ba86315
DB
131 "gnome-fs-directory", MAX(w, h), 0, NULL);
132 if (dm->folder_icon == NULL)
133 dm->folder_icon = gtk_widget_render_icon(menu, GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU, NULL);
6cc5e1a6
DB
134 }
135
2ba86315
DB
136 g_object_set_data_full(G_OBJECT(menu), "path", g_strdup(path), g_free);
137
138 /* Scan the specified directory to populate the menu with its subdirectories. */
4652f59b 139 DirectoryName * dir_list = NULL;
2ba86315
DB
140 GDir * dir = g_dir_open(path, 0, NULL);
141 if (dir != NULL)
142 {
143 const char * name;
4652f59b 144 while ((name = g_dir_read_name(dir)) != NULL) /* Memory owned by glib */
2ba86315
DB
145 {
146 /* Omit hidden files. */
147 if (name[0] != '.')
148 {
149 char * full = g_build_filename(path, name, NULL);
150 if (g_file_test(full, G_FILE_TEST_IS_DIR))
151 {
4652f59b
DB
152 /* Convert name to UTF-8 and to the collation key. */
153 char * directory_name = g_filename_display_name(name);
154 char * directory_name_collate_key = g_utf8_collate_key(directory_name, -1);
155
156 /* Locate insertion point. */
157 DirectoryName * dir_pred = NULL;
158 DirectoryName * dir_cursor;
159 for (dir_cursor = dir_list; dir_cursor != NULL; dir_pred = dir_cursor, dir_cursor = dir_cursor->flink)
160 {
161 if (strcmp(directory_name_collate_key, dir_cursor->directory_name_collate_key) <= 0)
162 break;
163 }
164
165 /* Allocate and initialize sorted directory name entry. */
166 dir_cursor = g_new0(DirectoryName, 1);
167 dir_cursor->directory_name = directory_name;
168 dir_cursor->directory_name_collate_key = directory_name_collate_key;
169 if (dir_pred == NULL)
170 {
171 dir_cursor->flink = dir_list;
172 dir_list = dir_cursor;
173 }
174 else
175 {
176 dir_cursor->flink = dir_pred->flink;
177 dir_pred->flink = dir_cursor;
178 }
2ba86315
DB
179 }
180 g_free(full);
6cc5e1a6 181 }
6cc5e1a6 182 }
2ba86315 183 g_dir_close(dir);
6cc5e1a6
DB
184 }
185
4652f59b
DB
186 /* The sorted directory name list is complete. Loop to create the menu. */
187 DirectoryName * dir_cursor;
188 while ((dir_cursor = dir_list) != NULL)
189 {
190 /* Create and initialize menu item. */
191 GtkWidget * item = gtk_image_menu_item_new_with_label(dir_cursor->directory_name);
192 gtk_image_menu_item_set_image(
193 GTK_IMAGE_MENU_ITEM(item),
194 gtk_image_new_from_stock(GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU));
195 GtkWidget * dummy = gtk_menu_new();
196 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), dummy);
197 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
198
199 /* Unlink and free sorted directory name element, but reuse the directory name string. */
200 dir_list = dir_cursor->flink;
201 g_object_set_data_full(G_OBJECT(item), "name", dir_cursor->directory_name, g_free);
202 g_free(dir_cursor->directory_name_collate_key);
203 g_free(dir_cursor);
204
205 /* Connect signals. */
6b775dbb
AG
206 g_signal_connect(G_OBJECT(item), "select", G_CALLBACK(dirmenu_menuitem_select), dm);
207 g_signal_connect(G_OBJECT(item), "deselect", G_CALLBACK(dirmenu_menuitem_deselect), dm);
4652f59b
DB
208 }
209
2ba86315
DB
210 /* Create "Open" and "Open in Terminal" items. */
211 GtkWidget * item = gtk_image_menu_item_new_from_stock( GTK_STOCK_OPEN, NULL );
6b775dbb 212 g_signal_connect(item, "activate", G_CALLBACK(dirmenu_menuitem_open_directory), dm);
2ba86315 213 GtkWidget * term = gtk_menu_item_new_with_mnemonic( _("Open in _Terminal") );
6b775dbb 214 g_signal_connect(term, "activate", G_CALLBACK(dirmenu_menuitem_open_in_terminal), dm);
2ba86315
DB
215
216 /* Insert or append based on caller's preference. */
217 if (open_at_top)
218 {
219 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new(), 0);
220 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), term, 0);
221 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), item, 0);
6cc5e1a6
DB
222 }
223 else {
2ba86315
DB
224 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
225 gtk_menu_shell_append(GTK_MENU_SHELL(menu), term);
226 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
6cc5e1a6
DB
227 }
228
2ba86315
DB
229 /* Show the menu and return. */
230 gtk_widget_show_all(menu);
6cc5e1a6
DB
231 return menu;
232}
233
2ba86315 234/* Show a menu of subdirectories. */
6b775dbb 235static void dirmenu_show_menu(GtkWidget * widget, DirMenuPlugin * dm, int btn, guint32 time)
6cc5e1a6 236{
2ba86315 237 /* Create a menu populated with all subdirectories. */
6b775dbb
AG
238 GtkWidget * menu = dirmenu_create_menu(dm, dm->path, FALSE);
239 g_signal_connect(menu, "selection-done", G_CALLBACK(dirmenu_menu_selection_done), dm);
6cc5e1a6 240
2ba86315 241 /* Show the menu. Use a positioning function to get it placed next to the top level widget. */
6b775dbb 242 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, (GtkMenuPositionFunc) dirmenu_popup_set_position, widget, btn, time);
6cc5e1a6
DB
243}
244
2ba86315 245/* Handler for button-press-event on top level widget. */
6b775dbb 246static gboolean dirmenu_button_press_event(GtkWidget * widget, GdkEventButton * event, LXPanel * p)
6cc5e1a6 247{
6b775dbb 248 DirMenuPlugin * dm = lxpanel_plugin_get_data(widget);
2ba86315
DB
249
250 if (event->button == 1)
251 {
6b775dbb 252 dirmenu_show_menu(widget, dm, event->button, event->time);
7a1c5048 253 return TRUE;
2ba86315 254 }
7a1c5048
AG
255 return FALSE;
256}
257
258static gboolean dirmenu_button_release_event(GtkWidget * widget, GdkEventButton * event, DirMenuPlugin * dm)
259{
260 if (event->button == 2)
2ba86315 261 {
6b775dbb 262 fm_terminal_launch(dm->path, NULL);
2ba86315 263 }
7a1c5048 264 return FALSE;
6cc5e1a6
DB
265}
266
2ba86315 267/* Plugin constructor. */
6b775dbb 268static GtkWidget *dirmenu_constructor(LXPanel *panel, config_setting_t *settings)
6cc5e1a6 269{
2ba86315
DB
270 /* Allocate and initialize plugin context and set into Plugin private data pointer. */
271 DirMenuPlugin * dm = g_new0(DirMenuPlugin, 1);
6b775dbb
AG
272 GtkWidget * p;
273 const char *str;
6cc5e1a6 274
2ba86315 275 /* Load parameters from the configuration file. */
6b775dbb
AG
276 if (config_setting_lookup_string(settings, "image", &str))
277 dm->image = g_strdup(str);
278 if (config_setting_lookup_string(settings, "path", &str))
279 dm->path = expand_tilda(str);
280 else
281 dm->path = g_strdup(fm_get_home_dir());
282 if (config_setting_lookup_string(settings, "name", &str))
283 dm->name = g_strdup(str);
284
285 /* Save construction pointers */
286 dm->panel = panel;
287 dm->settings = settings;
6cc5e1a6 288
2ba86315
DB
289 /* Allocate top level widget and set into Plugin widget pointer.
290 * It is not known why, but the button text will not draw if it is edited from empty to non-empty
291 * unless this strategy of initializing it with a non-empty value first is followed. */
6b775dbb
AG
292 p = lxpanel_button_new_for_icon(panel,
293 ((dm->image != NULL) ? dm->image : "file-manager"),
294 NULL, "Temp");
295 lxpanel_plugin_set_data(p, dm, dirmenu_destructor);
2ba86315
DB
296
297 /* Initialize the widget. */
298 dirmenu_apply_configuration(p);
299
7a1c5048
AG
300 g_signal_connect(G_OBJECT(p), "button-release-event",
301 G_CALLBACK(dirmenu_button_release_event), dm);
302
2ba86315 303 /* Show the widget and return. */
6b775dbb 304 return p;
2ba86315
DB
305}
306
307/* Plugin destructor. */
6b775dbb 308static void dirmenu_destructor(gpointer user_data)
2ba86315 309{
6b775dbb 310 DirMenuPlugin * dm = (DirMenuPlugin *)user_data;
2ba86315
DB
311
312 /* Release a reference on the folder icon if held. */
313 if (dm->folder_icon)
314 g_object_unref(dm->folder_icon);
315
316 /* Deallocate all memory. */
317 g_free(dm->image);
318 g_free(dm->path);
319 g_free(dm->name);
320 g_free(dm);
321}
6cc5e1a6 322
2ba86315 323/* Callback when the configuration dialog has recorded a configuration change. */
6b775dbb 324static gboolean dirmenu_apply_configuration(gpointer user_data)
2ba86315 325{
6b775dbb
AG
326 GtkWidget * p = user_data;
327 DirMenuPlugin * dm = lxpanel_plugin_get_data(p);
328 char * path = dm->path;
329
330 /* Normalize path */
331 if (path == NULL)
332 dm->path = g_strdup(fm_get_home_dir());
333 else if (path[0] == '~')
334 {
335 dm->path = expand_tilda(path);
336 g_free(path);
337 }
6cc5e1a6 338
6b775dbb
AG
339 /* Save configuration */
340 config_group_set_string(dm->settings, "path", dm->path);
341 config_group_set_string(dm->settings, "name", dm->name);
342 config_group_set_string(dm->settings, "image", dm->image);
6cc5e1a6 343
f7ecd6ce
AG
344 lxpanel_button_set_icon(p, ((dm->image != NULL) ? dm->image : "file-manager"), -1);
345 lxpanel_button_set_label(p, dm->name);
6b775dbb
AG
346
347 gtk_widget_set_tooltip_text(p, dm->path);
6b775dbb 348 return FALSE;
2ba86315 349}
6cc5e1a6 350
2ba86315 351/* Callback when the configuration dialog is to be shown. */
6b775dbb 352static GtkWidget *dirmenu_configure(LXPanel *panel, GtkWidget *p)
2ba86315 353{
6b775dbb
AG
354 DirMenuPlugin * dm = lxpanel_plugin_get_data(p);
355 return lxpanel_generic_config_dlg(_("Directory Menu"),
356 panel, dirmenu_apply_configuration, p,
2ba86315
DB
357 _("Directory"), &dm->path, CONF_TYPE_DIRECTORY_ENTRY,
358 _("Label"), &dm->name, CONF_TYPE_STR,
359 _("Icon"), &dm->image, CONF_TYPE_FILE_ENTRY,
360 NULL);
6cc5e1a6
DB
361}
362
2ba86315 363/* Plugin descriptor. */
6b775dbb
AG
364LXPanelPluginInit lxpanel_static_plugin_dirmenu = {
365 .name = N_("Directory Menu"),
366 .description = N_("Browse directory tree via menu (Author = PCMan)"),
367
368 .new_instance = dirmenu_constructor,
369 .config = dirmenu_configure,
6b775dbb 370 .button_press_event = dirmenu_button_press_event
6cc5e1a6 371};