4
* Copyright 2009 PCMan <pcman@debian>
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.
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.
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,
26
#include <glib/gi18n-lib.h>
27
#include <gio/gdesktopappinfo.h>
29
#include "fm-gtk-utils.h"
30
#include "fm-file-ops-job.h"
31
#include "fm-progress-dlg.h"
32
#include "fm-path-entry.h"
33
#include "fm-app-chooser-dlg.h"
35
#include "fm-config.h"
37
static GtkDialog* _fm_get_user_input_dialog (GtkWindow* parent, const char* title, const char* msg);
38
static gchar* _fm_user_input_dialog_run (GtkDialog* dlg, GtkEntry *entry);
40
void fm_show_error(GtkWindow* parent, const char* msg)
42
GtkWidget* dlg = gtk_message_dialog_new(parent, 0,
45
gtk_window_set_title((GtkWindow*)dlg, _("Error"));
46
gtk_dialog_run((GtkDialog*)dlg);
47
gtk_widget_destroy(dlg);
50
gboolean fm_yes_no(GtkWindow* parent, const char* question, gboolean default_yes)
53
GtkWidget* dlg = gtk_message_dialog_new_with_markup(parent, 0,
54
GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, question);
55
gtk_dialog_set_default_response(GTK_DIALOG(dlg), default_yes ? GTK_RESPONSE_YES : GTK_RESPONSE_NO);
56
ret = gtk_dialog_run((GtkDialog*)dlg);
57
gtk_widget_destroy(dlg);
58
return ret == GTK_RESPONSE_YES;
61
gboolean fm_ok_cancel(GtkWindow* parent, const char* question, gboolean default_ok)
64
GtkWidget* dlg = gtk_message_dialog_new_with_markup(parent, 0,
65
GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, question);
66
gtk_dialog_set_default_response(GTK_DIALOG(dlg), default_ok ? GTK_RESPONSE_OK : GTK_RESPONSE_CANCEL);
67
ret = gtk_dialog_run((GtkDialog*)dlg);
68
gtk_widget_destroy(dlg);
69
return ret == GTK_RESPONSE_OK;
72
int fm_ask(GtkWindow* parent, const char* question, ...)
76
va_start (args, question);
77
ret = fm_ask_valist(parent, question, args);
82
int fm_askv(GtkWindow* parent, const char* question, const char** options)
86
GtkWidget* dlg = gtk_message_dialog_new_with_markup(parent, 0,
87
GTK_MESSAGE_QUESTION, 0, question);
88
/* FIXME: need to handle defualt button and alternative button
92
/* FIXME: handle button image and stock buttons */
93
GtkWidget* btn = gtk_dialog_add_button(GTK_DIALOG( dlg ), *options, id);
97
ret = gtk_dialog_run((GtkDialog*)dlg);
102
gtk_widget_destroy(dlg);
106
int fm_ask_valist(GtkWindow* parent, const char* question, va_list options)
108
GArray* opts = g_array_sized_new(TRUE, TRUE, sizeof(char*), 6);
110
const char* opt = va_arg(options, const char*);
113
g_array_append_val(opts, opt);
114
opt = va_arg (options, const char *);
116
ret = fm_askv(parent, question, opts->data);
117
g_array_free(opts, TRUE);
123
gchar* fm_get_user_input(GtkWindow* parent, const char* title, const char* msg, const char* default_text)
125
GtkDialog* dlg = _fm_get_user_input_dialog( parent, title, msg);
126
GtkWidget* entry = gtk_entry_new();
127
gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
129
if(default_text && default_text[0])
130
gtk_entry_set_text(GTK_ENTRY( entry ), default_text);
132
return _fm_user_input_dialog_run( dlg, GTK_ENTRY( entry ) );
135
FmPath* fm_get_user_input_path(GtkWindow* parent, const char* title, const char* msg, FmPath* default_path)
138
GtkDialog* dlg = _fm_get_user_input_dialog( parent, title, msg);
139
GtkWidget* entry = gtk_entry_new();
140
char *str, *path_str = NULL;
143
gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
147
path_str = fm_path_display_name(default_path, FALSE);
148
gtk_entry_set_text(GTK_ENTRY( entry ), path_str);
151
str = _fm_user_input_dialog_run( dlg, GTK_ENTRY( entry ) );
152
path = fm_path_new(str);
160
gchar* fm_get_user_input_rename(GtkWindow* parent, const char* title, const char* msg, const char* default_text)
162
GtkDialog* dlg = _fm_get_user_input_dialog( parent, title, msg);
163
GtkWidget* entry = gtk_entry_new();
164
gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
166
if(default_text && default_text[0])
168
gtk_entry_set_text(GTK_ENTRY( entry ), default_text);
169
/* only select filename part without extension name. */
172
/* FIXME: handle the special case for *.tar.gz or *.tar.bz2
173
* We should exam the file extension with g_content_type_guess, and
174
* find out a longest valid extension name.
175
* For example, the extension name of foo.tar.gz is .tar.gz, not .gz. */
176
const char* dot = g_utf8_strrchr(default_text, -1, '.');
178
gtk_editable_select_region(GTK_EDITABLE(entry), 0, g_utf8_pointer_to_offset(default_text, dot));
180
gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
182
const char* dot = default_text;
183
while( dot = g_utf8_strchr(dot + 1, -1, '.') )
186
char* type = g_content_type_guess(dot-1, NULL, 0, &uncertain);
187
if(!g_content_type_is_unknown(type))
190
gtk_editable_select_region(entry, 0, g_utf8_pointer_to_offset(default_text, dot));
199
return _fm_user_input_dialog_run( dlg, GTK_ENTRY( entry ) );
202
static GtkDialog* _fm_get_user_input_dialog(GtkWindow* parent, const char* title, const char* msg)
204
GtkWidget* dlg = gtk_dialog_new_with_buttons(title, parent, GTK_DIALOG_NO_SEPARATOR,
205
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
206
GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
207
GtkWidget* label = gtk_label_new(msg);
208
gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
210
gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1);
211
gtk_box_set_spacing((GtkBox*)gtk_dialog_get_content_area(GTK_DIALOG(dlg)), 6);
212
gtk_box_pack_start((GtkBox*)gtk_dialog_get_content_area(GTK_DIALOG(dlg)), label, FALSE, TRUE, 6);
214
gtk_container_set_border_width(GTK_CONTAINER((GtkBox*)gtk_dialog_get_content_area(GTK_DIALOG(dlg))), 12);
215
gtk_container_set_border_width(GTK_CONTAINER(dlg), 5);
216
gtk_dialog_set_default_response(GTK_DIALOG(dlg), GTK_RESPONSE_OK);
217
gtk_window_set_default_size(GTK_WINDOW(dlg), 480, -1);
222
static gchar* _fm_user_input_dialog_run( GtkDialog* dlg, GtkEntry *entry)
225
int sel_start, sel_end;
228
/* FIXME: this workaround is used to overcome bug of gtk+.
229
* gtk+ seems to ignore select region and select all text for entry in dialog. */
230
has_sel = gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), &sel_start, &sel_end);
231
gtk_box_pack_start(GTK_BOX( GTK_DIALOG(dlg)->vbox ), GTK_WIDGET( entry ), FALSE, TRUE, 6);
232
gtk_widget_show_all(GTK_WIDGET(dlg));
235
gtk_editable_select_region(GTK_EDITABLE(entry), sel_start, sel_end);
237
while(gtk_dialog_run(dlg) == GTK_RESPONSE_OK)
239
const char* pstr = gtk_entry_get_text(entry);
242
str = g_strdup(pstr);
246
gtk_widget_destroy(GTK_WIDGET(dlg));
250
FmPath* fm_select_folder(GtkWindow* parent)
253
GtkFileChooser* chooser;
254
chooser = (GtkFileChooser*)gtk_file_chooser_dialog_new(_("Please select a folder"),
255
parent, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
256
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
257
GTK_STOCK_OK, GTK_RESPONSE_OK,
259
gtk_dialog_set_alternative_button_order((GtkDialog*)chooser,
261
GTK_RESPONSE_OK, NULL);
262
if( gtk_dialog_run((GtkDialog*)chooser) == GTK_RESPONSE_OK )
264
char* file = gtk_file_chooser_get_filename(chooser);
266
file = gtk_file_chooser_get_uri(chooser);
267
path = fm_path_new(file);
272
gtk_widget_destroy((GtkWidget*)chooser);
293
static void on_mount_action_finished(GObject* src, GAsyncResult *res, gpointer user_data)
295
struct MountData* data = user_data;
296
g_debug("on_mount_action_finished");
300
data->ret = g_volume_mount_finish(G_VOLUME(src), res, &data->err);
303
data->ret = g_file_mount_enclosing_volume_finish(G_FILE(src), res, &data->err);
306
#if GLIB_CHECK_VERSION(2, 22, 0)
307
data->ret = g_mount_unmount_with_operation_finish(G_MOUNT(src), res, &data->err);
309
data->ret = g_mount_unmount_finish(G_MOUNT(src), res, &data->err);
313
#if GLIB_CHECK_VERSION(2, 22, 0)
314
data->ret = g_mount_eject_with_operation_finish(G_MOUNT(src), res, &data->err);
316
data->ret = g_mount_eject_finish(G_MOUNT(src), res, &data->err);
320
#if GLIB_CHECK_VERSION(2, 22, 0)
321
data->ret = g_volume_eject_with_operation_finish(G_VOLUME(src), res, &data->err);
323
data->ret = g_volume_eject_finish(G_VOLUME(src), res, &data->err);
327
g_main_loop_quit(data->loop);
330
static gboolean fm_do_mount(GtkWindow* parent, GObject* obj, MountAction action, gboolean interactive)
333
struct MountData* data = g_new0(struct MountData, 1);
334
GMountOperation* op = interactive ? gtk_mount_operation_new(parent) : NULL;
335
GCancellable* cancellable = g_cancellable_new();
337
data->loop = g_main_loop_new (NULL, TRUE);
338
data->action = action;
343
g_volume_mount(G_VOLUME(obj), 0, op, cancellable, on_mount_action_finished, data);
346
g_file_mount_enclosing_volume(G_FILE(obj), 0, op, cancellable, on_mount_action_finished, data);
349
#if GLIB_CHECK_VERSION(2, 22, 0)
350
g_mount_unmount_with_operation(G_MOUNT(obj), G_MOUNT_UNMOUNT_NONE, op, cancellable, on_mount_action_finished, data);
352
g_mount_unmount(G_MOUNT(obj), G_MOUNT_UNMOUNT_NONE, cancellable, on_mount_action_finished, data);
356
#if GLIB_CHECK_VERSION(2, 22, 0)
357
g_mount_eject_with_operation(G_MOUNT(obj), G_MOUNT_UNMOUNT_NONE, op, cancellable, on_mount_action_finished, data);
359
g_mount_eject(G_MOUNT(obj), G_MOUNT_UNMOUNT_NONE, cancellable, on_mount_action_finished, data);
363
#if GLIB_CHECK_VERSION(2, 22, 0)
364
g_volume_eject_with_operation(G_VOLUME(obj), G_MOUNT_UNMOUNT_NONE, op, cancellable, on_mount_action_finished, data);
366
g_volume_eject(G_VOLUME(obj), G_MOUNT_UNMOUNT_NONE, cancellable, on_mount_action_finished, data);
371
if (g_main_loop_is_running(data->loop))
374
g_main_loop_run(data->loop);
378
g_main_loop_unref(data->loop);
385
if(data->err->domain == G_IO_ERROR)
387
if(data->err->code == G_IO_ERROR_FAILED)
389
/* Generate a more human-readable error message instead of using a gvfs one. */
391
/* The original error message is something like:
392
* Error unmounting: umount exited with exit code 1:
393
* helper failed with: umount: only root can unmount
394
* UUID=18cbf00c-e65f-445a-bccc-11964bdea05d from /media/sda4 */
396
/* Why they pass this back to us?
397
* This is not human-readable for the users at all. */
399
if(strstr(data->err->message, "only root can "))
401
g_debug("%s", data->err->message);
402
g_free(data->err->message);
403
data->err->message = g_strdup(_("Only system administrators have the permission to do this."));
406
else if(data->err->code == G_IO_ERROR_FAILED_HANDLED)
410
fm_show_error(parent, data->err->message);
412
g_error_free(data->err);
416
g_object_unref(cancellable);
422
gboolean fm_mount_path(GtkWindow* parent, FmPath* path, gboolean interactive)
424
GFile* gf = fm_path_to_gfile(path);
425
gboolean ret = fm_do_mount(parent, G_OBJECT(gf), MOUNT_GFILE, interactive);
430
gboolean fm_mount_volume(GtkWindow* parent, GVolume* vol, gboolean interactive)
432
return fm_do_mount(parent, G_OBJECT(vol), MOUNT_VOLUME, interactive);
435
gboolean fm_unmount_mount(GtkWindow* parent, GMount* mount, gboolean interactive)
437
return fm_do_mount(parent, G_OBJECT(mount), UMOUNT_MOUNT, interactive);
440
gboolean fm_unmount_volume(GtkWindow* parent, GVolume* vol, gboolean interactive)
442
GMount* mount = g_volume_get_mount(vol);
446
ret = fm_do_mount(parent, G_OBJECT(vol), UMOUNT_MOUNT, interactive);
447
g_object_unref(mount);
451
gboolean fm_eject_mount(GtkWindow* parent, GMount* mount, gboolean interactive)
453
return fm_do_mount(parent, G_OBJECT(mount), EJECT_MOUNT, interactive);
456
gboolean fm_eject_volume(GtkWindow* parent, GVolume* vol, gboolean interactive)
458
return fm_do_mount(parent, G_OBJECT(vol), EJECT_VOLUME, interactive);
462
/* File operations */
463
/* FIXME: only show the progress dialog if the job isn't finished
466
void fm_copy_files(FmPathList* files, FmPath* dest_dir)
468
FmJob* job = fm_file_ops_job_new(FM_FILE_OP_COPY, files);
469
fm_file_ops_job_set_dest(FM_FILE_OPS_JOB(job), dest_dir);
470
fm_file_ops_job_run_with_progress(FM_FILE_OPS_JOB(job));
473
void fm_move_files(FmPathList* files, FmPath* dest_dir)
475
FmJob* job = fm_file_ops_job_new(FM_FILE_OP_MOVE, files);
476
fm_file_ops_job_set_dest(FM_FILE_OPS_JOB(job), dest_dir);
477
fm_file_ops_job_run_with_progress(FM_FILE_OPS_JOB(job));
480
void fm_trash_files(FmPathList* files)
482
if(!fm_config->confirm_del || fm_yes_no(NULL, _("Do you want to move the selected files to trash can?"), TRUE))
484
FmJob* job = fm_file_ops_job_new(FM_FILE_OP_TRASH, files);
485
fm_file_ops_job_run_with_progress(FM_FILE_OPS_JOB(job));
489
void fm_untrash_files(FmPathList* files)
491
FmJob* job = fm_file_ops_job_new(FM_FILE_OP_UNTRASH, files);
492
fm_file_ops_job_run_with_progress(FM_FILE_OPS_JOB(job));
495
static void fm_delete_files_internal(FmPathList* files)
497
FmJob* job = fm_file_ops_job_new(FM_FILE_OP_DELETE, files);
498
fm_file_ops_job_run_with_progress(FM_FILE_OPS_JOB(job));
501
void fm_delete_files(FmPathList* files)
503
if(!fm_config->confirm_del || fm_yes_no(NULL, _("Do you want to delete the selected files?"), TRUE))
504
fm_delete_files_internal(files);
507
void fm_trash_or_delete_files(FmPathList* files)
509
if( !fm_list_is_empty(files) )
511
gboolean all_in_trash = TRUE;
512
if(fm_config->use_trash)
514
GList* l = fm_list_peek_head_link(files);
517
FmPath* path = FM_PATH(l->data);
518
if(!fm_path_is_trash(path))
519
all_in_trash = FALSE;
523
/* files already in trash:/// should only be deleted and cannot be trashed again. */
524
if(fm_config->use_trash && !all_in_trash)
525
fm_trash_files(files);
527
fm_delete_files(files);
531
void fm_move_or_copy_files_to(FmPathList* files, gboolean is_move)
533
FmPath* dest = fm_select_folder(NULL);
537
fm_move_files(files, dest);
539
fm_copy_files(files, dest);
545
void fm_rename_file(FmPath* file)
547
GFile* gf = fm_path_to_gfile(file), *parent, *dest;
549
gchar* new_name = fm_get_user_input_rename( NULL, _("Rename File"), _("Please enter a new name:"), file->name);
552
parent = g_file_get_parent(gf);
553
dest = g_file_get_child(parent, new_name);
554
g_object_unref(parent);
555
if(!g_file_move(gf, dest,
556
G_FILE_COPY_ALL_METADATA|
557
G_FILE_COPY_NO_FALLBACK_FOR_MOVE|
558
G_FILE_COPY_NOFOLLOW_SYMLINKS,
559
NULL, /* make this cancellable later. */
562
fm_show_error(NULL, err->message);
565
g_object_unref(dest);
569
void fm_empty_trash()
571
if(fm_yes_no(NULL, _("Are you sure you want to empty the trash can?"), TRUE))
573
FmPathList* paths = fm_path_list_new();
574
fm_list_push_tail(paths, fm_path_get_trash());
575
fm_delete_files_internal(paths);
576
fm_list_unref(paths);
580
static GAppInfo* choose_app(GList* file_infos, FmMimeType* mime_type, gpointer user_data, GError** err)
582
gpointer* data = (gpointer*)user_data;
583
GtkWindow* parent = (GtkWindow*)data[0];
584
return fm_choose_app_for_mime_type(parent, mime_type, mime_type != NULL);
587
static gboolean on_launch_error(GAppLaunchContext* ctx, GError* err, gpointer user_data)
589
gpointer* data = (gpointer*)user_data;
590
GtkWindow* parent = (GtkWindow*)data[0];
591
fm_show_error(parent, err->message);
595
static gboolean on_open_folder(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data, GError** err)
597
gpointer* data = (gpointer*)user_data;
598
FmLaunchFolderFunc func = (FmLaunchFolderFunc)data[1];
599
return func(ctx, folder_infos, data[2], err);
602
gboolean fm_launch_files_simple(GtkWindow* parent, GAppLaunchContext* ctx, GList* file_infos, FmLaunchFolderFunc func, gpointer user_data)
604
FmFileLauncher launcher = {
609
gpointer data[] = {parent, func, user_data};
610
GAppLaunchContext* _ctx = NULL;
614
_ctx = ctx = gdk_app_launch_context_new();
615
gdk_app_launch_context_set_screen(GDK_APP_LAUNCH_CONTEXT(ctx), parent ? gtk_widget_get_screen(GTK_WIDGET(parent)) : gdk_screen_get_default());
616
gdk_app_launch_context_set_timestamp(GDK_APP_LAUNCH_CONTEXT(ctx), gtk_get_current_event_time());
617
/* FIXME: how to handle gdk_app_launch_context_set_icon? */
619
ret = fm_launch_files(ctx, file_infos, &launcher, data);
621
g_object_unref(_ctx);
625
gboolean fm_launch_paths_simple(GtkWindow* parent, GAppLaunchContext* ctx, GList* paths, FmLaunchFolderFunc func, gpointer user_data)
627
FmFileLauncher launcher = {
632
gpointer data[] = {parent, func, user_data};
633
GAppLaunchContext* _ctx = NULL;
637
_ctx = ctx = gdk_app_launch_context_new();
638
gdk_app_launch_context_set_screen(GDK_APP_LAUNCH_CONTEXT(ctx), parent ? gtk_widget_get_screen(GTK_WIDGET(parent)) : gdk_screen_get_default());
639
gdk_app_launch_context_set_timestamp(GDK_APP_LAUNCH_CONTEXT(ctx), gtk_get_current_event_time());
640
/* FIXME: how to handle gdk_app_launch_context_set_icon? */
642
ret = fm_launch_paths(ctx, paths, &launcher, data);
644
g_object_unref(_ctx);
648
gboolean fm_launch_file_simple(GtkWindow* parent, GAppLaunchContext* ctx, FmFileInfo* file_info, FmLaunchFolderFunc func, gpointer user_data)
651
GList* files = g_list_prepend(NULL, file_info);
652
ret = fm_launch_files_simple(parent, ctx, files, func, user_data);
657
gboolean fm_launch_path_simple(GtkWindow* parent, GAppLaunchContext* ctx, FmPath* path, FmLaunchFolderFunc func, gpointer user_data)
660
GList* files = g_list_prepend(NULL, path);
661
ret = fm_launch_paths_simple(parent, ctx, files, func, user_data);