2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* abox.c - the dialog box widget used for filer operations.
22 * The actual code for specific operations is in action.c.
33 #include "gui_support.h"
40 #define RESPONSE_QUIET 1
42 /* Static prototypes */
43 static void abox_class_init(GObjectClass
*gclass
, gpointer data
);
44 static void abox_init(GTypeInstance
*object
, gpointer gclass
);
45 static gboolean
abox_delete(GtkWidget
*dialog
, GdkEventAny
*event
);
46 static void response(GtkDialog
*dialog
, gint response_id
);
47 static void abox_finalise(GObject
*object
);
48 static void shade(ABox
*abox
);
50 GType
abox_get_type(void)
52 static GType type
= 0;
56 static const GTypeInfo info
=
60 NULL
, /* base_finalise */
61 (GClassInitFunc
) abox_class_init
,
62 NULL
, /* class_finalise */
63 NULL
, /* class_data */
66 (GInstanceInitFunc
) abox_init
69 type
= g_type_register_static(GTK_TYPE_DIALOG
,
76 GtkWidget
* abox_new(const gchar
*title
, gboolean quiet
)
81 widget
= GTK_WIDGET(gtk_widget_new(abox_get_type(), NULL
));
82 abox
= (ABox
*) widget
;
84 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(abox
->quiet
), quiet
);
86 gtk_window_set_title(GTK_WINDOW(widget
), title
);
87 gtk_dialog_set_has_separator(GTK_DIALOG(widget
), FALSE
);
92 static void abox_class_init(GObjectClass
*gclass
, gpointer data
)
94 GtkWidgetClass
*widget
= (GtkWidgetClass
*) gclass
;
95 GtkDialogClass
*dialog
= (GtkDialogClass
*) gclass
;
96 ABoxClass
*abox
= (ABoxClass
*) gclass
;
98 widget
->delete_event
= abox_delete
;
99 dialog
->response
= response
;
100 abox
->flag_toggled
= NULL
;
101 abox
->abort_operation
= NULL
;
103 g_signal_new("flag_toggled", G_TYPE_FROM_CLASS(gclass
),
104 G_SIGNAL_RUN_LAST
, G_STRUCT_OFFSET(ABoxClass
, flag_toggled
),
105 NULL
, NULL
, g_cclosure_marshal_VOID__INT
,
106 G_TYPE_NONE
, 1, G_TYPE_INT
);
108 g_signal_new("abort_operation", G_TYPE_FROM_CLASS(gclass
),
109 G_SIGNAL_RUN_LAST
, G_STRUCT_OFFSET(ABoxClass
, abort_operation
),
110 NULL
, NULL
, g_cclosure_marshal_VOID__VOID
,
113 gclass
->finalize
= abox_finalise
;
116 static void abox_init(GTypeInstance
*object
, gpointer gclass
)
118 GtkWidget
*frame
, *text
, *scrollbar
, *button
;
119 ABox
*abox
= ABOX(object
);
120 GtkDialog
*dialog
= GTK_DIALOG(object
);
123 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_MOUSE
);
125 abox
->dir_label
= gtk_label_new(_("<dir>"));
126 gtk_widget_set_size_request(abox
->dir_label
, 8, -1);
127 abox
->results
= NULL
;
129 abox
->next_dir
= NULL
;
130 abox
->next_timer
= 0;
131 abox
->question
= FALSE
;
132 gtk_misc_set_alignment(GTK_MISC(abox
->dir_label
), 0.5, 0.5);
133 gtk_box_pack_start(GTK_BOX(dialog
->vbox
),
134 abox
->dir_label
, FALSE
, TRUE
, 0);
136 abox
->log_hbox
= gtk_hbox_new(FALSE
, 0);
137 gtk_box_pack_start(GTK_BOX(dialog
->vbox
),
138 abox
->log_hbox
, TRUE
, TRUE
, 4);
140 frame
= gtk_frame_new(NULL
);
141 gtk_box_pack_start_defaults(GTK_BOX(abox
->log_hbox
), frame
);
143 text
= gtk_text_view_new();
144 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_IN
);
145 gtk_container_add(GTK_CONTAINER(frame
), text
);
147 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text
), GTK_WRAP_WORD
);
150 scrollbar
= gtk_vscrollbar_new(NULL
);
151 gtk_widget_set_scroll_adjustments(text
, NULL
,
152 gtk_range_get_adjustment(GTK_RANGE(scrollbar
)));
153 gtk_text_buffer_create_tag(
154 gtk_text_view_get_buffer(GTK_TEXT_VIEW(abox
->log
)),
155 "error", "foreground", "red",
157 gtk_text_buffer_create_tag(
158 gtk_text_view_get_buffer(GTK_TEXT_VIEW(abox
->log
)),
159 "question", "weight", "bold",
161 gtk_text_view_set_editable(GTK_TEXT_VIEW(text
), FALSE
);
162 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text
), FALSE
);
163 gtk_widget_set_size_request(text
, 400, 100);
165 gtk_box_pack_start(GTK_BOX(abox
->log_hbox
), scrollbar
, FALSE
, TRUE
, 0);
167 gtk_dialog_add_buttons(dialog
,
168 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
169 GTK_STOCK_NO
, GTK_RESPONSE_NO
,
170 GTK_STOCK_YES
, GTK_RESPONSE_YES
,
173 abox
->cmp_area
= gtk_table_new(2, 6, FALSE
);
174 gtk_box_pack_start(GTK_BOX(dialog
->vbox
),
175 abox
->cmp_area
, FALSE
, FALSE
, 2);
176 gtk_table_set_row_spacings(GTK_TABLE(abox
->cmp_area
), 2);
177 gtk_table_set_col_spacings(GTK_TABLE(abox
->cmp_area
), 2);
179 for (i
= 0; i
< 2; i
++)
182 abox
->cmp_icon
[i
] = gtk_image_new();
183 gtk_table_attach(GTK_TABLE(abox
->cmp_area
),
186 GTK_SHRINK
, GTK_SHRINK
, 4, 1);
187 abox
->cmp_name
[i
] = gtk_label_new("");
188 gtk_label_set_line_wrap(GTK_LABEL(abox
->cmp_name
[i
]), TRUE
);
189 gtk_misc_set_alignment(GTK_MISC(abox
->cmp_name
[i
]), 0., 0.5);
190 gtk_table_attach(GTK_TABLE(abox
->cmp_area
),
193 GTK_EXPAND
| GTK_FILL
, GTK_SHRINK
, 4, 1);
194 abox
->cmp_size
[i
] = gtk_label_new("");
195 gtk_table_attach(GTK_TABLE(abox
->cmp_area
),
198 GTK_SHRINK
, GTK_SHRINK
, 4, 1);
199 abox
->cmp_date
[i
] = gtk_label_new("");
200 gtk_table_attach(GTK_TABLE(abox
->cmp_area
),
203 GTK_SHRINK
, GTK_SHRINK
, 4, 1);
205 abox
->cmp_arrow
=gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_IN
);
206 gtk_widget_set_size_request(abox
->cmp_arrow
, 32, 64);
207 gtk_table_attach(GTK_TABLE(abox
->cmp_area
),
210 GTK_SHRINK
, GTK_EXPAND
| GTK_FILL
, 1, 2);
214 abox
->flag_box
= gtk_hbox_new(FALSE
, 16);
215 gtk_box_pack_end(GTK_BOX(dialog
->vbox
),
216 abox
->flag_box
, FALSE
, TRUE
, 2);
218 button
= button_new_mixed(GTK_STOCK_GOTO_LAST
, _("_Quiet"));
219 GTK_WIDGET_SET_FLAGS(button
, GTK_CAN_DEFAULT
);
220 gtk_dialog_add_action_widget(dialog
, button
, RESPONSE_QUIET
);
221 gtk_dialog_set_default_response(dialog
, RESPONSE_QUIET
);
223 gtk_widget_show_all(dialog
->vbox
);
224 gtk_widget_hide(abox
->cmp_area
);
226 abox
->quiet
= abox_add_flag(abox
,
227 _("Quiet"), _("Don't confirm every operation"),
233 static void flag_toggled(GtkToggleButton
*toggle
, ABox
*abox
)
237 code
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(toggle
),
242 g_signal_emit_by_name(abox
, "flag_toggled", code
);
246 GtkWidget
*abox_add_flag(ABox
*abox
, const gchar
*label
, const gchar
*tip
,
247 gint response
, gboolean default_value
)
251 check
= gtk_check_button_new_with_label(label
);
252 gtk_tooltips_set_tip(tooltips
, check
, tip
, NULL
);
253 g_object_set_data(G_OBJECT(check
), "abox-response",
254 GINT_TO_POINTER(response
));
255 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check
), default_value
);
256 g_signal_connect(check
, "toggled", G_CALLBACK(flag_toggled
), abox
);
257 gtk_box_pack_end(GTK_BOX(abox
->flag_box
), check
, FALSE
, TRUE
, 0);
258 gtk_widget_show(check
);
263 static void response(GtkDialog
*dialog
, gint response_id
)
265 ABox
*abox
= ABOX(dialog
);
267 if (response_id
== GTK_RESPONSE_CANCEL
)
268 g_signal_emit_by_name(abox
, "abort_operation");
269 else if (response_id
== RESPONSE_QUIET
)
271 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(abox
->quiet
),
273 gtk_dialog_response(dialog
, GTK_RESPONSE_YES
);
275 else if (response_id
== GTK_RESPONSE_YES
||
276 response_id
== GTK_RESPONSE_NO
)
278 abox
->question
= FALSE
;
283 /* Display the question. Unshade the Yes, No and entry box (if any).
284 * Will send a response signal when the user makes a choice.
286 void abox_ask(ABox
*abox
, const gchar
*question
)
288 g_return_if_fail(abox
!= NULL
);
289 g_return_if_fail(question
!= NULL
);
290 g_return_if_fail(IS_ABOX(abox
));
292 abox_log(abox
, question
, "question");
294 abox
->question
= TRUE
;
298 void abox_cancel_ask(ABox
*abox
)
300 g_return_if_fail(abox
!= NULL
);
301 g_return_if_fail(IS_ABOX(abox
));
303 abox
->question
= FALSE
;
307 void abox_log(ABox
*abox
, const gchar
*message
, const gchar
*style
)
310 GtkTextBuffer
*text_buffer
;
311 GtkTextView
*log
= GTK_TEXT_VIEW(abox
->log
);
314 if (!g_utf8_validate(message
, -1, NULL
))
315 u8
= to_utf8(message
);
317 text_buffer
= gtk_text_view_get_buffer(log
);
319 gtk_text_buffer_get_end_iter(text_buffer
, &end
);
320 gtk_text_buffer_insert_with_tags_by_name(text_buffer
,
321 &end
, u8
? u8
: message
, -1, style
, NULL
);
322 gtk_text_view_scroll_to_mark(
324 gtk_text_buffer_get_mark(text_buffer
, "insert"),
330 static void abox_finalise(GObject
*object
)
332 GObjectClass
*parent_class
;
333 ABox
*abox
= ABOX(object
);
337 null_g_free(&abox
->next_dir
);
338 g_source_remove(abox
->next_timer
);
341 parent_class
= gtk_type_class(GTK_TYPE_DIALOG
);
343 if (G_OBJECT_CLASS(parent_class
)->finalize
)
344 (*G_OBJECT_CLASS(parent_class
)->finalize
)(object
);
347 static gboolean
show_next_dir(gpointer data
)
349 ABox
*abox
= (ABox
*) data
;
351 g_return_val_if_fail(IS_ABOX(abox
), FALSE
);
353 gtk_label_set_text(GTK_LABEL(abox
->dir_label
), abox
->next_dir
);
354 null_g_free(&abox
->next_dir
);
359 /* Display this message in the current-object area.
360 * The display won't update too fast, even if you call this very often.
362 void abox_set_current_object(ABox
*abox
, const gchar
*message
)
364 g_return_if_fail(abox
!= NULL
);
365 g_return_if_fail(message
!= NULL
);
366 g_return_if_fail(IS_ABOX(abox
));
368 /* If a string is already set then replace it, but assume the
369 * timer is already running...
373 g_free(abox
->next_dir
);
376 gtk_label_set_text(GTK_LABEL(abox
->dir_label
), message
);
377 abox
->next_timer
= g_timeout_add(500, show_next_dir
, abox
);
380 abox
->next_dir
= g_strdup(message
);
383 static void lost_preview(GtkWidget
*window
, ABox
*abox
)
385 abox
->preview
= NULL
;
388 static void select_row_callback(GtkTreeView
*treeview
,
390 GtkTreeViewColumn
*col
,
397 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(abox
->results
));
398 gtk_tree_model_get_iter(model
, &iter
, path
);
399 gtk_tree_model_get(model
, &iter
, 0, &leaf
, 1, &dir
, -1);
403 if (strcmp(abox
->preview
->real_path
, dir
) == 0)
404 display_set_autoselect(abox
->preview
, leaf
);
406 filer_change_to(abox
->preview
, dir
, leaf
);
410 abox
->preview
= filer_opendir(dir
, NULL
, NULL
);
413 display_set_autoselect(abox
->preview
, leaf
);
414 g_signal_connect_object(abox
->preview
->window
, "destroy",
415 G_CALLBACK(lost_preview
), abox
, 0);
423 /* Add a list-of-results area. You must use this before adding files
424 * with abox_add_filename().
426 void abox_add_results(ABox
*abox
)
428 GtkTreeViewColumn
*column
;
429 GtkWidget
*scroller
, *frame
;
431 GtkCellRenderer
*cell_renderer
;
433 g_return_if_fail(abox
!= NULL
);
434 g_return_if_fail(IS_ABOX(abox
));
435 g_return_if_fail(abox
->results
== NULL
);
437 scroller
= gtk_scrolled_window_new(NULL
, NULL
);
438 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller
),
439 GTK_POLICY_NEVER
, GTK_POLICY_ALWAYS
);
441 frame
= gtk_frame_new(NULL
);
442 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_IN
);
443 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(abox
)->vbox
),
444 frame
, TRUE
, TRUE
, 4);
446 gtk_container_add(GTK_CONTAINER(frame
), scroller
);
448 model
= gtk_list_store_new(2, G_TYPE_STRING
, G_TYPE_STRING
);
449 abox
->results
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(model
));
450 g_object_unref(G_OBJECT(model
));
452 cell_renderer
= gtk_cell_renderer_text_new();
453 column
= gtk_tree_view_column_new_with_attributes(
454 _("Name"), cell_renderer
, "text", 0, NULL
);
455 gtk_tree_view_column_set_resizable(column
, TRUE
);
456 gtk_tree_view_column_set_sizing(column
, GTK_TREE_VIEW_COLUMN_GROW_ONLY
);
457 gtk_tree_view_append_column(GTK_TREE_VIEW(abox
->results
), column
);
458 gtk_tree_view_insert_column_with_attributes(
459 GTK_TREE_VIEW(abox
->results
),
460 1, (gchar
*) _("Directory"), cell_renderer
,
463 gtk_container_add(GTK_CONTAINER(scroller
), abox
->results
);
465 gtk_widget_set_size_request(abox
->results
, 100, 100);
466 gtk_box_set_child_packing(GTK_BOX(GTK_DIALOG(abox
)->vbox
),
467 abox
->log_hbox
, FALSE
, TRUE
, 4, GTK_PACK_START
);
469 g_signal_connect(abox
->results
, "row-activated",
470 G_CALLBACK(select_row_callback
), abox
);
472 gtk_widget_show_all(frame
);
475 void abox_add_filename(ABox
*abox
, const gchar
*path
)
481 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(abox
->results
));
483 gtk_list_store_append(GTK_LIST_STORE(model
), &iter
);
485 dir
= g_path_get_dirname(path
);
486 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
492 /* Clear search results area */
493 void abox_clear_results(ABox
*abox
)
497 g_return_if_fail(abox
!= NULL
);
498 g_return_if_fail(IS_ABOX(abox
));
500 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(abox
->results
));
502 gtk_list_store_clear(GTK_LIST_STORE(model
));
505 void abox_add_combo(ABox
*abox
, const gchar
*tlabel
, GList
*presets
,
506 const gchar
*text
, GtkWidget
*help_button
)
508 GtkWidget
*hbox
, *label
, *combo
;
510 g_return_if_fail(abox
!= NULL
);
511 g_return_if_fail(IS_ABOX(abox
));
512 g_return_if_fail(abox
->entry
== NULL
);
514 hbox
= gtk_hbox_new(FALSE
, 0);
517 label
= gtk_label_new(tlabel
);
518 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, TRUE
, 4);
521 combo
= gtk_combo_new();
522 gtk_combo_disable_activate(GTK_COMBO(combo
));
523 gtk_combo_set_use_arrows_always(GTK_COMBO(combo
), TRUE
);
524 gtk_combo_set_popdown_strings(GTK_COMBO(combo
), presets
);
525 abox
->entry
= GTK_COMBO(combo
)->entry
;
526 gtk_entry_set_activates_default(GTK_ENTRY(abox
->entry
), TRUE
);
528 gtk_entry_set_text(GTK_ENTRY(abox
->entry
), text
);
529 gtk_box_pack_start(GTK_BOX(hbox
), combo
, TRUE
, TRUE
, 4);
531 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(abox
)->vbox
), hbox
,
533 gtk_box_pack_start(GTK_BOX(hbox
), help_button
, FALSE
, TRUE
, 4);
535 gtk_widget_show_all(hbox
);
540 void abox_add_entry(ABox
*abox
, const gchar
*text
, GtkWidget
*help_button
)
542 GtkWidget
*hbox
, *label
;
544 g_return_if_fail(abox
!= NULL
);
545 g_return_if_fail(IS_ABOX(abox
));
546 g_return_if_fail(abox
->entry
== NULL
);
548 hbox
= gtk_hbox_new(FALSE
, 0);
549 label
= gtk_label_new(_("Expression:"));
550 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, TRUE
, 4);
551 abox
->entry
= gtk_entry_new();
552 gtk_widget_set_name(abox
->entry
, "fixed-style");
553 gtk_entry_set_text(GTK_ENTRY(abox
->entry
), text
);
554 gtk_box_pack_start(GTK_BOX(hbox
), abox
->entry
, TRUE
, TRUE
, 4);
555 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(abox
)->vbox
),
556 hbox
, FALSE
, TRUE
, 4);
557 gtk_box_pack_start(GTK_BOX(hbox
), help_button
,
560 gtk_entry_set_activates_default(GTK_ENTRY(abox
->entry
), TRUE
);
562 gtk_widget_show_all(hbox
);
567 static void shade(ABox
*abox
)
569 GtkDialog
*dialog
= (GtkDialog
*) abox
;
570 gboolean quiet
, on
= abox
->question
;
572 quiet
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(abox
->quiet
));
574 gtk_dialog_set_response_sensitive(dialog
, GTK_RESPONSE_YES
, on
);
575 gtk_dialog_set_response_sensitive(dialog
, GTK_RESPONSE_NO
, on
);
578 gtk_dialog_set_response_sensitive(dialog
, RESPONSE_QUIET
, TRUE
);
580 gtk_dialog_set_response_sensitive(dialog
,
581 RESPONSE_QUIET
, FALSE
);
583 /* Unsetting the focus means that set_default will put it in the
586 gtk_window_set_focus(GTK_WINDOW(abox
), NULL
);
587 /* Note: Gtk+-2.0.0 will segfault on Return if an insensitive
588 * widget is the default.
591 gtk_dialog_set_default_response(dialog
, GTK_RESPONSE_YES
);
593 gtk_dialog_set_default_response(dialog
, RESPONSE_QUIET
);
597 gtk_widget_set_sensitive(abox
->entry
, on
);
599 gtk_widget_grab_focus(abox
->entry
);
603 static gboolean
abox_delete(GtkWidget
*dialog
, GdkEventAny
*event
)
605 g_signal_emit_by_name(dialog
, "abort_operation");
609 void abox_show_compare(ABox
*abox
, gboolean show
)
612 gtk_widget_show(abox
->cmp_area
);
614 gtk_widget_hide(abox
->cmp_area
);
617 void abox_set_file(ABox
*abox
, int i
, const gchar
*path
)
622 g_return_if_fail(i
>= 0 && i
< 2);
623 g_return_if_fail(IS_ABOX(abox
));
625 if (!path
|| !path
[0])
627 gtk_widget_hide(abox
->cmp_icon
[i
]);
628 gtk_widget_hide(abox
->cmp_name
[i
]);
629 gtk_widget_hide(abox
->cmp_size
[i
]);
630 gtk_widget_hide(abox
->cmp_date
[i
]);
631 gtk_widget_hide(abox
->cmp_arrow
);
635 base
= g_path_get_basename(path
);
636 item
= diritem_new(base
);
638 diritem_restat(path
, item
, NULL
);
640 gtk_image_set_from_pixbuf(GTK_IMAGE(abox
->cmp_icon
[i
]),
641 di_image(item
)->pixbuf
);
642 gtk_widget_show(abox
->cmp_icon
[i
]);
644 gtk_label_set_text(GTK_LABEL(abox
->cmp_name
[i
]), item
->leafname
);
645 gtk_widget_show(abox
->cmp_name
[i
]);
646 gtk_widget_show(abox
->cmp_arrow
);
648 if (item
->lstat_errno
)
650 gtk_label_set_text(GTK_LABEL(abox
->cmp_size
[i
]), "Error");
651 gtk_label_set_text(GTK_LABEL(abox
->cmp_date
[i
]),
652 g_strerror(item
->lstat_errno
));
658 gtk_label_set_text(GTK_LABEL(abox
->cmp_size
[i
]),
659 format_size_aligned(item
->size
));
661 str
= pretty_time(&item
->mtime
);
662 gtk_label_set_text(GTK_LABEL(abox
->cmp_date
[i
]), str
);
666 gtk_widget_show(abox
->cmp_size
[i
]);
667 gtk_widget_show(abox
->cmp_date
[i
]);
672 void abox_set_percentage(ABox
*abox
, int per
)
674 if(!abox
->progress
) {
675 GtkDialog
*dialog
= GTK_DIALOG(abox
);
677 abox
->progress
=gtk_progress_bar_new ();
678 gtk_box_pack_start(GTK_BOX(dialog
->vbox
),
679 abox
->progress
, FALSE
, FALSE
, 2);
680 gtk_widget_show(abox
->progress
);
682 if(per
<0 || per
>100) {
683 gtk_widget_hide(abox
->progress
);
686 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(abox
->progress
),