7b57a4bbf06cd805c09917cab166b6adbf3dd299
[lxde/lxpanel.git] / src / plugins / dirmenu.c
1 /**
2 * Copyright (c) 2006 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 <gdk-pixbuf/gdk-pixbuf.h>
23 #include <glib/gi18n.h>
24 #include <string.h>
25
26 #include "panel.h"
27 #include "misc.h"
28 #include "plugin.h"
29 #include "dbg.h"
30
31 /* NOTE: dirty hack for g_quark_from_static_string */
32 #define NAME_ID GPOINTER_TO_UINT("name")
33 #define PATH_ID GPOINTER_TO_UINT("path")
34
35 typedef struct {
36 panel* panel;
37 char* image;
38 char* path;
39 GtkWidget *button;
40 } dirmenu;
41
42 static GdkPixbuf* folder_icon = NULL;
43
44 static GtkWidget* create_menu( plugin* p,
45 const char* path,
46 gboolean open_at_top );
47
48 static void open_dir( plugin* p, const char* path )
49 {
50 char* cmd;
51 char* quote = g_shell_quote( path );
52 const char* fm = lxpanel_get_file_manager( p->panel );
53 if( strstr( fm, "%s" ) )
54 cmd = g_strdup_printf( fm, quote );
55 else
56 cmd = g_strdup_printf( "%s %s", fm, quote );
57 g_free( quote );
58 g_spawn_command_line_async( cmd, NULL );
59 g_free( cmd );
60 }
61
62 static void on_open_dir( GtkWidget* item, plugin* p )
63 {
64 GtkWidget* menu = gtk_widget_get_parent(item);
65 const char* path = g_object_get_qdata( menu, PATH_ID );
66 open_dir( p, path );
67 }
68
69 static void open_in_term( plugin* p, const char* path )
70 {
71 const char* term = lxpanel_get_terminal( p->panel );
72 chdir( path );
73 g_spawn_command_line_async( term, NULL );
74 }
75
76 static void on_open_in_term( GtkWidget* item, plugin* p )
77 {
78 GtkWidget* menu = gtk_widget_get_parent(item);
79 const char* path = g_object_get_qdata( menu, PATH_ID );
80 open_in_term( p, path );
81 }
82
83 static void
84 menu_pos( GtkMenu *menu, gint *x, gint *y, gboolean *push_in, plugin* p )
85 {
86 int ox, oy, w, h;
87 dirmenu *dm = (dirmenu *)p->priv;
88
89 ENTER;
90 gdk_window_get_origin( dm->button->window, &ox, &oy );
91 w = GTK_WIDGET(menu)->requisition.width;
92 h = GTK_WIDGET(menu)->requisition.height;
93 if (p->panel->orientation == ORIENT_HORIZ) {
94 *x = ox;
95 if (*x + w > gdk_screen_width())
96 *x = ox + dm->button->allocation.width - w;
97 *y = oy - h;
98 if (*y < 0)
99 *y = oy + dm->button->allocation.height;
100 } else {
101 *x = ox + dm->button->allocation.width;
102 if (*x > gdk_screen_width())
103 *x = ox - w;
104 *y = oy;
105 if (*y + h > gdk_screen_height())
106 *y = oy + dm->button->allocation.height - h;
107 }
108 *push_in = TRUE;
109 RET();
110 }
111
112 static void on_select( GtkMenuItem* item, plugin* p )
113 {
114 GtkMenu* parent;
115 GtkWidget* sub = gtk_menu_item_get_submenu( item );
116 char* path;
117 if( !sub )
118 return;
119 parent = (GtkMenu*)gtk_widget_get_parent( (GtkWidget*)item );
120 path = (char*)g_object_get_qdata( sub, PATH_ID );
121 if( !path ){
122 path = g_build_filename( (char*)g_object_get_qdata( parent, PATH_ID ),
123 (char*)g_object_get_qdata( item, NAME_ID ), NULL );
124 sub = create_menu( p, path, TRUE );
125 g_free( path );
126 gtk_menu_item_set_submenu( item, sub );
127 }
128 }
129
130 #if GTK_CHECK_VERSION(2, 10, 0)
131 /* NOTE: It seems that this doesn't work in older versions of gtk+?? */
132 static void on_deselect( GtkMenuItem* item, plugin* p )
133 {
134 /* delete old menu on deselect to save resource */
135 gtk_menu_item_set_submenu( item, gtk_menu_new() );
136 }
137 #endif
138
139 void on_sel_done( GtkWidget *menu, plugin* p )
140 {
141 gtk_widget_destroy( menu );
142 if( folder_icon )
143 {
144 g_object_unref( folder_icon );
145 folder_icon = NULL;
146 }
147 }
148
149 static GtkWidget* create_menu( plugin* p,
150 const char* path,
151 gboolean open_at_top )
152 {
153 GDir* dir;
154 GtkWidget *menu = gtk_menu_new();
155 GtkWidget *item, *term;
156 /* GList *list = NULL; */
157
158 if( G_UNLIKELY(NULL == folder_icon) )
159 {
160 int w, h;
161 gtk_icon_size_lookup_for_settings( gtk_widget_get_settings(menu),
162 GTK_ICON_SIZE_MENU, &w, &h );
163 folder_icon = gtk_icon_theme_load_icon( gtk_icon_theme_get_default(),
164 "gnome-fs-directory", MAX( w, h ), 0, NULL );
165
166 if( ! folder_icon )
167 folder_icon = gtk_widget_render_icon( menu, GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU, NULL );
168 }
169
170 g_object_set_qdata_full( menu, PATH_ID, g_strdup(path), g_free );
171
172 if( dir = g_dir_open( path, 0, NULL ) ) {
173 const char* name;
174 while( name = g_dir_read_name( dir ) ) {
175 if( name[0] == '.' )
176 continue;
177 char* full = g_build_filename( path, name, NULL );
178 if( g_file_test( full, G_FILE_TEST_IS_DIR) ) {
179 char* disp = g_filename_display_name( name );
180 GtkWidget *dummy;
181 item = gtk_image_menu_item_new_with_label( disp );
182 g_free( disp );
183 g_object_set_qdata_full( item, NAME_ID, g_strdup(name), g_free );
184 gtk_image_menu_item_set_image( (GtkImageMenuItem*)item,
185 gtk_image_new_from_stock(GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU) );
186 dummy = gtk_menu_new();
187 gtk_menu_item_set_submenu( item, dummy );
188 gtk_menu_shell_append( GTK_MENU_SHELL(menu), item );
189 g_signal_connect( item, "select",
190 G_CALLBACK(on_select), p );
191 #if GTK_CHECK_VERSION(2, 10, 0)
192 /* NOTE: It seems that this doesn't work in older
193 versions of gtk+?? */
194 g_signal_connect( item, "deselect",
195 G_CALLBACK(on_deselect), p);
196 #endif
197 }
198 g_free( full );
199 }
200 g_dir_close( dir );
201 }
202
203 item = gtk_image_menu_item_new_from_stock( GTK_STOCK_OPEN, NULL );
204 g_signal_connect( item, "activate",
205 G_CALLBACK( on_open_dir ), p );
206 term = gtk_menu_item_new_with_mnemonic( _("Open in _Terminal") );
207 g_signal_connect( term, "activate",
208 G_CALLBACK( on_open_in_term ), p );
209
210 if( open_at_top ) {
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 gtk_widget_show_all( menu );
222 return menu;
223 }
224
225 static void show_menu( GtkWidget* widget, plugin *p, int btn, guint32 time )
226 {
227 dirmenu *dm = (dirmenu *)p->priv;
228 char* path = dm->path ? expand_tilda(dm->path) : NULL;
229 GtkWidget* menu = create_menu( p,
230 path ? path : g_get_home_dir(),
231 FALSE );
232 g_free( path );
233
234 g_signal_connect( menu, "selection-done", G_CALLBACK(on_sel_done), NULL );
235 gtk_menu_popup( GTK_MENU(menu),
236 NULL, NULL,
237 (GtkMenuPositionFunc)menu_pos, p,
238 btn, time);
239 }
240
241 static gint
242 clicked (GtkWidget *widget, GdkEventButton *event, gpointer data)
243 {
244 plugin *p = (plugin*)data;
245 dirmenu *dm = (dirmenu *)p->priv;
246
247 ENTER;
248 if (event->type != GDK_BUTTON_PRESS)
249 RET(FALSE);
250
251 if (event->button == 1) {
252 show_menu( widget, p, event->button, event->time );
253 } else {
254 char* path = dm->path ? expand_tilda( dm->path ) : NULL;
255 const char* ppath = path ? path : g_get_home_dir();
256 if( event->button == 2 )
257 open_in_term( p, ppath );
258 else
259 open_dir( p, ppath );
260 g_free( path );
261 }
262
263 RET(FALSE);
264 }
265
266 static void
267 dirmenu_destructor(plugin *p)
268 {
269 dirmenu *dm = (dirmenu *)p->priv;
270 ENTER;
271 g_free( dm->image );
272 g_free( dm->path );
273 g_free(dm);
274 RET();
275 }
276
277 static int
278 dirmenu_constructor(plugin *p, char **fp)
279 {
280 line s;
281 gchar *fname;
282 dirmenu *dm;
283 int w, h;
284
285 ENTER;
286 s.len = 256;
287 dm = g_new0(dirmenu, 1);
288 g_return_val_if_fail(dm != NULL, 0);
289
290 dm->panel = p->panel;
291 p->priv = dm;
292 fname = NULL;
293 if( fp )
294 {
295 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
296 if (s.type == LINE_NONE) {
297 ERR( "dirmenu: illegal token %s\n", s.str);
298 goto error;
299 }
300 if (s.type == LINE_VAR) {
301 if (!g_ascii_strcasecmp(s.t[0], "image")) {
302 dm->image = g_strdup( s.t[1] );
303 fname = expand_tilda(s.t[1]);
304 }
305 else if (!g_ascii_strcasecmp(s.t[0], "path")) {
306 dm->path = g_strdup( s.t[1] );
307 }
308 else {
309 ERR( "dirmenu: unknown var %s\n", s.t[0]);
310 goto error;
311 }
312 } else {
313 ERR( "dirmenu: illegal in this context %s\n", s.str);
314 goto error;
315 }
316 }
317 }
318 if (p->panel->orientation == ORIENT_HORIZ) {
319 w = 10000;
320 h = p->panel->ah;
321 } else {
322 w = p->panel->aw;
323 h = 10000;
324 }
325
326 if (! fname)
327 fname = strdup("file-manager");
328
329 dm->button = fb_button_new_from_file(fname, w, h, 0x202020, TRUE);
330
331 gtk_container_set_border_width( GTK_CONTAINER(dm->button), 0 );
332 g_signal_connect( dm->button, "button_press_event",
333 G_CALLBACK(clicked), p );
334
335 gtk_widget_show( dm->button );
336 g_free(fname);
337
338 fname = dm->path ? expand_tilda(dm->path) : NULL;
339 gtk_tooltips_set_tip(GTK_TOOLTIPS (dm->panel->tooltips),
340 dm->button,
341 fname ? fname : g_get_home_dir(), NULL);
342 g_free( fname );
343
344 /* store the created plugin widget in plugin->pwid */
345 p->pwid = dm->button;
346
347 RET(1);
348
349 error:
350 g_free(fname);
351 dirmenu_destructor(p);
352 ERR( "%s - exit\n", __FUNCTION__);
353 RET(0);
354 }
355
356 static void save_config( plugin* p, FILE* fp )
357 {
358 dirmenu* dm = (dirmenu*)p->priv;
359 lxpanel_put_str( fp, "path", dm->path );
360 lxpanel_put_str( fp, "image", dm->image );
361 }
362
363 plugin_class dirmenu_plugin_class = {
364 fname: NULL,
365 count: 0,
366
367 type : "dirmenu",
368 name : N_("Directory Menu"),
369 version: "1.0",
370 description : N_("Browse directory tree via menu (Author: PCMan)"),
371
372 constructor : dirmenu_constructor,
373 destructor : dirmenu_destructor,
374 config : NULL,
375 save : save_config
376 };