2
* Copyright (c) 2006-2014 LxDE Developers, see the file AUTHORS for details.
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation; either version 2 of the License, or
7
* (at your option) any later version.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program; if not, write to the Free Software Foundation,
16
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
#include <glib/gi18n.h>
23
#include <libfm/fm-gtk.h>
29
/* Temporary for sort of directory names. */
30
typedef struct _directory_name {
31
struct _directory_name * flink;
32
char * directory_name;
33
char * directory_name_collate_key;
36
/* Private context for directory menu plugin. */
38
LXPanel * panel; /* The panel and settings are required to apply config */
39
config_setting_t * settings;
40
char * image; /* Icon for top level widget */
41
char * path; /* Top level path for widget */
42
char * name; /* User's label for widget */
43
GdkPixbuf * folder_icon; /* Icon for folders */
46
static GtkWidget * dirmenu_create_menu(DirMenuPlugin * dm, const char * path, gboolean open_at_top);
47
static void dirmenu_destructor(gpointer user_data);
48
static gboolean dirmenu_apply_configuration(gpointer user_data);
51
/* Handler for activate event on popup Open menu item. */
52
static void dirmenu_menuitem_open_directory(GtkWidget * item, DirMenuPlugin * dm)
54
FmPath *path = fm_path_new_for_str(g_object_get_data(G_OBJECT(gtk_widget_get_parent(item)), "path"));
55
lxpanel_launch_path(dm->panel, path);
59
/* Handler for activate event on popup Open In Terminal menu item. */
60
static void dirmenu_menuitem_open_in_terminal(GtkWidget * item, DirMenuPlugin * dm)
62
fm_terminal_launch(g_object_get_data(G_OBJECT(gtk_widget_get_parent(item)), "path"), NULL);
65
/* Handler for select event on popup menu item. */
66
static void dirmenu_menuitem_select(GtkMenuItem * item, DirMenuPlugin * dm)
68
GtkWidget * sub = gtk_menu_item_get_submenu(item);
71
/* On first reference, populate the submenu using the parent directory and the item directory name. */
72
GtkMenu * parent = GTK_MENU(gtk_widget_get_parent(GTK_WIDGET(item)));
73
char * path = (char *) g_object_get_data(G_OBJECT(sub), "path");
76
path = g_build_filename(
77
(char *) g_object_get_data(G_OBJECT(parent), "path"),
78
(char *) g_object_get_data(G_OBJECT(item), "name"),
80
sub = dirmenu_create_menu(dm, path, TRUE);
82
gtk_menu_item_set_submenu(item, sub);
87
/* Handler for deselect event on popup menu item. */
88
static void dirmenu_menuitem_deselect(GtkMenuItem * item, DirMenuPlugin * dm)
90
/* Delete old menu on deselect to save resource. */
91
gtk_menu_item_set_submenu(item, gtk_menu_new());
94
/* Handler for selection-done event on popup menu. */
95
static void dirmenu_menu_selection_done(GtkWidget * menu, DirMenuPlugin * dm)
97
gtk_widget_destroy(menu);
100
/* Position-calculation callback for popup menu. */
101
static void dirmenu_popup_set_position(GtkWidget * menu, gint * px, gint * py, gboolean * push_in, GtkWidget * p)
103
DirMenuPlugin * dm = lxpanel_plugin_get_data(p);
105
/* Determine the coordinates. */
106
lxpanel_plugin_popup_set_position_helper(dm->panel, p, menu, px, py);
110
/* Create a menu populated with all subdirectories. */
111
static GtkWidget * dirmenu_create_menu(DirMenuPlugin * dm, const char * path, gboolean open_at_top)
114
GtkWidget * menu = gtk_menu_new();
116
if (dm->folder_icon == NULL)
120
gtk_icon_size_lookup_for_settings(gtk_widget_get_settings(menu), GTK_ICON_SIZE_MENU, &w, &h);
121
dm->folder_icon = gtk_icon_theme_load_icon(
122
panel_get_icon_theme(dm->panel),
123
"gnome-fs-directory", MAX(w, h), 0, NULL);
124
if (dm->folder_icon == NULL)
125
dm->folder_icon = gtk_widget_render_icon(menu, GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU, NULL);
128
g_object_set_data_full(G_OBJECT(menu), "path", g_strdup(path), g_free);
130
/* Scan the specified directory to populate the menu with its subdirectories. */
131
DirectoryName * dir_list = NULL;
132
GDir * dir = g_dir_open(path, 0, NULL);
136
while ((name = g_dir_read_name(dir)) != NULL) /* Memory owned by glib */
138
/* Omit hidden files. */
141
char * full = g_build_filename(path, name, NULL);
142
if (g_file_test(full, G_FILE_TEST_IS_DIR))
144
/* Convert name to UTF-8 and to the collation key. */
145
char * directory_name = g_filename_display_name(name);
146
char * directory_name_collate_key = g_utf8_collate_key(directory_name, -1);
148
/* Locate insertion point. */
149
DirectoryName * dir_pred = NULL;
150
DirectoryName * dir_cursor;
151
for (dir_cursor = dir_list; dir_cursor != NULL; dir_pred = dir_cursor, dir_cursor = dir_cursor->flink)
153
if (strcmp(directory_name_collate_key, dir_cursor->directory_name_collate_key) <= 0)
157
/* Allocate and initialize sorted directory name entry. */
158
dir_cursor = g_new0(DirectoryName, 1);
159
dir_cursor->directory_name = directory_name;
160
dir_cursor->directory_name_collate_key = directory_name_collate_key;
161
if (dir_pred == NULL)
163
dir_cursor->flink = dir_list;
164
dir_list = dir_cursor;
168
dir_cursor->flink = dir_pred->flink;
169
dir_pred->flink = dir_cursor;
178
/* The sorted directory name list is complete. Loop to create the menu. */
179
DirectoryName * dir_cursor;
180
while ((dir_cursor = dir_list) != NULL)
182
/* Create and initialize menu item. */
183
GtkWidget * item = gtk_image_menu_item_new_with_label(dir_cursor->directory_name);
184
gtk_image_menu_item_set_image(
185
GTK_IMAGE_MENU_ITEM(item),
186
gtk_image_new_from_stock(GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU));
187
GtkWidget * dummy = gtk_menu_new();
188
gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), dummy);
189
gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
191
/* Unlink and free sorted directory name element, but reuse the directory name string. */
192
dir_list = dir_cursor->flink;
193
g_object_set_data_full(G_OBJECT(item), "name", dir_cursor->directory_name, g_free);
194
g_free(dir_cursor->directory_name_collate_key);
197
/* Connect signals. */
198
g_signal_connect(G_OBJECT(item), "select", G_CALLBACK(dirmenu_menuitem_select), dm);
199
g_signal_connect(G_OBJECT(item), "deselect", G_CALLBACK(dirmenu_menuitem_deselect), dm);
202
/* Create "Open" and "Open in Terminal" items. */
203
GtkWidget * item = gtk_image_menu_item_new_from_stock( GTK_STOCK_OPEN, NULL );
204
g_signal_connect(item, "activate", G_CALLBACK(dirmenu_menuitem_open_directory), dm);
205
GtkWidget * term = gtk_menu_item_new_with_mnemonic( _("Open in _Terminal") );
206
g_signal_connect(term, "activate", G_CALLBACK(dirmenu_menuitem_open_in_terminal), dm);
208
/* Insert or append based on caller's preference. */
211
gtk_menu_shell_insert(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new(), 0);
212
gtk_menu_shell_insert(GTK_MENU_SHELL(menu), term, 0);
213
gtk_menu_shell_insert(GTK_MENU_SHELL(menu), item, 0);
216
gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
217
gtk_menu_shell_append(GTK_MENU_SHELL(menu), term);
218
gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
221
/* Show the menu and return. */
222
gtk_widget_show_all(menu);
226
/* Show a menu of subdirectories. */
227
static void dirmenu_show_menu(GtkWidget * widget, DirMenuPlugin * dm, int btn, guint32 time)
229
/* Create a menu populated with all subdirectories. */
230
GtkWidget * menu = dirmenu_create_menu(dm, dm->path, FALSE);
231
g_signal_connect(menu, "selection-done", G_CALLBACK(dirmenu_menu_selection_done), dm);
233
/* Show the menu. Use a positioning function to get it placed next to the top level widget. */
234
gtk_menu_popup(GTK_MENU(menu), NULL, NULL, (GtkMenuPositionFunc) dirmenu_popup_set_position, widget, btn, time);
237
/* Handler for button-press-event on top level widget. */
238
static gboolean dirmenu_button_press_event(GtkWidget * widget, GdkEventButton * event, LXPanel * p)
240
DirMenuPlugin * dm = lxpanel_plugin_get_data(widget);
242
if (event->button == 1)
244
dirmenu_show_menu(widget, dm, event->button, event->time);
248
fm_terminal_launch(dm->path, NULL);
253
/* Plugin constructor. */
254
static GtkWidget *dirmenu_constructor(LXPanel *panel, config_setting_t *settings)
256
/* Allocate and initialize plugin context and set into Plugin private data pointer. */
257
DirMenuPlugin * dm = g_new0(DirMenuPlugin, 1);
261
/* Load parameters from the configuration file. */
262
if (config_setting_lookup_string(settings, "image", &str))
263
dm->image = g_strdup(str);
264
if (config_setting_lookup_string(settings, "path", &str))
265
dm->path = expand_tilda(str);
267
dm->path = g_strdup(fm_get_home_dir());
268
if (config_setting_lookup_string(settings, "name", &str))
269
dm->name = g_strdup(str);
271
/* Save construction pointers */
273
dm->settings = settings;
275
/* Allocate top level widget and set into Plugin widget pointer.
276
* It is not known why, but the button text will not draw if it is edited from empty to non-empty
277
* unless this strategy of initializing it with a non-empty value first is followed. */
278
p = lxpanel_button_new_for_icon(panel,
279
((dm->image != NULL) ? dm->image : "file-manager"),
281
lxpanel_plugin_set_data(p, dm, dirmenu_destructor);
282
gtk_container_set_border_width(GTK_CONTAINER(p), 0);
284
/* Initialize the widget. */
285
dirmenu_apply_configuration(p);
287
/* Show the widget and return. */
291
/* Plugin destructor. */
292
static void dirmenu_destructor(gpointer user_data)
294
DirMenuPlugin * dm = (DirMenuPlugin *)user_data;
296
/* Release a reference on the folder icon if held. */
298
g_object_unref(dm->folder_icon);
300
/* Deallocate all memory. */
307
/* Recursively apply a configuration change. */
308
static void dirmenu_apply_configuration_to_children(GtkWidget * w, DirMenuPlugin * dm)
310
if (GTK_IS_CONTAINER(w))
311
gtk_container_foreach(GTK_CONTAINER(w), (GtkCallback) dirmenu_apply_configuration_to_children, (gpointer) dm);
312
else if (GTK_IS_LABEL(w))
314
if (dm->name == NULL)
315
gtk_label_set_text(GTK_LABEL(w), NULL);
317
lxpanel_draw_label_text(dm->panel, w, dm->name, FALSE, 1, TRUE);
321
/* Callback when the configuration dialog has recorded a configuration change. */
322
static gboolean dirmenu_apply_configuration(gpointer user_data)
324
GtkWidget * p = user_data;
325
DirMenuPlugin * dm = lxpanel_plugin_get_data(p);
326
char * path = dm->path;
330
dm->path = g_strdup(fm_get_home_dir());
331
else if (path[0] == '~')
333
dm->path = expand_tilda(path);
337
/* Save configuration */
338
config_group_set_string(dm->settings, "path", dm->path);
339
config_group_set_string(dm->settings, "name", dm->name);
340
config_group_set_string(dm->settings, "image", dm->image);
342
lxpanel_button_set_icon(p, ((dm->image != NULL) ? dm->image : "file-manager"),
343
panel_get_icon_size(dm->panel));
345
gtk_widget_set_tooltip_text(p, dm->path);
346
gtk_container_foreach(GTK_CONTAINER(p), (GtkCallback) dirmenu_apply_configuration_to_children, (gpointer) dm);
350
/* Callback when the configuration dialog is to be shown. */
351
static GtkWidget *dirmenu_configure(LXPanel *panel, GtkWidget *p)
353
DirMenuPlugin * dm = lxpanel_plugin_get_data(p);
354
return lxpanel_generic_config_dlg(_("Directory Menu"),
355
panel, dirmenu_apply_configuration, p,
356
_("Directory"), &dm->path, CONF_TYPE_DIRECTORY_ENTRY,
357
_("Label"), &dm->name, CONF_TYPE_STR,
358
_("Icon"), &dm->image, CONF_TYPE_FILE_ENTRY,
362
/* Callback when panel configuration changes. */
363
static void dirmenu_panel_configuration_changed(LXPanel *panel, GtkWidget *p)
365
dirmenu_apply_configuration(p);
368
/* Plugin descriptor. */
369
LXPanelPluginInit lxpanel_static_plugin_dirmenu = {
370
.name = N_("Directory Menu"),
371
.description = N_("Browse directory tree via menu (Author = PCMan)"),
373
.new_instance = dirmenu_constructor,
374
.config = dirmenu_configure,
375
.reconfigure = dirmenu_panel_configuration_changed,
376
.button_press_event = dirmenu_button_press_event