2 * ui_utils.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2006 The Geany contributors
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 along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 * User Interface general utility functions.
32 #include "callbacks.h"
34 #include "documentprivate.h"
35 #include "encodingsprivate.h"
36 #include "filetypes.h"
37 #include "geanymenubuttonaction.h"
39 #include "keybindings.h"
41 #include "msgwindow.h"
44 #include "sciwrappers.h"
58 #include <gdk/gdkkeysyms.h>
61 #define DEFAULT_STATUSBAR_TEMPLATE N_(\
71 GEANY_EXPORT_SYMBOL GeanyInterfacePrefs interface_prefs
;
72 GeanyMainWidgets main_widgets
;
77 static GtkBuilder
*builder
= NULL
;
78 static GtkWidget
* window1
= NULL
;
79 static GtkWidget
* toolbar_popup_menu1
= NULL
;
80 static GtkWidget
* edit_menu1
= NULL
;
81 static GtkWidget
* prefs_dialog
= NULL
;
82 static GtkWidget
* project_dialog
= NULL
;
86 /* pointers to widgets only sensitive when there is at least one document, the pointers can
87 * also be GtkAction objects, so check each pointer before using it */
88 GPtrArray
*document_buttons
;
89 GtkWidget
*menu_insert_include_items
[2];
90 GtkWidget
*popup_goto_items
[4];
91 GtkWidget
*popup_copy_items
[3];
92 GtkWidget
*menu_copy_items
[3];
93 GtkWidget
*redo_items
[3];
94 GtkWidget
*undo_items
[3];
95 GtkWidget
*save_buttons
[4];
96 GtkWidget
*config_files_menu
;
109 GQueue
*recent_queue
;
112 void (*activate_cb
)(GtkMenuItem
*, gpointer
);
116 static void update_recent_menu(GeanyRecentFiles
*grf
);
117 static void recent_file_loaded(const gchar
*utf8_filename
, GeanyRecentFiles
*grf
);
118 static void recent_file_activate_cb(GtkMenuItem
*menuitem
, gpointer user_data
);
119 static void recent_project_activate_cb(GtkMenuItem
*menuitem
, gpointer user_data
);
120 static GtkWidget
*progress_bar_create(void);
121 static void ui_menu_sort_by_label(GtkMenu
*menu
);
124 /* simple wrapper for gtk_widget_set_sensitive() to allow widget being NULL */
125 void ui_widget_set_sensitive(GtkWidget
*widget
, gboolean set
)
128 gtk_widget_set_sensitive(widget
, set
);
132 /* allow_override is TRUE if text can be ignored when another message has been set
133 * that didn't use allow_override and has not timed out. */
134 static void set_statusbar(const gchar
*text
, gboolean allow_override
)
137 static glong last_time
= 0;
139 const gint GEANY_STATUS_TIMEOUT
= 1;
141 if (! interface_prefs
.statusbar_visible
)
142 return; /* just do nothing if statusbar is not visible */
145 id
= gtk_statusbar_get_context_id(GTK_STATUSBAR(ui_widgets
.statusbar
), "geany-main");
147 g_get_current_time(&timeval
);
149 if (! allow_override
)
151 gtk_statusbar_pop(GTK_STATUSBAR(ui_widgets
.statusbar
), id
);
152 gtk_statusbar_push(GTK_STATUSBAR(ui_widgets
.statusbar
), id
, text
);
153 last_time
= timeval
.tv_sec
;
156 if (timeval
.tv_sec
> last_time
+ GEANY_STATUS_TIMEOUT
)
158 gtk_statusbar_pop(GTK_STATUSBAR(ui_widgets
.statusbar
), id
);
159 gtk_statusbar_push(GTK_STATUSBAR(ui_widgets
.statusbar
), id
, text
);
164 /** Displays text on the statusbar.
165 * @param log Whether the message should be recorded in the Status window.
166 * @param format A @c printf -style string. */
168 void ui_set_statusbar(gboolean log
, const gchar
*format
, ...)
173 va_start(args
, format
);
174 string
= g_strdup_vprintf(format
, args
);
177 if (! prefs
.suppress_status_messages
)
178 set_statusbar(string
, FALSE
);
180 if (log
|| prefs
.suppress_status_messages
)
181 msgwin_status_add("%s", string
);
187 /* note: some comments below are for translators */
188 static gchar
*create_statusbar_statistics(GeanyDocument
*doc
,
189 guint line
, guint vcol
, guint pos
)
191 const gchar
*cur_tag
;
193 const gchar
*expos
; /* % expansion position */
194 const gchar sp
[] = " ";
196 ScintillaObject
*sci
= doc
->editor
->sci
;
198 if (!EMPTY(ui_prefs
.statusbar_template
))
199 fmt
= ui_prefs
.statusbar_template
;
201 fmt
= _(DEFAULT_STATUSBAR_TEMPLATE
);
203 stats_str
= g_string_sized_new(120);
205 while ((expos
= strchr(fmt
, '%')) != NULL
)
207 /* append leading text before % char */
208 g_string_append_len(stats_str
, fmt
, expos
- fmt
);
213 g_string_append_printf(stats_str
, "%d", line
+ 1);
216 g_string_append_printf(stats_str
, "%d",
217 sci_get_line_count(doc
->editor
->sci
));
220 g_string_append_printf(stats_str
, "%d", vcol
);
223 g_string_append_printf(stats_str
, "%d", vcol
+ 1);
226 g_string_append_printf(stats_str
, "%u", pos
);
230 gint len
= sci_get_selected_text_length2(sci
);
231 /* check if whole lines are selected */
232 if (!len
|| sci_get_col_from_position(sci
,
233 sci_get_selection_start(sci
)) != 0 ||
234 sci_get_col_from_position(sci
,
235 sci_get_selection_end(sci
)) != 0)
236 g_string_append_printf(stats_str
, "%d", len
);
238 g_string_append_printf(stats_str
, _("%dL"),
239 sci_get_lines_selected(doc
->editor
->sci
) - 1);
243 g_string_append_printf(stats_str
, "%d",
244 sci_get_selected_text_length2(doc
->editor
->sci
));
248 g_string_append(stats_str
, (doc
->readonly
) ? _("RO ") :
249 /* OVR = overwrite/overtype, INS = insert */
250 (sci_get_overtype(doc
->editor
->sci
) ? _("OVR") : _("INS")));
255 g_string_append(stats_str
, _("RO ")); /* RO = read-only */
256 g_string_append(stats_str
, sp
+ 1);
261 switch (editor_get_indent_prefs(doc
->editor
)->type
)
263 case GEANY_INDENT_TYPE_TABS
:
264 g_string_append(stats_str
, _("TAB"));
266 case GEANY_INDENT_TYPE_SPACES
: /* SP = space */
267 g_string_append(stats_str
, _("SP"));
269 case GEANY_INDENT_TYPE_BOTH
: /* T/S = tabs and spaces */
270 g_string_append(stats_str
, _("T/S"));
278 g_string_append(stats_str
, _("MOD")); /* MOD = modified */
279 g_string_append(stats_str
, sp
);
283 g_string_append(stats_str
, utils_get_eol_short_name(sci_get_eol_mode(doc
->editor
->sci
)));
286 g_string_append(stats_str
,
287 doc
->encoding
? doc
->encoding
: _("unknown"));
288 if (encodings_is_unicode_charset(doc
->encoding
) && (doc
->has_bom
))
290 g_string_append_c(stats_str
, ' ');
291 g_string_append(stats_str
, _("(with BOM)")); /* BOM = byte order mark */
295 g_string_append(stats_str
, filetypes_get_display_name(doc
->file_type
));
298 symbols_get_current_scope(doc
, &cur_tag
);
299 g_string_append(stats_str
, cur_tag
);
302 g_string_append_c(stats_str
, ' ');
303 g_string_append_printf(stats_str
, "%d",
304 sci_get_style_at(doc
->editor
->sci
, pos
));
307 g_string_append_len(stats_str
, expos
, 1);
310 /* skip past %c chars */
316 /* add any remaining text */
317 g_string_append(stats_str
, fmt
);
319 return g_string_free(stats_str
, FALSE
);
323 /* updates the status bar document statistics */
324 void ui_update_statusbar(GeanyDocument
*doc
, gint pos
)
326 g_return_if_fail(doc
== NULL
|| doc
->is_valid
);
328 if (! interface_prefs
.statusbar_visible
)
329 return; /* just do nothing if statusbar is not visible */
332 doc
= document_get_current();
340 pos
= sci_get_current_position(doc
->editor
->sci
);
341 line
= sci_get_line_from_position(doc
->editor
->sci
, pos
);
343 /* Add temporary fix for sci infinite loop in Document::GetColumn(int)
344 * when current pos is beyond document end (can occur when removing
345 * blocks of selected lines especially esp. brace sections near end of file). */
346 if (pos
<= sci_get_length(doc
->editor
->sci
))
347 vcol
= sci_get_col_from_position(doc
->editor
->sci
, pos
);
350 vcol
+= sci_get_cursor_virtual_space(doc
->editor
->sci
);
352 stats_str
= create_statusbar_statistics(doc
, line
, vcol
, pos
);
354 /* can be overridden by status messages */
355 set_statusbar(stats_str
, TRUE
);
358 else /* no documents */
360 set_statusbar("", TRUE
); /* can be overridden by status messages */
365 /* This sets the window title according to the current filename. */
366 void ui_set_window_title(GeanyDocument
*doc
)
369 GeanyProject
*project
= app
->project
;
371 g_return_if_fail(doc
== NULL
|| doc
->is_valid
);
374 doc
= document_get_current();
376 str
= g_string_new(NULL
);
380 g_string_append(str
, doc
->changed
? "*" : "");
382 if (doc
->file_name
== NULL
)
383 g_string_append(str
, DOC_FILENAME(doc
));
386 gchar
*short_name
= document_get_basename_for_display(doc
, interface_prefs
.tab_label_len
);
387 gchar
*dirname
= g_path_get_dirname(DOC_FILENAME(doc
));
389 g_string_append(str
, short_name
);
390 g_string_append(str
, " - ");
391 g_string_append(str
, dirname
? dirname
: "");
395 g_string_append(str
, " - ");
399 g_string_append_c(str
, '[');
400 g_string_append(str
, project
->name
);
401 g_string_append(str
, "] - ");
403 g_string_append(str
, "Geany");
404 if (cl_options
.new_instance
)
406 g_string_append(str
, _(" (new instance)"));
408 gtk_window_set_title(GTK_WINDOW(main_widgets
.window
), str
->str
);
409 g_string_free(str
, TRUE
);
413 void ui_set_editor_font(const gchar
*font_name
)
417 g_return_if_fail(font_name
!= NULL
);
419 /* do nothing if font has not changed */
420 if (interface_prefs
.editor_font
!= NULL
)
421 if (strcmp(font_name
, interface_prefs
.editor_font
) == 0)
424 g_free(interface_prefs
.editor_font
);
425 interface_prefs
.editor_font
= g_strdup(font_name
);
427 /* We copy the current style, and update the font in all open tabs. */
428 for (i
= 0; i
< documents_array
->len
; i
++)
430 if (documents
[i
]->editor
)
432 editor_set_font(documents
[i
]->editor
, interface_prefs
.editor_font
);
436 ui_set_statusbar(TRUE
, _("Font updated (%s)."), interface_prefs
.editor_font
);
440 void ui_set_fullscreen(void)
442 if (ui_prefs
.fullscreen
)
444 gtk_window_fullscreen(GTK_WINDOW(main_widgets
.window
));
448 gtk_window_unfullscreen(GTK_WINDOW(main_widgets
.window
));
453 void ui_update_popup_reundo_items(GeanyDocument
*doc
)
455 gboolean enable_undo
;
456 gboolean enable_redo
;
459 g_return_if_fail(doc
== NULL
|| doc
->is_valid
);
468 enable_undo
= document_can_undo(doc
);
469 enable_redo
= document_can_redo(doc
);
472 /* index 0 is the popup menu, 1 is the menubar, 2 is the toolbar */
473 len
= G_N_ELEMENTS(widgets
.undo_items
);
474 for (i
= 0; i
< len
; i
++)
476 ui_widget_set_sensitive(widgets
.undo_items
[i
], enable_undo
);
478 len
= G_N_ELEMENTS(widgets
.redo_items
);
479 for (i
= 0; i
< len
; i
++)
481 ui_widget_set_sensitive(widgets
.redo_items
[i
], enable_redo
);
486 void ui_update_popup_copy_items(GeanyDocument
*doc
)
491 g_return_if_fail(doc
== NULL
|| doc
->is_valid
);
496 enable
= sci_has_selection(doc
->editor
->sci
);
498 len
= G_N_ELEMENTS(widgets
.popup_copy_items
);
499 for (i
= 0; i
< len
; i
++)
500 ui_widget_set_sensitive(widgets
.popup_copy_items
[i
], enable
);
504 void ui_update_popup_goto_items(gboolean enable
)
507 len
= G_N_ELEMENTS(widgets
.popup_goto_items
);
508 for (i
= 0; i
< len
; i
++)
509 ui_widget_set_sensitive(widgets
.popup_goto_items
[i
], enable
);
513 void ui_menu_copy_items_set_sensitive(gboolean sensitive
)
517 len
= G_N_ELEMENTS(widgets
.menu_copy_items
);
518 for (i
= 0; i
< len
; i
++)
519 ui_widget_set_sensitive(widgets
.menu_copy_items
[i
], sensitive
);
523 void ui_update_menu_copy_items(GeanyDocument
*doc
)
525 gboolean enable
= FALSE
;
526 GtkWidget
*focusw
= gtk_window_get_focus(GTK_WINDOW(main_widgets
.window
));
528 g_return_if_fail(doc
== NULL
|| doc
->is_valid
);
530 if (IS_SCINTILLA(focusw
))
531 enable
= (doc
== NULL
) ? FALSE
: sci_has_selection(doc
->editor
->sci
);
533 if (GTK_IS_EDITABLE(focusw
))
534 enable
= gtk_editable_get_selection_bounds(GTK_EDITABLE(focusw
), NULL
, NULL
);
536 if (GTK_IS_TEXT_VIEW(focusw
))
538 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(
539 GTK_TEXT_VIEW(focusw
));
540 enable
= gtk_text_buffer_get_selection_bounds(buffer
, NULL
, NULL
);
543 ui_menu_copy_items_set_sensitive(enable
);
547 void ui_update_insert_include_item(GeanyDocument
*doc
, gint item
)
549 gboolean enable
= FALSE
;
551 g_return_if_fail(doc
== NULL
|| doc
->is_valid
);
553 if (doc
== NULL
|| doc
->file_type
== NULL
)
555 else if (doc
->file_type
->id
== GEANY_FILETYPES_C
|| doc
->file_type
->id
== GEANY_FILETYPES_CPP
)
558 ui_widget_set_sensitive(widgets
.menu_insert_include_items
[item
], enable
);
562 void ui_update_fold_items(void)
564 ui_widget_show_hide(ui_lookup_widget(main_widgets
.window
, "menu_fold_all1"), editor_prefs
.folding
);
565 ui_widget_show_hide(ui_lookup_widget(main_widgets
.window
, "menu_unfold_all1"), editor_prefs
.folding
);
566 ui_widget_show_hide(ui_lookup_widget(main_widgets
.window
, "separator22"), editor_prefs
.folding
);
570 /* @include include name or NULL for empty with cursor ready for typing it */
571 static void insert_include(GeanyDocument
*doc
, gint pos
, const gchar
*include
)
576 g_return_if_fail(doc
!= NULL
);
577 g_return_if_fail(pos
== -1 || pos
>= 0);
580 pos
= sci_get_current_position(doc
->editor
->sci
);
584 text
= g_strdup("#include \"\"\n");
585 pos_after
= pos
+ 10;
589 text
= g_strconcat("#include <", include
, ">\n", NULL
);
592 sci_start_undo_action(doc
->editor
->sci
);
593 sci_insert_text(doc
->editor
->sci
, pos
, text
);
594 sci_end_undo_action(doc
->editor
->sci
);
597 sci_goto_pos(doc
->editor
->sci
, pos_after
, FALSE
);
601 static void on_popup_insert_include_activate(GtkMenuItem
*menuitem
, gpointer user_data
)
603 insert_include(document_get_current(), editor_info
.click_pos
, user_data
);
607 static void on_menu_insert_include_activate(GtkMenuItem
*menuitem
, gpointer user_data
)
609 insert_include(document_get_current(), -1, user_data
);
613 static void insert_include_items(GtkMenu
*me
, GtkMenu
*mp
, gchar
**includes
, gchar
*label
)
616 GtkWidget
*edit_menu
, *edit_menu_item
;
617 GtkWidget
*popup_menu
, *popup_menu_item
;
619 edit_menu
= gtk_menu_new();
620 popup_menu
= gtk_menu_new();
621 edit_menu_item
= gtk_menu_item_new_with_label(label
);
622 popup_menu_item
= gtk_menu_item_new_with_label(label
);
623 gtk_menu_item_set_submenu(GTK_MENU_ITEM(edit_menu_item
), edit_menu
);
624 gtk_menu_item_set_submenu(GTK_MENU_ITEM(popup_menu_item
), popup_menu
);
626 while (includes
[i
] != NULL
)
628 GtkWidget
*tmp_menu
= gtk_menu_item_new_with_label(includes
[i
]);
629 GtkWidget
*tmp_popup
= gtk_menu_item_new_with_label(includes
[i
]);
631 gtk_container_add(GTK_CONTAINER(edit_menu
), tmp_menu
);
632 gtk_container_add(GTK_CONTAINER(popup_menu
), tmp_popup
);
633 g_signal_connect(tmp_menu
, "activate",
634 G_CALLBACK(on_menu_insert_include_activate
), (gpointer
) includes
[i
]);
635 g_signal_connect(tmp_popup
, "activate",
636 G_CALLBACK(on_popup_insert_include_activate
), (gpointer
) includes
[i
]);
639 gtk_widget_show_all(edit_menu_item
);
640 gtk_widget_show_all(popup_menu_item
);
641 gtk_container_add(GTK_CONTAINER(me
), edit_menu_item
);
642 gtk_container_add(GTK_CONTAINER(mp
), popup_menu_item
);
646 void ui_create_insert_menu_items(void)
648 GtkMenu
*menu_edit
= GTK_MENU(ui_lookup_widget(main_widgets
.window
, "insert_include2_menu"));
649 GtkMenu
*menu_popup
= GTK_MENU(ui_lookup_widget(main_widgets
.editor_menu
, "insert_include1_menu"));
651 const gchar
*c_includes_stdlib
[] = {
652 "assert.h", "ctype.h", "errno.h", "float.h", "limits.h", "locale.h", "math.h", "setjmp.h",
653 "signal.h", "stdarg.h", "stddef.h", "stdio.h", "stdlib.h", "string.h", "time.h", NULL
655 const gchar
*c_includes_c99
[] = {
656 "complex.h", "fenv.h", "inttypes.h", "iso646.h", "stdbool.h", "stdint.h",
657 "tgmath.h", "wchar.h", "wctype.h", NULL
659 const gchar
*c_includes_cpp
[] = {
660 "cstdio", "cstring", "cctype", "cmath", "ctime", "cstdlib", "cstdarg", NULL
662 const gchar
*c_includes_cppstdlib
[] = {
663 "iostream", "fstream", "iomanip", "sstream", "exception", "stdexcept",
664 "memory", "locale", NULL
666 const gchar
*c_includes_stl
[] = {
667 "bitset", "deque", "list", "map", "set", "queue", "stack", "vector", "algorithm",
668 "iterator", "functional", "string", "complex", "valarray", NULL
671 blank
= gtk_menu_item_new_with_label("#include \"...\"");
672 gtk_container_add(GTK_CONTAINER(menu_edit
), blank
);
673 gtk_widget_show(blank
);
674 g_signal_connect(blank
, "activate", G_CALLBACK(on_menu_insert_include_activate
), NULL
);
675 blank
= gtk_separator_menu_item_new ();
676 gtk_container_add(GTK_CONTAINER(menu_edit
), blank
);
677 gtk_widget_show(blank
);
679 blank
= gtk_menu_item_new_with_label("#include \"...\"");
680 gtk_container_add(GTK_CONTAINER(menu_popup
), blank
);
681 gtk_widget_show(blank
);
682 g_signal_connect(blank
, "activate", G_CALLBACK(on_popup_insert_include_activate
), NULL
);
683 blank
= gtk_separator_menu_item_new();
684 gtk_container_add(GTK_CONTAINER(menu_popup
), blank
);
685 gtk_widget_show(blank
);
687 insert_include_items(menu_edit
, menu_popup
, (gchar
**) c_includes_stdlib
, _("C Standard Library"));
688 insert_include_items(menu_edit
, menu_popup
, (gchar
**) c_includes_c99
, _("ISO C99"));
689 insert_include_items(menu_edit
, menu_popup
, (gchar
**) c_includes_cpp
, _("C++ (C Standard Library)"));
690 insert_include_items(menu_edit
, menu_popup
, (gchar
**) c_includes_cppstdlib
, _("C++ Standard Library"));
691 insert_include_items(menu_edit
, menu_popup
, (gchar
**) c_includes_stl
, _("C++ STL"));
695 static void insert_date(GeanyDocument
*doc
, gint pos
, const gchar
*date_style
)
697 const gchar
*format
= NULL
;
700 g_return_if_fail(doc
!= NULL
);
701 g_return_if_fail(pos
== -1 || pos
>= 0);
704 pos
= sci_get_current_position(doc
->editor
->sci
);
706 /* set default value */
707 if (utils_str_equal("", ui_prefs
.custom_date_format
))
709 g_free(ui_prefs
.custom_date_format
);
710 ui_prefs
.custom_date_format
= g_strdup("%d.%m.%Y");
713 if (utils_str_equal(_("dd.mm.yyyy"), date_style
))
715 else if (utils_str_equal(_("mm.dd.yyyy"), date_style
))
717 else if (utils_str_equal(_("yyyy/mm/dd"), date_style
))
719 else if (utils_str_equal(_("dd.mm.yyyy hh:mm:ss"), date_style
))
720 format
= "%d.%m.%Y %H:%M:%S";
721 else if (utils_str_equal(_("mm.dd.yyyy hh:mm:ss"), date_style
))
722 format
= "%m.%d.%Y %H:%M:%S";
723 else if (utils_str_equal(_("yyyy/mm/dd hh:mm:ss"), date_style
))
724 format
= "%Y/%m/%d %H:%M:%S";
725 else if (utils_str_equal(_("_Use Custom Date Format"), date_style
))
726 format
= ui_prefs
.custom_date_format
;
729 gchar
*str
= dialogs_show_input(_("Custom Date Format"), GTK_WINDOW(main_widgets
.window
),
730 _("Enter here a custom date and time format. "
731 "For a list of available conversion specifiers see https://docs.gtk.org/glib/method.DateTime.format.html."),
732 ui_prefs
.custom_date_format
);
734 SETPTR(ui_prefs
.custom_date_format
, str
);
738 time_str
= utils_get_date_time(format
, NULL
);
739 if (time_str
!= NULL
)
741 sci_start_undo_action(doc
->editor
->sci
);
742 sci_insert_text(doc
->editor
->sci
, pos
, time_str
);
743 sci_goto_pos(doc
->editor
->sci
, pos
+ strlen(time_str
), FALSE
);
744 sci_end_undo_action(doc
->editor
->sci
);
750 ui_set_statusbar(TRUE
,
751 _("Date format string could not be converted (possibly too long)."));
756 static void on_popup_insert_date_activate(GtkMenuItem
*menuitem
, gpointer user_data
)
758 insert_date(document_get_current(), editor_info
.click_pos
, user_data
);
762 static void on_menu_insert_date_activate(GtkMenuItem
*menuitem
, gpointer user_data
)
764 insert_date(document_get_current(), -1, user_data
);
768 static void insert_date_items(GtkMenu
*me
, GtkMenu
*mp
, gchar
*label
)
772 item
= gtk_menu_item_new_with_mnemonic(label
);
773 gtk_container_add(GTK_CONTAINER(me
), item
);
774 gtk_widget_show(item
);
775 g_signal_connect(item
, "activate", G_CALLBACK(on_menu_insert_date_activate
), label
);
777 item
= gtk_menu_item_new_with_mnemonic(label
);
778 gtk_container_add(GTK_CONTAINER(mp
), item
);
779 gtk_widget_show(item
);
780 g_signal_connect(item
, "activate", G_CALLBACK(on_popup_insert_date_activate
), label
);
784 void ui_create_insert_date_menu_items(void)
786 GtkMenu
*menu_edit
= GTK_MENU(ui_lookup_widget(main_widgets
.window
, "insert_date1_menu"));
787 GtkMenu
*menu_popup
= GTK_MENU(ui_lookup_widget(main_widgets
.editor_menu
, "insert_date2_menu"));
791 insert_date_items(menu_edit
, menu_popup
, _("dd.mm.yyyy"));
792 insert_date_items(menu_edit
, menu_popup
, _("mm.dd.yyyy"));
793 insert_date_items(menu_edit
, menu_popup
, _("yyyy/mm/dd"));
795 item
= gtk_separator_menu_item_new();
796 gtk_container_add(GTK_CONTAINER(menu_edit
), item
);
797 gtk_widget_show(item
);
798 item
= gtk_separator_menu_item_new();
799 gtk_container_add(GTK_CONTAINER(menu_popup
), item
);
800 gtk_widget_show(item
);
802 insert_date_items(menu_edit
, menu_popup
, _("dd.mm.yyyy hh:mm:ss"));
803 insert_date_items(menu_edit
, menu_popup
, _("mm.dd.yyyy hh:mm:ss"));
804 insert_date_items(menu_edit
, menu_popup
, _("yyyy/mm/dd hh:mm:ss"));
806 item
= gtk_separator_menu_item_new();
807 gtk_container_add(GTK_CONTAINER(menu_edit
), item
);
808 gtk_widget_show(item
);
809 item
= gtk_separator_menu_item_new();
810 gtk_container_add(GTK_CONTAINER(menu_popup
), item
);
811 gtk_widget_show(item
);
813 str
= _("_Use Custom Date Format");
814 item
= gtk_menu_item_new_with_mnemonic(str
);
815 gtk_container_add(GTK_CONTAINER(menu_edit
), item
);
816 gtk_widget_show(item
);
817 g_signal_connect(item
, "activate", G_CALLBACK(on_menu_insert_date_activate
), str
);
818 ui_hookup_widget(main_widgets
.window
, item
, "insert_date_custom1");
820 item
= gtk_menu_item_new_with_mnemonic(str
);
821 gtk_container_add(GTK_CONTAINER(menu_popup
), item
);
822 gtk_widget_show(item
);
823 g_signal_connect(item
, "activate", G_CALLBACK(on_popup_insert_date_activate
), str
);
824 ui_hookup_widget(main_widgets
.editor_menu
, item
, "insert_date_custom2");
826 insert_date_items(menu_edit
, menu_popup
, _("_Set Custom Date Format"));
830 void ui_save_buttons_toggle(gboolean enable
)
833 gboolean dirty_tabs
= FALSE
;
835 if (ui_prefs
.allow_always_save
)
836 enable
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets
.notebook
)) > 0;
838 ui_widget_set_sensitive(widgets
.save_buttons
[0], enable
);
839 ui_widget_set_sensitive(widgets
.save_buttons
[1], enable
);
841 /* save all menu item and tool button */
842 for (i
= 0; i
< documents_array
->len
; i
++)
844 /* check whether there are files where changes were made and if there are some,
845 * we need the save all button / item */
846 if (documents
[i
]->is_valid
&& documents
[i
]->changed
)
853 ui_widget_set_sensitive(widgets
.save_buttons
[2], dirty_tabs
);
854 ui_widget_set_sensitive(widgets
.save_buttons
[3], dirty_tabs
);
858 #define add_doc_widget(widget_name) \
859 g_ptr_array_add(widgets.document_buttons, ui_lookup_widget(main_widgets.window, widget_name))
861 #define add_doc_toolitem(widget_name) \
862 g_ptr_array_add(widgets.document_buttons, toolbar_get_action_by_name(widget_name))
864 static void init_document_widgets(void)
866 widgets
.document_buttons
= g_ptr_array_new();
868 /* Cache the document-sensitive widgets so we don't have to keep looking them up
869 * when using ui_document_buttons_update(). */
870 add_doc_widget("menu_close1");
871 add_doc_widget("close_other_documents1");
872 add_doc_widget("menu_change_font1");
873 add_doc_widget("menu_close_all1");
874 add_doc_widget("menu_save1");
875 add_doc_widget("menu_save_all1");
876 add_doc_widget("menu_save_as1");
877 add_doc_widget("menu_count_words1");
878 add_doc_widget("menu_build1");
879 add_doc_widget("add_comments1");
880 add_doc_widget("menu_paste1");
881 add_doc_widget("menu_undo2");
882 add_doc_widget("properties1");
883 add_doc_widget("menu_reload1");
884 add_doc_widget("menu_document1");
885 add_doc_widget("menu_choose_color1");
886 add_doc_widget("menu_color_schemes");
887 add_doc_widget("menu_markers_margin1");
888 add_doc_widget("menu_linenumber_margin1");
889 add_doc_widget("menu_show_white_space1");
890 add_doc_widget("menu_show_line_endings1");
891 add_doc_widget("menu_show_indentation_guides1");
892 add_doc_widget("menu_zoom_in1");
893 add_doc_widget("menu_zoom_out1");
894 add_doc_widget("normal_size1");
895 add_doc_widget("treeview6");
896 add_doc_widget("print1");
897 add_doc_widget("menu_reload_as1");
898 add_doc_widget("menu_select_all1");
899 add_doc_widget("insert_date1");
900 add_doc_widget("insert_alternative_white_space1");
901 add_doc_widget("menu_format1");
902 add_doc_widget("commands2");
903 add_doc_widget("menu_open_selected_file1");
904 add_doc_widget("page_setup1");
905 add_doc_widget("find1");
906 add_doc_widget("find_next1");
907 add_doc_widget("find_previous1");
908 add_doc_widget("go_to_next_marker1");
909 add_doc_widget("go_to_previous_marker1");
910 add_doc_widget("replace1");
911 add_doc_widget("find_nextsel1");
912 add_doc_widget("find_prevsel1");
913 add_doc_widget("find_usage1");
914 add_doc_widget("find_document_usage1");
915 add_doc_widget("mark_all1");
916 add_doc_widget("go_to_line1");
917 add_doc_widget("goto_tag_definition1");
918 add_doc_widget("goto_tag_declaration1");
919 add_doc_widget("reset_indentation1");
920 add_doc_toolitem("Close");
921 add_doc_toolitem("CloseAll");
922 add_doc_toolitem("Search");
923 add_doc_toolitem("SearchEntry");
924 add_doc_toolitem("ZoomIn");
925 add_doc_toolitem("ZoomOut");
926 add_doc_toolitem("Indent");
927 add_doc_toolitem("UnIndent");
928 add_doc_toolitem("Cut");
929 add_doc_toolitem("Copy");
930 add_doc_toolitem("Paste");
931 add_doc_toolitem("Delete");
932 add_doc_toolitem("Save");
933 add_doc_toolitem("SaveAs");
934 add_doc_toolitem("SaveAll");
935 add_doc_toolitem("Compile");
936 add_doc_toolitem("Run");
937 add_doc_toolitem("Reload");
938 add_doc_toolitem("Color");
939 add_doc_toolitem("Goto");
940 add_doc_toolitem("GotoEntry");
941 add_doc_toolitem("Replace");
942 add_doc_toolitem("Print");
946 void ui_document_buttons_update(void)
949 gboolean enable
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets
.notebook
)) > 0;
951 for (i
= 0; i
< widgets
.document_buttons
->len
; i
++)
953 GtkWidget
*widget
= g_ptr_array_index(widgets
.document_buttons
, i
);
954 if (GTK_IS_ACTION(widget
))
955 gtk_action_set_sensitive(GTK_ACTION(widget
), enable
);
957 ui_widget_set_sensitive(widget
, enable
);
962 static void on_doc_sensitive_widget_destroy(GtkWidget
*widget
, G_GNUC_UNUSED gpointer user_data
)
964 g_ptr_array_remove_fast(widgets
.document_buttons
, widget
);
968 /** Adds a widget to the list of widgets that should be set sensitive/insensitive
969 * when some documents are present/no documents are open.
970 * It will be removed when the widget is destroyed.
971 * @param widget The widget to add.
976 void ui_add_document_sensitive(GtkWidget
*widget
)
978 gboolean enable
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets
.notebook
)) > 0;
980 ui_widget_set_sensitive(widget
, enable
);
982 g_ptr_array_add(widgets
.document_buttons
, widget
);
983 g_signal_connect(widget
, "destroy", G_CALLBACK(on_doc_sensitive_widget_destroy
), NULL
);
987 void ui_widget_show_hide(GtkWidget
*widget
, gboolean show
)
991 gtk_widget_show(widget
);
995 gtk_widget_hide(widget
);
1000 void ui_sidebar_show_hide(void)
1004 /* check that there are no other notebook pages before hiding the sidebar completely
1005 * other pages could be e.g. the file browser plugin */
1006 if (! interface_prefs
.sidebar_openfiles_visible
&& ! interface_prefs
.sidebar_symbol_visible
&&
1007 gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets
.sidebar_notebook
)) <= 2)
1009 ui_prefs
.sidebar_visible
= FALSE
;
1012 widget
= ui_lookup_widget(main_widgets
.window
, "menu_show_sidebar1");
1013 if (ui_prefs
.sidebar_visible
!= gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget
)))
1015 ignore_callback
= TRUE
;
1016 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget
), ui_prefs
.sidebar_visible
);
1017 ignore_callback
= FALSE
;
1020 ui_widget_show_hide(main_widgets
.sidebar_notebook
, ui_prefs
.sidebar_visible
);
1022 ui_widget_show_hide(gtk_notebook_get_nth_page(
1023 GTK_NOTEBOOK(main_widgets
.sidebar_notebook
), 0), interface_prefs
.sidebar_symbol_visible
);
1024 ui_widget_show_hide(gtk_notebook_get_nth_page(
1025 GTK_NOTEBOOK(main_widgets
.sidebar_notebook
), 1), interface_prefs
.sidebar_openfiles_visible
);
1029 void ui_document_show_hide(GeanyDocument
*doc
)
1031 const gchar
*widget_name
;
1033 const GeanyIndentPrefs
*iprefs
;
1035 g_return_if_fail(doc
== NULL
|| doc
->is_valid
);
1038 doc
= document_get_current();
1043 ignore_callback
= TRUE
;
1045 gtk_check_menu_item_set_active(
1046 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets
.window
, "menu_line_wrapping1")),
1047 doc
->editor
->line_wrapping
);
1049 gtk_check_menu_item_set_active(
1050 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets
.window
, "line_breaking1")),
1051 doc
->editor
->line_breaking
);
1053 iprefs
= editor_get_indent_prefs(doc
->editor
);
1055 item
= ui_lookup_widget(main_widgets
.window
, "menu_use_auto_indentation1");
1056 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
), doc
->editor
->auto_indent
);
1058 switch (iprefs
->type
)
1060 case GEANY_INDENT_TYPE_SPACES
:
1061 widget_name
= "spaces1"; break;
1062 case GEANY_INDENT_TYPE_TABS
:
1063 widget_name
= "tabs1"; break;
1064 case GEANY_INDENT_TYPE_BOTH
:
1066 widget_name
= "tabs_and_spaces1"; break;
1068 item
= ui_lookup_widget(main_widgets
.window
, widget_name
);
1069 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
), TRUE
);
1071 if (iprefs
->width
>= 1 && iprefs
->width
<= 8)
1075 name
= g_strdup_printf("indent_width_%d", iprefs
->width
);
1076 item
= ui_lookup_widget(main_widgets
.window
, name
);
1077 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
), TRUE
);
1081 gtk_check_menu_item_set_active(
1082 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets
.window
, "set_file_readonly1")),
1085 item
= ui_lookup_widget(main_widgets
.window
, "menu_write_unicode_bom1");
1086 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
), doc
->has_bom
);
1087 ui_widget_set_sensitive(item
, encodings_is_unicode_charset(doc
->encoding
));
1089 switch (sci_get_eol_mode(doc
->editor
->sci
))
1091 case SC_EOL_CR
: widget_name
= "cr"; break;
1092 case SC_EOL_LF
: widget_name
= "lf"; break;
1093 default: widget_name
= "crlf"; break;
1095 gtk_check_menu_item_set_active(
1096 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets
.window
, widget_name
)), TRUE
);
1098 encodings_select_radio_item(doc
->encoding
);
1099 filetypes_select_radio_item(doc
->file_type
);
1101 ignore_callback
= FALSE
;
1105 void ui_set_search_entry_background(GtkWidget
*widget
, gboolean success
)
1107 gtk_widget_set_name(widget
, success
? NULL
: "geany-search-entry-no-match");
1111 static void recent_create_menu(GeanyRecentFiles
*grf
)
1115 len
= MIN(file_prefs
.mru_length
, g_queue_get_length(grf
->recent_queue
));
1116 for (i
= 0; i
< len
; i
++)
1118 /* create menu item for the recent files menu in the menu bar */
1119 const gchar
*filename
= g_queue_peek_nth(grf
->recent_queue
, i
);
1120 GtkWidget
*tmp
= gtk_menu_item_new_with_label(filename
);
1122 gtk_widget_show(tmp
);
1123 gtk_container_add(GTK_CONTAINER(grf
->menubar
), tmp
);
1124 g_signal_connect(tmp
, "activate", G_CALLBACK(grf
->activate_cb
), NULL
);
1125 /* create menu item for the recent files menu in the toolbar */
1126 if (grf
->toolbar
!= NULL
)
1128 tmp
= gtk_menu_item_new_with_label(filename
);
1129 gtk_widget_show(tmp
);
1130 gtk_container_add(GTK_CONTAINER(grf
->toolbar
), tmp
);
1131 g_signal_connect(tmp
, "activate", G_CALLBACK(grf
->activate_cb
), NULL
);
1137 static GeanyRecentFiles
*recent_get_recent_files(void)
1139 static GeanyRecentFiles grf
= { RECENT_FILE_FILE
, NULL
, NULL
, NULL
, NULL
};
1141 if (G_UNLIKELY(grf
.recent_queue
== NULL
))
1143 grf
.recent_queue
= ui_prefs
.recent_queue
;
1144 grf
.menubar
= ui_widgets
.recent_files_menu_menubar
;
1145 grf
.toolbar
= geany_menu_button_action_get_menu(GEANY_MENU_BUTTON_ACTION(
1146 toolbar_get_action_by_name("Open")));
1147 grf
.activate_cb
= recent_file_activate_cb
;
1153 static GeanyRecentFiles
*recent_get_recent_projects(void)
1155 static GeanyRecentFiles grf
= { RECENT_FILE_PROJECT
, NULL
, NULL
, NULL
, NULL
};
1157 if (G_UNLIKELY(grf
.recent_queue
== NULL
))
1159 grf
.recent_queue
= ui_prefs
.recent_projects_queue
;
1160 grf
.menubar
= ui_widgets
.recent_projects_menu_menubar
;
1162 grf
.activate_cb
= recent_project_activate_cb
;
1168 void ui_create_recent_menus(void)
1170 recent_create_menu(recent_get_recent_files());
1171 recent_create_menu(recent_get_recent_projects());
1175 static void recent_file_activate_cb(GtkMenuItem
*menuitem
, G_GNUC_UNUSED gpointer user_data
)
1177 gchar
*utf8_filename
= ui_menu_item_get_text(menuitem
);
1178 gchar
*locale_filename
= utils_get_locale_from_utf8(utf8_filename
);
1180 if (document_open_file(locale_filename
, FALSE
, NULL
, NULL
) != NULL
)
1181 recent_file_loaded(utf8_filename
, recent_get_recent_files());
1183 g_free(locale_filename
);
1184 g_free(utf8_filename
);
1188 static void recent_project_activate_cb(GtkMenuItem
*menuitem
, G_GNUC_UNUSED gpointer user_data
)
1190 gchar
*utf8_filename
= ui_menu_item_get_text(menuitem
);
1191 gchar
*locale_filename
= utils_get_locale_from_utf8(utf8_filename
);
1193 if (app
->project
&& !project_close(FALSE
)) {}
1194 else if (project_load_file_with_session(locale_filename
))
1195 recent_file_loaded(utf8_filename
, recent_get_recent_projects());
1197 g_free(locale_filename
);
1198 g_free(utf8_filename
);
1202 static void add_recent_file(const gchar
*utf8_filename
, GeanyRecentFiles
*grf
,
1203 const GtkRecentData
*rdata
)
1205 if (g_queue_find_custom(grf
->recent_queue
, utf8_filename
, (GCompareFunc
) strcmp
) == NULL
)
1208 if (grf
->type
== RECENT_FILE_FILE
&& rdata
)
1210 GtkRecentManager
*manager
= gtk_recent_manager_get_default();
1211 gchar
*uri
= g_filename_to_uri(utf8_filename
, NULL
, NULL
);
1214 gtk_recent_manager_add_full(manager
, uri
, rdata
);
1219 g_queue_push_head(grf
->recent_queue
, g_strdup(utf8_filename
));
1220 if (g_queue_get_length(grf
->recent_queue
) > file_prefs
.mru_length
)
1222 g_free(g_queue_pop_tail(grf
->recent_queue
));
1224 update_recent_menu(grf
);
1226 /* filename already in recent list */
1228 recent_file_loaded(utf8_filename
, grf
);
1232 void ui_add_recent_document(GeanyDocument
*doc
)
1234 /* what are the groups for actually? */
1235 static const gchar
*groups
[2] = {
1239 GtkRecentData rdata
;
1241 /* Prepare the data for gtk_recent_manager_add_full() */
1242 rdata
.display_name
= NULL
;
1243 rdata
.description
= NULL
;
1244 rdata
.mime_type
= doc
->file_type
->mime_type
;
1245 /* if we ain't got no mime-type, fallback to plain text */
1246 if (! rdata
.mime_type
)
1247 rdata
.mime_type
= (gchar
*) "text/plain";
1248 rdata
.app_name
= (gchar
*) "geany";
1249 rdata
.app_exec
= (gchar
*) "geany %u";
1250 rdata
.groups
= (gchar
**) groups
;
1251 rdata
.is_private
= FALSE
;
1253 add_recent_file(doc
->file_name
, recent_get_recent_files(), &rdata
);
1257 void ui_add_recent_project_file(const gchar
*utf8_filename
)
1259 add_recent_file(utf8_filename
, recent_get_recent_projects(), NULL
);
1263 /* Returns: newly allocated string with the UTF-8 menu text. */
1264 gchar
*ui_menu_item_get_text(GtkMenuItem
*menu_item
)
1266 const gchar
*text
= NULL
;
1268 if (gtk_bin_get_child(GTK_BIN(menu_item
)))
1270 GtkWidget
*child
= gtk_bin_get_child(GTK_BIN(menu_item
));
1272 if (GTK_IS_LABEL(child
))
1273 text
= gtk_label_get_text(GTK_LABEL(child
));
1275 /* GTK owns text so it's much safer to return a copy of it in case the memory is reallocated */
1276 return g_strdup(text
);
1280 static gint
find_recent_file_item(gconstpointer list_data
, gconstpointer user_data
)
1282 gchar
*menu_text
= ui_menu_item_get_text(GTK_MENU_ITEM(list_data
));
1285 if (utils_str_equal(menu_text
, user_data
))
1295 /* update the project menu item's sensitivity */
1296 void ui_update_recent_project_menu(void)
1298 GeanyRecentFiles
*grf
= recent_get_recent_projects();
1299 GList
*children
, *item
;
1301 /* only need to update the menubar menu, the project doesn't have a toolbar item */
1302 children
= gtk_container_get_children(GTK_CONTAINER(grf
->menubar
));
1303 for (item
= children
; item
; item
= item
->next
)
1305 gboolean sensitive
= TRUE
;
1309 const gchar
*filename
= gtk_menu_item_get_label(item
->data
);
1310 sensitive
= g_strcmp0(app
->project
->file_name
, filename
) != 0;
1312 gtk_widget_set_sensitive(item
->data
, sensitive
);
1314 g_list_free(children
);
1318 /* Use instead of gtk_menu_reorder_child() to update the menubar properly on OS X */
1319 static void menu_reorder_child(GtkMenu
*menu
, GtkWidget
*child
, gint position
)
1321 gtk_menu_reorder_child(menu
, child
, position
);
1322 #ifdef MAC_INTEGRATION
1323 /* On OS X GtkMenuBar is kept in sync with the native OS X menubar using
1324 * signals. Unfortunately gtk_menu_reorder_child() doesn't emit anything
1325 * so we have to update the OS X menubar manually. */
1326 gtkosx_application_sync_menubar(gtkosx_application_get());
1331 static void add_recent_file_menu_item(const gchar
*utf8_filename
, GeanyRecentFiles
*grf
, GtkWidget
*menu
)
1333 GtkWidget
*child
= gtk_menu_item_new_with_label(utf8_filename
);
1335 gtk_widget_show(child
);
1336 if (menu
!= grf
->toolbar
)
1337 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu
), child
);
1340 /* this is a bit ugly, but we need to use gtk_container_add(). Using
1341 * gtk_menu_shell_prepend() doesn't emit GtkContainer's "add" signal
1342 * which we need in GeanyMenubuttonAction */
1343 gtk_container_add(GTK_CONTAINER(menu
), child
);
1344 menu_reorder_child(GTK_MENU(menu
), child
, 0);
1346 g_signal_connect(child
, "activate", G_CALLBACK(grf
->activate_cb
), NULL
);
1350 static void recent_file_loaded(const gchar
*utf8_filename
, GeanyRecentFiles
*grf
)
1353 GtkWidget
*parents
[] = { grf
->menubar
, grf
->toolbar
};
1356 /* first reorder the queue */
1357 item
= g_queue_find_custom(grf
->recent_queue
, utf8_filename
, (GCompareFunc
) strcmp
);
1358 g_return_if_fail(item
!= NULL
);
1360 g_queue_unlink(grf
->recent_queue
, item
);
1361 g_queue_push_head_link(grf
->recent_queue
, item
);
1363 for (i
= 0; i
< G_N_ELEMENTS(parents
); i
++)
1370 children
= gtk_container_get_children(GTK_CONTAINER(parents
[i
]));
1371 item
= g_list_find_custom(children
, utf8_filename
, (GCompareFunc
) find_recent_file_item
);
1372 /* either reorder or prepend a new one */
1374 menu_reorder_child(GTK_MENU(parents
[i
]), item
->data
, 0);
1376 add_recent_file_menu_item(utf8_filename
, grf
, parents
[i
]);
1377 g_list_free(children
);
1380 if (grf
->type
== RECENT_FILE_PROJECT
)
1381 ui_update_recent_project_menu();
1385 static void update_recent_menu(GeanyRecentFiles
*grf
)
1388 GtkWidget
*parents
[] = { grf
->menubar
, grf
->toolbar
};
1391 filename
= g_queue_peek_head(grf
->recent_queue
);
1393 for (i
= 0; i
< G_N_ELEMENTS(parents
); i
++)
1400 /* clean the MRU list before adding an item */
1401 children
= gtk_container_get_children(GTK_CONTAINER(parents
[i
]));
1402 if (g_list_length(children
) > file_prefs
.mru_length
- 1)
1404 GList
*item
= g_list_nth(children
, file_prefs
.mru_length
- 1);
1405 while (item
!= NULL
)
1407 if (GTK_IS_MENU_ITEM(item
->data
))
1408 gtk_widget_destroy(GTK_WIDGET(item
->data
));
1409 item
= g_list_next(item
);
1412 g_list_free(children
);
1414 /* create the new item */
1415 add_recent_file_menu_item(filename
, grf
, parents
[i
]);
1418 if (grf
->type
== RECENT_FILE_PROJECT
)
1419 ui_update_recent_project_menu();
1423 void ui_toggle_editor_features(GeanyUIEditorFeatures feature
)
1427 foreach_document (i
)
1429 GeanyDocument
*doc
= documents
[i
];
1433 case GEANY_EDITOR_SHOW_MARKERS_MARGIN
:
1434 sci_set_symbol_margin(doc
->editor
->sci
, editor_prefs
.show_markers_margin
);
1436 case GEANY_EDITOR_SHOW_LINE_NUMBERS
:
1437 sci_set_line_numbers(doc
->editor
->sci
, editor_prefs
.show_linenumber_margin
);
1439 case GEANY_EDITOR_SHOW_WHITE_SPACE
:
1440 sci_set_visible_white_spaces(doc
->editor
->sci
, editor_prefs
.show_white_space
);
1442 case GEANY_EDITOR_SHOW_LINE_ENDINGS
:
1443 sci_set_visible_eols(doc
->editor
->sci
, editor_prefs
.show_line_endings
);
1445 case GEANY_EDITOR_SHOW_INDENTATION_GUIDES
:
1446 editor_set_indentation_guides(doc
->editor
);
1453 void ui_update_view_editor_menu_items(void)
1455 ignore_callback
= TRUE
;
1456 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets
.window
, "menu_markers_margin1")), editor_prefs
.show_markers_margin
);
1457 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets
.window
, "menu_linenumber_margin1")), editor_prefs
.show_linenumber_margin
);
1458 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets
.window
, "menu_show_white_space1")), editor_prefs
.show_white_space
);
1459 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets
.window
, "menu_show_line_endings1")), editor_prefs
.show_line_endings
);
1460 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets
.window
, "menu_show_indentation_guides1")), editor_prefs
.show_indent_guide
);
1461 ignore_callback
= FALSE
;
1465 /** Creates a GNOME HIG-style frame (with no border and indented child alignment).
1466 * @param label_text The label text.
1467 * @param alignment An address to store the alignment widget pointer.
1469 * @return @transfer{floating} The frame widget, setting the alignment container for
1470 * packing child widgets.
1472 * @deprecated 1.29: Use GTK API directly
1475 GtkWidget
*ui_frame_new_with_alignment(const gchar
*label_text
, GtkWidget
**alignment
)
1477 GtkWidget
*label
, *align
;
1478 GtkWidget
*frame
= gtk_frame_new(NULL
);
1480 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_NONE
);
1482 align
= gtk_alignment_new(0.5, 0.5, 1, 1);
1483 gtk_container_add(GTK_CONTAINER(frame
), align
);
1484 gtk_alignment_set_padding(GTK_ALIGNMENT(align
), 0, 0, 12, 0);
1486 label
= ui_label_new_bold(label_text
);
1487 gtk_frame_set_label_widget(GTK_FRAME(frame
), label
);
1494 /** Makes a fixed border for dialogs without increasing the button box border.
1495 * @param dialog The parent container for the @c GtkVBox.
1497 * @return @transfer{none} The packed @c GtkVBox. */
1499 GtkWidget
*ui_dialog_vbox_new(GtkDialog
*dialog
)
1501 GtkWidget
*vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 12); /* need child vbox to set a separate border. */
1503 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 6);
1504 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog
))), vbox
, TRUE
, TRUE
, 0);
1509 /* Reorders a dialog's buttons
1510 * @param dialog A dialog
1511 * @param response First response ID to reorder
1512 * @param ... more response IDs, terminated by -1
1514 * Like gtk_dialog_set_alternative_button_order(), but reorders the default
1515 * buttons layout, not the alternative one. This is useful if you e.g. added a
1516 * button to a dialog which already had some and need yours not to be on the
1519 /* Heavily based on gtk_dialog_set_alternative_button_order().
1520 * This relies on the action area to be a GtkBox, but although not documented
1521 * the API expose it to be a GtkHButtonBox though GtkBuilder, so it should be
1523 void ui_dialog_set_primary_button_order(GtkDialog
*dialog
, gint response
, ...)
1526 GtkWidget
*action_area
= gtk_dialog_get_action_area(dialog
);
1529 va_start(ap
, response
);
1530 for (position
= 0; response
!= -1; position
++)
1532 GtkWidget
*child
= gtk_dialog_get_widget_for_response(dialog
, response
);
1534 gtk_box_reorder_child(GTK_BOX(action_area
), child
, position
);
1536 g_warning("%s: no child button with response id %d.", G_STRFUNC
, response
);
1538 response
= va_arg(ap
, gint
);
1544 /** Creates a @c GtkButton with custom text and a stock image similar to
1545 * @c gtk_button_new_from_stock().
1546 * @param stock_id A @c GTK_STOCK_NAME string.
1547 * @param text Button label text, can include mnemonics.
1549 * @return @transfer{floating} The new @c GtkButton.
1552 GtkWidget
*ui_button_new_with_image(const gchar
*stock_id
, const gchar
*text
)
1554 GtkWidget
*image
, *button
;
1556 button
= gtk_button_new_with_mnemonic(text
);
1557 gtk_widget_show(button
);
1558 image
= gtk_image_new_from_stock(stock_id
, GTK_ICON_SIZE_BUTTON
);
1559 gtk_button_set_image(GTK_BUTTON(button
), image
);
1560 /* note: image is shown by gtk */
1565 /** Creates a @c GtkImageMenuItem with a stock image and a custom label.
1566 * @param stock_id Stock image ID, e.g. @c GTK_STOCK_OPEN.
1567 * @param label Menu item label, can include mnemonics.
1568 * @return @transfer{floating} The new @c GtkImageMenuItem.
1574 ui_image_menu_item_new(const gchar
*stock_id
, const gchar
*label
)
1576 GtkWidget
*item
= gtk_image_menu_item_new_with_mnemonic(label
);
1577 GtkWidget
*image
= gtk_image_new_from_stock(stock_id
, GTK_ICON_SIZE_MENU
);
1579 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), image
);
1580 gtk_widget_show(image
);
1585 static void entry_clear_icon_release_cb(GtkEntry
*entry
, gint icon_pos
,
1586 GdkEvent
*event
, gpointer data
)
1588 if (event
->button
.button
== 1 && icon_pos
== 1)
1590 gtk_entry_set_text(entry
, "");
1591 gtk_widget_grab_focus(GTK_WIDGET(entry
));
1596 /** Adds a small clear icon to the right end of the passed @a entry.
1597 * A callback to clear the contents of the GtkEntry is automatically added.
1599 * @param entry The GtkEntry object to which the icon should be attached.
1604 void ui_entry_add_clear_icon(GtkEntry
*entry
)
1606 g_object_set(entry
, "secondary-icon-stock", GTK_STOCK_CLEAR
,
1607 "secondary-icon-activatable", TRUE
, NULL
);
1608 g_signal_connect(entry
, "icon-release", G_CALLBACK(entry_clear_icon_release_cb
), NULL
);
1612 /* Adds a :activate-backwards signal emitted by default when <Shift>Return is pressed */
1613 void ui_entry_add_activate_backward_signal(GtkEntry
*entry
)
1615 static gboolean installed
= FALSE
;
1617 g_return_if_fail(GTK_IS_ENTRY(entry
));
1619 if (G_UNLIKELY(! installed
))
1621 GtkBindingSet
*binding_set
;
1625 /* try to handle the unexpected case where GTK would already have installed the signal */
1626 if (g_signal_lookup("activate-backward", G_TYPE_FROM_INSTANCE(entry
)))
1628 g_warning("Signal GtkEntry:activate-backward is unexpectedly already installed");
1632 g_signal_new("activate-backward", G_TYPE_FROM_INSTANCE(entry
),
1633 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
, 0, NULL
, NULL
,
1634 g_cclosure_marshal_VOID__VOID
, G_TYPE_NONE
, 0);
1635 binding_set
= gtk_binding_set_by_class(GTK_ENTRY_GET_CLASS(entry
));
1636 gtk_binding_entry_add_signal(binding_set
, GDK_KEY_Return
, GDK_SHIFT_MASK
, "activate-backward", 0);
1641 static void add_to_size_group(GtkWidget
*widget
, gpointer size_group
)
1643 g_return_if_fail(GTK_IS_SIZE_GROUP(size_group
));
1644 gtk_size_group_add_widget(GTK_SIZE_GROUP(size_group
), widget
);
1648 /* Copies the spacing and layout of the master GtkHButtonBox and synchronises
1649 * the width of each button box's children.
1650 * Should be called after all child widgets have been packed. */
1651 void ui_hbutton_box_copy_layout(GtkButtonBox
*master
, GtkButtonBox
*copy
)
1653 GtkSizeGroup
*size_group
;
1655 gtk_box_set_spacing(GTK_BOX(copy
), 10);
1656 gtk_button_box_set_layout(copy
, gtk_button_box_get_layout(master
));
1658 /* now we need to put the widest widget from each button box in a size group,
1659 * but we don't know the width before they are drawn, and for different label
1660 * translations the widest widget can vary, so we just add all widgets. */
1661 size_group
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
1662 gtk_container_foreach(GTK_CONTAINER(master
), add_to_size_group
, size_group
);
1663 gtk_container_foreach(GTK_CONTAINER(copy
), add_to_size_group
, size_group
);
1664 g_object_unref(size_group
);
1668 static gboolean
tree_model_find_text(GtkTreeModel
*model
,
1669 GtkTreeIter
*iter
, gint column
, const gchar
*text
)
1672 gboolean found
= FALSE
;
1674 if (gtk_tree_model_get_iter_first(model
, iter
))
1678 gtk_tree_model_get(model
, iter
, 0, &combo_text
, -1);
1679 found
= utils_str_equal(combo_text
, text
);
1685 while (gtk_tree_model_iter_next(model
, iter
));
1691 /** Prepends @a text to the drop down list, removing a duplicate element in
1692 * the list if found. Also ensures there are <= @a history_len elements.
1693 * @param combo_entry .
1694 * @param text @nullable Text to add, or @c NULL for current entry text.
1695 * @param history_len Max number of items, or @c 0 for default. */
1697 void ui_combo_box_add_to_history(GtkComboBoxText
*combo_entry
,
1698 const gchar
*text
, gint history_len
)
1700 GtkComboBox
*combo
= GTK_COMBO_BOX(combo_entry
);
1701 GtkTreeModel
*model
;
1705 if (history_len
<= 0)
1708 text
= gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo
))));
1710 model
= gtk_combo_box_get_model(combo
);
1712 if (tree_model_find_text(model
, &iter
, 0, text
))
1714 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
1716 gtk_combo_box_text_prepend_text(combo_entry
, text
);
1719 path
= gtk_tree_path_new_from_indices(history_len
, -1);
1720 if (gtk_tree_model_get_iter(model
, &iter
, path
))
1722 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
1724 gtk_tree_path_free(path
);
1728 /* Same as gtk_combo_box_text_prepend_text(), except that text is only prepended if it not already
1729 * exists in the combo's model. */
1730 void ui_combo_box_prepend_text_once(GtkComboBoxText
*combo
, const gchar
*text
)
1732 GtkTreeModel
*model
;
1735 model
= gtk_combo_box_get_model(GTK_COMBO_BOX(combo
));
1736 if (tree_model_find_text(model
, &iter
, 0, text
))
1737 return; /* don't prepend duplicate */
1739 gtk_combo_box_text_prepend_text(combo
, text
);
1743 /* Changes the color of the notebook tab text and open files items according to
1744 * document status. */
1745 void ui_update_tab_status(GeanyDocument
*doc
)
1747 gtk_widget_set_name(doc
->priv
->tab_label
, document_get_status_widget_class(doc
));
1749 sidebar_openfiles_update(doc
);
1753 static gboolean
tree_model_iter_get_next(GtkTreeModel
*model
, GtkTreeIter
*iter
,
1760 return gtk_tree_model_iter_next(model
, iter
);
1762 path
= gtk_tree_model_get_path(model
, iter
);
1763 result
= gtk_tree_path_prev(path
) && gtk_tree_model_get_iter(model
, iter
, path
);
1764 gtk_tree_path_free(path
);
1769 /* note: the while loop might be more efficient when searching upwards if it
1770 * used tree paths instead of tree iters, but in practice it probably doesn't matter much. */
1771 static gboolean
tree_view_find(GtkTreeView
*treeview
, TVMatchCallback cb
, gboolean down
)
1773 GtkTreeSelection
*treesel
;
1775 GtkTreeModel
*model
;
1777 treesel
= gtk_tree_view_get_selection(treeview
);
1778 if (gtk_tree_selection_get_selected(treesel
, &model
, &iter
))
1780 /* get the next selected item */
1781 if (! tree_model_iter_get_next(model
, &iter
, down
))
1782 return FALSE
; /* no more items */
1784 else /* no selection */
1786 if (! gtk_tree_model_get_iter_first(model
, &iter
))
1787 return TRUE
; /* no items */
1791 gtk_tree_selection_select_iter(treesel
, &iter
);
1793 break; /* found next message */
1795 if (! tree_model_iter_get_next(model
, &iter
, down
))
1796 return FALSE
; /* no more items */
1798 /* scroll item in view */
1799 if (ui_prefs
.msgwindow_visible
)
1801 GtkTreePath
*path
= gtk_tree_model_get_path(
1802 gtk_tree_view_get_model(treeview
), &iter
);
1804 gtk_tree_view_scroll_to_cell(treeview
, path
, NULL
, TRUE
, 0.5, 0.5);
1805 gtk_tree_path_free(path
);
1811 /* Returns FALSE if the treeview has items but no matching next item. */
1812 gboolean
ui_tree_view_find_next(GtkTreeView
*treeview
, TVMatchCallback cb
)
1814 return tree_view_find(treeview
, cb
, TRUE
);
1818 /* Returns FALSE if the treeview has items but no matching next item. */
1819 gboolean
ui_tree_view_find_previous(GtkTreeView
*treeview
, TVMatchCallback cb
)
1821 return tree_view_find(treeview
, cb
, FALSE
);
1825 /* Shamelessly stolen from GTK */
1826 static gboolean
ui_tree_view_query_tooltip_cb(GtkWidget
*widget
, gint x
, gint y
,
1827 gboolean keyboard_tip
, GtkTooltip
*tooltip
, gpointer data
)
1829 GValue value
= { 0 };
1830 GValue transformed
= { 0 };
1833 GtkTreeModel
*model
;
1834 GtkTreeView
*tree_view
= GTK_TREE_VIEW(widget
);
1835 gint column
= GPOINTER_TO_INT(data
);
1836 gboolean tootlip_set
= FALSE
;
1838 if (! gtk_tree_view_get_tooltip_context(tree_view
, &x
, &y
, keyboard_tip
, &model
, &path
, &iter
))
1841 gtk_tree_model_get_value(model
, &iter
, column
, &value
);
1843 g_value_init(&transformed
, G_TYPE_STRING
);
1844 if (g_value_transform(&value
, &transformed
) && g_value_get_string(&transformed
))
1846 gtk_tooltip_set_text(tooltip
, g_value_get_string(&transformed
));
1847 gtk_tree_view_set_tooltip_row(tree_view
, tooltip
, path
);
1851 g_value_unset(&transformed
);
1852 g_value_unset(&value
);
1853 gtk_tree_path_free(path
);
1859 /** Adds text tooltips to a tree view.
1861 * This is similar to gtk_tree_view_set_tooltip_column() but considers the column contents to be
1862 * text, not markup -- it uses gtk_tooltip_set_text() rather than gtk_tooltip_set_markup() to set
1863 * the tooltip's value.
1865 * @warning Unlike gtk_tree_view_set_tooltip_column() you currently cannot change or remove the
1866 * tooltip column after it has been added. Trying to do so will probably give funky results.
1868 * @param tree_view The tree view
1869 * @param column The column to get the tooltip from
1871 * @since 1.25 (API 223)
1873 /* Note: @p column is int and not uint both to match gtk_tree_view_set_tooltip_column() signature
1874 * and to allow future support of -1 to unset if ever wanted */
1876 void ui_tree_view_set_tooltip_text_column(GtkTreeView
*tree_view
, gint column
)
1878 g_return_if_fail(column
>= 0);
1879 g_return_if_fail(GTK_IS_TREE_VIEW(tree_view
));
1881 g_signal_connect(tree_view
, "query-tooltip",
1882 G_CALLBACK(ui_tree_view_query_tooltip_cb
), GINT_TO_POINTER(column
));
1883 gtk_widget_set_has_tooltip(GTK_WIDGET(tree_view
), TRUE
);
1888 * Modifies the font of a widget using gtk_widget_modify_font().
1890 * @param widget The widget.
1891 * @param str The font name as expected by pango_font_description_from_string().
1894 void ui_widget_modify_font_from_string(GtkWidget
*widget
, const gchar
*str
)
1896 PangoFontDescription
*pfd
;
1898 pfd
= pango_font_description_from_string(str
);
1899 gtk_widget_modify_font(widget
, pfd
);
1900 pango_font_description_free(pfd
);
1904 /** Creates a @c GtkHBox with @a entry packed into it and an open button which runs a
1905 * file chooser, replacing entry text (if successful) with the path returned from the
1906 * @c GtkFileChooser.
1907 * @note @a entry can be the child of an unparented widget, such as @c GtkComboBoxEntry.
1908 * @param title @nullable The file chooser dialog title, or @c NULL.
1909 * @param action The mode of the file chooser.
1910 * @param entry Can be an unpacked @c GtkEntry, or the child of an unpacked widget,
1911 * such as @c GtkComboBoxEntry.
1913 * @return @transfer{floating} The @c GtkHBox.
1915 /* @see ui_setup_open_button_callback(). */
1917 GtkWidget
*ui_path_box_new(const gchar
*title
, GtkFileChooserAction action
, GtkEntry
*entry
)
1919 GtkWidget
*vbox
, *dirbtn
, *openimg
, *hbox
, *path_entry
, *parent
, *next_parent
;
1921 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
1922 path_entry
= GTK_WIDGET(entry
);
1924 /* prevent path_entry being vertically stretched to the height of dirbtn */
1925 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
1927 parent
= path_entry
;
1928 while ((next_parent
= gtk_widget_get_parent(parent
)) != NULL
)
1929 parent
= next_parent
;
1931 gtk_box_pack_start(GTK_BOX(vbox
), parent
, TRUE
, FALSE
, 0);
1933 dirbtn
= gtk_button_new();
1934 openimg
= gtk_image_new_from_stock(GTK_STOCK_OPEN
, GTK_ICON_SIZE_BUTTON
);
1935 gtk_container_add(GTK_CONTAINER(dirbtn
), openimg
);
1936 ui_setup_open_button_callback(dirbtn
, title
, action
, entry
);
1938 gtk_box_pack_end(GTK_BOX(hbox
), dirbtn
, FALSE
, FALSE
, 0);
1939 gtk_box_pack_end(GTK_BOX(hbox
), vbox
, TRUE
, TRUE
, 0);
1944 static void ui_path_box_open_clicked(GtkButton
*button
, gpointer user_data
);
1947 /* Setup a GtkButton to run a GtkFileChooser, setting entry text if successful.
1948 * title can be NULL.
1949 * action is the file chooser mode to use. */
1950 void ui_setup_open_button_callback(GtkWidget
*open_btn
, const gchar
*title
,
1951 GtkFileChooserAction action
, GtkEntry
*entry
)
1953 GtkWidget
*path_entry
= GTK_WIDGET(entry
);
1956 g_object_set_data_full(G_OBJECT(open_btn
), "title", g_strdup(title
),
1957 (GDestroyNotify
) g_free
);
1958 g_object_set_data(G_OBJECT(open_btn
), "action", GINT_TO_POINTER(action
));
1959 g_signal_connect(open_btn
, "clicked", G_CALLBACK(ui_path_box_open_clicked
), path_entry
);
1964 static gchar
*run_file_chooser(const gchar
*title
, GtkFileChooserAction action
,
1965 const gchar
*utf8_path
)
1967 GtkWidget
*dialog
= gtk_file_chooser_dialog_new(title
,
1968 GTK_WINDOW(main_widgets
.window
), action
,
1969 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
1970 GTK_STOCK_OPEN
, GTK_RESPONSE_OK
, NULL
);
1972 gchar
*ret_path
= NULL
;
1974 gtk_widget_set_name(dialog
, "GeanyDialog");
1975 locale_path
= utils_get_locale_from_utf8(utf8_path
);
1976 if (action
== GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
)
1978 if (g_path_is_absolute(locale_path
) && g_file_test(locale_path
, G_FILE_TEST_IS_DIR
))
1979 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
), locale_path
);
1981 else if (action
== GTK_FILE_CHOOSER_ACTION_OPEN
)
1983 if (g_path_is_absolute(locale_path
))
1984 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog
), locale_path
);
1986 g_free(locale_path
);
1988 if (gtk_dialog_run(GTK_DIALOG(dialog
)) == GTK_RESPONSE_OK
)
1992 dir_locale
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog
));
1993 ret_path
= utils_get_utf8_from_locale(dir_locale
);
1996 gtk_widget_destroy(dialog
);
2002 gchar
*ui_get_project_directory(const gchar
*path
)
2005 const gchar
*title
= _("Select Project Base Path");
2008 utf8_path
= win32_show_folder_dialog(ui_widgets
.prefs_dialog
, title
, path
);
2010 utf8_path
= run_file_chooser(title
, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
, path
);
2017 static void ui_path_box_open_clicked(GtkButton
*button
, gpointer user_data
)
2019 GtkFileChooserAction action
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button
), "action"));
2020 GtkEntry
*entry
= user_data
;
2021 const gchar
*title
= g_object_get_data(G_OBJECT(button
), "title");
2022 gchar
*utf8_path
= NULL
;
2024 /* TODO: extend for other actions */
2025 g_return_if_fail(action
== GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
||
2026 action
== GTK_FILE_CHOOSER_ACTION_OPEN
);
2029 title
= (action
== GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
) ?
2030 _("Select Folder") : _("Select File");
2032 if (action
== GTK_FILE_CHOOSER_ACTION_OPEN
)
2035 utf8_path
= win32_show_file_dialog(GTK_WINDOW(ui_widgets
.prefs_dialog
), title
,
2036 gtk_entry_get_text(GTK_ENTRY(entry
)));
2038 utf8_path
= run_file_chooser(title
, action
, gtk_entry_get_text(GTK_ENTRY(entry
)));
2041 else if (action
== GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
)
2043 gchar
*path
= g_path_get_dirname(gtk_entry_get_text(GTK_ENTRY(entry
)));
2045 utf8_path
= win32_show_folder_dialog(ui_widgets
.prefs_dialog
, title
,
2046 gtk_entry_get_text(GTK_ENTRY(entry
)));
2048 utf8_path
= run_file_chooser(title
, action
, path
);
2053 if (utf8_path
!= NULL
)
2055 gtk_entry_set_text(GTK_ENTRY(entry
), utf8_path
);
2061 void ui_statusbar_showhide(gboolean state
)
2063 /* handle statusbar visibility */
2066 gtk_widget_show(ui_widgets
.statusbar
);
2067 ui_update_statusbar(NULL
, -1);
2070 gtk_widget_hide(ui_widgets
.statusbar
);
2074 /** Packs all @c GtkWidgets passed after the row argument into a table, using
2075 * one widget per cell. The first widget is not expanded as the table grows,
2076 * as this is usually a label.
2078 * @param row The row number of the table.
2081 void ui_table_add_row(GtkTable
*table
, gint row
, ...)
2087 va_start(args
, row
);
2088 for (i
= 0; (widget
= va_arg(args
, GtkWidget
*), widget
!= NULL
); i
++)
2090 gint options
= (i
== 0) ? GTK_FILL
: GTK_EXPAND
| GTK_FILL
;
2092 gtk_table_attach(GTK_TABLE(table
), widget
, i
, i
+ 1, row
, row
+ 1,
2099 /* comment-out all lines that are not already commented out except sections */
2100 static void comment_conf_files(ScintillaObject
*sci
)
2102 gint line
, line_count
;
2104 line_count
= sci_get_line_count(sci
);
2105 for (line
= 0; line
< line_count
; line
++)
2107 gint pos_start
= sci_get_position_from_line(sci
, line
);
2108 gint pos_end
= sci_get_line_end_position(sci
, line
);
2111 for (pos
= pos_start
; pos
< pos_end
; pos
++)
2113 gchar c
= sci_get_char_at(sci
, pos
);
2114 if (c
== '[' || c
== '#')
2118 sci_insert_text(sci
, pos_start
, "#");
2126 static void on_config_file_clicked(GtkWidget
*widget
, gpointer user_data
)
2128 const gchar
*file_name
= user_data
;
2129 GeanyFiletype
*ft
= NULL
;
2131 if (strstr(file_name
, G_DIR_SEPARATOR_S
"filetypes."))
2132 ft
= filetypes
[GEANY_FILETYPES_CONF
];
2134 if (g_file_test(file_name
, G_FILE_TEST_EXISTS
))
2135 document_open_file(file_name
, FALSE
, ft
, NULL
);
2138 gchar
*utf8_filename
= utils_get_utf8_from_locale(file_name
);
2140 gchar
*base_name
= NULL
;
2141 gchar
*global_content
= NULL
;
2142 GeanyDocument
*doc
= NULL
;
2144 /* get the path inside app->configdir - can contain subdirectories */
2145 if (g_str_has_prefix(file_name
, app
->configdir
))
2147 gsize len
= strlen(app
->configdir
);
2148 if (file_name
[len
] == G_DIR_SEPARATOR
)
2149 base_name
= g_strdup(file_name
+ len
+ 1);
2153 base_name
= g_path_get_basename(file_name
);
2155 global_file
= g_build_filename(app
->datadir
, base_name
, NULL
);
2157 /* if the requested file doesn't exist in the user's config dir, try loading the file
2158 * from the global data directory and use its contents for the newly created file */
2159 if (g_file_test(global_file
, G_FILE_TEST_EXISTS
))
2160 g_file_get_contents(global_file
, &global_content
, NULL
, NULL
);
2162 doc
= document_new_file(utf8_filename
, ft
, global_content
);
2165 if (doc
->file_type
->id
== GEANY_FILETYPES_CONF
)
2166 comment_conf_files(doc
->editor
->sci
);
2169 sci_select_all(doc
->editor
->sci
);
2170 keybindings_send_command(GEANY_KEY_GROUP_FORMAT
,
2171 GEANY_KEYS_FORMAT_COMMENTLINETOGGLE
);
2173 sci_set_current_line(doc
->editor
->sci
, 0);
2174 document_set_text_changed(doc
, FALSE
);
2175 sci_empty_undo_buffer(doc
->editor
->sci
);
2176 ui_document_show_hide(doc
); /* update the document menu */
2178 utils_free_pointers(4, utf8_filename
, base_name
, global_file
, global_content
, NULL
);
2183 static void free_on_closure_notify(gpointer data
, GClosure
*closure
)
2189 /* @note You should connect to the "document-save" signal yourself to detect
2190 * if the user has just saved the config file, reloading it. */
2191 void ui_add_config_file_menu_item(const gchar
*real_path
, const gchar
*label
, GtkContainer
*parent
)
2196 parent
= GTK_CONTAINER(widgets
.config_files_menu
);
2202 base_name
= g_path_get_basename(real_path
);
2203 item
= gtk_menu_item_new_with_label(base_name
);
2207 item
= gtk_menu_item_new_with_mnemonic(label
);
2209 gtk_widget_show(item
);
2210 gtk_container_add(parent
, item
);
2211 g_signal_connect_data(item
, "activate", G_CALLBACK(on_config_file_clicked
),
2212 g_strdup(real_path
), free_on_closure_notify
, 0);
2216 static gboolean
sort_menu(gpointer data
)
2218 ui_menu_sort_by_label(GTK_MENU(data
));
2223 static void create_config_files_menu(void)
2225 GtkWidget
*menu
, *item
;
2227 widgets
.config_files_menu
= menu
= gtk_menu_new();
2229 item
= ui_lookup_widget(main_widgets
.window
, "configuration_files1");
2230 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item
), menu
);
2232 item
= gtk_menu_item_new_with_mnemonic(_("_Filetype Configuration"));
2233 gtk_container_add(GTK_CONTAINER(menu
), item
);
2234 ui_widgets
.config_files_filetype_menu
= gtk_menu_new();
2235 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item
), ui_widgets
.config_files_filetype_menu
);
2236 gtk_widget_show(item
);
2238 /* sort menu after all items added */
2239 g_idle_add(sort_menu
, widgets
.config_files_menu
);
2243 /* adds factory icons with a named icon source using the stock items id */
2244 static void add_stock_icons(const GtkStockItem
*items
, gsize count
)
2246 GtkIconFactory
*factory
= gtk_icon_factory_new();
2247 GtkIconSource
*source
= gtk_icon_source_new();
2250 for (i
= 0; i
< count
; i
++)
2252 GtkIconSet
*set
= gtk_icon_set_new();
2254 gtk_icon_source_set_icon_name(source
, items
[i
].stock_id
);
2255 gtk_icon_set_add_source(set
, source
);
2256 gtk_icon_factory_add(factory
, items
[i
].stock_id
, set
);
2257 gtk_icon_set_unref(set
);
2259 gtk_icon_source_free(source
);
2260 gtk_icon_factory_add_default(factory
);
2261 g_object_unref(factory
);
2265 void ui_init_stock_items(void)
2267 GtkStockItem items
[] =
2269 { GEANY_STOCK_SAVE_ALL
, N_("Save All"), 0, 0, GETTEXT_PACKAGE
},
2270 { GEANY_STOCK_CLOSE_ALL
, N_("Close All"), 0, 0, GETTEXT_PACKAGE
},
2271 { GEANY_STOCK_BUILD
, N_("Build"), 0, 0, GETTEXT_PACKAGE
}
2274 gtk_stock_add(items
, G_N_ELEMENTS(items
));
2275 add_stock_icons(items
, G_N_ELEMENTS(items
));
2279 void ui_init_toolbar_widgets(void)
2281 widgets
.save_buttons
[1] = toolbar_get_widget_by_name("Save");
2282 widgets
.save_buttons
[3] = toolbar_get_widget_by_name("SaveAll");
2283 widgets
.redo_items
[2] = toolbar_get_widget_by_name("Redo");
2284 widgets
.undo_items
[2] = toolbar_get_widget_by_name("Undo");
2288 void ui_swap_sidebar_pos(void)
2290 GtkWidget
*pane
= ui_lookup_widget(main_widgets
.window
, "hpaned1");
2291 GtkWidget
*left
= gtk_paned_get_child1(GTK_PANED(pane
));
2292 GtkWidget
*right
= gtk_paned_get_child2(GTK_PANED(pane
));
2295 g_object_ref(right
);
2296 gtk_container_remove (GTK_CONTAINER (pane
), left
);
2297 gtk_container_remove (GTK_CONTAINER (pane
), right
);
2298 /* only scintilla notebook should expand */
2299 gtk_paned_pack1(GTK_PANED(pane
), right
, right
== main_widgets
.notebook
, TRUE
);
2300 gtk_paned_pack2(GTK_PANED(pane
), left
, left
== main_widgets
.notebook
, TRUE
);
2301 g_object_unref(left
);
2302 g_object_unref(right
);
2304 gtk_paned_set_position(GTK_PANED(pane
), gtk_widget_get_allocated_width(pane
)
2305 - gtk_paned_get_position(GTK_PANED(pane
)));
2309 static void init_recent_files(void)
2311 GtkWidget
*toolbar_recent_files_menu
;
2313 /* add recent files to the File menu */
2314 ui_widgets
.recent_files_menuitem
= ui_lookup_widget(main_widgets
.window
, "recent_files1");
2315 ui_widgets
.recent_files_menu_menubar
= gtk_menu_new();
2316 gtk_menu_item_set_submenu(GTK_MENU_ITEM(ui_widgets
.recent_files_menuitem
),
2317 ui_widgets
.recent_files_menu_menubar
);
2319 /* add recent files to the toolbar Open button */
2320 toolbar_recent_files_menu
= gtk_menu_new();
2321 g_object_ref(toolbar_recent_files_menu
);
2322 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
2323 toolbar_get_action_by_name("Open")), toolbar_recent_files_menu
);
2327 static void ui_menu_move(GtkWidget
*menu
, GtkWidget
*old
, GtkWidget
*new)
2330 gtk_menu_item_set_submenu(GTK_MENU_ITEM(old
), NULL
);
2331 gtk_menu_item_set_submenu(GTK_MENU_ITEM(new), menu
);
2332 g_object_unref(menu
);
2336 typedef struct GeanySharedMenu
2339 const gchar
*menubar_item
;
2340 const gchar
*popup_item
;
2344 #define foreach_menu(item, array) \
2345 for (item = array; item->menu; item++)
2347 static void on_editor_menu_show(GtkWidget
*widget
, GeanySharedMenu
*items
)
2349 GeanySharedMenu
*item
;
2351 foreach_menu(item
, items
)
2353 GtkWidget
*popup
= ui_lookup_widget(main_widgets
.editor_menu
, item
->popup_item
);
2354 GtkWidget
*bar
= ui_lookup_widget(main_widgets
.window
, item
->menubar_item
);
2355 GtkWidget
*menu
= ui_lookup_widget(main_widgets
.window
, item
->menu
);
2357 ui_menu_move(menu
, bar
, popup
);
2362 static void on_editor_menu_hide(GtkWidget
*widget
, GeanySharedMenu
*items
)
2364 GeanySharedMenu
*item
;
2366 foreach_menu(item
, items
)
2368 GtkWidget
*popup
= ui_lookup_widget(main_widgets
.editor_menu
, item
->popup_item
);
2369 GtkWidget
*bar
= ui_lookup_widget(main_widgets
.window
, item
->menubar_item
);
2370 GtkWidget
*menu
= ui_lookup_widget(main_widgets
.window
, item
->menu
);
2372 ui_menu_move(menu
, popup
, bar
);
2377 /* Currently ui_init() is called before keyfile.c stash group code is initialized,
2378 * so this is called after that's done. */
2379 void ui_init_prefs(void)
2381 StashGroup
*group
= stash_group_new(PACKAGE
);
2384 configuration_add_various_pref_group(group
, "interface");
2386 stash_group_add_boolean(group
, &interface_prefs
.show_symbol_list_expanders
,
2387 "show_symbol_list_expanders", TRUE
);
2388 stash_group_add_boolean(group
, &interface_prefs
.compiler_tab_autoscroll
,
2389 "compiler_tab_autoscroll", TRUE
);
2390 stash_group_add_string(group
, &ui_prefs
.statusbar_template
,
2391 "statusbar_template", _(DEFAULT_STATUSBAR_TEMPLATE
));
2392 stash_group_add_boolean(group
, &ui_prefs
.new_document_after_close
,
2393 "new_document_after_close", FALSE
);
2394 stash_group_add_boolean(group
, &interface_prefs
.msgwin_status_visible
,
2395 "msgwin_status_visible", TRUE
);
2396 stash_group_add_boolean(group
, &interface_prefs
.msgwin_compiler_visible
,
2397 "msgwin_compiler_visible", TRUE
);
2398 stash_group_add_boolean(group
, &interface_prefs
.msgwin_messages_visible
,
2399 "msgwin_messages_visible", TRUE
);
2400 stash_group_add_boolean(group
, &interface_prefs
.msgwin_scribble_visible
,
2401 "msgwin_scribble_visible", TRUE
);
2402 stash_group_add_boolean(group
, &interface_prefs
.warn_on_project_close
,
2403 "warn_on_project_close", TRUE
);
2404 stash_group_add_spin_button_integer(group
, &interface_prefs
.tab_label_len
,
2405 "tab_label_length", 99999, "spin_tab_label_len");
2409 /* Used to find out the name of the GtkBuilder retrieved object since
2410 * some objects will be GTK_IS_BUILDABLE() and use the GtkBuildable
2411 * 'name' property for that and those that don't implement GtkBuildable
2412 * will have a "gtk-builder-name" stored in the GObject's data list. */
2413 static const gchar
*ui_guess_object_name(GObject
*obj
)
2415 const gchar
*name
= NULL
;
2417 g_return_val_if_fail(G_IS_OBJECT(obj
), NULL
);
2419 if (GTK_IS_BUILDABLE(obj
))
2420 name
= gtk_buildable_get_name(GTK_BUILDABLE(obj
));
2422 name
= g_object_get_data(obj
, "gtk-builder-name");
2430 /* Compatibility functions */
2431 GtkWidget
*create_edit_menu1(void)
2437 GtkWidget
*create_prefs_dialog(void)
2439 return prefs_dialog
;
2443 GtkWidget
*create_project_dialog(void)
2445 return project_dialog
;
2449 GtkWidget
*create_toolbar_popup_menu1(void)
2451 return toolbar_popup_menu1
;
2455 GtkWidget
*create_window1(void)
2461 static GtkWidget
*ui_get_top_parent(GtkWidget
*widget
)
2465 g_return_val_if_fail(GTK_IS_WIDGET(widget
), NULL
);
2469 if (GTK_IS_MENU(widget
))
2470 parent
= gtk_menu_get_attach_widget(GTK_MENU(widget
));
2472 parent
= gtk_widget_get_parent(widget
);
2474 parent
= (GtkWidget
*) g_object_get_data(G_OBJECT(widget
), "GladeParentKey");
2484 void ui_init_builder(void)
2486 gchar
*interface_file
;
2489 GSList
*iter
, *all_objects
;
2490 GtkWidget
*widget
, *toplevel
;
2492 /* prevent function from being called twice */
2493 if (GTK_IS_BUILDER(builder
))
2496 builder
= gtk_builder_new();
2498 gtk_builder_set_translation_domain(builder
, GETTEXT_PACKAGE
);
2501 interface_file
= g_build_filename(app
->datadir
, "geany.glade", NULL
);
2502 if (! gtk_builder_add_from_file(builder
, interface_file
, &error
))
2504 /* Show the user this message so they know WTF happened */
2505 dialogs_show_msgbox_with_secondary(GTK_MESSAGE_ERROR
,
2506 _("Geany cannot start!"), error
->message
);
2508 g_error("Cannot create user-interface: %s", error
->message
);
2509 g_error_free(error
);
2510 g_free(interface_file
);
2511 g_object_unref(builder
);
2514 g_free(interface_file
);
2516 callbacks_connect(builder
);
2518 edit_menu1
= GTK_WIDGET(gtk_builder_get_object(builder
, "edit_menu1"));
2519 prefs_dialog
= GTK_WIDGET(gtk_builder_get_object(builder
, "prefs_dialog"));
2520 project_dialog
= GTK_WIDGET(gtk_builder_get_object(builder
, "project_dialog"));
2521 toolbar_popup_menu1
= GTK_WIDGET(gtk_builder_get_object(builder
, "toolbar_popup_menu1"));
2522 window1
= GTK_WIDGET(gtk_builder_get_object(builder
, "window1"));
2524 g_object_set_data(G_OBJECT(edit_menu1
), "edit_menu1", edit_menu1
);
2525 g_object_set_data(G_OBJECT(prefs_dialog
), "prefs_dialog", prefs_dialog
);
2526 g_object_set_data(G_OBJECT(project_dialog
), "project_dialog", project_dialog
);
2527 g_object_set_data(G_OBJECT(toolbar_popup_menu1
), "toolbar_popup_menu1", toolbar_popup_menu1
);
2528 g_object_set_data(G_OBJECT(window1
), "window1", window1
);
2530 all_objects
= gtk_builder_get_objects(builder
);
2531 for (iter
= all_objects
; iter
!= NULL
; iter
= g_slist_next(iter
))
2533 if (! GTK_IS_WIDGET(iter
->data
))
2536 widget
= GTK_WIDGET(iter
->data
);
2538 name
= ui_guess_object_name(G_OBJECT(widget
));
2541 g_warning("Unable to get name from GtkBuilder object");
2546 /* Set a default name for use from CSS by name */
2547 gtk_widget_set_name(widget
, name
);
2550 toplevel
= ui_get_top_parent(widget
);
2552 ui_hookup_widget(toplevel
, widget
, name
);
2554 g_slist_free(all_objects
);
2558 static void load_css_theme(const gchar
*fn
, guint priority
)
2560 GtkCssProvider
*provider
= gtk_css_provider_new();
2561 GError
*error
= NULL
;
2563 if (! gtk_css_provider_load_from_path(provider
, fn
, &error
))
2565 g_warning("Failed to load custom CSS: %s", error
->message
);
2566 g_error_free(error
);
2570 gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
2571 GTK_STYLE_PROVIDER(provider
), priority
);
2572 geany_debug("Loaded GTK+ CSS theme '%s'", fn
);
2574 g_object_unref(provider
);
2578 static void init_css_styles(void)
2582 // load the main geany.css file from system data dir
2583 theme_fn
= g_build_filename(app
->datadir
, "geany.css", NULL
);
2584 load_css_theme(theme_fn
, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
2587 // load themes to handle breakage between various GTK+ versions
2596 { 20, G_MAXUINT
, "geany-3.20.css" },
2597 { 0, 19, "geany-3.0.css" },
2600 guint gtk_version
= gtk_get_minor_version();
2601 for (guint i
= 0; i
< G_N_ELEMENTS(css_files
); i
++)
2603 if (gtk_version
>= css_files
[i
].min_version
&&
2604 gtk_version
<= css_files
[i
].max_version
)
2606 theme_fn
= g_build_filename(app
->datadir
, css_files
[i
].file
, NULL
);
2607 load_css_theme(theme_fn
, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
2612 // if the user provided a geany.css file in their config dir, try and load that
2613 theme_fn
= g_build_filename(app
->configdir
, "geany.css", NULL
);
2614 if (g_file_test(theme_fn
, G_FILE_TEST_EXISTS
))
2615 load_css_theme(theme_fn
, GTK_STYLE_PROVIDER_PRIORITY_USER
);
2620 static void add_css_config_file_item(void)
2623 theme_fn
= g_build_filename(app
->configdir
, "geany.css", NULL
);
2624 ui_add_config_file_menu_item(theme_fn
, NULL
, NULL
);
2632 init_recent_files();
2634 ui_widgets
.statusbar
= ui_lookup_widget(main_widgets
.window
, "statusbar");
2635 ui_widgets
.print_page_setup
= ui_lookup_widget(main_widgets
.window
, "page_setup1");
2637 main_widgets
.progressbar
= progress_bar_create();
2639 /* current word sensitive items */
2640 widgets
.popup_goto_items
[0] = ui_lookup_widget(main_widgets
.editor_menu
, "goto_tag_definition2");
2641 widgets
.popup_goto_items
[1] = ui_lookup_widget(main_widgets
.editor_menu
, "context_action1");
2642 widgets
.popup_goto_items
[2] = ui_lookup_widget(main_widgets
.editor_menu
, "find_usage2");
2643 widgets
.popup_goto_items
[3] = ui_lookup_widget(main_widgets
.editor_menu
, "find_document_usage2");
2645 widgets
.popup_copy_items
[0] = ui_lookup_widget(main_widgets
.editor_menu
, "cut1");
2646 widgets
.popup_copy_items
[1] = ui_lookup_widget(main_widgets
.editor_menu
, "copy1");
2647 widgets
.popup_copy_items
[2] = ui_lookup_widget(main_widgets
.editor_menu
, "delete1");
2648 widgets
.menu_copy_items
[0] = ui_lookup_widget(main_widgets
.window
, "menu_cut1");
2649 widgets
.menu_copy_items
[1] = ui_lookup_widget(main_widgets
.window
, "menu_copy1");
2650 widgets
.menu_copy_items
[2] = ui_lookup_widget(main_widgets
.window
, "menu_delete1");
2651 widgets
.menu_insert_include_items
[0] = ui_lookup_widget(main_widgets
.editor_menu
, "insert_include1");
2652 widgets
.menu_insert_include_items
[1] = ui_lookup_widget(main_widgets
.window
, "insert_include2");
2653 widgets
.save_buttons
[0] = ui_lookup_widget(main_widgets
.window
, "menu_save1");
2654 widgets
.save_buttons
[2] = ui_lookup_widget(main_widgets
.window
, "menu_save_all1");
2655 widgets
.redo_items
[0] = ui_lookup_widget(main_widgets
.editor_menu
, "redo1");
2656 widgets
.redo_items
[1] = ui_lookup_widget(main_widgets
.window
, "menu_redo2");
2657 widgets
.undo_items
[0] = ui_lookup_widget(main_widgets
.editor_menu
, "undo1");
2658 widgets
.undo_items
[1] = ui_lookup_widget(main_widgets
.window
, "menu_undo2");
2660 /* reparent context submenus as needed */
2662 GeanySharedMenu arr
[] = {
2663 {"commands2_menu", "commands2", "commands1"},
2664 {"menu_format1_menu", "menu_format1", "menu_format2"},
2665 {"more1_menu", "more1", "search2"},
2668 static GeanySharedMenu items
[G_N_ELEMENTS(arr
)];
2670 memcpy(items
, arr
, sizeof(arr
));
2671 g_signal_connect(main_widgets
.editor_menu
, "show", G_CALLBACK(on_editor_menu_show
), items
);
2672 g_signal_connect(main_widgets
.editor_menu
, "hide", G_CALLBACK(on_editor_menu_hide
), items
);
2675 ui_init_toolbar_widgets();
2676 init_document_widgets();
2678 create_config_files_menu();
2679 add_css_config_file_item();
2683 void ui_finalize_builder(void)
2685 if (GTK_IS_BUILDER(builder
))
2686 g_object_unref(builder
);
2688 /* cleanup refs lingering even after GtkBuilder is destroyed */
2689 if (GTK_IS_WIDGET(edit_menu1
))
2690 gtk_widget_destroy(edit_menu1
);
2691 if (GTK_IS_WIDGET(prefs_dialog
))
2692 gtk_widget_destroy(prefs_dialog
);
2693 if (GTK_IS_WIDGET(project_dialog
))
2694 gtk_widget_destroy(project_dialog
);
2695 if (GTK_IS_WIDGET(toolbar_popup_menu1
))
2696 gtk_widget_destroy(toolbar_popup_menu1
);
2697 if (GTK_IS_WIDGET(window1
))
2698 gtk_widget_destroy(window1
);
2702 static void auto_separator_update(GeanyAutoSeparator
*autosep
)
2704 g_return_if_fail(autosep
->item_count
>= 0);
2706 if (autosep
->widget
)
2708 if (autosep
->item_count
> 0)
2709 ui_widget_show_hide(autosep
->widget
, autosep
->show_count
> 0);
2711 gtk_widget_destroy(autosep
->widget
);
2716 static void on_auto_separator_item_show_hide(GtkWidget
*widget
, gpointer user_data
)
2718 GeanyAutoSeparator
*autosep
= user_data
;
2720 if (gtk_widget_get_visible(widget
))
2721 autosep
->show_count
++;
2723 autosep
->show_count
--;
2724 auto_separator_update(autosep
);
2728 static void on_auto_separator_item_destroy(GtkWidget
*widget
, gpointer user_data
)
2730 GeanyAutoSeparator
*autosep
= user_data
;
2732 autosep
->item_count
--;
2733 autosep
->item_count
= MAX(autosep
->item_count
, 0);
2734 /* gtk_widget_get_visible() won't work now the widget is being destroyed,
2735 * so assume widget was visible */
2736 autosep
->show_count
--;
2737 autosep
->show_count
= MAX(autosep
->item_count
, 0);
2738 auto_separator_update(autosep
);
2742 /* Show the separator widget if @a item or another is visible. */
2743 /* Note: This would be neater taking a widget argument, setting a "visible-count"
2744 * property, and using reference counting to keep the widget alive whilst its visible group
2746 void ui_auto_separator_add_ref(GeanyAutoSeparator
*autosep
, GtkWidget
*item
)
2748 /* set widget ptr NULL when widget destroyed */
2749 if (autosep
->item_count
== 0)
2750 g_signal_connect(autosep
->widget
, "destroy",
2751 G_CALLBACK(gtk_widget_destroyed
), &autosep
->widget
);
2753 if (gtk_widget_get_visible(item
))
2754 autosep
->show_count
++;
2756 autosep
->item_count
++;
2757 auto_separator_update(autosep
);
2759 g_signal_connect(item
, "show", G_CALLBACK(on_auto_separator_item_show_hide
), autosep
);
2760 g_signal_connect(item
, "hide", G_CALLBACK(on_auto_separator_item_show_hide
), autosep
);
2761 g_signal_connect(item
, "destroy", G_CALLBACK(on_auto_separator_item_destroy
), autosep
);
2766 * Sets @a text as the contents of the tooltip for @a widget.
2768 * @param widget The widget the tooltip should be set for.
2769 * @param text The text for the tooltip.
2772 * @deprecated 0.21 use gtk_widget_set_tooltip_text() instead
2775 void ui_widget_set_tooltip_text(GtkWidget
*widget
, const gchar
*text
)
2777 gtk_widget_set_tooltip_text(widget
, text
);
2781 /** Returns a widget from a name in a component, usually created by Glade.
2782 * Call it with the toplevel widget in the component (i.e. a window/dialog),
2783 * or alternatively any widget in the component, and the name of the widget
2784 * you want returned.
2785 * @param widget Widget with the @a widget_name property set.
2786 * @param widget_name Name to lookup.
2788 * @return @transfer{none} The widget found.
2789 * @see ui_hookup_widget().
2794 GtkWidget
*ui_lookup_widget(GtkWidget
*widget
, const gchar
*widget_name
)
2796 GtkWidget
*parent
, *found_widget
;
2798 g_return_val_if_fail(widget
!= NULL
, NULL
);
2799 g_return_val_if_fail(widget_name
!= NULL
, NULL
);
2803 if (GTK_IS_MENU(widget
))
2804 parent
= gtk_menu_get_attach_widget(GTK_MENU(widget
));
2806 parent
= gtk_widget_get_parent(widget
);
2808 parent
= (GtkWidget
*) g_object_get_data(G_OBJECT(widget
), "GladeParentKey");
2814 found_widget
= (GtkWidget
*) g_object_get_data(G_OBJECT(widget
), widget_name
);
2815 if (G_UNLIKELY(found_widget
== NULL
))
2816 g_warning("Widget not found: %s", widget_name
);
2817 return found_widget
;
2821 /* wraps gtk_builder_get_object()
2822 * unlike ui_lookup_widget(), it does only support getting object created from the main
2823 * UI file, but it can fetch any object, not only widgets */
2824 gpointer
ui_builder_get_object (const gchar
*name
)
2826 return gtk_builder_get_object (builder
, name
);
2831 static guint progress_bar_timer_id
= 0;
2834 static GtkWidget
*progress_bar_create(void)
2836 GtkWidget
*bar
= gtk_progress_bar_new();
2838 /* Set the progressbar's height to 1 to fit it in the statusbar */
2839 gtk_widget_set_size_request(bar
, -1, 1);
2840 gtk_box_pack_start (GTK_BOX(ui_widgets
.statusbar
), bar
, FALSE
, FALSE
, 3);
2846 static gboolean
progress_bar_pulse(gpointer data
)
2848 gtk_progress_bar_pulse(GTK_PROGRESS_BAR(main_widgets
.progressbar
));
2855 * Starts a constantly pulsing progressbar in the right corner of the statusbar
2856 * (if the statusbar is visible). This is a convenience function which adds a timer to
2857 * pulse the progressbar constantly until ui_progress_bar_stop() is called.
2858 * You can use this function when you have time consuming asynchronous operation and want to
2859 * display some activity in the GUI and when you don't know about detailed progress steps.
2860 * The progressbar widget is hidden by default when it is not active. This function and
2861 * ui_progress_bar_stop() will show and hide it automatically for you.
2863 * You can also access the progressbar widget directly using @c geany->main_widgets->progressbar
2864 * and use the GtkProgressBar API to set discrete fractions to display better progress information.
2865 * In this case, you need to show and hide the widget yourself. You can find some example code
2866 * in @c src/printing.c.
2868 * @param text @nullable The text to be shown as the progress bar label or @c NULL to leave it empty.
2873 void ui_progress_bar_start(const gchar
*text
)
2875 g_return_if_fail(progress_bar_timer_id
== 0);
2877 if (! interface_prefs
.statusbar_visible
)
2880 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(main_widgets
.progressbar
), text
);
2882 progress_bar_timer_id
= g_timeout_add(200, progress_bar_pulse
, NULL
);
2884 gtk_widget_show(GTK_WIDGET(main_widgets
.progressbar
));
2888 /** Stops a running progress bar and hides the widget again.
2893 void ui_progress_bar_stop(void)
2895 gtk_widget_hide(GTK_WIDGET(main_widgets
.progressbar
));
2897 if (progress_bar_timer_id
!= 0)
2899 g_source_remove(progress_bar_timer_id
);
2900 progress_bar_timer_id
= 0;
2905 static gint
compare_menu_item_labels(gconstpointer a
, gconstpointer b
)
2907 GtkMenuItem
*item_a
= GTK_MENU_ITEM(a
);
2908 GtkMenuItem
*item_b
= GTK_MENU_ITEM(b
);
2912 /* put entries with submenus at the end of the menu */
2913 if (gtk_menu_item_get_submenu(item_a
) && !gtk_menu_item_get_submenu(item_b
))
2915 else if (!gtk_menu_item_get_submenu(item_a
) && gtk_menu_item_get_submenu(item_b
))
2918 sa
= ui_menu_item_get_text(item_a
);
2919 sb
= ui_menu_item_get_text(item_b
);
2920 result
= utils_str_casecmp(sa
, sb
);
2927 /* Currently @a menu should contain only GtkMenuItems with labels. */
2928 static void ui_menu_sort_by_label(GtkMenu
*menu
)
2930 GList
*list
= gtk_container_get_children(GTK_CONTAINER(menu
));
2934 list
= g_list_sort(list
, compare_menu_item_labels
);
2936 foreach_list(node
, list
)
2938 menu_reorder_child(menu
, node
->data
, pos
);
2945 void ui_label_set_markup(GtkLabel
*label
, const gchar
*format
, ...)
2950 va_start(a
, format
);
2951 text
= g_markup_vprintf_escaped(format
, a
);
2954 gtk_label_set_text(label
, text
);
2955 gtk_label_set_use_markup(label
, TRUE
);
2960 GtkWidget
*ui_label_new_bold(const gchar
*text
)
2964 label
= gtk_label_new(NULL
);
2965 ui_label_set_markup(GTK_LABEL(label
), "<b>%s</b>", text
);
2971 * Adds a list of document items to @a menu.
2973 * @param active @nullable Which document to highlight, or @c NULL.
2974 * @param callback is used for each menu item's @c "activate" signal and will be
2975 * passed the corresponding document pointer as @c user_data.
2976 * @warning You should check @c doc->is_valid in the callback.
2980 void ui_menu_add_document_items(GtkMenu
*menu
, GeanyDocument
*active
, GCallback callback
)
2982 ui_menu_add_document_items_sorted(menu
, active
, callback
, NULL
);
2987 * Adds a list of document items to @a menu.
2989 * @a compare_func might be NULL to not sort the documents in the menu. In this case,
2990 * the order of the document tabs is used.
2992 * See document_compare_by_display_name() for an example sort function.
2995 * @param active @nullable Which document to highlight, or @c NULL.
2996 * @param callback is used for each menu item's @c "activate" signal and will be passed
2997 * the corresponding document pointer as @c user_data.
2998 * @param compare_func is used to sort the list. Might be @c NULL to not sort the list.
2999 * @warning You should check @c doc->is_valid in the callback.
3003 void ui_menu_add_document_items_sorted(GtkMenu
*menu
, GeanyDocument
*active
,
3004 GCallback callback
, GCompareFunc compare_func
)
3006 GtkWidget
*menu_item
, *menu_item_label
, *image
;
3010 GPtrArray
*sorted_documents
;
3012 len
= (guint
) gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets
.notebook
));
3014 sorted_documents
= g_ptr_array_sized_new(len
);
3015 /* copy the documents_array into the new one */
3018 g_ptr_array_add(sorted_documents
, documents
[i
]);
3020 if (compare_func
== NULL
)
3021 compare_func
= document_compare_by_tab_order
;
3023 /* and now sort it */
3024 g_ptr_array_sort(sorted_documents
, compare_func
);
3026 for (i
= 0; i
< sorted_documents
->len
; i
++)
3028 doc
= g_ptr_array_index(sorted_documents
, i
);
3030 base_name
= g_path_get_basename(DOC_FILENAME(doc
));
3031 menu_item
= gtk_image_menu_item_new_with_label(base_name
);
3032 image
= gtk_image_new_from_gicon(doc
->file_type
->icon
, GTK_ICON_SIZE_MENU
);
3033 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item
), image
);
3034 gtk_widget_set_tooltip_text(menu_item
, DOC_FILENAME(doc
));
3036 gtk_widget_show(menu_item
);
3037 gtk_container_add(GTK_CONTAINER(menu
), menu_item
);
3038 g_signal_connect(menu_item
, "activate", callback
, doc
);
3040 menu_item_label
= gtk_bin_get_child(GTK_BIN(menu_item
));
3041 gtk_widget_set_name(menu_item_label
, document_get_status_widget_class(doc
));
3044 ui_label_set_markup(GTK_LABEL(menu_item_label
), "<b>%s</b>", base_name
);
3048 g_ptr_array_free(sorted_documents
, TRUE
);
3052 /** Checks whether the passed @a keyval is the Enter or Return key.
3053 * There are three different Enter/Return key values
3054 * (@c GDK_KEY_Return, @c GDK_KEY_ISO_Enter, @c GDK_KEY_KP_Enter).
3055 * This is just a convenience function.
3056 * @param keyval A keyval.
3057 * @return @c TRUE if @a keyval is the one of the Enter/Return key values, otherwise @c FALSE.
3060 gboolean
ui_is_keyval_enter_or_return(guint keyval
)
3062 return (keyval
== GDK_KEY_Return
|| keyval
== GDK_KEY_ISO_Enter
|| keyval
== GDK_KEY_KP_Enter
);
3066 /** Reads an integer from the GTK default settings registry
3067 * (see http://library.gnome.org/devel/gtk/stable/GtkSettings.html).
3068 * @param property_name The property to read.
3069 * @param default_value The default value in case the value could not be read.
3070 * @return The value for the property if it exists, otherwise the @a default_value.
3073 gint
ui_get_gtk_settings_integer(const gchar
*property_name
, gint default_value
)
3075 if (g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(
3076 gtk_settings_get_default())), property_name
))
3079 g_object_get(G_OBJECT(gtk_settings_get_default()), property_name
, &value
, NULL
);
3083 return default_value
;
3087 void ui_editable_insert_text_callback(GtkEditable
*editable
, gchar
*new_text
,
3088 gint new_text_len
, gint
*position
, gpointer data
)
3090 gboolean first
= position
!= NULL
&& *position
== 0;
3093 if (new_text_len
== -1)
3094 new_text_len
= (gint
) strlen(new_text
);
3096 for (i
= 0; i
< new_text_len
; i
++, new_text
++)
3098 if ((!first
|| !strchr("+-", *new_text
)) && !isdigit(*new_text
))
3100 g_signal_stop_emission_by_name(editable
, "insert-text");
3108 /* gets the icon that applies to a particular MIME type */
3109 GIcon
*ui_get_mime_icon(const gchar
*mime_type
)
3114 ctype
= g_content_type_from_mime_type(mime_type
);
3117 GdkScreen
*screen
= gdk_screen_get_default();
3119 icon
= g_content_type_get_icon(ctype
);
3122 GtkIconInfo
*icon_info
;
3124 icon_info
= gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_for_screen(screen
), icon
, 16, 0);
3127 g_object_unref(icon
);
3131 gtk_icon_info_free(icon_info
);
3137 /* fallback if icon lookup failed, like it might happen on Windows (?) */
3140 const gchar
*icon_name
= "text-x-generic";
3142 if (strstr(mime_type
, "directory"))
3143 icon_name
= "folder";
3145 icon
= g_themed_icon_new(icon_name
);
3151 void ui_focus_current_document(void)
3153 GeanyDocument
*doc
= document_get_current();
3156 document_grab_focus(doc
);
3160 /** Finds the label text associated with stock_id
3161 * @param stock_id stock_id to lookup e.g. @c GTK_STOCK_OPEN.
3162 * @return The label text for stock
3163 * @since Geany 1.22 */
3165 const gchar
*ui_lookup_stock_label(const gchar
*stock_id
)
3169 if (gtk_stock_lookup(stock_id
, &item
))
3172 g_warning("No stock id '%s'!", stock_id
);
3177 /* finds the next iter at any level
3178 * @param iter in/out, the current iter, will be changed to the next one
3179 * @param down whether to try the child iter
3180 * @return TRUE if there @p iter was set, or FALSE if there is no next iter */
3181 gboolean
ui_tree_model_iter_any_next(GtkTreeModel
*model
, GtkTreeIter
*iter
, gboolean down
)
3184 GtkTreeIter copy
= *iter
;
3186 /* go down if the item has children */
3187 if (down
&& gtk_tree_model_iter_children(model
, &guess
, iter
))
3189 /* or to the next item at the same level */
3190 else if (gtk_tree_model_iter_next(model
, ©
))
3192 /* or to the next item at a parent level */
3193 else if (gtk_tree_model_iter_parent(model
, &guess
, iter
))
3198 if (gtk_tree_model_iter_next(model
, ©
))
3203 else if (gtk_tree_model_iter_parent(model
, ©
, &guess
))
3216 GtkWidget
*ui_create_encodings_combo_box(gboolean has_detect
, gint default_enc
)
3218 GtkCellRenderer
*renderer
;
3220 GtkWidget
*combo
= gtk_combo_box_new();
3221 GtkTreeStore
*store
= encodings_encoding_store_new(has_detect
);
3223 if (default_enc
< 0 || default_enc
>= GEANY_ENCODINGS_MAX
)
3224 default_enc
= has_detect
? GEANY_ENCODINGS_MAX
: GEANY_ENCODING_NONE
;
3226 gtk_combo_box_set_model(GTK_COMBO_BOX(combo
), GTK_TREE_MODEL(store
));
3227 if (encodings_encoding_store_get_iter(store
, &iter
, default_enc
))
3228 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo
), &iter
);
3229 renderer
= gtk_cell_renderer_text_new();
3230 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo
), renderer
, TRUE
);
3231 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo
), renderer
,
3232 encodings_encoding_store_cell_data_func
, NULL
, NULL
);
3238 gint
ui_encodings_combo_box_get_active_encoding(GtkComboBox
*combo
)
3241 gint enc
= GEANY_ENCODING_NONE
;
3243 /* there should always be an active iter anyway, but we check just in case */
3244 if (gtk_combo_box_get_active_iter(combo
, &iter
))
3246 GtkTreeModel
*model
= gtk_combo_box_get_model(combo
);
3247 enc
= encodings_encoding_store_get_encoding(GTK_TREE_STORE(model
), &iter
);
3254 gboolean
ui_encodings_combo_box_set_active_encoding(GtkComboBox
*combo
, gint enc
)
3257 GtkTreeModel
*model
= gtk_combo_box_get_model(combo
);
3259 if (encodings_encoding_store_get_iter(GTK_TREE_STORE(model
), &iter
, enc
))
3261 gtk_combo_box_set_active_iter(combo
, &iter
);
3267 void ui_menu_popup(GtkMenu
* menu
, GtkMenuPositionFunc func
, gpointer data
, guint button
, guint32 activate_time
)
3269 /* Use appropriate function for menu popup:
3270 - gtk_menu_popup_at_pointer is not available on GTK older than 3.22
3271 - gtk_menu_popup is deprecated and causes issues on multimonitor wayland setups */
3272 #if GTK_CHECK_VERSION(3,22,0)
3273 gtk_menu_popup_at_pointer(GTK_MENU(menu
), NULL
);
3275 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, func
, data
, button
, activate_time
);