~ubuntu-branches/ubuntu/wily/lxpanel/wily-proposed

« back to all changes in this revision

Viewing changes to plugins/dirmenu.c

  • Committer: Package Import Robot
  • Author(s): Julien Lavergne
  • Date: 2015-01-31 15:30:45 UTC
  • mfrom: (1.3.3)
  • mto: This revision was merged to the branch mainline in revision 45.
  • Revision ID: package-import@ubuntu.com-20150131153045-1r9i4602vrplnx3i
ImportĀ upstreamĀ versionĀ 0.7.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 * Copyright (c) 2006-2014 LxDE Developers, see the file AUTHORS for details.
 
3
 *
 
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.
 
8
 *
 
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.
 
13
 *
 
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.
 
17
 */
 
18
 
 
19
#include <stdlib.h>
 
20
#include <unistd.h>
 
21
 
 
22
#include <glib/gi18n.h>
 
23
#include <libfm/fm-gtk.h>
 
24
#include <string.h>
 
25
 
 
26
#include "misc.h"
 
27
#include "plugin.h"
 
28
 
 
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;
 
34
} DirectoryName;
 
35
 
 
36
/* Private context for directory menu plugin. */
 
37
typedef struct {
 
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 */
 
44
} DirMenuPlugin;
 
45
 
 
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);
 
49
 
 
50
 
 
51
/* Handler for activate event on popup Open menu item. */
 
52
static void dirmenu_menuitem_open_directory(GtkWidget * item, DirMenuPlugin * dm)
 
53
{
 
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);
 
56
    fm_path_unref(path);
 
57
}
 
58
 
 
59
/* Handler for activate event on popup Open In Terminal menu item. */
 
60
static void dirmenu_menuitem_open_in_terminal(GtkWidget * item, DirMenuPlugin * dm)
 
61
{
 
62
    fm_terminal_launch(g_object_get_data(G_OBJECT(gtk_widget_get_parent(item)), "path"), NULL);
 
63
}
 
64
 
 
65
/* Handler for select event on popup menu item. */
 
66
static void dirmenu_menuitem_select(GtkMenuItem * item, DirMenuPlugin * dm)
 
67
{
 
68
    GtkWidget * sub = gtk_menu_item_get_submenu(item);
 
69
    if (sub != NULL)
 
70
    {
 
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");
 
74
        if (path == NULL)
 
75
        {
 
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"),
 
79
                NULL);
 
80
            sub = dirmenu_create_menu(dm, path, TRUE);
 
81
            g_free(path);
 
82
            gtk_menu_item_set_submenu(item, sub);
 
83
        }
 
84
    }
 
85
}
 
86
 
 
87
/* Handler for deselect event on popup menu item. */
 
88
static void dirmenu_menuitem_deselect(GtkMenuItem * item, DirMenuPlugin * dm)
 
89
{
 
90
    /* Delete old menu on deselect to save resource. */
 
91
    gtk_menu_item_set_submenu(item, gtk_menu_new());
 
92
}
 
93
 
 
94
/* Handler for selection-done event on popup menu. */
 
95
static void dirmenu_menu_selection_done(GtkWidget * menu, DirMenuPlugin * dm)
 
96
{
 
97
    gtk_widget_destroy(menu);
 
98
}
 
99
 
 
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)
 
102
{
 
103
    DirMenuPlugin * dm = lxpanel_plugin_get_data(p);
 
104
 
 
105
    /* Determine the coordinates. */
 
106
    lxpanel_plugin_popup_set_position_helper(dm->panel, p, menu, px, py);
 
107
    *push_in = TRUE;
 
108
}
 
109
 
 
110
/* Create a menu populated with all subdirectories. */
 
111
static GtkWidget * dirmenu_create_menu(DirMenuPlugin * dm, const char * path, gboolean open_at_top)
 
