Fix compatibility with LIBFM 1.0.1 and 1.1.0.
[lxde/pcmanfm.git] / src / tab-page.c
1 // fm-tab-page.c
2 //
3 // Copyright 2011 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
4 // Copyright 2012-2014 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
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 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include <libfm/fm-gtk.h>
26 #include <glib/gi18n.h>
27
28 #include "app-config.h"
29 #include "main-win.h"
30 #include "tab-page.h"
31
32 #include "gseal-gtk-compat.h"
33
34 #include <stdlib.h>
35 #include <fnmatch.h>
36
37 /* Additional entries for FmFileMenu popup */
38 /* it is also used for FmSidePane context menu popup */
39 static const char folder_menu_xml[]=
40 "<popup>"
41 "<placeholder name='ph1'>"
42 "<menuitem action='NewTab'/>"
43 "<menuitem action='NewWin'/>"
44 "<menuitem action='Term'/>"
45 /* "<menuitem action='Search'/>" */
46 "</placeholder>"
47 "</popup>";
48
49 static void on_open_in_new_tab(GtkAction* act, FmMainWin* win);
50 static void on_open_in_new_win(GtkAction* act, FmMainWin* win);
51 static void on_open_folder_in_terminal(GtkAction* act, FmMainWin* win);
52
53 /* Action entries for popup menu entries above */
54 static GtkActionEntry folder_menu_actions[]=
55 {
56 {"NewTab", GTK_STOCK_NEW, N_("Open in New T_ab"), NULL, NULL, G_CALLBACK(on_open_in_new_tab)},
57 {"NewWin", GTK_STOCK_NEW, N_("Open in New Win_dow"), NULL, NULL, G_CALLBACK(on_open_in_new_win)},
58 {"Search", GTK_STOCK_FIND, NULL, NULL, NULL, NULL},
59 {"Term", "utilities-terminal", N_("Open in Termina_l"), NULL, NULL, G_CALLBACK(on_open_folder_in_terminal)},
60 };
61
62 #define GET_MAIN_WIN(page) FM_MAIN_WIN(gtk_widget_get_toplevel(GTK_WIDGET(page)))
63
64 enum {
65 CHDIR,
66 OPEN_DIR,
67 STATUS,
68 GOT_FOCUS,
69 N_SIGNALS
70 };
71
72 static guint signals[N_SIGNALS];
73
74 static void fm_tab_page_finalize(GObject *object);
75 static void fm_tab_page_chdir_without_history(FmTabPage* page, FmPath* path);
76 static void on_folder_fs_info(FmFolder* folder, FmTabPage* page);
77 static void on_folder_start_loading(FmFolder* folder, FmTabPage* page);
78 static void on_folder_finish_loading(FmFolder* folder, FmTabPage* page);
79 static void on_folder_removed(FmFolder* folder, FmTabPage* page);
80 static void on_folder_unmount(FmFolder* folder, FmTabPage* page);
81 static void on_folder_content_changed(FmFolder* folder, FmTabPage* page);
82 static FmJobErrorAction on_folder_error(FmFolder* folder, GError* err, FmJobErrorSeverity severity, FmTabPage* page);
83
84 static void on_folder_view_sel_changed(FmFolderView* fv, gint n_sel, FmTabPage* page);
85 #if FM_CHECK_VERSION(1, 2, 0)
86 static void on_folder_view_columns_changed(FmFolderView *fv, FmTabPage *page);
87 #endif
88 static gboolean on_folder_view_focus_in(GtkWidget *widget, GdkEvent *event, FmTabPage *page);
89 static char* format_status_text(FmTabPage* page);
90
91 #if GTK_CHECK_VERSION(3, 0, 0)
92 static void fm_tab_page_destroy(GtkWidget *page);
93 #else
94 static void fm_tab_page_destroy(GtkObject *page);
95 #endif
96
97 static void fm_tab_page_realize(GtkWidget *page);
98 static void fm_tab_page_unrealize(GtkWidget *page);
99
100 static GQuark popup_qdata;
101
102 G_DEFINE_TYPE(FmTabPage, fm_tab_page, GTK_TYPE_HPANED)
103
104 static void fm_tab_page_class_init(FmTabPageClass *klass)
105 {
106 GObjectClass *g_object_class = G_OBJECT_CLASS(klass);
107 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
108 #if GTK_CHECK_VERSION(3, 0, 0)
109 widget_class->destroy = fm_tab_page_destroy;
110 #else
111 GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS(klass);
112 gtk_object_class->destroy = fm_tab_page_destroy;
113 #endif
114 g_object_class->finalize = fm_tab_page_finalize;
115 widget_class->realize = fm_tab_page_realize;
116 widget_class->unrealize = fm_tab_page_unrealize;
117
118 /* signals that current working directory is changed. */
119 signals[CHDIR] =
120 g_signal_new("chdir",
121 G_TYPE_FROM_CLASS(klass),
122 G_SIGNAL_RUN_FIRST,
123 G_STRUCT_OFFSET (FmTabPageClass, chdir),
124 NULL, NULL,
125 g_cclosure_marshal_VOID__POINTER,
126 G_TYPE_NONE, 1, G_TYPE_POINTER);
127
128 #if 0
129 /* FIXME: is this really needed? */
130 /* signals that the user wants to open a new dir. */
131 signals[OPEN_DIR] =
132 g_signal_new("open-dir",
133 G_TYPE_FROM_CLASS(klass),
134 G_SIGNAL_RUN_FIRST,
135 G_STRUCT_OFFSET (FmTabPageClass, open_dir),
136 NULL, NULL,
137 g_cclosure_marshal_VOID__UINT_POINTER,
138 G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_POINTER);
139 #endif
140
141 /* emit when the status bar message is changed */
142 signals[STATUS] =
143 g_signal_new("status",
144 G_TYPE_FROM_CLASS(klass),
145 G_SIGNAL_RUN_FIRST,
146 G_STRUCT_OFFSET (FmTabPageClass, status),
147 NULL, NULL,
148 g_cclosure_marshal_VOID__UINT_POINTER,
149 G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_POINTER);
150 /* folder view received the focus */
151 signals[GOT_FOCUS] =
152 g_signal_new("got-focus",
153 G_TYPE_FROM_CLASS(klass),
154 G_SIGNAL_RUN_FIRST,
155 G_STRUCT_OFFSET (FmTabPageClass, got_focus),
156 NULL, NULL,
157 g_cclosure_marshal_VOID__VOID,
158 G_TYPE_NONE, 0);
159
160 popup_qdata = g_quark_from_static_string("tab-page::popup-filelist");
161 }
162
163
164 static void fm_tab_page_finalize(GObject *object)
165 {
166 FmTabPage *page;
167 int i;
168
169 g_return_if_fail(object != NULL);
170 g_return_if_fail(FM_IS_TAB_PAGE(object));
171
172 page = (FmTabPage*)object;
173
174 for(i = 0; i < FM_STATUS_TEXT_NUM; ++i)
175 g_free(page->status_text[i]);
176
177 #if FM_CHECK_VERSION(1, 0, 2)
178 g_free(page->filter_pattern);
179 #endif
180
181 G_OBJECT_CLASS(fm_tab_page_parent_class)->finalize(object);
182 }
183
184 static void free_folder(FmTabPage* page)
185 {
186 if(page->folder)
187 {
188 g_signal_handlers_disconnect_by_func(page->folder, on_folder_start_loading, page);
189 g_signal_handlers_disconnect_by_func(page->folder, on_folder_finish_loading, page);
190 g_signal_handlers_disconnect_by_func(page->folder, on_folder_fs_info, page);
191 g_signal_handlers_disconnect_by_func(page->folder, on_folder_error, page);
192 g_signal_handlers_disconnect_by_func(page->folder, on_folder_content_changed, page);
193 g_signal_handlers_disconnect_by_func(page->folder, on_folder_removed, page);
194 g_signal_handlers_disconnect_by_func(page->folder, on_folder_unmount, page);
195 g_object_unref(page->folder);
196 page->folder = NULL;
197 #if FM_CHECK_VERSION(1, 2, 0)
198 if (page->want_focus)
199 fm_path_unref(page->want_focus);
200 page->want_focus = NULL;
201 #endif
202 }
203 }
204
205 /* workaround on FmStandardView: it should forward focus-in events but doesn't do */
206 static void on_folder_view_add(GtkContainer *container, GtkWidget *child, FmTabPage *page)
207 {
208 g_signal_connect(child, "focus-in-event",
209 G_CALLBACK(on_folder_view_focus_in), page);
210 }
211
212 static void on_folder_view_remove(GtkContainer *container, GtkWidget *child, FmTabPage *page)
213 {
214 g_signal_handlers_disconnect_by_func(child, on_folder_view_focus_in, page);
215 }
216
217 static void _connect_focus_in(FmFolderView *folder_view, FmTabPage *page)
218 {
219 GList *children, *l;
220
221 g_signal_connect(folder_view, "focus-in-event",
222 G_CALLBACK(on_folder_view_focus_in), page);
223 g_signal_connect(folder_view, "add",
224 G_CALLBACK(on_folder_view_add), page);
225 g_signal_connect(folder_view, "remove",
226 G_CALLBACK(on_folder_view_remove), page);
227 children = gtk_container_get_children(GTK_CONTAINER(folder_view));
228 for (l = children; l; l = l->next)
229 g_signal_connect(l->data, "focus-in-event",
230 G_CALLBACK(on_folder_view_focus_in), page);
231 g_list_free(children);
232 }
233
234 static void _disconnect_focus_in(FmFolderView *folder_view, FmTabPage *page)
235 {
236 GList *children, *l;
237
238 g_signal_handlers_disconnect_by_func(folder_view, on_folder_view_focus_in, page);
239 g_signal_handlers_disconnect_by_func(folder_view, on_folder_view_add, page);
240 g_signal_handlers_disconnect_by_func(folder_view, on_folder_view_remove, page);
241 children = gtk_container_get_children(GTK_CONTAINER(folder_view));
242 for (l = children; l; l = l->next)
243 g_signal_handlers_disconnect_by_func(l->data, on_folder_view_focus_in, page);
244 g_list_free(children);
245 }
246
247 #if FM_CHECK_VERSION(1, 2, 0)
248 static void on_home_path_changed(FmAppConfig *cfg, FmSidePane *sp)
249 {
250 if (cfg->home_path && cfg->home_path[0])
251 fm_side_pane_set_home_dir(sp, cfg->home_path);
252 else
253 fm_side_pane_set_home_dir(sp, fm_get_home_dir());
254 }
255 #endif
256
257 #if GTK_CHECK_VERSION(3, 0, 0)
258 void fm_tab_page_destroy(GtkWidget *object)
259 #else
260 void fm_tab_page_destroy(GtkObject *object)
261 #endif
262 {
263 FmTabPage* page = FM_TAB_PAGE(object);
264
265 g_debug("fm_tab_page_destroy, folder: %s",
266 page->folder ? fm_path_get_basename(fm_folder_get_path(page->folder)) : "(none)");
267 free_folder(page);
268 if(page->nav_history)
269 {
270 g_object_unref(page->nav_history);
271 page->nav_history = NULL;
272 }
273 if(page->folder_view)
274 {
275 /* tab page may be inactive now and there is a chance it does not contain
276 own view therefore we have to destroy own folder view widget manually */
277 GtkWidget *fv = GTK_WIDGET(page->folder_view);
278 GtkWidget *parent = gtk_widget_get_parent(fv);
279 GList *panes, *l;
280
281 if (parent)
282 {
283 panes = gtk_container_get_children(GTK_CONTAINER(page->views));
284 for (l = panes; l; l = l->next)
285 if ((GtkWidget*)l->data == fv)
286 break;
287 if (l == NULL)
288 gtk_container_remove(GTK_CONTAINER(parent), fv);
289 g_list_free(panes);
290 }
291
292 g_signal_handlers_disconnect_by_func(page->folder_view, on_folder_view_sel_changed, page);
293 #if FM_CHECK_VERSION(1, 2, 0)
294 g_signal_handlers_disconnect_by_func(page->folder_view, on_folder_view_columns_changed, page);
295 #endif
296 #if FM_CHECK_VERSION(1, 2, 0)
297 g_signal_handlers_disconnect_by_func(app_config, on_home_path_changed, page->side_pane);
298 #endif
299 _disconnect_focus_in(page->folder_view, page);
300 g_object_unref(page->folder_view);
301 page->folder_view = NULL;
302 }
303 #if FM_CHECK_VERSION(1, 0, 2)
304 g_strfreev(page->columns);
305 page->columns = NULL;
306 #endif
307 if(page->update_scroll_id)
308 {
309 g_source_remove(page->update_scroll_id);
310 page->update_scroll_id = 0;
311 }
312 #if FM_CHECK_VERSION(1, 2, 0)
313 fm_side_pane_set_popup_updater(page->side_pane, NULL, NULL);
314 #endif
315 if (page->dd)
316 {
317 g_object_unref(page->dd);
318 page->dd = NULL;
319 }
320
321 #if GTK_CHECK_VERSION(3, 0, 0)
322 if(GTK_WIDGET_CLASS(fm_tab_page_parent_class)->destroy)
323 (*GTK_WIDGET_CLASS(fm_tab_page_parent_class)->destroy)(object);
324 #else
325 if(GTK_OBJECT_CLASS(fm_tab_page_parent_class)->destroy)
326 (*GTK_OBJECT_CLASS(fm_tab_page_parent_class)->destroy)(object);
327 #endif
328 }
329
330 static void on_folder_content_changed(FmFolder* folder, FmTabPage* page)
331 {
332 /* update status text */
333 g_free(page->status_text[FM_STATUS_TEXT_NORMAL]);
334 page->status_text[FM_STATUS_TEXT_NORMAL] = format_status_text(page);
335 g_signal_emit(page, signals[STATUS], 0,
336 (guint)FM_STATUS_TEXT_NORMAL,
337 page->status_text[FM_STATUS_TEXT_NORMAL]);
338 }
339
340 static void on_folder_view_sel_changed(FmFolderView* fv, gint n_sel, FmTabPage* page)
341 {
342 char* msg = page->status_text[FM_STATUS_TEXT_SELECTED_FILES];
343 GString *str;
344 g_free(msg);
345
346 if(n_sel > 0)
347 {
348 str = g_string_sized_new(64);
349 /* FIXME: display total size of all selected files. */
350 if(n_sel == 1) /* only one file is selected */
351 {
352 FmFileInfoList* files = fm_folder_view_dup_selected_files(fv);
353 FmFileInfo* fi = fm_file_info_list_peek_head(files);
354 const char* size_str = fm_file_info_get_disp_size(fi);
355 #if FM_CHECK_VERSION(1, 2, 0)
356 GList *l;
357 #endif
358 if(size_str)
359 {
360 g_string_printf(str, "\"%s\" (%s) %s",
361 fm_file_info_get_disp_name(fi),
362 size_str ? size_str : "",
363 fm_file_info_get_desc(fi));
364 }
365 else
366 {
367 g_string_printf(str, "\"%s\" %s",
368 fm_file_info_get_disp_name(fi),
369 fm_file_info_get_desc(fi));
370 }
371 #if FM_CHECK_VERSION(1, 2, 0)
372 /* ---- statusbar plugins support ---- */
373 CHECK_MODULES();
374 for (l = _tab_page_modules; l; l = l->next)
375 {
376 FmTabPageStatusInit *module = l->data;
377 char *message = module->sel_message(files, n_sel);
378 if (message && message[0])
379 {
380 g_string_append_c(str, ' ');
381 g_string_append(str, message);
382 }
383 g_free(message);
384 }
385 #endif
386 fm_file_info_list_unref(files);
387 }
388 else
389 {
390 FmFileInfoList* files;
391 goffset sum;
392 GList *l;
393 char size_str[128];
394
395 g_string_printf(str, ngettext("%d item selected", "%d items selected", n_sel), n_sel);
396 /* don't count if too many files are selected, that isn't lightweight */
397 if (n_sel < 1000)
398 {
399 sum = 0;
400 files = fm_folder_view_dup_selected_files(fv);
401 for (l = fm_file_info_list_peek_head_link(files); l; l = l->next)
402 {
403 if (fm_file_info_is_dir(l->data))
404 {
405 /* if we got a directory then we cannot tell it's size
406 unless we do deep count but we cannot afford it */
407 sum = -1;
408 break;
409 }
410 sum += fm_file_info_get_size(l->data);
411 }
412 if (sum >= 0)
413 {
414 fm_file_size_to_str(size_str, sizeof(size_str), sum,
415 fm_config->si_unit);
416 g_string_append_printf(str, " (%s)", size_str);
417 }
418 #if FM_CHECK_VERSION(1, 2, 0)
419 /* ---- statusbar plugins support ---- */
420 CHECK_MODULES();
421 for (l = _tab_page_modules; l; l = l->next)
422 {
423 FmTabPageStatusInit *module = l->data;
424 char *message = module->sel_message(files, n_sel);
425 if (message && message[0])
426 {
427 g_string_append_c(str, ' ');
428 g_string_append(str, message);
429 }
430 g_free(message);
431 }
432 #endif
433 fm_file_info_list_unref(files);
434 }
435 /* FIXME: can we show some more info on selection?
436 that isn't lightweight if a lot of files are selected */
437 }
438 msg = g_string_free(str, FALSE);
439 }
440 else
441 msg = NULL;
442 page->status_text[FM_STATUS_TEXT_SELECTED_FILES] = msg;
443 g_signal_emit(page, signals[STATUS], 0,
444 (guint)FM_STATUS_TEXT_SELECTED_FILES, msg);
445 }
446
447 #if FM_CHECK_VERSION(1, 2, 0)
448 static void on_folder_view_columns_changed(FmFolderView *fv, FmTabPage *page)
449 {
450 GSList *columns = fm_folder_view_get_columns(fv), *l;
451 char **cols;
452 guint i;
453
454 if (columns == NULL)
455 return;
456 i = g_slist_length(columns);
457 cols = g_new(char *, i+1);
458 for (i = 0, l = columns; l; i++, l = l->next)
459 {
460 FmFolderViewColumnInfo *info = l->data;
461
462 if (info->width > 0)
463 cols[i] = g_strdup_printf("%s:%d",
464 fm_folder_model_col_get_name(info->col_id),
465 info->width);
466 else
467 cols[i] = g_strdup(fm_folder_model_col_get_name(info->col_id));
468 }
469 g_slist_free(columns);
470 cols[i] = NULL; /* terminate the list */
471 if (page->own_config)
472 {
473 g_strfreev(page->columns);
474 page->columns = cols;
475 fm_app_config_save_config_for_path(fm_folder_view_get_cwd(fv),
476 page->sort_type, page->sort_by, -1,
477 page->show_hidden, cols);
478 }
479 else
480 {
481 g_strfreev(app_config->columns);
482 app_config->columns = cols;
483 pcmanfm_save_config(FALSE);
484 }
485 }
486 #endif
487
488 static gboolean on_folder_view_focus_in(GtkWidget *widget, GdkEvent *event, FmTabPage *page)
489 {
490 g_signal_emit(page, signals[GOT_FOCUS], 0);
491 return FALSE;
492 }
493
494 static FmJobErrorAction on_folder_error(FmFolder* folder, GError* err, FmJobErrorSeverity severity, FmTabPage* page)
495 {
496 GtkWindow* win = GTK_WINDOW(GET_MAIN_WIN(page));
497 if(err->domain == G_IO_ERROR)
498 {
499 if( err->code == G_IO_ERROR_NOT_MOUNTED && severity < FM_JOB_ERROR_CRITICAL )
500 {
501 FmPath* path = fm_folder_get_path(folder);
502 if(fm_mount_path(win, path, TRUE))
503 return FM_JOB_RETRY;
504 }
505 }
506 if(severity >= FM_JOB_ERROR_MODERATE)
507 {
508 /* Only show more severe errors to the users and
509 * ignore milder errors. Otherwise too many error
510 * message boxes can be annoying.
511 * This fixes bug #3411298- Show "Permission denied" when switching to super user mode.
512 * https://sourceforge.net/tracker/?func=detail&aid=3411298&group_id=156956&atid=801864
513 * */
514 fm_show_error(win, NULL, err->message);
515 }
516 return FM_JOB_CONTINUE;
517 }
518
519 static void fm_tab_page_realize(GtkWidget *page)
520 {
521 GTK_WIDGET_CLASS(fm_tab_page_parent_class)->realize(page);
522 if (FM_TAB_PAGE(page)->busy)
523 fm_set_busy_cursor(page);
524 }
525
526 static void fm_tab_page_unrealize(GtkWidget *page)
527 {
528 if (FM_TAB_PAGE(page)->busy)
529 fm_unset_busy_cursor(page);
530 GTK_WIDGET_CLASS(fm_tab_page_parent_class)->unrealize(page);
531 }
532
533 static void _tab_set_busy_cursor(FmTabPage* page)
534 {
535 page->busy = TRUE;
536 if (gtk_widget_get_realized(GTK_WIDGET(page)))
537 fm_set_busy_cursor(GTK_WIDGET(page));
538 }
539
540 static void _tab_unset_busy_cursor(FmTabPage* page)
541 {
542 page->busy = FALSE;
543 if (gtk_widget_get_realized(GTK_WIDGET(page)))
544 fm_unset_busy_cursor(GTK_WIDGET(page));
545 }
546
547 #if FM_CHECK_VERSION(1, 0, 2)
548 static gboolean fm_tab_page_path_filter(FmFileInfo *file, gpointer user_data)
549 {
550 FmTabPage *page;
551 const char *disp_name;
552 char *casefold, *key;
553 gboolean result;
554
555 g_return_val_if_fail(FM_IS_TAB_PAGE(user_data), FALSE);
556 page = (FmTabPage*)user_data;
557 if (page->filter_pattern == NULL)
558 return TRUE;
559 disp_name = fm_file_info_get_disp_name(file);
560 casefold = g_utf8_casefold(disp_name, -1);
561 key = g_utf8_normalize(casefold, -1, G_NORMALIZE_ALL);
562 g_free(casefold);
563 result = (fnmatch(page->filter_pattern, key, 0) == 0);
564 g_free(key);
565 return result;
566 }
567 #endif
568
569 static void on_folder_start_loading(FmFolder* folder, FmTabPage* page)
570 {
571 FmFolderView* fv = page->folder_view;
572 /* g_debug("start-loading"); */
573 /* FIXME: this should be set on toplevel parent */
574 _tab_set_busy_cursor(page);
575
576 #if FM_CHECK_VERSION(1, 0, 2)
577 if(fm_folder_is_incremental(folder))
578 {
579 /* create a model for the folder and set it to the view
580 it is delayed for non-incremental folders since adding rows into
581 model is much faster without handlers connected to its signals */
582 FmFolderModel* model = fm_folder_model_new(folder, page->show_hidden);
583 if (page->filter_pattern)
584 {
585 fm_folder_model_add_filter(model, fm_tab_page_path_filter, page);
586 fm_folder_model_apply_filters(model);
587 }
588 fm_folder_view_set_model(fv, model);
589 fm_folder_model_set_sort(model, page->sort_by, page->sort_type);
590 g_object_unref(model);
591 }
592 else
593 #endif
594 fm_folder_view_set_model(fv, NULL);
595 }
596
597 static gboolean update_scroll(gpointer data)
598 {
599 FmTabPage* page = data;
600 GtkScrolledWindow* scroll = GTK_SCROLLED_WINDOW(page->folder_view);
601 #if !FM_CHECK_VERSION(1, 0, 2)
602 const FmNavHistoryItem* item;
603
604 item = fm_nav_history_get_cur(page->nav_history);
605 /* scroll to recorded position */
606 gtk_adjustment_set_value(gtk_scrolled_window_get_vadjustment(scroll), item->scroll_pos);
607 #else
608 gtk_adjustment_set_value(gtk_scrolled_window_get_vadjustment(scroll),
609 fm_nav_history_get_scroll_pos(page->nav_history));
610 #endif
611 #if FM_CHECK_VERSION(1, 2, 0)
612 if (page->want_focus)
613 {
614 fm_folder_view_select_file_path(page->folder_view, page->want_focus);
615 fm_folder_view_scroll_to_path(page->folder_view, page->want_focus, TRUE);
616 fm_path_unref(page->want_focus);
617 page->want_focus = NULL;
618 }
619 #endif
620 page->update_scroll_id = 0;
621 return FALSE;
622 }
623
624 static void on_folder_finish_loading(FmFolder* folder, FmTabPage* page)
625 {
626 FmFolderView* fv = page->folder_view;
627
628 /* Note: most of the time, we delay the creation of the
629 * folder model and do it after the whole folder is loaded.
630 * That is because adding rows into model is much faster when no handlers
631 * are connected to its signals. So we detach the model from folder view
632 * and create the model again when it's fully loaded.
633 * This optimization, however, is not used for FmFolder objects
634 * with incremental loading (search://) */
635 if(fm_folder_view_get_model(fv) == NULL)
636 {
637 /* create a model for the folder and set it to the view */
638 FmFolderModel* model = fm_folder_model_new(folder, page->show_hidden);
639 fm_folder_view_set_model(fv, model);
640 #if FM_CHECK_VERSION(1, 0, 2)
641 if (page->filter_pattern)
642 {
643 fm_folder_model_add_filter(model, fm_tab_page_path_filter, page);
644 fm_folder_model_apply_filters(model);
645 }
646 /* since 1.0.2 sorting should be applied on model instead of view */
647 fm_folder_model_set_sort(model, page->sort_by, page->sort_type);
648 #endif
649 g_object_unref(model);
650 }
651 fm_folder_query_filesystem_info(folder); /* FIXME: is this needed? */
652
653 // fm_path_entry_set_path(entry, path);
654 /* delaying scrolling since drawing folder view is delayed */
655 if(!page->update_scroll_id)
656 page->update_scroll_id = gdk_threads_add_timeout(20, update_scroll, page);
657
658 /* update status bar */
659 /* update status text */
660 g_free(page->status_text[FM_STATUS_TEXT_NORMAL]);
661 page->status_text[FM_STATUS_TEXT_NORMAL] = format_status_text(page);
662 g_signal_emit(page, signals[STATUS], 0,
663 (guint)FM_STATUS_TEXT_NORMAL,
664 page->status_text[FM_STATUS_TEXT_NORMAL]);
665
666 _tab_unset_busy_cursor(page);
667 /* g_debug("finish-loading"); */
668 }
669
670 static void on_folder_unmount(FmFolder* folder, FmTabPage* page)
671 {
672 if (app_config->close_on_unmount)
673 gtk_widget_destroy(GTK_WIDGET(page));
674 else
675 #if FM_CHECK_VERSION(1, 2, 0)
676 if (app_config->home_path && app_config->home_path[0])
677 {
678 FmPath *path = fm_path_new_for_str(app_config->home_path);
679
680 fm_tab_page_chdir(page, path);
681 fm_path_unref(path);
682 }
683 else
684 #endif
685 fm_tab_page_chdir(page, fm_path_get_home());
686 }
687
688 static void on_folder_removed(FmFolder* folder, FmTabPage* page)
689 {
690 if (app_config->close_on_unmount)
691 gtk_widget_destroy(GTK_WIDGET(page));
692 else
693 #if FM_CHECK_VERSION(1, 2, 0)
694 if (app_config->home_path && app_config->home_path[0])
695 {
696 FmPath *path = fm_path_new_for_str(app_config->home_path);
697
698 fm_tab_page_chdir(page, path);
699 fm_path_unref(path);
700 }
701 else
702 #endif
703 fm_tab_page_chdir(page, fm_path_get_home());
704 }
705
706 static void on_folder_fs_info(FmFolder* folder, FmTabPage* page)
707 {
708 guint64 free, total;
709 char* msg = page->status_text[FM_STATUS_TEXT_FS_INFO];
710 g_free(msg);
711 /* g_debug("%p, fs-info: %d", folder, (int)folder->has_fs_info); */
712 if(fm_folder_get_filesystem_info(folder, &total, &free))
713 {
714 char total_str[ 64 ];
715 char free_str[ 64 ];
716 fm_file_size_to_str(free_str, sizeof(free_str), free, fm_config->si_unit);
717 fm_file_size_to_str(total_str, sizeof(total_str), total, fm_config->si_unit);
718 msg = g_strdup_printf(_("Free space: %s (Total: %s)"), free_str, total_str );
719 }
720 else
721 msg = NULL;
722 page->status_text[FM_STATUS_TEXT_FS_INFO] = msg;
723 g_signal_emit(page, signals[STATUS], 0,
724 (guint)FM_STATUS_TEXT_FS_INFO, msg);
725 }
726
727 static char* format_status_text(FmTabPage* page)
728 {
729 FmFolderModel* model = fm_folder_view_get_model(page->folder_view);
730 FmFolder* folder = fm_folder_view_get_folder(page->folder_view);
731 if(model && folder)
732 {
733 FmFileInfoList* files = fm_folder_get_files(folder);
734 GString* msg = g_string_sized_new(128);
735 int total_files = fm_file_info_list_get_length(files);
736 int shown_files = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(model), NULL);
737 int hidden_files = total_files - shown_files;
738 const char* visible_fmt = ngettext("%d item", "%d items", shown_files);
739 const char* hidden_fmt = ngettext(" (%d hidden)", " (%d hidden)", hidden_files);
740
741 g_string_append_printf(msg, visible_fmt, shown_files);
742 if(hidden_files > 0)
743 g_string_append_printf(msg, hidden_fmt, hidden_files);
744 return g_string_free(msg, FALSE);
745 }
746 return NULL;
747 }
748
749 static void on_open_in_new_tab(GtkAction* act, FmMainWin* win)
750 {
751 GObject* act_grp;
752 FmFileInfoList* sels;
753 GList* l;
754
755 g_object_get(act, "action-group", &act_grp, NULL);
756 sels = g_object_get_qdata(act_grp, popup_qdata);
757 g_object_unref(act_grp);
758 for( l = fm_file_info_list_peek_head_link(sels); l; l=l->next )
759 {
760 FmFileInfo* fi = (FmFileInfo*)l->data;
761 fm_main_win_add_tab(win, fm_file_info_get_path(fi));
762 }
763 }
764
765 static void on_open_in_new_win(GtkAction* act, FmMainWin* win)
766 {
767 GObject* act_grp;
768 FmFileInfoList* sels;
769 GList* l;
770
771 g_object_get(act, "action-group", &act_grp, NULL);
772 sels = g_object_get_qdata(act_grp, popup_qdata);
773 g_object_unref(act_grp);
774 for( l = fm_file_info_list_peek_head_link(sels); l; l=l->next )
775 {
776 FmFileInfo* fi = (FmFileInfo*)l->data;
777 fm_main_win_add_win(win, fm_file_info_get_path(fi));
778 }
779 }
780
781 static void on_open_folder_in_terminal(GtkAction* act, FmMainWin* win)
782 {
783 GObject* act_grp;
784 FmFileInfoList* files;
785 GList* l;
786
787 g_object_get(act, "action-group", &act_grp, NULL);
788 files = g_object_get_qdata(act_grp, popup_qdata);
789 g_object_unref(act_grp);
790 for(l=fm_file_info_list_peek_head_link(files);l;l=l->next)
791 {
792 FmFileInfo* fi = (FmFileInfo*)l->data;
793 pcmanfm_open_folder_in_terminal(GTK_WINDOW(win), fm_file_info_get_path(fi));
794 }
795 }
796
797 /* folder view popups */
798 static void update_files_popup(FmFolderView* fv, GtkWindow* win,
799 GtkUIManager* ui, GtkActionGroup* act_grp,
800 FmFileInfoList* files)
801 {
802 GList* l;
803 gboolean all_native = TRUE;
804
805 for(l = fm_file_info_list_peek_head_link(files); l; l = l->next)
806 if(!fm_file_info_is_dir(l->data))
807 return; /* actions are valid only if all selected are directories */
808 else if (!fm_file_info_is_native(l->data))
809 all_native = FALSE;
810 g_object_set_qdata_full(G_OBJECT(act_grp), popup_qdata,
811 fm_file_info_list_ref(files),
812 (GDestroyNotify)fm_file_info_list_unref);
813 gtk_action_group_set_translation_domain(act_grp, NULL);
814 gtk_action_group_add_actions(act_grp, folder_menu_actions,
815 G_N_ELEMENTS(folder_menu_actions), win);
816 gtk_ui_manager_add_ui_from_string(ui, folder_menu_xml, -1, NULL);
817 if (!all_native)
818 gtk_action_set_visible(gtk_action_group_get_action(act_grp, "Term"), FALSE);
819 }
820
821 static gboolean open_folder_func(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data, GError** err)
822 {
823 FmMainWin* win = FM_MAIN_WIN(user_data);
824 GList* l = folder_infos;
825 FmFileInfo* fi = (FmFileInfo*)l->data;
826 fm_main_win_chdir(win, fm_file_info_get_path(fi));
827 l=l->next;
828 for(; l; l=l->next)
829 {
830 FmFileInfo* fi = (FmFileInfo*)l->data;
831 fm_main_win_add_tab(win, fm_file_info_get_path(fi));
832 }
833 return TRUE;
834 }
835
836 #if FM_CHECK_VERSION(1, 2, 0)
837 void _update_sidepane_popup(FmSidePane* sp, GtkUIManager* ui,
838 GtkActionGroup* act_grp,
839 FmFileInfo* file, gpointer user_data)
840 {
841 FmMainWin *win = GET_MAIN_WIN(user_data); /* user_data is FmTabPage */
842 FmFileInfoList* files;
843
844 /* bookmark may contain not a directory */
845 if (G_UNLIKELY(!file || !fm_file_info_is_dir(file)))
846 return;
847 /* well, it should be FmMainWin but let safeguard it */
848 if (G_UNLIKELY(!IS_FM_MAIN_WIN(win)))
849 return;
850 files = fm_file_info_list_new();
851 fm_file_info_list_push_tail(files, file);
852 g_object_set_qdata_full(G_OBJECT(act_grp), popup_qdata, files,
853 (GDestroyNotify)fm_file_info_list_unref);
854 gtk_action_group_set_translation_domain(act_grp, NULL);
855 gtk_action_group_add_actions(act_grp, folder_menu_actions,
856 G_N_ELEMENTS(folder_menu_actions), win);
857 /* we use the same XML for simplicity */
858 gtk_ui_manager_add_ui_from_string(ui, folder_menu_xml, -1, NULL);
859 if (!fm_file_info_is_native(file))
860 gtk_action_set_visible(gtk_action_group_get_action(act_grp, "Term"), FALSE);
861 }
862 #endif
863
864 static gboolean on_drag_motion(FmTabLabel *label, GdkDragContext *drag_context,
865 gint x, gint y, guint time, FmTabPage *page)
866 {
867 GdkAtom target;
868 GdkDragAction action = 0;
869 FmFileInfo *file_info = NULL;
870
871 /* if change_tab_on_drop is set then we should ignore it and drop file
872 using classic behavior - drop after it unfolded, so not drop on label */
873 if (!app_config->change_tab_on_drop && page->folder_view)
874 file_info = fm_folder_view_get_cwd_info(page->folder_view);
875 fm_dnd_dest_set_dest_file(page->dd, file_info);
876 if (file_info == NULL)
877 return FALSE; /* not in drop zone */
878 target = fm_dnd_dest_find_target(page->dd, drag_context);
879 if (target != GDK_NONE && fm_dnd_dest_is_target_supported(page->dd, target))
880 action = fm_dnd_dest_get_default_action(page->dd, drag_context, target);
881 if (action == 0)
882 return FALSE; /* cannot drop on that destination */
883 gdk_drag_status(drag_context, action, time);
884 return TRUE;
885 }
886
887 static void fm_tab_page_init(FmTabPage *page)
888 {
889 GtkPaned* paned = GTK_PANED(page);
890 FmTabLabel* tab_label;
891 FmFolderView* folder_view;
892 GList* focus_chain = NULL;
893 AtkObject *atk_widget, *atk_label;
894 AtkRelation *relation;
895 FmSidePaneMode mode = app_config->side_pane_mode;
896
897 page->side_pane = fm_side_pane_new();
898 fm_side_pane_set_mode(page->side_pane, (mode & FM_SP_MODE_MASK));
899 #if FM_CHECK_VERSION(1, 2, 0)
900 fm_side_pane_set_popup_updater(page->side_pane, _update_sidepane_popup, page);
901 if (app_config->home_path && app_config->home_path[0])
902 fm_side_pane_set_home_dir(page->side_pane, app_config->home_path);
903 g_signal_connect(app_config, "changed::home_path",
904 G_CALLBACK(on_home_path_changed), page->side_pane);
905 #endif
906 /* TODO: add a close button to side pane */
907 gtk_paned_add1(paned, GTK_WIDGET(page->side_pane));
908 focus_chain = g_list_prepend(focus_chain, page->side_pane);
909
910 /* setup initial view mode for the tab from configuration */
911 page->view_mode = app_config->view_mode;
912
913 /* handlers below will be used when FmMainWin detects new page added */
914 folder_view = (FmFolderView*)fm_standard_view_new(app_config->view_mode,
915 update_files_popup,
916 open_folder_func);
917 /* FIXME: it is inefficient to set view mode to default one then change
918 it per-folder but it will be default in most cases but might it be
919 even more inefficient to add an object property for the mode and set
920 it in fm_tab_page_init() from the property? let make it later */
921 page->folder_view = g_object_ref_sink(folder_view);
922 fm_folder_view_set_selection_mode(folder_view, GTK_SELECTION_MULTIPLE);
923 page->nav_history = fm_nav_history_new();
924 page->views = GTK_BOX(gtk_hbox_new(TRUE, 4));
925 gtk_box_pack_start(page->views, GTK_WIDGET(folder_view), TRUE, TRUE, 0);
926 gtk_paned_add2(paned, GTK_WIDGET(page->views));
927 focus_chain = g_list_prepend(focus_chain, page->views);
928
929 /* We need this to change tab order to focus folder view before left pane. */
930 gtk_container_set_focus_chain(GTK_CONTAINER(page), focus_chain);
931 g_list_free(focus_chain);
932
933 // gtk_widget_show_all(GTK_WIDGET(page));
934 // if(mode & FM_SP_HIDE)
935 // gtk_widget_hide(GTK_WIDGET(page->side_pane));
936
937 /* create tab label */
938 tab_label = (FmTabLabel*)fm_tab_label_new("");
939 gtk_label_set_max_width_chars(tab_label->label, app_config->max_tab_chars);
940 #if ! GTK_CHECK_VERSION(3, 0, 0)
941 gtk_label_set_ellipsize(tab_label->label, PANGO_ELLIPSIZE_END);
942 #endif
943 page->tab_label = tab_label;
944
945 atk_widget = gtk_widget_get_accessible(GTK_WIDGET(folder_view));
946 atk_label = gtk_widget_get_accessible(GTK_WIDGET(tab_label));
947 relation = atk_relation_new(&atk_widget, 1, ATK_RELATION_LABEL_FOR);
948 atk_relation_set_add(atk_object_ref_relation_set(atk_label), relation);
949 g_object_unref(relation);
950
951 g_signal_connect(folder_view, "sel-changed",
952 G_CALLBACK(on_folder_view_sel_changed), page);
953 #if FM_CHECK_VERSION(1, 2, 0)
954 g_signal_connect(folder_view, "columns-changed",
955 G_CALLBACK(on_folder_view_columns_changed), page);
956 #endif
957 _connect_focus_in(folder_view, page);
958 /*
959 g_signal_connect(page->folder_view, "chdir",
960 G_CALLBACK(on_folder_view_chdir), page);
961 g_signal_connect(page->folder_view, "loaded",
962 G_CALLBACK(on_folder_view_loaded), page);
963 g_signal_connect(page->folder_view, "error",
964 G_CALLBACK(on_folder_view_error), page);
965 */
966
967 /* setup D&D on the tab label */
968 page->dd = fm_dnd_dest_new_with_handlers(GTK_WIDGET(tab_label));
969 g_signal_connect(tab_label, "drag-motion", G_CALLBACK(on_drag_motion), page);
970
971 /* the folder view is already loded, call the "loaded" callback ourself. */
972 //if(fm_folder_view_is_loaded(folder_view))
973 // on_folder_view_loaded(folder_view, fm_folder_view_get_cwd(folder_view), page);
974 page->busy = FALSE;
975 }
976
977 FmTabPage *fm_tab_page_new(FmPath* path)
978 {
979 FmTabPage* page = (FmTabPage*)g_object_new(FM_TYPE_TAB_PAGE, NULL);
980
981 fm_tab_page_chdir(page, path);
982 return page;
983 }
984
985 static void fm_tab_page_chdir_without_history(FmTabPage* page, FmPath* path)
986 {
987 char* disp_name = fm_path_display_basename(path);
988 char *disp_path;
989 FmStandardViewMode view_mode;
990 gboolean show_hidden;
991 char **columns; /* unused with libfm < 1.0.2 */
992 #if FM_CHECK_VERSION(1, 2, 0)
993 FmPath *prev_path = NULL;
994 #endif
995
996 #if FM_CHECK_VERSION(1, 0, 2)
997 if (page->filter_pattern && page->filter_pattern[0])
998 {
999 /* include pattern into page title */
1000 char *text = g_strdup_printf("%s [%s]", disp_name, page->filter_pattern);
1001 g_free(disp_name);
1002 disp_name = text;
1003 }
1004 #endif
1005 fm_tab_label_set_text(page->tab_label, disp_name);
1006 g_free(disp_name);
1007
1008 #if FM_CHECK_VERSION(1, 2, 0)
1009 if (app_config->focus_previous && page->folder)
1010 {
1011 prev_path = fm_folder_get_path(page->folder);
1012 if (fm_path_equal(fm_path_get_parent(prev_path), path))
1013 fm_path_ref(prev_path);
1014 else
1015 prev_path = NULL;
1016 }
1017 #endif
1018
1019 disp_path = fm_path_display_name(path, FALSE);
1020 fm_tab_label_set_tooltip_text(FM_TAB_LABEL(page->tab_label), disp_path);
1021 g_free(disp_path);
1022
1023 free_folder(page);
1024
1025 page->folder = fm_folder_from_path(path);
1026 g_signal_connect(page->folder, "start-loading", G_CALLBACK(on_folder_start_loading), page);
1027 g_signal_connect(page->folder, "finish-loading", G_CALLBACK(on_folder_finish_loading), page);
1028 g_signal_connect(page->folder, "error", G_CALLBACK(on_folder_error), page);
1029 g_signal_connect(page->folder, "fs-info", G_CALLBACK(on_folder_fs_info), page);
1030 /* destroy the page when the folder is unmounted or deleted. */
1031 g_signal_connect(page->folder, "removed", G_CALLBACK(on_folder_removed), page);
1032 g_signal_connect(page->folder, "unmount", G_CALLBACK(on_folder_unmount), page);
1033 g_signal_connect(page->folder, "content-changed", G_CALLBACK(on_folder_content_changed), page);
1034
1035 #if FM_CHECK_VERSION(1, 2, 0)
1036 page->want_focus = prev_path;
1037 #endif
1038
1039 /* get sort and view modes for new path */
1040 page->own_config = fm_app_config_get_config_for_path(path, &page->sort_type,
1041 &page->sort_by,
1042 &view_mode,
1043 &show_hidden, &columns);
1044 if (!page->own_config)
1045 /* bug #3615242: view mode is reset to default when changing directory */
1046 view_mode = page->view_mode;
1047 page->show_hidden = show_hidden;
1048 fm_folder_view_set_show_hidden(page->folder_view, show_hidden);
1049 #if FM_CHECK_VERSION(1, 2, 0)
1050 fm_side_pane_set_show_hidden(page->side_pane, show_hidden);
1051 #endif
1052
1053 if(fm_folder_is_loaded(page->folder))
1054 {
1055 on_folder_start_loading(page->folder, page);
1056 on_folder_finish_loading(page->folder, page);
1057 on_folder_fs_info(page->folder, page);
1058 }
1059 else
1060 on_folder_start_loading(page->folder, page);
1061
1062 /* change view and sort modes according to new path */
1063 fm_standard_view_set_mode(FM_STANDARD_VIEW(page->folder_view), view_mode);
1064 #if FM_CHECK_VERSION(1, 0, 2)
1065 /* update columns from config */
1066 if (columns)
1067 {
1068 guint i, n = g_strv_length(columns);
1069 FmFolderViewColumnInfo *infos = g_new(FmFolderViewColumnInfo, n);
1070 GSList *infos_list = NULL;
1071
1072 for (i = 0; i < n; i++)
1073 {
1074 char *name = g_strdup(columns[i]), *delim;
1075
1076 #if FM_CHECK_VERSION(1, 2, 0)
1077 infos[i].width = 0;
1078 #endif
1079 delim = strchr(name, ':');
1080 if (delim)
1081 {
1082 *delim++ = '\0';
1083 #if FM_CHECK_VERSION(1, 2, 0)
1084 infos[i].width = atoi(delim);
1085 #endif
1086 }
1087 infos[i].col_id = fm_folder_model_get_col_by_name(name);
1088 g_free(name);
1089 infos_list = g_slist_append(infos_list, &infos[i]);
1090 }
1091 #if FM_CHECK_VERSION(1, 2, 0)
1092 g_signal_handlers_block_by_func(page->folder_view,
1093 on_folder_view_columns_changed, page);
1094 #endif
1095 fm_folder_view_set_columns(page->folder_view, infos_list);
1096 #if FM_CHECK_VERSION(1, 2, 0)
1097 g_signal_handlers_unblock_by_func(page->folder_view,
1098 on_folder_view_columns_changed, page);
1099 #endif
1100 g_slist_free(infos_list);
1101 g_free(infos);
1102 if (page->own_config)
1103 page->columns = g_strdupv(columns);
1104 }
1105 #else
1106 /* since 1.0.2 sorting should be applied on model instead */
1107 fm_folder_view_sort(page->folder_view, page->sort_type, page->sort_by);
1108 #endif
1109
1110 fm_side_pane_chdir(page->side_pane, path);
1111
1112 /* tell the world that our current working directory is changed */
1113 g_signal_emit(page, signals[CHDIR], 0, path);
1114 }
1115
1116 void fm_tab_page_chdir(FmTabPage* page, FmPath* path)
1117 {
1118 FmPath* cwd = fm_tab_page_get_cwd(page);
1119 int scroll_pos;
1120 if(cwd && path && fm_path_equal(cwd, path))
1121 return;
1122 scroll_pos = gtk_adjustment_get_value(gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(page->folder_view)));
1123 fm_nav_history_chdir(page->nav_history, path, scroll_pos);
1124 fm_tab_page_chdir_without_history(page, path);
1125 }
1126
1127 void fm_tab_page_set_show_hidden(FmTabPage* page, gboolean show_hidden)
1128 {
1129 fm_folder_view_set_show_hidden(page->folder_view, show_hidden);
1130 #if FM_CHECK_VERSION(1, 2, 0)
1131 fm_side_pane_set_show_hidden(page->side_pane, show_hidden);
1132 #endif
1133 /* update status text */
1134 g_free(page->status_text[FM_STATUS_TEXT_NORMAL]);
1135 page->status_text[FM_STATUS_TEXT_NORMAL] = format_status_text(page);
1136 g_signal_emit(page, signals[STATUS], 0,
1137 (guint)FM_STATUS_TEXT_NORMAL,
1138 page->status_text[FM_STATUS_TEXT_NORMAL]);
1139 }
1140
1141 FmPath* fm_tab_page_get_cwd(FmTabPage* page)
1142 {
1143 return page->folder ? fm_folder_get_path(page->folder) : NULL;
1144 }
1145
1146 FmSidePane* fm_tab_page_get_side_pane(FmTabPage* page)
1147 {
1148 return page->side_pane;
1149 }
1150
1151 FmFolderView* fm_tab_page_get_folder_view(FmTabPage* page)
1152 {
1153 return page->folder_view;
1154 }
1155
1156 FmFolder* fm_tab_page_get_folder(FmTabPage* page)
1157 {
1158 return fm_folder_view_get_folder(page->folder_view);
1159 }
1160
1161 FmNavHistory* fm_tab_page_get_history(FmTabPage* page)
1162 {
1163 return page->nav_history;
1164 }
1165
1166 void fm_tab_page_forward(FmTabPage* page)
1167 {
1168 #if FM_CHECK_VERSION(1, 0, 2)
1169 guint index = fm_nav_history_get_cur_index(page->nav_history);
1170
1171 if (index > 0)
1172 #else
1173 if(fm_nav_history_can_forward(page->nav_history))
1174 #endif
1175 {
1176 #if !FM_CHECK_VERSION(1, 0, 2)
1177 const FmNavHistoryItem* item;
1178 #endif
1179 GtkAdjustment* vadjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(page->folder_view));
1180 int scroll_pos = gtk_adjustment_get_value(vadjustment);
1181 #if FM_CHECK_VERSION(1, 0, 2)
1182 FmPath *path = fm_nav_history_go_to(page->nav_history, index - 1, scroll_pos);
1183 fm_tab_page_chdir_without_history(page, path);
1184 #else
1185 fm_nav_history_forward(page->nav_history, scroll_pos);
1186 item = fm_nav_history_get_cur(page->nav_history);
1187 fm_tab_page_chdir_without_history(page, item->path);
1188 #endif
1189 }
1190 }
1191
1192 void fm_tab_page_back(FmTabPage* page)
1193 {
1194 if(fm_nav_history_can_back(page->nav_history))
1195 {
1196 #if !FM_CHECK_VERSION(1, 0, 2)
1197 const FmNavHistoryItem* item;
1198 #endif
1199 GtkAdjustment* vadjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(page->folder_view));
1200 int scroll_pos = gtk_adjustment_get_value(vadjustment);
1201 #if FM_CHECK_VERSION(1, 0, 2)
1202 guint index = fm_nav_history_get_cur_index(page->nav_history);
1203 FmPath *path = fm_nav_history_go_to(page->nav_history, index + 1, scroll_pos);
1204 fm_tab_page_chdir_without_history(page, path);
1205 #else
1206 fm_nav_history_back(page->nav_history, scroll_pos);
1207 item = fm_nav_history_get_cur(page->nav_history);
1208 fm_tab_page_chdir_without_history(page, item->path);
1209 #endif
1210 }
1211 }
1212
1213 #if FM_CHECK_VERSION(1, 0, 2)
1214 void fm_tab_page_history(FmTabPage* page, guint history_item)
1215 {
1216 GtkAdjustment* vadjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(page->folder_view));
1217 int scroll_pos = gtk_adjustment_get_value(vadjustment);
1218 FmPath *path = fm_nav_history_go_to(page->nav_history, history_item, scroll_pos);
1219 fm_tab_page_chdir_without_history(page, path);
1220 }
1221 #else
1222 void fm_tab_page_history(FmTabPage* page, GList* history_item_link)
1223 {
1224 const FmNavHistoryItem* item = (FmNavHistoryItem*)history_item_link->data;
1225 GtkAdjustment* vadjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(page->folder_view));
1226 int scroll_pos = gtk_adjustment_get_value(vadjustment);
1227 fm_nav_history_jump(page->nav_history, history_item_link, scroll_pos);
1228 item = fm_nav_history_get_cur(page->nav_history);
1229 fm_tab_page_chdir_without_history(page, item->path);
1230 }
1231 #endif
1232
1233 const char* fm_tab_page_get_title(FmTabPage* page)
1234 {
1235 FmTabLabel* label = page->tab_label;
1236 return gtk_label_get_text(label->label);
1237 }
1238
1239 const char* fm_tab_page_get_status_text(FmTabPage* page, FmStatusTextType type)
1240 {
1241 return (type < FM_STATUS_TEXT_NUM) ? page->status_text[type] : NULL;
1242 }
1243
1244 void fm_tab_page_reload(FmTabPage* page)
1245 {
1246 FmFolder* folder = fm_folder_view_get_folder(page->folder_view);
1247
1248 if(folder)
1249 {
1250 GtkAdjustment* vadjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(page->folder_view));
1251 int scroll_pos = gtk_adjustment_get_value(vadjustment);
1252 /* save the scroll position before reload */
1253 #if FM_CHECK_VERSION(1, 0, 2)
1254 int idx = fm_nav_history_get_cur_index(page->nav_history);
1255 fm_nav_history_go_to(page->nav_history, idx, scroll_pos);
1256 #else
1257 FmNavHistoryItem* item = (FmNavHistoryItem*)fm_nav_history_get_cur(page->nav_history);
1258 /* NOTE: ignoring const modifier due to invalid pre-1.0.2 design */
1259 item->scroll_pos = scroll_pos;
1260 #endif
1261 fm_folder_reload(folder);
1262 }
1263 }
1264
1265 /**
1266 * fm_tab_page_take_view_back
1267 * @page: the page instance
1268 *
1269 * If folder view that is bound to page isn't present in the container,
1270 * then moves it from the container where it is currently, to the @page.
1271 * This API should be called only in single-pane mode, for two-pane use
1272 * fm_tab_page_set_passive_view() instead.
1273 *
1274 * Returns: %TRUE if folder was not in @page and moved successfully.
1275 */
1276 gboolean fm_tab_page_take_view_back(FmTabPage *page)
1277 {
1278 GList *panes, *l;
1279 GtkWidget *folder_view = GTK_WIDGET(page->folder_view);
1280
1281 gtk_widget_set_state(folder_view, GTK_STATE_NORMAL);
1282 panes = gtk_container_get_children(GTK_CONTAINER(page->views));
1283 for (l = panes; l; l = l->next)
1284 if ((GtkWidget*)l->data == folder_view)
1285 break;
1286 g_list_free(panes);
1287 if (l)
1288 return FALSE;
1289 /* we already keep the reference, no need to do it again */
1290 gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(folder_view)),
1291 folder_view);
1292 gtk_box_pack_start(page->views, folder_view, TRUE, TRUE, 0);
1293 return TRUE;
1294 }
1295
1296 /**
1297 * fm_tab_page_set_passive_view
1298 * @page: the page instance
1299 * @view: the folder view to add
1300 * @on_right: %TRUE if @view should be moved to right pane
1301 *
1302 * If folder @view isn't already in designed place then moves it from the
1303 * container where it is currently, to the @page. If @on_right is %TRUE
1304 * then attempts to move into right pane. If @on_right is %FALSE then
1305 * attempts to move into left pane.
1306 *
1307 * Also if folder view that is bound to @page isn't presented in the
1308 * container, then moves it from the container where it is currently, to
1309 * the @page on the side opposite to @view.
1310 *
1311 * See also: fm_tab_page_take_view_back().
1312 *
1313 * Returns: %TRUE if @view was moved successfully.
1314 */
1315 gboolean fm_tab_page_set_passive_view(FmTabPage *page, FmFolderView *view,
1316 gboolean on_right)
1317 {
1318 GtkWidget *pane;
1319
1320 g_return_val_if_fail(page != NULL && view != NULL, FALSE);
1321 if (!fm_tab_page_take_view_back(page))
1322 {
1323 #if !FM_CHECK_VERSION(1, 2, 0)
1324 /* workaround on ExoIconView bug - it doesn't follow state change
1325 so we re-add the folder view into our container to force change */
1326 GtkWidget *fv = GTK_WIDGET(page->folder_view);
1327
1328 /* we already keep the reference, no need to do it again */
1329 gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(fv)), fv);
1330 gtk_box_pack_start(page->views, fv, TRUE, TRUE, 0);
1331 #endif
1332 }
1333 pane = GTK_WIDGET(view);
1334 gtk_widget_set_state(pane, GTK_STATE_ACTIVE);
1335 g_object_ref(view);
1336 /* gtk_widget_reparent() is buggy so we do it manually */
1337 if (gtk_widget_get_parent(pane))
1338 gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(pane)), pane);
1339 gtk_box_pack_start(page->views, pane, TRUE, TRUE, 0);
1340 g_object_unref(view);
1341 if (!on_right)
1342 gtk_box_reorder_child(page->views, pane, 0);
1343 return TRUE;
1344 }
1345
1346 /**
1347 * fm_tab_page_get_passive_view
1348 * @page: the page instance
1349 *
1350 * Checks if the @page contains some folder view that was added via call
1351 * to fm_tab_page_set_passive_view() and was not moved out yet.
1352 *
1353 * Returns: (transfer none): the folder view or %NULL.
1354 */
1355 FmFolderView *fm_tab_page_get_passive_view(FmTabPage *page)
1356 {
1357 GList *panes, *l;
1358 FmFolderView *view;
1359
1360 panes = gtk_container_get_children(GTK_CONTAINER(page->views));
1361 for (l = panes; l; l = l->next)
1362 if ((FmFolderView*)l->data != page->folder_view)
1363 break;
1364 view = l ? l->data : NULL;
1365 g_list_free(panes);
1366 return view;
1367 }
1368
1369 #if FM_CHECK_VERSION(1, 0, 2)
1370 /**
1371 * fm_tab_page_set_filter_pattern
1372 * @page: the page instance
1373 * @pattern: (allow-none): new pattern
1374 *
1375 * Changes filter for the folder view in the @page. If @pattern is %NULL
1376 * then folder contents will be not filtered anymore.
1377 */
1378 void fm_tab_page_set_filter_pattern(FmTabPage *page, const char *pattern)
1379 {
1380 FmFolderModel *model = NULL;
1381 char *disp_name;
1382
1383 /* validate pattern */
1384 if (pattern && pattern[0] == '\0')
1385 pattern = NULL;
1386 if (page->folder_view != NULL)
1387 model = fm_folder_view_get_model(page->folder_view);
1388 if (page->filter_pattern == NULL && pattern == NULL)
1389 return; /* nothing to change */
1390 /* if we have model then update filter chain in it */
1391 if (model)
1392 {
1393 if (page->filter_pattern == NULL && pattern)
1394 fm_folder_model_add_filter(model, fm_tab_page_path_filter, page);
1395 else if (page->filter_pattern && pattern == NULL)
1396 fm_folder_model_remove_filter(model, fm_tab_page_path_filter, page);
1397 }
1398 /* update page own data */
1399 g_free(page->filter_pattern);
1400 if (pattern)
1401 {
1402 char *casefold = g_utf8_casefold(pattern, -1);
1403 page->filter_pattern = g_utf8_normalize(casefold, -1, G_NORMALIZE_ALL);
1404 g_free(casefold);
1405 }
1406 else
1407 page->filter_pattern = NULL;
1408 /* apply changes if needed */
1409 if (model)
1410 fm_folder_model_apply_filters(model);
1411 /* update tab page title */
1412 disp_name = fm_path_display_basename(fm_folder_view_get_cwd(page->folder_view));
1413 if (page->filter_pattern)
1414 {
1415 /* include pattern into page title */
1416 char *text = g_strdup_printf("%s [%s]", disp_name, page->filter_pattern);
1417 g_free(disp_name);
1418 disp_name = text;
1419 }
1420 fm_tab_label_set_text(page->tab_label, disp_name);
1421 g_free(disp_name);
1422 }
1423 #endif