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