112
{
 
113
    /* Create a menu. */
 
114
    GtkWidget * menu = gtk_menu_new();
 
115
 
 
116
    if (dm->folder_icon == NULL)
 
117
    {
 
118
        int w;
 
119
        int h;
 
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);
 
126
    }
 
127
 
 
128
    g_object_set_data_full(G_OBJECT(menu), "path", g_strdup(path), g_free);
 
129
 
 
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);
 
133
    if (dir != NULL)
 
134
    {
 
135
        const char * name;
 
136
        while ((name = g_dir_read_name(dir)) != NULL)   /* Memory owned by glib */
 
137
        {
 
138
            /* Omit hidden files. */
 
139
            if (name[0] != '.')
 
140
            {
 
141
                char * full = g_build_filename(path, name, NULL);
 
142
                if (g_file_test(full, G_FILE_TEST_IS_DIR))
 
143
                {
 
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);
 
147
 
 
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)
 
152
                    {
 
153
                        if (strcmp(directory_name_collate_key, dir_cursor->directory_name_collate_key) <= 0)
 
154
                            break;
 
155
                    }
 
156
 
 
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)
 
162
                    {
 
163
                        dir_cursor->flink = dir_list;
 
164
                        dir_list = dir_cursor;
 
165
                    }
 
166
                    else
 
167
                    {
 
168
                        dir_cursor->flink = dir_pred->flink;
 
169
                        dir_pred->flink = dir_cursor;
 
170
                    }
 
171
                }
 
172
                g_free(full);
 
173
            }
 
174
        }
 
175
        g_dir_close(dir);
 
176
    }
 
177
 
 
178
    /* The sorted directory name list is complete.  Loop to create the menu. */
 
179
    DirectoryName * dir_cursor;
 
180
    while ((dir_cursor = dir_list) != NULL)
 
181
    {
 
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);
 
190
 
 
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);
 
195
        g_free(dir_cursor);
 
196
 
 
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);
 
200
    }
 
201
 
 
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);
 
207
 
 
208
    /* Insert or append based on caller's preference. */
 
209
    if (open_at_top)
 
210
    {
 
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);
 
214
    }
 
215
    else {
 
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);
 
219
    }
 
220
 
 
221
    /* Show the menu and return. */
 
222
    gtk_widget_show_all(menu);
 
223
    return menu;
 
224
}
 
225
 
 
226
/* Show a menu of subdirectories. */
 
227
static void dirmenu_show_menu(GtkWidget * widget, DirMenuPlugin * dm, int btn, guint32 time)
 
228
{
 
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);
 
232
 
 
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);
 
235
}
 
236
 
 
237
/* Handler for button-press-event on top level widget. */
 
238
static gboolean dirmenu_button_press_event(GtkWidget * widget, GdkEventButton * event, LXPanel * p)
 
239
{
 
240
    DirMenuPlugin * dm = lxpanel_plugin_get_data(widget);
 
241
 
 
242
    if (event->button == 1)
 
243
    {
 
244
        dirmenu_show_menu(widget, dm, event->button, event->time);
 
245
    }
 
246
    else
 
247
    {
 
248
        fm_terminal_launch(dm->path, NULL);
 
249
    }
 
250
    return TRUE;
 
251
}
 
252
 
 
253
/* Plugin constructor. */
 
254
static GtkWidget *dirmenu_constructor(LXPanel *panel, config_setting_t *settings)
 
255
{
 
256
    /* Allocate and initialize plugin context and set into Plugin private data pointer. */
 
257
    DirMenuPlugin * dm = g_new0(DirMenuPlugin, 1);
 
258
    GtkWidget * p;
 
259
    const char *str;
 
260
 
 
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);
 
266
    else
 
267
        dm->path = g_strdup(fm_get_home_dir());
 
268
    if (config_setting_lookup_string(settings, "name", &str))
 
269
        dm->name = g_strdup(str);
 
270
 
 
271
    /* Save construction pointers */
 
