3fe39f12d0ce1c2565b2d473f52ef3a29e962834
[lxde/pcmanfm.git] / src / main-win.c
1 /*
2 * main-win.c
3 *
4 * Copyright 2009 PCMan <pcman.tw@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <glib/gi18n.h>
27
28 #include "pcmanfm.h"
29
30 #include "app-config.h"
31 #include "main-win.h"
32 #include "pref.h"
33
34 static void fm_main_win_finalize (GObject *object);
35 G_DEFINE_TYPE(FmMainWin, fm_main_win, GTK_TYPE_WINDOW);
36
37 static GtkWidget* create_tab_label(FmMainWin* win, FmPath* path, FmFolderView* view);
38 static void update_tab_label(FmMainWin* win, FmFolderView* fv, const char* title);
39 static void update_volume_info(FmMainWin* win);
40
41 static void on_focus_in(GtkWidget* w, GdkEventFocus* evt);
42
43 static void on_new_win(GtkAction* act, FmMainWin* win);
44 static void on_new_tab(GtkAction* act, FmMainWin* win);
45 static void on_close_tab(GtkAction* act, FmMainWin* win);
46 static void on_close_win(GtkAction* act, FmMainWin* win);
47
48 static void on_open_in_new_tab(GtkAction* act, FmMainWin* win);
49 static void on_open_in_new_win(GtkAction* act, FmMainWin* win);
50
51 static void on_cut(GtkAction* act, FmMainWin* win);
52 static void on_copy(GtkAction* act, FmMainWin* win);
53 static void on_copy_to(GtkAction* act, FmMainWin* win);
54 static void on_move_to(GtkAction* act, FmMainWin* win);
55 static void on_paste(GtkAction* act, FmMainWin* win);
56 static void on_del(GtkAction* act, FmMainWin* win);
57 static void on_rename(GtkAction* act, FmMainWin* win);
58
59 static void on_select_all(GtkAction* act, FmMainWin* win);
60 static void on_invert_select(GtkAction* act, FmMainWin* win);
61 static void on_preference(GtkAction* act, FmMainWin* win);
62
63 static void on_go(GtkAction* act, FmMainWin* win);
64 static void on_go_back(GtkAction* act, FmMainWin* win);
65 static void on_go_forward(GtkAction* act, FmMainWin* win);
66 static void on_go_up(GtkAction* act, FmMainWin* win);
67 static void on_go_home(GtkAction* act, FmMainWin* win);
68 static void on_go_desktop(GtkAction* act, FmMainWin* win);
69 static void on_go_trash(GtkAction* act, FmMainWin* win);
70 static void on_go_computer(GtkAction* act, FmMainWin* win);
71 static void on_go_network(GtkAction* act, FmMainWin* win);
72 static void on_go_apps(GtkAction* act, FmMainWin* win);
73 static void on_show_hidden(GtkToggleAction* act, FmMainWin* win);
74 static void on_change_mode(GtkRadioAction* act, GtkRadioAction *cur, FmMainWin* win);
75 static void on_sort_by(GtkRadioAction* act, GtkRadioAction *cur, FmMainWin* win);
76 static void on_sort_type(GtkRadioAction* act, GtkRadioAction *cur, FmMainWin* win);
77 static void on_about(GtkAction* act, FmMainWin* win);
78
79 static void on_location(GtkAction* act, FmMainWin* win);
80
81 static void on_create_new(GtkAction* action, FmMainWin* win);
82 static void on_prop(GtkAction* action, FmMainWin* win);
83
84 static void on_switch_page(GtkNotebook* nb, GtkNotebookPage* page, guint num, FmMainWin* win);
85 static void on_page_removed(GtkNotebook* nb, GtkWidget* page, guint num, FmMainWin* win);
86
87 #include "main-win-ui.c" /* ui xml definitions and actions */
88
89 static GQuark nav_history_id = 0;
90 static GSList* all_wins = NULL;
91
92 static void fm_main_win_class_init(FmMainWinClass *klass)
93 {
94 GObjectClass *g_object_class;
95 GtkWidgetClass* widget_class;
96 g_object_class = G_OBJECT_CLASS(klass);
97 g_object_class->finalize = fm_main_win_finalize;
98
99 widget_class = (GtkWidgetClass*)klass;
100 widget_class->focus_in_event = on_focus_in;
101
102 fm_main_win_parent_class = (GtkWindowClass*)g_type_class_peek(GTK_TYPE_WINDOW);
103
104 /* special style used by close button */
105 gtk_rc_parse_string(
106 "style \"close-btn-style\" {\n"
107 "GtkWidget::focus-padding = 0\n"
108 "GtkWidget::focus-line-width = 0\n"
109 "xthickness = 0\n"
110 "ythickness = 0\n"
111 "}\n"
112 "widget \"*.close-btn\" style \"close-btn-style\""
113 );
114
115 nav_history_id = g_quark_from_static_string("nav-history");
116 }
117
118 static void on_entry_activate(GtkEntry* entry, FmMainWin* self)
119 {
120 fm_folder_view_chdir_by_name(self->folder_view, gtk_entry_get_text(entry));
121 }
122
123 static void on_view_loaded( FmFolderView* view, FmPath* path, gpointer user_data)
124 {
125 FmMainWin* win = FM_MAIN_WIN(user_data);
126 FmIcon* icon;
127 /* FIXME: we shouldn't access private data member directly. */
128 fm_path_entry_set_model( win->location, path, view->model );
129 if(FM_FOLDER_MODEL(view->model)->dir->dir_fi)
130 {
131 icon = FM_FOLDER_MODEL(view->model)->dir->dir_fi->icon;
132 if(icon)
133 {
134 icon->gicon;
135 /* FIXME: load icon. we need to change window icon when switching pages. */
136 gtk_window_set_icon_name(win, "folder");
137 }
138 }
139 update_volume_info(win);
140 }
141
142 static void open_folder_hook(FmFileInfo* fi, gpointer user_data)
143 {
144 FmMainWin* win = FM_MAIN_WIN(user_data);
145 fm_main_win_chdir(win, fi->path);
146 }
147
148 static void on_file_clicked(FmFolderView* fv, FmFolderViewClickType type, FmFileInfo* fi, FmMainWin* win)
149 {
150 char* fpath, *uri;
151 GAppLaunchContext* ctx;
152 switch(type)
153 {
154 case FM_FV_ACTIVATED: /* file activated */
155 if(fm_file_info_is_dir(fi))
156 {
157 fm_main_win_chdir( win, fi->path);
158 }
159 else if(!fm_file_info_is_symlink(fi) && fi->target) /* FIXME: use accessor functions. */
160 {
161 /* symlinks also has fi->target, but we only handle shortcuts here. */
162 fm_main_win_chdir_by_name( win, fi->target);
163 }
164 else
165 {
166 fm_launch_file(win, NULL, fi);
167 }
168 break;
169 case FM_FV_CONTEXT_MENU:
170 if(fi)
171 {
172 FmFileMenu* menu;
173 GtkMenu* popup;
174 FmFileInfoList* files = fm_folder_view_get_selected_files(fv);
175 menu = fm_file_menu_new_for_files(files, TRUE);
176 fm_file_menu_set_folder_hook(menu, open_folder_hook, win);
177 fm_list_unref(files);
178
179 /* merge some specific menu items for folders */
180 if(fm_file_menu_is_single_file_type(menu) && fm_file_info_is_dir(fi))
181 {
182 GtkUIManager* ui = fm_file_menu_get_ui(menu);
183 GtkActionGroup* act_grp = fm_file_menu_get_action_group(menu);
184 gtk_action_group_set_translation_domain(act_grp, NULL);
185 gtk_action_group_add_actions(act_grp, folder_menu_actions, G_N_ELEMENTS(folder_menu_actions), win);
186 gtk_ui_manager_add_ui_from_string(ui, folder_menu_xml, -1, NULL);
187 }
188
189 popup = fm_file_menu_get_menu(menu);
190 gtk_menu_popup(popup, NULL, NULL, NULL, fi, 3, gtk_get_current_event_time());
191 }
192 else /* no files are selected. Show context menu of current folder. */
193 {
194 gtk_menu_popup(win->popup, NULL, NULL, NULL, NULL, 3, gtk_get_current_event_time());
195 }
196 break;
197 case FM_FV_MIDDLE_CLICK:
198 g_debug("middle click!");
199 break;
200 }
201 }
202
203 static void on_sel_changed(FmFolderView* fv, FmFileInfoList* files, FmMainWin* win)
204 {
205 /* popup previous message if there is any */
206 gtk_statusbar_pop(win->statusbar, win->statusbar_ctx2);
207 if(files)
208 {
209 char* msg;
210 /* FIXME: display total size of all selected files. */
211 if(fm_list_get_length(files) == 1) /* only one file is selected */
212 {
213 FmFileInfo* fi = fm_list_peek_head(files);
214 const char* size_str = fm_file_info_get_disp_size(fi);
215 if(size_str)
216 {
217 msg = g_strdup_printf("\"%s\" (%s) %s",
218 fm_file_info_get_disp_name(fi),
219 size_str ? size_str : "",
220 fm_file_info_get_desc(fi));
221 }
222 else
223 {
224 msg = g_strdup_printf("\"%s\" %s",
225 fm_file_info_get_disp_name(fi),
226 fm_file_info_get_desc(fi));
227 }
228 }
229 else
230 msg = g_strdup_printf("%d items selected", fm_list_get_length(files));
231 gtk_statusbar_push(win->statusbar, win->statusbar_ctx2, msg);
232 g_free(msg);
233 }
234 }
235
236 static void on_status(FmFolderView* fv, const char* msg, FmMainWin* win)
237 {
238 gtk_statusbar_pop(win->statusbar, win->statusbar_ctx);
239 gtk_statusbar_push(win->statusbar, win->statusbar_ctx, msg);
240 }
241
242 static void on_bookmark(GtkMenuItem* mi, FmMainWin* win)
243 {
244 FmPath* path = (FmPath*)g_object_get_data(mi, "path");
245 switch(FM_APP_CONFIG(fm_config)->bm_open_method)
246 {
247 case 0: /* current tab */
248 fm_main_win_chdir(win, path);
249 break;
250 case 1: /* new tab */
251 fm_main_win_add_tab(win, path);
252 break;
253 case 2: /* new window */
254 fm_main_win_add_win(win, path);
255 break;
256 }
257 }
258
259 static void create_bookmarks_menu(FmMainWin* win)
260 {
261 GList* l;
262 int i = 0;
263 /* FIXME: direct access to data member is not allowed */
264 for(l=win->bookmarks->items;l;l=l->next)
265 {
266 FmBookmarkItem* item = (FmBookmarkItem*)l->data;
267 GtkWidget* mi = gtk_image_menu_item_new_with_label(item->name);
268 gtk_widget_show(mi);
269 // gtk_image_menu_item_set_image(); // FIXME: set icons for menu items
270 g_object_set_data_full(mi, "path", fm_path_ref(item->path), (GDestroyNotify)fm_path_unref);
271 g_signal_connect(mi, "activate", G_CALLBACK(on_bookmark), win);
272 gtk_menu_shell_insert(win->bookmarks_menu, mi, i);
273 ++i;
274 }
275 if(i > 0)
276 gtk_menu_shell_insert(win->bookmarks_menu, gtk_separator_menu_item_new(), i);
277 }
278
279 static void on_bookmarks_changed(FmBookmarks* bm, FmMainWin* win)
280 {
281 /* delete old items first. */
282 GList* mis = gtk_container_get_children(win->bookmarks_menu);
283 GList* l;
284 for(l = mis;l;l=l->next)
285 {
286 GtkWidget* item = (GtkWidget*)l->data;
287 if( GTK_IS_SEPARATOR_MENU_ITEM(item) )
288 break;
289 gtk_widget_destroy(item);
290 }
291 g_list_free(mis);
292
293 create_bookmarks_menu(win);
294 }
295
296 static void load_bookmarks(FmMainWin* win, GtkUIManager* ui)
297 {
298 GtkWidget* mi = gtk_ui_manager_get_widget(ui, "/menubar/BookmarksMenu");
299 win->bookmarks_menu = gtk_menu_item_get_submenu(mi);
300 win->bookmarks = fm_bookmarks_get();
301 g_signal_connect(win->bookmarks, "changed", G_CALLBACK(on_bookmarks_changed), win);
302
303 create_bookmarks_menu(win);
304 }
305
306 static void on_history_item(GtkMenuItem* mi, FmMainWin* win)
307 {
308 GList* l = g_object_get_data(mi, "path");
309 FmPath* path = (FmPath*)l->data;
310 fm_nav_history_jump(win->nav_history, l);
311 /* FIXME: should this be driven by signal emitted on FmNavHistory? */
312 fm_main_win_chdir_without_history(win, path);
313 }
314
315 static void on_show_history_menu(GtkMenuToolButton* btn, FmMainWin* win)
316 {
317 GtkMenuShell* menu = (GtkMenuShell*)gtk_menu_tool_button_get_menu(btn);
318 GList *l;
319 FmPathList* nh = fm_nav_history_list(win->nav_history);
320 GList* cur = fm_nav_history_get_cur_link(win->nav_history);
321
322 /* delete old items */
323 gtk_container_foreach(menu, (GtkCallback)gtk_widget_destroy, NULL);
324
325 for(l = fm_list_peek_head_link(nh); l; l=l->next)
326 {
327 FmPath* path = (FmPath*)l->data;
328 char* str = fm_path_display_name(path, TRUE);
329 GtkMenuItem* mi;
330 if( l == cur )
331 {
332 mi = gtk_check_menu_item_new_with_label(str);
333 gtk_check_menu_item_set_draw_as_radio(mi, TRUE);
334 gtk_check_menu_item_set_active(mi, TRUE);
335 }
336 else
337 mi = gtk_menu_item_new_with_label(str);
338
339 g_object_set_data_full(mi, "path", l, NULL);
340 g_signal_connect(mi, "activate", G_CALLBACK(on_history_item), win);
341 gtk_menu_shell_append(menu, mi);
342 }
343 fm_list_unref(nh);
344
345 gtk_widget_show_all( GTK_WIDGET(menu) );
346 }
347
348 static void fm_main_win_init(FmMainWin *self)
349 {
350 GtkWidget *vbox, *menubar, *toolitem, *next_btn, *scroll;
351 GtkUIManager* ui;
352 GtkActionGroup* act_grp;
353 GtkAction* act;
354 GtkAccelGroup* accel_grp;
355 GtkShadowType shadow_type;
356
357 pcmanfm_ref();
358 all_wins = g_slist_prepend(all_wins, self);
359
360 vbox = gtk_vbox_new(FALSE, 0);
361
362 self->hpaned = gtk_hpaned_new();
363 gtk_paned_set_position(self->hpaned, 150);
364
365 /* places left pane */
366 self->places_view = fm_places_view_new();
367 scroll = gtk_scrolled_window_new(NULL, NULL);
368 gtk_scrolled_window_set_policy(scroll, GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
369 gtk_container_add(scroll, self->places_view);
370 gtk_paned_add1(self->hpaned, scroll);
371 g_signal_connect_swapped(self->places_view, "chdir", G_CALLBACK(fm_main_win_chdir), self);
372
373 /* notebook right pane */
374 self->notebook = gtk_notebook_new();
375 gtk_notebook_set_scrollable(self->notebook, TRUE);
376 g_signal_connect(self->notebook, "switch-page", G_CALLBACK(on_switch_page), self);
377 g_signal_connect(self->notebook, "page-removed", G_CALLBACK(on_page_removed), self);
378 gtk_paned_add2(self->hpaned, self->notebook);
379
380 /* create menu bar and toolbar */
381 ui = gtk_ui_manager_new();
382 act_grp = gtk_action_group_new("Main");
383 gtk_action_group_set_translation_domain(act_grp, NULL);
384 gtk_action_group_add_actions(act_grp, main_win_actions, G_N_ELEMENTS(main_win_actions), self);
385 gtk_action_group_add_toggle_actions(act_grp, main_win_toggle_actions, G_N_ELEMENTS(main_win_toggle_actions), self);
386 gtk_action_group_add_radio_actions(act_grp, main_win_mode_actions, G_N_ELEMENTS(main_win_mode_actions), FM_FV_ICON_VIEW, on_change_mode, self);
387 gtk_action_group_add_radio_actions(act_grp, main_win_sort_type_actions, G_N_ELEMENTS(main_win_sort_type_actions), GTK_SORT_ASCENDING, on_sort_type, self);
388 gtk_action_group_add_radio_actions(act_grp, main_win_sort_by_actions, G_N_ELEMENTS(main_win_sort_by_actions), 0, on_sort_by, self);
389
390 accel_grp = gtk_ui_manager_get_accel_group(ui);
391 gtk_window_add_accel_group(self, accel_grp);
392
393 gtk_ui_manager_insert_action_group(ui, act_grp, 0);
394 gtk_ui_manager_add_ui_from_string(ui, main_menu_xml, -1, NULL);
395
396 menubar = gtk_ui_manager_get_widget(ui, "/menubar");
397 self->toolbar = gtk_ui_manager_get_widget(ui, "/toolbar");
398 /* FIXME: should make these optional */
399 gtk_toolbar_set_icon_size(self->toolbar, GTK_ICON_SIZE_SMALL_TOOLBAR);
400 gtk_toolbar_set_style(self->toolbar, GTK_TOOLBAR_ICONS);
401
402 /* create 'Next' button manually and add a popup menu to it */
403 toolitem = g_object_new(GTK_TYPE_MENU_TOOL_BUTTON, NULL);
404 gtk_toolbar_insert(GTK_TOOLBAR(self->toolbar), toolitem, 2);
405 gtk_widget_show(GTK_WIDGET(toolitem));
406 act = gtk_ui_manager_get_action(ui, "/menubar/GoMenu/Next");
407 gtk_activatable_set_related_action(toolitem, act);
408
409 /* set up history menu */
410 self->history_menu = gtk_menu_new();
411 gtk_menu_tool_button_set_menu(toolitem, self->history_menu);
412 g_signal_connect(toolitem, "show-menu", G_CALLBACK(on_show_history_menu), self);
413
414 self->popup = gtk_ui_manager_get_widget(ui, "/popup");
415
416 gtk_box_pack_start( (GtkBox*)vbox, menubar, FALSE, TRUE, 0 );
417 gtk_box_pack_start( (GtkBox*)vbox, self->toolbar, FALSE, TRUE, 0 );
418
419 /* load bookmarks menu */
420 load_bookmarks(self, ui);
421
422 /* the location bar */
423 self->location = fm_path_entry_new();
424 g_signal_connect(self->location, "activate", on_entry_activate, self);
425
426 toolitem = gtk_tool_item_new();
427 gtk_container_add( toolitem, self->location );
428 gtk_tool_item_set_expand(toolitem, TRUE);
429 gtk_toolbar_insert((GtkToolbar*)self->toolbar, toolitem, gtk_toolbar_get_n_items(self->toolbar) - 1 );
430
431 gtk_box_pack_start( (GtkBox*)vbox, self->hpaned, TRUE, TRUE, 0 );
432
433 /* status bar */
434 self->statusbar = gtk_statusbar_new();
435 /* status bar column showing volume free space */
436 gtk_widget_style_get(self->statusbar, "shadow-type", &shadow_type, NULL);
437 self->vol_status = gtk_frame_new(NULL);
438 gtk_frame_set_shadow_type(self->vol_status, shadow_type);
439 gtk_box_pack_start(self->statusbar, self->vol_status, FALSE, TRUE, 0);
440 gtk_container_add(self->vol_status, gtk_label_new(NULL));
441
442 gtk_box_pack_start( (GtkBox*)vbox, self->statusbar, FALSE, TRUE, 0 );
443 self->statusbar_ctx = gtk_statusbar_get_context_id(self->statusbar, "status");
444 self->statusbar_ctx2 = gtk_statusbar_get_context_id(self->statusbar, "status2");
445
446 g_object_unref(act_grp);
447 self->ui = ui;
448
449 gtk_container_add( (GtkContainer*)self, vbox );
450 gtk_widget_show_all(vbox);
451
452 /* create new tab */
453 fm_main_win_add_tab(self, fm_path_get_home());
454 gtk_widget_grab_focus(self->folder_view);
455 }
456
457
458 GtkWidget* fm_main_win_new(void)
459 {
460 return (GtkWidget*)g_object_new(FM_MAIN_WIN_TYPE, NULL);
461 }
462
463
464 static void fm_main_win_finalize(GObject *object)
465 {
466 FmMainWin *self;
467
468 g_return_if_fail(object != NULL);
469 g_return_if_fail(IS_FM_MAIN_WIN(object));
470
471 self = FM_MAIN_WIN(object);
472
473 if(self->vol_status_cancellable)
474 g_object_unref(self->vol_status_cancellable);
475
476 g_object_unref(self->ui);
477 g_object_unref(self->bookmarks);
478
479 if (G_OBJECT_CLASS(fm_main_win_parent_class)->finalize)
480 (* G_OBJECT_CLASS(fm_main_win_parent_class)->finalize)(object);
481
482 all_wins = g_slist_remove(all_wins, self);
483 pcmanfm_unref();
484 }
485
486 void on_about(GtkAction* act, FmMainWin* win)
487 {
488 GtkWidget* dlg;
489 GtkBuilder* builder = gtk_builder_new();
490 gtk_builder_add_from_file(builder, PACKAGE_UI_DIR "/about.ui", NULL);
491 dlg = (GtkWidget*)gtk_builder_get_object(builder, "dlg");
492 g_object_unref(builder);
493 gtk_dialog_run((GtkDialog*)dlg);
494 gtk_widget_destroy(dlg);
495 }
496
497 void on_show_hidden(GtkToggleAction* act, FmMainWin* win)
498 {
499 gboolean active = gtk_toggle_action_get_active(act);
500 fm_folder_view_set_show_hidden( win->folder_view, active );
501 }
502
503 void on_change_mode(GtkRadioAction* act, GtkRadioAction *cur, FmMainWin* win)
504 {
505 int mode = gtk_radio_action_get_current_value(cur);
506 fm_folder_view_set_mode( win->folder_view, mode );
507 }
508
509 void on_sort_by(GtkRadioAction* act, GtkRadioAction *cur, FmMainWin* win)
510 {
511 int val = gtk_radio_action_get_current_value(cur);
512 fm_folder_view_sort(win->folder_view, -1, val);
513 }
514
515 void on_sort_type(GtkRadioAction* act, GtkRadioAction *cur, FmMainWin* win)
516 {
517 int val = gtk_radio_action_get_current_value(cur);
518 fm_folder_view_sort(win->folder_view, val, -1);
519 }
520
521 void on_focus_in(GtkWidget* w, GdkEventFocus* evt)
522 {
523 if(all_wins->data != w)
524 {
525 all_wins = g_slist_remove(all_wins, w);
526 all_wins = g_slist_prepend(all_wins, w);
527 }
528 ((GtkWidgetClass*)fm_main_win_parent_class)->focus_in_event(w, evt);
529 }
530
531 void on_new_win(GtkAction* act, FmMainWin* win)
532 {
533 fm_main_win_add_win(win, fm_path_get_home());
534 }
535
536 void on_new_tab(GtkAction* act, FmMainWin* win)
537 {
538 FmPath* path = fm_folder_view_get_cwd(win->folder_view);
539 fm_main_win_add_tab(win, path);
540 }
541
542 void on_close_win(GtkAction* act, FmMainWin* win)
543 {
544 gtk_widget_destroy(win);
545 }
546
547 void on_close_tab(GtkAction* act, FmMainWin* win)
548 {
549 /* FIXME: this is a little bit dirty */
550 GtkNotebook* nb = (GtkNotebook*)win->notebook;
551 gtk_widget_destroy(win->folder_view);
552
553 if(win->vol_status_cancellable)
554 {
555 g_cancellable_cancel(win->vol_status_cancellable);
556 g_object_unref(win->vol_status_cancellable);
557 win->vol_status_cancellable = NULL;
558 }
559 if(gtk_notebook_get_n_pages(nb) == 0)
560 {
561 GtkWidget* main_win = gtk_widget_get_toplevel(nb);
562 gtk_widget_destroy(main_win);
563 }
564 }
565
566 void on_open_in_new_tab(GtkAction* act, FmMainWin* win)
567 {
568 FmPathList* sels = fm_folder_view_get_selected_file_paths(win->folder_view);
569 GList* l;
570 for( l = fm_list_peek_head_link(sels); l; l=l->next )
571 {
572 FmPath* path = (FmPath*)l->data;
573 fm_main_win_add_tab(win, path);
574 }
575 fm_list_unref(sels);
576 }
577
578
579 void on_open_in_new_win(GtkAction* act, FmMainWin* win)
580 {
581 FmPathList* sels = fm_folder_view_get_selected_file_paths(win->folder_view);
582 GList* l;
583 for( l = fm_list_peek_head_link(sels); l; l=l->next )
584 {
585 FmPath* path = (FmPath*)l->data;
586 fm_main_win_add_win(win, path);
587 }
588 fm_list_unref(sels);
589 }
590
591
592 void on_go(GtkAction* act, FmMainWin* win)
593 {
594 fm_main_win_chdir_by_name( win, gtk_entry_get_text(win->location));
595 }
596
597 void on_go_back(GtkAction* act, FmMainWin* win)
598 {
599 if(fm_nav_history_get_can_back(win->nav_history))
600 {
601 fm_nav_history_back(win->nav_history);
602 /* FIXME: should this be driven by a signal emitted on FmNavHistory? */
603 fm_main_win_chdir_without_history(win, fm_nav_history_get_cur(win->nav_history));
604 }
605 }
606
607 void on_go_forward(GtkAction* act, FmMainWin* win)
608 {
609 if(fm_nav_history_get_can_forward(win->nav_history))
610 {
611 fm_nav_history_forward(win->nav_history);
612 /* FIXME: should this be driven by a signal emitted on FmNavHistory? */
613 fm_main_win_chdir_without_history(win, fm_nav_history_get_cur(win->nav_history));
614 }
615 }
616
617 void on_go_up(GtkAction* act, FmMainWin* win)
618 {
619 FmPath* parent = fm_path_get_parent(fm_folder_view_get_cwd(win->folder_view));
620 if(parent)
621 fm_main_win_chdir( win, parent);
622 }
623
624 void on_go_home(GtkAction* act, FmMainWin* win)
625 {
626 fm_main_win_chdir_by_name( win, g_get_home_dir());
627 }
628
629 void on_go_desktop(GtkAction* act, FmMainWin* win)
630 {
631 fm_main_win_chdir_by_name( win, g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP));
632 }
633
634 void on_go_trash(GtkAction* act, FmMainWin* win)
635 {
636 fm_main_win_chdir_by_name( win, "trash:///");
637 }
638
639 void on_go_computer(GtkAction* act, FmMainWin* win)
640 {
641 fm_main_win_chdir_by_name( win, "computer:///");
642 }
643
644 void on_go_network(GtkAction* act, FmMainWin* win)
645 {
646 fm_main_win_chdir_by_name( win, "network:///");
647 }
648
649 void on_go_apps(GtkAction* act, FmMainWin* win)
650 {
651 fm_main_win_chdir_by_name( win, "applications:///");
652 }
653
654 void fm_main_win_chdir_by_name(FmMainWin* win, const char* path_str)
655 {
656 FmPath* path = fm_path_new(path_str);
657 fm_main_win_chdir(win, path);
658 fm_path_unref(path);
659 }
660
661 void fm_main_win_chdir_without_history(FmMainWin* win, FmPath* path)
662 {
663 /* FIXME: how to handle UTF-8 here? */
664 char* disp_path = fm_path_to_str(path);
665 char* disp_name = fm_path_display_basename(path);
666 gtk_entry_set_text(win->location, disp_path);
667
668 update_tab_label(win, win->folder_view, disp_name);
669 gtk_window_set_title(win, disp_name);
670 g_free(disp_path);
671 g_free(disp_name);
672
673 fm_folder_view_chdir(win->folder_view, path);
674 /* fm_nav_history_set_cur(); */
675 }
676
677 void fm_main_win_chdir(FmMainWin* win, FmPath* path)
678 {
679 fm_main_win_chdir_without_history(win, path);
680 fm_nav_history_chdir(win->nav_history, path);
681 }
682
683 static void close_btn_style_set(GtkWidget *btn, GtkRcStyle *prev, gpointer data)
684 {
685 gint w, h;
686 gtk_icon_size_lookup_for_settings(gtk_widget_get_settings(btn), GTK_ICON_SIZE_MENU, &w, &h);
687 gtk_widget_set_size_request(btn, w + 2, h + 2);
688 }
689
690 static void on_close_tab_btn(GtkButton* btn, GtkWidget* view)
691 {
692 GtkNotebook* nb = GTK_NOTEBOOK(gtk_widget_get_parent(view));
693 FmMainWin* win = FM_MAIN_WIN(gtk_widget_get_toplevel(nb));
694 gtk_widget_destroy(view);
695
696 if(win->vol_status_cancellable)
697 {
698 g_cancellable_cancel(win->vol_status_cancellable);
699 g_object_unref(win->vol_status_cancellable);
700 win->vol_status_cancellable = NULL;
701 }
702 if(gtk_notebook_get_n_pages(nb) == 0)
703 {
704 GtkWidget* main_win = gtk_widget_get_toplevel(nb);
705 gtk_widget_destroy(main_win);
706 }
707 }
708
709 GtkWidget* create_tab_label(FmMainWin* win, FmPath* path, FmFolderView* view)
710 {
711 GtkWidget * evt_box;
712 GtkWidget* tab_label;
713 GtkWidget* tab_text;
714 GtkWidget* close_btn;
715 char* disp_name;
716 int w, h;
717
718 /* Create tab label */
719 evt_box = gtk_event_box_new();
720 gtk_event_box_set_visible_window(GTK_EVENT_BOX(evt_box), FALSE);
721
722 tab_label = gtk_hbox_new( FALSE, 0 );
723
724 disp_name = g_filename_display_name(path->name);
725 tab_text = gtk_label_new(disp_name);
726 g_free(disp_name);
727 gtk_box_pack_start(GTK_BOX(tab_label), tab_text, FALSE, FALSE, 4 );
728
729 close_btn = gtk_button_new ();
730 gtk_button_set_focus_on_click ( GTK_BUTTON ( close_btn ), FALSE );
731 gtk_button_set_relief( GTK_BUTTON ( close_btn ), GTK_RELIEF_NONE );
732 gtk_container_add ( GTK_CONTAINER ( close_btn ),
733 gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU));
734 gtk_container_set_border_width(close_btn, 0);
735 gtk_widget_set_name(close_btn, "close-btn");
736 g_signal_connect(close_btn, "style-set", G_CALLBACK(close_btn_style_set), NULL);
737
738 gtk_box_pack_end( GTK_BOX( tab_label ), close_btn, FALSE, FALSE, 0 );
739
740 g_signal_connect(close_btn, "clicked", G_CALLBACK(on_close_tab_btn), view);
741
742 gtk_container_add(GTK_CONTAINER(evt_box), tab_label);
743 gtk_widget_set_events( GTK_WIDGET(evt_box), GDK_ALL_EVENTS_MASK);
744 /*
745 gtk_drag_dest_set ( GTK_WIDGET( evt_box ), GTK_DEST_DEFAULT_ALL,
746 drag_targets,
747 sizeof( drag_targets ) / sizeof( GtkTargetEntry ),
748 GDK_ACTION_DEFAULT | GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK );
749 g_signal_connect ( ( gpointer ) evt_box, "drag-motion",
750 G_CALLBACK ( on_tab_drag_motion ),
751 file_browser );
752 */
753 gtk_widget_show_all(GTK_WIDGET(evt_box));
754 return GTK_WIDGET(evt_box);
755 }
756
757 void update_tab_label(FmMainWin* win, FmFolderView* fv, const char* title)
758 {
759 GtkWidget* tab = gtk_notebook_get_tab_label((GtkNotebook*)win->notebook, win->folder_view);
760 GtkWidget* hbox = gtk_bin_get_child((GtkBin*)tab);
761 GList* children = gtk_container_get_children(hbox);
762 GtkWidget* label = (GtkWidget*)children->data;
763 g_list_free(children);
764 gtk_label_set_text((GtkLabel*)label, title);
765 }
766
767 /* FIXME: remote filesystems are sometimes regarded as local ones. */
768 static void on_vol_info_available(GObject *src, GAsyncResult *res, FmMainWin* win)
769 {
770 GFileInfo* inf = g_file_query_filesystem_info_finish((GFile*)src, res, NULL);
771 guint64 total, free;
772 char total_str[ 64 ];
773 char free_str[ 64 ];
774 char buf[128];
775 if(!inf)
776 return;
777 if(g_file_info_has_attribute(inf, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE))
778 {
779 total = g_file_info_get_attribute_uint64(inf, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
780 free = g_file_info_get_attribute_uint64(inf, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
781
782 fm_file_size_to_str(free_str, free, TRUE);
783 fm_file_size_to_str(total_str, total, TRUE);
784 g_snprintf( buf, G_N_ELEMENTS(buf),
785 _("Free space: %s (Total: %s )"), free_str, total_str );
786 gtk_label_set_text(gtk_bin_get_child(win->vol_status), buf);
787 gtk_widget_show(win->vol_status);
788 }
789 else
790 gtk_widget_hide(win->vol_status);
791 g_object_unref(inf);
792 }
793
794 void update_volume_info(FmMainWin* win)
795 {
796 FmFolderModel* model;
797 if(win->vol_status_cancellable)
798 {
799 g_cancellable_cancel(win->vol_status_cancellable);
800 g_object_unref(win->vol_status_cancellable);
801 win->vol_status_cancellable = NULL;
802 }
803 if(win->folder_view && (model = FM_FOLDER_VIEW(win->folder_view)->model))
804 {
805 /* FIXME: we should't access private data member directly. */
806 GFile* gf = model->dir->gf;
807 win->vol_status_cancellable = g_cancellable_new();
808 g_file_query_filesystem_info_async(gf,
809 G_FILE_ATTRIBUTE_FILESYSTEM_SIZE","
810 G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
811 G_PRIORITY_LOW, win->vol_status_cancellable,
812 on_vol_info_available, win);
813 }
814 else
815 gtk_widget_hide(win->vol_status);
816 }
817
818 gint fm_main_win_add_tab(FmMainWin* win, FmPath* path)
819 {
820 /* create label for the tab */
821 GtkWidget* label;
822 GtkWidget* folder_view;
823 gint ret;
824
825 /* create folder view */
826 folder_view = fm_folder_view_new( FM_FV_ICON_VIEW );
827 fm_folder_view_set_show_hidden(folder_view, FALSE);
828 fm_folder_view_sort(folder_view, GTK_SORT_DESCENDING, COL_FILE_NAME);
829 fm_folder_view_set_selection_mode(folder_view, GTK_SELECTION_MULTIPLE);
830 g_signal_connect(folder_view, "clicked", on_file_clicked, win);
831 g_signal_connect(folder_view, "status", on_status, win);
832 g_signal_connect(folder_view, "sel-changed", on_sel_changed, win);
833
834 fm_folder_view_chdir(folder_view, path);
835 gtk_widget_show(folder_view);
836
837 label = create_tab_label(win, path, folder_view);
838
839 ret = gtk_notebook_append_page(win->notebook, folder_view, label);
840 gtk_notebook_set_tab_reorderable(win->notebook, folder_view, TRUE);
841 gtk_notebook_set_current_page(win->notebook, ret);
842
843 if(gtk_notebook_get_n_pages(win->notebook) > 1)
844 gtk_notebook_set_show_tabs(win->notebook, TRUE);
845 else
846 gtk_notebook_set_show_tabs(win->notebook, FALSE);
847
848 /* set current folder view */
849 win->folder_view = folder_view;
850 /* create navigation history */
851 win->nav_history = fm_nav_history_new();
852 g_object_set_qdata_full((GObject*)folder_view, nav_history_id, win->nav_history, (GDestroyNotify)g_object_unref);
853 return ret;
854 }
855
856
857 FmMainWin* fm_main_win_add_win(FmMainWin* win, FmPath* path)
858 {
859 win = fm_main_win_new();
860 gtk_window_set_default_size(win, 640, 480);
861 fm_main_win_chdir(win, path);
862 gtk_window_present(win);
863 return win;
864 }
865
866 void on_cut(GtkAction* act, FmMainWin* win)
867 {
868 GtkWidget* focus = gtk_window_get_focus((GtkWindow*)win);
869 if(GTK_IS_EDITABLE(focus) &&
870 gtk_editable_get_selection_bounds((GtkEditable*)focus, NULL, NULL) )
871 {
872 gtk_editable_cut_clipboard((GtkEditable*)focus);
873 }
874 else
875 {
876 FmPathList* files = fm_folder_view_get_selected_file_paths(win->folder_view);
877 if(files)
878 {
879 fm_clipboard_cut_files(win, files);
880 fm_list_unref(files);
881 }
882 }
883 }
884
885 void on_copy(GtkAction* act, FmMainWin* win)
886 {
887 GtkWidget* focus = gtk_window_get_focus((GtkWindow*)win);
888 if(GTK_IS_EDITABLE(focus) &&
889 gtk_editable_get_selection_bounds((GtkEditable*)focus, NULL, NULL) )
890 {
891 gtk_editable_copy_clipboard((GtkEditable*)focus);
892 }
893 else
894 {
895 FmPathList* files = fm_folder_view_get_selected_file_paths(win->folder_view);
896 if(files)
897 {
898 fm_clipboard_copy_files(win, files);
899 fm_list_unref(files);
900 }
901 }
902 }
903
904 void on_copy_to(GtkAction* act, FmMainWin* win)
905 {
906 FmPathList* files = fm_folder_view_get_selected_file_paths(win->folder_view);
907 if(files)
908 {
909 fm_copy_files_to(files);
910 fm_list_unref(files);
911 }
912 }
913
914 void on_move_to(GtkAction* act, FmMainWin* win)
915 {
916 FmPathList* files = fm_folder_view_get_selected_file_paths(win->folder_view);
917 if(files)
918 {
919 fm_move_files_to(files);
920 fm_list_unref(files);
921 }
922 }
923
924 void on_paste(GtkAction* act, FmMainWin* win)
925 {
926 GtkWidget* focus = gtk_window_get_focus((GtkWindow*)win);
927 if(GTK_IS_EDITABLE(focus) )
928 {
929 gtk_editable_paste_clipboard((GtkEditable*)focus);
930 }
931 else
932 {
933 FmPath* path = fm_folder_view_get_cwd(win->folder_view);
934 fm_clipboard_paste_files(win->folder_view, path);
935 }
936 }
937
938 void on_del(GtkAction* act, FmMainWin* win)
939 {
940 FmPathList* files = fm_folder_view_get_selected_file_paths(win->folder_view);
941 fm_trash_or_delete_files(files);
942 fm_list_unref(files);
943 }
944
945 void on_rename(GtkAction* act, FmMainWin* win)
946 {
947 FmPathList* files = fm_folder_view_get_selected_file_paths(win->folder_view);
948 if( !fm_list_is_empty(files) )
949 {
950 fm_rename_file(fm_list_peek_head(files));
951 /* FIXME: is it ok to only rename the first selected file here. */
952 }
953 fm_list_unref(files);
954 }
955
956 void on_select_all(GtkAction* act, FmMainWin* win)
957 {
958 fm_folder_view_select_all(win->folder_view);
959 }
960
961 void on_invert_select(GtkAction* act, FmMainWin* win)
962 {
963 fm_folder_view_select_invert(win->folder_view);
964 }
965
966 void on_preference(GtkAction* act, FmMainWin* win)
967 {
968 fm_edit_preference(win, 0);
969 }
970
971 void on_location(GtkAction* act, FmMainWin* win)
972 {
973 gtk_widget_grab_focus(win->location);
974 }
975
976 void on_prop(GtkAction* action, FmMainWin* win)
977 {
978 FmFolderView* fv = FM_FOLDER_VIEW(win->folder_view);
979 /* FIXME: should prevent directly accessing data members */
980 FmFileInfo* fi = FM_FOLDER_MODEL(fv->model)->dir->dir_fi;
981 FmFileInfoList* files = fm_file_info_list_new();
982 fm_list_push_tail(files, fi);
983 fm_show_file_properties(files);
984 fm_list_unref(files);
985 }
986
987 void on_switch_page(GtkNotebook* nb, GtkNotebookPage* page, guint num, FmMainWin* win)
988 {
989 FmFolderView* fv = FM_FOLDER_VIEW(gtk_notebook_get_nth_page(nb, num));
990
991 if(win->folder_view)
992 g_signal_handlers_disconnect_by_func(win->folder_view, on_view_loaded, win);
993 win->folder_view = fv;
994
995 if(fv)
996 {
997 FmPath* cwd = fm_folder_view_get_cwd(fv);
998 win->nav_history = (FmNavHistory*)g_object_get_qdata((GObject*)fv, nav_history_id);
999
1000 /* FIXME: we shouldn't access private data member. */
1001 fm_path_entry_set_model( win->location, cwd, fv->model );
1002 g_signal_connect(fv, "loaded", G_CALLBACK(on_view_loaded), win);
1003
1004 if(cwd)
1005 {
1006 char* disp_name;
1007 disp_name = g_filename_display_name(cwd->name);
1008 gtk_window_set_title((GtkWindow*)win, disp_name);
1009 g_free(disp_name);
1010 }
1011
1012 update_volume_info(win);
1013 }
1014 }
1015
1016 void on_page_removed(GtkNotebook* nb, GtkWidget* page, guint num, FmMainWin* win)
1017 {
1018 if(gtk_notebook_get_n_pages(nb) > 1)
1019 gtk_notebook_set_show_tabs(nb, TRUE);
1020 else
1021 gtk_notebook_set_show_tabs(nb, FALSE);
1022 }
1023
1024 void on_create_new(GtkAction* action, FmMainWin* win)
1025 {
1026 FmFolderView* fv = FM_FOLDER_VIEW(win->folder_view);
1027 const char* name = gtk_action_get_name(action);
1028 GError* err = NULL;
1029 FmPath* cwd = fm_folder_view_get_cwd(fv);
1030 FmPath* dest;
1031 char* basename;
1032 _retry:
1033 basename = fm_get_user_input(win, _("Create New..."), _("Enter a name for the newly created file:"), _("New"));
1034 if(!basename)
1035 return;
1036
1037 dest = fm_path_new_child(cwd, basename);
1038 g_free(basename);
1039
1040 if( strcmp(name, "NewFolder") == 0 )
1041 {
1042 GFile* gf = fm_path_to_gfile(dest);
1043 if(!g_file_make_directory(gf, NULL, &err))
1044 {
1045 if(err->domain = G_IO_ERROR && err->code == G_IO_ERROR_EXISTS)
1046 {
1047 fm_path_unref(dest);
1048 g_error_free(err);
1049 g_object_unref(gf);
1050 err = NULL;
1051 goto _retry;
1052 }
1053 fm_show_error(win, err->message);
1054 g_error_free(err);
1055 }
1056
1057 if(!err) /* select the newly created file */
1058 {
1059 /*FIXME: this doesn't work since the newly created file will
1060 * only be shown after file-created event was fired on its
1061 * folder's monitor and after FmFolder handles it in idle
1062 * handler. So, we cannot select it since it's not yet in
1063 * the folder model now. */
1064 /* fm_folder_view_select_file_path(fv, dest); */
1065 }
1066 g_object_unref(gf);
1067 }
1068 else if( strcmp(name, "NewBlank") == 0 )
1069 {
1070 GFile* gf = fm_path_to_gfile(dest);
1071 GFileOutputStream* f = g_file_create(gf, G_FILE_CREATE_NONE, NULL, &err);
1072 if(f)
1073 {
1074 g_output_stream_close(f, NULL, NULL);
1075 g_object_unref(f);
1076 }
1077 else
1078 {
1079 if(err->domain = G_IO_ERROR && err->code == G_IO_ERROR_EXISTS)
1080 {
1081 fm_path_unref(dest);
1082 g_error_free(err);
1083 g_object_unref(gf);
1084 err = NULL;
1085 goto _retry;
1086 }
1087 fm_show_error(win, err->message);
1088 g_error_free(err);
1089 }
1090
1091 if(!err) /* select the newly created file */
1092 {
1093 /*FIXME: this doesn't work since the newly created file will
1094 * only be shown after file-created event was fired on its
1095 * folder's monitor and after FmFolder handles it in idle
1096 * handler. So, we cannot select it since it's not yet in
1097 * the folder model now. */
1098 /* fm_folder_view_select_file_path(fv, dest); */
1099 }
1100 g_object_unref(gf);
1101 }
1102 else /* templates */
1103 {
1104
1105 }
1106 fm_path_unref(dest);
1107 }
1108
1109 FmMainWin* fm_main_win_get_last_active()
1110 {
1111 return all_wins ? (FmMainWin*)all_wins->data : NULL;
1112 }
1113
1114 void fm_main_win_open_in_last_active(FmPath* path)
1115 {
1116 FmMainWin* win = fm_main_win_get_last_active();
1117 if(!win)
1118 {
1119 win = fm_main_win_new();
1120 gtk_window_set_default_size(win, app_config->win_width, app_config->win_height);
1121 fm_main_win_chdir(win, path);
1122 }
1123 else
1124 fm_main_win_add_tab(win, path);
1125 gtk_window_present(win);
1126 }