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