272
    dm->panel = panel;
 
273
    dm->settings = settings;
 
274
 
 
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"),
 
280
                            NULL, "Temp");
 
281
    lxpanel_plugin_set_data(p, dm, dirmenu_destructor);
 
282
    gtk_container_set_border_width(GTK_CONTAINER(p), 0);
 
283
 
 
284
    /* Initialize the widget. */
 
285
    dirmenu_apply_configuration(p);
 
286
 
 
287
    /* Show the widget and return. */
 
288
    return p;
 
289
}
 
290
 
 
291
/* Plugin destructor. */
 
292
static void dirmenu_destructor(gpointer user_data)
 
293
{
 
294
    DirMenuPlugin * dm = (DirMenuPlugin *)user_data;
 
295
 
 
296
    /* Release a reference on the folder icon if held. */
 
297
    if (dm->folder_icon)
 
298
        g_object_unref(dm->folder_icon);
 
299
 
 
300
    /* Deallocate all memory. */
 
301
    g_free(dm->image);
 
302
    g_free(dm->path);
 
303
    g_free(dm->name);
 
304
    g_free(dm);
 
305
}
 
306
 
 
307
/* Recursively apply a configuration change. */
 
308
static void dirmenu_apply_configuration_to_children(GtkWidget * w, DirMenuPlugin * dm)
 
309
{
 
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))
 
313
    {
 
314
        if (dm->name == NULL)
 
315
            gtk_label_set_text(GTK_LABEL(w), NULL);
 
316
        else
 
317
            lxpanel_draw_label_text(dm->panel, w, dm->name, FALSE, 1, TRUE);
 
318
    }
 
319
}
 
320
 
 
321
/* Callback when the configuration dialog has recorded a configuration change. */
 
322
static gboolean dirmenu_apply_configuration(gpointer user_data)
 
323
{
 
324
    GtkWidget * p = user_data;
 
325
    DirMenuPlugin * dm = lxpanel_plugin_get_data(p);
 
326
    char * path = dm->path;
 
327
 
 
328
    /* Normalize path */
 
329
    if (path == NULL)
 
330
        dm->path = g_strdup(fm_get_home_dir());
 
331
    else if (path[0] == '~')
 
332
    {
 
333
        dm->path = expand_tilda(path);
 
334
        g_free(path);
 
335
    }
 
336
 
 
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);
 
341
 
 
342
    lxpanel_button_set_icon(p, ((dm->image != NULL) ? dm->image : "file-manager"),
 
343
                            panel_get_icon_size(dm->panel));
 
344
 
 
345
    gtk_widget_set_tooltip_text(p, dm->path);
 
346
    gtk_container_foreach(GTK_CONTAINER(p), (GtkCallback) dirmenu_apply_configuration_to_children, (gpointer) dm);
 
347
    return FALSE;
 
348
}
 
349
 
 
350
/* Callback when the configuration dialog is to be shown. */
 
351
static GtkWidget *dirmenu_configure(LXPanel *panel, GtkWidget *p)
 
352
{
 
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,
 
359
        NULL);
 
360
}
 
361
 
 
362
/* Callback when panel configuration changes. */
 
363
static void dirmenu_panel_configuration_changed(LXPanel *panel, GtkWidget *p)
 
364
{
 
365
    dirmenu_apply_configuration(p);
 
366
}
 
367
 
 
368
/* Plugin descriptor. */
 
369
LXPanelPluginInit lxpanel_static_plugin_dirmenu = {
 
370
    .name = N_("Directory Menu"),
 
371
    .description = N_("Browse directory tree via menu (Author = PCMan)"),
 
372
 
 
373
    .new_instance = dirmenu_constructor,
 
374
    .config = dirmenu_configure,
 
375
    .reconfigure = dirmenu_panel_configuration_changed,
 
376
    .button_press_event = dirmenu_button_press_event
 
377
};