Fix build warning about missing return value.
[geany-mirror.git] / src / ui_utils.c
blobe06ffbeb9002d3ddc86ce966b7a189ac4a002105
1 /*
2 * ui_utils.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2006-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2012 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
6 * Copyright 2011-2012 Matthew Brush <mbrush(at)codebrainz(dot)ca>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 /** @file ui_utils.h
24 * User Interface general utility functions.
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
31 #include "ui_utils.h"
33 #include "app.h"
34 #include "callbacks.h"
35 #include "dialogs.h"
36 #include "documentprivate.h"
37 #include "encodings.h"
38 #include "filetypes.h"
39 #include "geanymenubuttonaction.h"
40 #include "keyfile.h"
41 #include "main.h"
42 #include "msgwindow.h"
43 #include "prefs.h"
44 #include "project.h"
45 #include "sciwrappers.h"
46 #include "sidebar.h"
47 #include "stash.h"
48 #include "support.h"
49 #include "symbols.h"
50 #include "toolbar.h"
51 #include "utils.h"
53 #include "gtkcompat.h"
55 #include <string.h>
56 #include <ctype.h>
57 #include <stdarg.h>
58 #include <gdk/gdkkeysyms.h>
61 #define DEFAULT_STATUSBAR_TEMPLATE N_(\
62 "line: %l / %L\t " \
63 "col: %c\t " \
64 "sel: %s\t " \
65 "%w %t %m" \
66 "mode: %M " \
67 "encoding: %e " \
68 "filetype: %f " \
69 "scope: %S")
71 GeanyInterfacePrefs interface_prefs;
72 GeanyMainWidgets main_widgets;
74 UIPrefs ui_prefs;
75 UIWidgets ui_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;
84 static struct
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;
98 widgets;
100 enum
102 RECENT_FILE_FILE,
103 RECENT_FILE_PROJECT
106 typedef struct
108 gint type;
109 GQueue *recent_queue;
110 GtkWidget *menubar;
111 GtkWidget *toolbar;
112 void (*activate_cb)(GtkMenuItem *, gpointer);
113 } GeanyRecentFiles;
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);
123 /* simple wrapper for gtk_widget_set_sensitive() to allow widget being NULL */
124 void ui_widget_set_sensitive(GtkWidget *widget, gboolean set)
126 if (widget != NULL)
127 gtk_widget_set_sensitive(widget, set);
131 /* allow_override is TRUE if text can be ignored when another message has been set
132 * that didn't use allow_override and has not timed out. */
133 static void set_statusbar(const gchar *text, gboolean allow_override)
135 static guint id = 0;
136 static glong last_time = 0;
137 GTimeVal timeval;
138 const gint GEANY_STATUS_TIMEOUT = 1;
140 if (! interface_prefs.statusbar_visible)
141 return; /* just do nothing if statusbar is not visible */
143 if (id == 0)
144 id = gtk_statusbar_get_context_id(GTK_STATUSBAR(ui_widgets.statusbar), "geany-main");
146 g_get_current_time(&timeval);
148 if (! allow_override)
150 gtk_statusbar_pop(GTK_STATUSBAR(ui_widgets.statusbar), id);
151 gtk_statusbar_push(GTK_STATUSBAR(ui_widgets.statusbar), id, text);
152 last_time = timeval.tv_sec;
154 else
155 if (timeval.tv_sec > last_time + GEANY_STATUS_TIMEOUT)
157 gtk_statusbar_pop(GTK_STATUSBAR(ui_widgets.statusbar), id);
158 gtk_statusbar_push(GTK_STATUSBAR(ui_widgets.statusbar), id, text);
163 /** Displays text on the statusbar.
164 * @param log Whether the message should be recorded in the Status window.
165 * @param format A @c printf -style string. */
166 void ui_set_statusbar(gboolean log, const gchar *format, ...)
168 gchar *string;
169 va_list args;
171 va_start(args, format);
172 string = g_strdup_vprintf(format, args);
173 va_end(args);
175 if (! prefs.suppress_status_messages)
176 set_statusbar(string, FALSE);
178 if (log || prefs.suppress_status_messages)
179 msgwin_status_add("%s", string);
181 g_free(string);
185 /* note: some comments below are for translators */
186 static gchar *create_statusbar_statistics(GeanyDocument *doc,
187 guint line, guint col, guint pos)
189 const gchar *cur_tag;
190 const gchar *fmt;
191 const gchar *expos; /* % expansion position */
192 const gchar sp[] = " ";
193 GString *stats_str;
194 ScintillaObject *sci = doc->editor->sci;
196 if (!EMPTY(ui_prefs.statusbar_template))
197 fmt = ui_prefs.statusbar_template;
198 else
199 fmt = _(DEFAULT_STATUSBAR_TEMPLATE);
201 stats_str = g_string_sized_new(120);
203 while ((expos = strchr(fmt, '%')) != NULL)
205 /* append leading text before % char */
206 g_string_append_len(stats_str, fmt, expos - fmt);
208 switch (*++expos)
210 case 'l':
211 g_string_append_printf(stats_str, "%d", line + 1);
212 break;
213 case 'L':
214 g_string_append_printf(stats_str, "%d",
215 sci_get_line_count(doc->editor->sci));
216 break;
217 case 'c':
218 g_string_append_printf(stats_str, "%d", col);
219 break;
220 case 'C':
221 g_string_append_printf(stats_str, "%d", col + 1);
222 break;
223 case 'p':
224 g_string_append_printf(stats_str, "%u", pos);
225 break;
226 case 's':
228 gint len = sci_get_selected_text_length(sci) - 1;
229 /* check if whole lines are selected */
230 if (!len || sci_get_col_from_position(sci,
231 sci_get_selection_start(sci)) != 0 ||
232 sci_get_col_from_position(sci,
233 sci_get_selection_end(sci)) != 0)
234 g_string_append_printf(stats_str, "%d", len);
235 else /* L = lines */
236 g_string_append_printf(stats_str, _("%dL"),
237 sci_get_lines_selected(doc->editor->sci) - 1);
238 break;
240 case 'w':
241 /* RO = read-only */
242 g_string_append(stats_str, (doc->readonly) ? _("RO ") :
243 /* OVR = overwrite/overtype, INS = insert */
244 (sci_get_overtype(doc->editor->sci) ? _("OVR") : _("INS")));
245 break;
246 case 'r':
247 if (doc->readonly)
249 g_string_append(stats_str, _("RO ")); /* RO = read-only */
250 g_string_append(stats_str, sp + 1);
252 break;
253 case 't':
255 switch (editor_get_indent_prefs(doc->editor)->type)
257 case GEANY_INDENT_TYPE_TABS:
258 g_string_append(stats_str, _("TAB"));
259 break;
260 case GEANY_INDENT_TYPE_SPACES: /* SP = space */
261 g_string_append(stats_str, _("SP"));
262 break;
263 case GEANY_INDENT_TYPE_BOTH: /* T/S = tabs and spaces */
264 g_string_append(stats_str, _("T/S"));
265 break;
267 break;
269 case 'm':
270 if (doc->changed)
272 g_string_append(stats_str, _("MOD")); /* MOD = modified */
273 g_string_append(stats_str, sp);
275 break;
276 case 'M':
277 g_string_append(stats_str, editor_get_eol_char_name(doc->editor));
278 break;
279 case 'e':
280 g_string_append(stats_str,
281 doc->encoding ? doc->encoding : _("unknown"));
282 if (encodings_is_unicode_charset(doc->encoding) && (doc->has_bom))
284 g_string_append_c(stats_str, ' ');
285 g_string_append(stats_str, _("(with BOM)")); /* BOM = byte order mark */
287 break;
288 case 'f':
289 g_string_append(stats_str, filetypes_get_display_name(doc->file_type));
290 break;
291 case 'S':
292 symbols_get_current_scope(doc, &cur_tag);
293 g_string_append(stats_str, cur_tag);
294 break;
295 case 'Y':
296 g_string_append_c(stats_str, ' ');
297 g_string_append_printf(stats_str, "%d",
298 sci_get_style_at(doc->editor->sci, pos));
299 break;
300 default:
301 g_string_append_len(stats_str, expos, 1);
304 /* skip past %c chars */
305 if (*expos)
306 fmt = expos + 1;
307 else
308 break;
310 /* add any remaining text */
311 g_string_append(stats_str, fmt);
313 return g_string_free(stats_str, FALSE);
317 /* updates the status bar document statistics */
318 void ui_update_statusbar(GeanyDocument *doc, gint pos)
320 g_return_if_fail(doc == NULL || doc->is_valid);
322 if (! interface_prefs.statusbar_visible)
323 return; /* just do nothing if statusbar is not visible */
325 if (doc == NULL)
326 doc = document_get_current();
328 if (doc != NULL)
330 guint line, col;
331 gchar *stats_str;
333 if (pos == -1)
334 pos = sci_get_current_position(doc->editor->sci);
335 line = sci_get_line_from_position(doc->editor->sci, pos);
337 /* Add temporary fix for sci infinite loop in Document::GetColumn(int)
338 * when current pos is beyond document end (can occur when removing
339 * blocks of selected lines especially esp. brace sections near end of file). */
340 if (pos <= sci_get_length(doc->editor->sci))
341 col = sci_get_col_from_position(doc->editor->sci, pos);
342 else
343 col = 0;
345 stats_str = create_statusbar_statistics(doc, line, col, pos);
347 /* can be overridden by status messages */
348 set_statusbar(stats_str, TRUE);
349 g_free(stats_str);
351 else /* no documents */
353 set_statusbar("", TRUE); /* can be overridden by status messages */
358 /* This sets the window title according to the current filename. */
359 void ui_set_window_title(GeanyDocument *doc)
361 GString *str;
362 GeanyProject *project = app->project;
364 g_return_if_fail(doc == NULL || doc->is_valid);
366 if (doc == NULL)
367 doc = document_get_current();
369 str = g_string_new(NULL);
371 if (doc != NULL)
373 g_string_append(str, doc->changed ? "*" : "");
375 if (doc->file_name == NULL)
376 g_string_append(str, DOC_FILENAME(doc));
377 else
379 gchar *short_name = document_get_basename_for_display(doc, 30);
380 gchar *dirname = g_path_get_dirname(DOC_FILENAME(doc));
382 g_string_append(str, short_name);
383 g_string_append(str, " - ");
384 g_string_append(str, dirname ? dirname : "");
385 g_free(short_name);
386 g_free(dirname);
388 g_string_append(str, " - ");
390 if (project)
392 g_string_append_c(str, '[');
393 g_string_append(str, project->name);
394 g_string_append(str, "] - ");
396 g_string_append(str, "Geany");
397 if (cl_options.new_instance)
399 g_string_append(str, _(" (new instance)"));
401 gtk_window_set_title(GTK_WINDOW(main_widgets.window), str->str);
402 g_string_free(str, TRUE);
406 void ui_set_editor_font(const gchar *font_name)
408 guint i;
410 g_return_if_fail(font_name != NULL);
412 /* do nothing if font has not changed */
413 if (interface_prefs.editor_font != NULL)
414 if (strcmp(font_name, interface_prefs.editor_font) == 0)
415 return;
417 g_free(interface_prefs.editor_font);
418 interface_prefs.editor_font = g_strdup(font_name);
420 /* We copy the current style, and update the font in all open tabs. */
421 for (i = 0; i < documents_array->len; i++)
423 if (documents[i]->editor)
425 editor_set_font(documents[i]->editor, interface_prefs.editor_font);
429 ui_set_statusbar(TRUE, _("Font updated (%s)."), interface_prefs.editor_font);
433 void ui_set_fullscreen(void)
435 if (ui_prefs.fullscreen)
437 gtk_window_fullscreen(GTK_WINDOW(main_widgets.window));
439 else
441 gtk_window_unfullscreen(GTK_WINDOW(main_widgets.window));
446 void ui_update_popup_reundo_items(GeanyDocument *doc)
448 gboolean enable_undo;
449 gboolean enable_redo;
450 guint i, len;
452 g_return_if_fail(doc == NULL || doc->is_valid);
454 if (doc == NULL)
456 enable_undo = FALSE;
457 enable_redo = FALSE;
459 else
461 enable_undo = document_can_undo(doc);
462 enable_redo = document_can_redo(doc);
465 /* index 0 is the popup menu, 1 is the menubar, 2 is the toolbar */
466 len = G_N_ELEMENTS(widgets.undo_items);
467 for (i = 0; i < len; i++)
469 ui_widget_set_sensitive(widgets.undo_items[i], enable_undo);
471 len = G_N_ELEMENTS(widgets.redo_items);
472 for (i = 0; i < len; i++)
474 ui_widget_set_sensitive(widgets.redo_items[i], enable_redo);
479 void ui_update_popup_copy_items(GeanyDocument *doc)
481 gboolean enable;
482 guint i, len;
484 g_return_if_fail(doc == NULL || doc->is_valid);
486 if (doc == NULL)
487 enable = FALSE;
488 else
489 enable = sci_has_selection(doc->editor->sci);
491 len = G_N_ELEMENTS(widgets.popup_copy_items);
492 for (i = 0; i < len; i++)
493 ui_widget_set_sensitive(widgets.popup_copy_items[i], enable);
497 void ui_update_popup_goto_items(gboolean enable)
499 guint i, len;
500 len = G_N_ELEMENTS(widgets.popup_goto_items);
501 for (i = 0; i < len; i++)
502 ui_widget_set_sensitive(widgets.popup_goto_items[i], enable);
506 void ui_update_menu_copy_items(GeanyDocument *doc)
508 gboolean enable = FALSE;
509 guint i, len;
510 GtkWidget *focusw = gtk_window_get_focus(GTK_WINDOW(main_widgets.window));
512 g_return_if_fail(doc == NULL || doc->is_valid);
514 if (IS_SCINTILLA(focusw))
515 enable = (doc == NULL) ? FALSE : sci_has_selection(doc->editor->sci);
516 else
517 if (GTK_IS_EDITABLE(focusw))
518 enable = gtk_editable_get_selection_bounds(GTK_EDITABLE(focusw), NULL, NULL);
519 else
520 if (GTK_IS_TEXT_VIEW(focusw))
522 GtkTextBuffer *buffer = gtk_text_view_get_buffer(
523 GTK_TEXT_VIEW(focusw));
524 enable = gtk_text_buffer_get_selection_bounds(buffer, NULL, NULL);
527 len = G_N_ELEMENTS(widgets.menu_copy_items);
528 for (i = 0; i < len; i++)
529 ui_widget_set_sensitive(widgets.menu_copy_items[i], enable);
533 void ui_update_insert_include_item(GeanyDocument *doc, gint item)
535 gboolean enable = FALSE;
537 g_return_if_fail(doc == NULL || doc->is_valid);
539 if (doc == NULL || doc->file_type == NULL)
540 enable = FALSE;
541 else if (doc->file_type->id == GEANY_FILETYPES_C || doc->file_type->id == GEANY_FILETYPES_CPP)
542 enable = TRUE;
544 ui_widget_set_sensitive(widgets.menu_insert_include_items[item], enable);
548 void ui_update_fold_items(void)
550 ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "menu_fold_all1"), editor_prefs.folding);
551 ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "menu_unfold_all1"), editor_prefs.folding);
552 ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "separator22"), editor_prefs.folding);
556 static void insert_include_items(GtkMenu *me, GtkMenu *mp, gchar **includes, gchar *label)
558 guint i = 0;
559 GtkWidget *tmp_menu;
560 GtkWidget *tmp_popup;
561 GtkWidget *edit_menu, *edit_menu_item;
562 GtkWidget *popup_menu, *popup_menu_item;
564 edit_menu = gtk_menu_new();
565 popup_menu = gtk_menu_new();
566 edit_menu_item = gtk_menu_item_new_with_label(label);
567 popup_menu_item = gtk_menu_item_new_with_label(label);
568 gtk_menu_item_set_submenu(GTK_MENU_ITEM(edit_menu_item), edit_menu);
569 gtk_menu_item_set_submenu(GTK_MENU_ITEM(popup_menu_item), popup_menu);
571 while (includes[i] != NULL)
573 tmp_menu = gtk_menu_item_new_with_label(includes[i]);
574 tmp_popup = gtk_menu_item_new_with_label(includes[i]);
575 gtk_container_add(GTK_CONTAINER(edit_menu), tmp_menu);
576 gtk_container_add(GTK_CONTAINER(popup_menu), tmp_popup);
577 g_signal_connect(tmp_menu, "activate",
578 G_CALLBACK(on_menu_insert_include_activate), (gpointer) includes[i]);
579 g_signal_connect(tmp_popup, "activate",
580 G_CALLBACK(on_insert_include_activate), (gpointer) includes[i]);
581 i++;
583 gtk_widget_show_all(edit_menu_item);
584 gtk_widget_show_all(popup_menu_item);
585 gtk_container_add(GTK_CONTAINER(me), edit_menu_item);
586 gtk_container_add(GTK_CONTAINER(mp), popup_menu_item);
590 void ui_create_insert_menu_items(void)
592 GtkMenu *menu_edit = GTK_MENU(ui_lookup_widget(main_widgets.window, "insert_include2_menu"));
593 GtkMenu *menu_popup = GTK_MENU(ui_lookup_widget(main_widgets.editor_menu, "insert_include1_menu"));
594 GtkWidget *blank;
595 const gchar *c_includes_stdlib[] = {
596 "assert.h", "ctype.h", "errno.h", "float.h", "limits.h", "locale.h", "math.h", "setjmp.h",
597 "signal.h", "stdarg.h", "stddef.h", "stdio.h", "stdlib.h", "string.h", "time.h", NULL
599 const gchar *c_includes_c99[] = {
600 "complex.h", "fenv.h", "inttypes.h", "iso646.h", "stdbool.h", "stdint.h",
601 "tgmath.h", "wchar.h", "wctype.h", NULL
603 const gchar *c_includes_cpp[] = {
604 "cstdio", "cstring", "cctype", "cmath", "ctime", "cstdlib", "cstdarg", NULL
606 const gchar *c_includes_cppstdlib[] = {
607 "iostream", "fstream", "iomanip", "sstream", "exception", "stdexcept",
608 "memory", "locale", NULL
610 const gchar *c_includes_stl[] = {
611 "bitset", "deque", "list", "map", "set", "queue", "stack", "vector", "algorithm",
612 "iterator", "functional", "string", "complex", "valarray", NULL
615 blank = gtk_menu_item_new_with_label("#include \"...\"");
616 gtk_container_add(GTK_CONTAINER(menu_edit), blank);
617 gtk_widget_show(blank);
618 g_signal_connect(blank, "activate", G_CALLBACK(on_menu_insert_include_activate),
619 (gpointer) "blank");
620 blank = gtk_separator_menu_item_new ();
621 gtk_container_add(GTK_CONTAINER(menu_edit), blank);
622 gtk_widget_show(blank);
624 blank = gtk_menu_item_new_with_label("#include \"...\"");
625 gtk_container_add(GTK_CONTAINER(menu_popup), blank);
626 gtk_widget_show(blank);
627 g_signal_connect(blank, "activate", G_CALLBACK(on_insert_include_activate),
628 (gpointer) "blank");
629 blank = gtk_separator_menu_item_new();
630 gtk_container_add(GTK_CONTAINER(menu_popup), blank);
631 gtk_widget_show(blank);
633 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_stdlib, _("C Standard Library"));
634 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_c99, _("ISO C99"));
635 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_cpp, _("C++ (C Standard Library)"));
636 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_cppstdlib, _("C++ Standard Library"));
637 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_stl, _("C++ STL"));
641 static void insert_date_items(GtkMenu *me, GtkMenu *mp, gchar *label)
643 GtkWidget *item;
645 item = gtk_menu_item_new_with_mnemonic(label);
646 gtk_container_add(GTK_CONTAINER(me), item);
647 gtk_widget_show(item);
648 g_signal_connect(item, "activate", G_CALLBACK(on_menu_insert_date_activate), label);
650 item = gtk_menu_item_new_with_mnemonic(label);
651 gtk_container_add(GTK_CONTAINER(mp), item);
652 gtk_widget_show(item);
653 g_signal_connect(item, "activate", G_CALLBACK(on_insert_date_activate), label);
657 void ui_create_insert_date_menu_items(void)
659 GtkMenu *menu_edit = GTK_MENU(ui_lookup_widget(main_widgets.window, "insert_date1_menu"));
660 GtkMenu *menu_popup = GTK_MENU(ui_lookup_widget(main_widgets.editor_menu, "insert_date2_menu"));
661 GtkWidget *item;
662 gchar *str;
664 insert_date_items(menu_edit, menu_popup, _("dd.mm.yyyy"));
665 insert_date_items(menu_edit, menu_popup, _("mm.dd.yyyy"));
666 insert_date_items(menu_edit, menu_popup, _("yyyy/mm/dd"));
668 item = gtk_separator_menu_item_new();
669 gtk_container_add(GTK_CONTAINER(menu_edit), item);
670 gtk_widget_show(item);
671 item = gtk_separator_menu_item_new();
672 gtk_container_add(GTK_CONTAINER(menu_popup), item);
673 gtk_widget_show(item);
675 insert_date_items(menu_edit, menu_popup, _("dd.mm.yyyy hh:mm:ss"));
676 insert_date_items(menu_edit, menu_popup, _("mm.dd.yyyy hh:mm:ss"));
677 insert_date_items(menu_edit, menu_popup, _("yyyy/mm/dd hh:mm:ss"));
679 item = gtk_separator_menu_item_new();
680 gtk_container_add(GTK_CONTAINER(menu_edit), item);
681 gtk_widget_show(item);
682 item = gtk_separator_menu_item_new();
683 gtk_container_add(GTK_CONTAINER(menu_popup), item);
684 gtk_widget_show(item);
686 str = _("_Use Custom Date Format");
687 item = gtk_menu_item_new_with_mnemonic(str);
688 gtk_container_add(GTK_CONTAINER(menu_edit), item);
689 gtk_widget_show(item);
690 g_signal_connect(item, "activate", G_CALLBACK(on_menu_insert_date_activate), str);
691 ui_hookup_widget(main_widgets.window, item, "insert_date_custom1");
693 item = gtk_menu_item_new_with_mnemonic(str);
694 gtk_container_add(GTK_CONTAINER(menu_popup), item);
695 gtk_widget_show(item);
696 g_signal_connect(item, "activate", G_CALLBACK(on_insert_date_activate), str);
697 ui_hookup_widget(main_widgets.editor_menu, item, "insert_date_custom2");
699 insert_date_items(menu_edit, menu_popup, _("_Set Custom Date Format"));
703 void ui_save_buttons_toggle(gboolean enable)
705 guint i;
706 gboolean dirty_tabs = FALSE;
708 if (ui_prefs.allow_always_save)
709 enable = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)) > 0;
711 ui_widget_set_sensitive(widgets.save_buttons[0], enable);
712 ui_widget_set_sensitive(widgets.save_buttons[1], enable);
714 /* save all menu item and tool button */
715 for (i = 0; i < documents_array->len; i++)
717 /* check whether there are files where changes were made and if there are some,
718 * we need the save all button / item */
719 if (documents[i]->is_valid && documents[i]->changed)
721 dirty_tabs = TRUE;
722 break;
726 ui_widget_set_sensitive(widgets.save_buttons[2], dirty_tabs);
727 ui_widget_set_sensitive(widgets.save_buttons[3], dirty_tabs);
731 #define add_doc_widget(widget_name) \
732 g_ptr_array_add(widgets.document_buttons, ui_lookup_widget(main_widgets.window, widget_name))
734 #define add_doc_toolitem(widget_name) \
735 g_ptr_array_add(widgets.document_buttons, toolbar_get_action_by_name(widget_name))
737 static void init_document_widgets(void)
739 widgets.document_buttons = g_ptr_array_new();
741 /* Cache the document-sensitive widgets so we don't have to keep looking them up
742 * when using ui_document_buttons_update(). */
743 add_doc_widget("menu_close1");
744 add_doc_widget("close_other_documents1");
745 add_doc_widget("menu_change_font1");
746 add_doc_widget("menu_close_all1");
747 add_doc_widget("menu_save1");
748 add_doc_widget("menu_save_all1");
749 add_doc_widget("menu_save_as1");
750 add_doc_widget("menu_count_words1");
751 add_doc_widget("menu_build1");
752 add_doc_widget("add_comments1");
753 add_doc_widget("menu_paste1");
754 add_doc_widget("menu_undo2");
755 add_doc_widget("preferences2");
756 add_doc_widget("menu_reload1");
757 add_doc_widget("menu_document1");
758 add_doc_widget("menu_choose_color1");
759 add_doc_widget("menu_color_schemes");
760 add_doc_widget("menu_markers_margin1");
761 add_doc_widget("menu_linenumber_margin1");
762 add_doc_widget("menu_show_white_space1");
763 add_doc_widget("menu_show_line_endings1");
764 add_doc_widget("menu_show_indentation_guides1");
765 add_doc_widget("menu_zoom_in1");
766 add_doc_widget("menu_zoom_out1");
767 add_doc_widget("normal_size1");
768 add_doc_widget("treeview6");
769 add_doc_widget("print1");
770 add_doc_widget("menu_reload_as1");
771 add_doc_widget("menu_select_all1");
772 add_doc_widget("insert_date1");
773 add_doc_widget("insert_alternative_white_space1");
774 add_doc_widget("menu_format1");
775 add_doc_widget("commands2");
776 add_doc_widget("menu_open_selected_file1");
777 add_doc_widget("page_setup1");
778 add_doc_widget("find1");
779 add_doc_widget("find_next1");
780 add_doc_widget("find_previous1");
781 add_doc_widget("go_to_next_marker1");
782 add_doc_widget("go_to_previous_marker1");
783 add_doc_widget("replace1");
784 add_doc_widget("find_nextsel1");
785 add_doc_widget("find_prevsel1");
786 add_doc_widget("find_usage1");
787 add_doc_widget("find_document_usage1");
788 add_doc_widget("mark_all1");
789 add_doc_widget("go_to_line1");
790 add_doc_widget("goto_tag_definition1");
791 add_doc_widget("goto_tag_declaration1");
792 add_doc_widget("reset_indentation1");
793 add_doc_toolitem("Close");
794 add_doc_toolitem("CloseAll");
795 add_doc_toolitem("Search");
796 add_doc_toolitem("SearchEntry");
797 add_doc_toolitem("ZoomIn");
798 add_doc_toolitem("ZoomOut");
799 add_doc_toolitem("Indent");
800 add_doc_toolitem("UnIndent");
801 add_doc_toolitem("Cut");
802 add_doc_toolitem("Copy");
803 add_doc_toolitem("Paste");
804 add_doc_toolitem("Delete");
805 add_doc_toolitem("Save");
806 add_doc_toolitem("SaveAs");
807 add_doc_toolitem("SaveAll");
808 add_doc_toolitem("Compile");
809 add_doc_toolitem("Run");
810 add_doc_toolitem("Reload");
811 add_doc_toolitem("Color");
812 add_doc_toolitem("Goto");
813 add_doc_toolitem("GotoEntry");
814 add_doc_toolitem("Replace");
815 add_doc_toolitem("Print");
819 void ui_document_buttons_update(void)
821 guint i;
822 gboolean enable = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)) > 0;
824 for (i = 0; i < widgets.document_buttons->len; i++)
826 GtkWidget *widget = g_ptr_array_index(widgets.document_buttons, i);
827 if (GTK_IS_ACTION(widget))
828 gtk_action_set_sensitive(GTK_ACTION(widget), enable);
829 else
830 ui_widget_set_sensitive(widget, enable);
835 static void on_doc_sensitive_widget_destroy(GtkWidget *widget, G_GNUC_UNUSED gpointer user_data)
837 g_ptr_array_remove_fast(widgets.document_buttons, widget);
841 /** Adds a widget to the list of widgets that should be set sensitive/insensitive
842 * when some documents are present/no documents are open.
843 * It will be removed when the widget is destroyed.
844 * @param widget The widget to add.
846 * @since 0.15
848 void ui_add_document_sensitive(GtkWidget *widget)
850 gboolean enable = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)) > 0;
852 ui_widget_set_sensitive(widget, enable);
854 g_ptr_array_add(widgets.document_buttons, widget);
855 g_signal_connect(widget, "destroy", G_CALLBACK(on_doc_sensitive_widget_destroy), NULL);
859 void ui_widget_show_hide(GtkWidget *widget, gboolean show)
861 if (show)
863 gtk_widget_show(widget);
865 else
867 gtk_widget_hide(widget);
872 void ui_sidebar_show_hide(void)
874 GtkWidget *widget;
876 /* check that there are no other notebook pages before hiding the sidebar completely
877 * other pages could be e.g. the file browser plugin */
878 if (! interface_prefs.sidebar_openfiles_visible && ! interface_prefs.sidebar_symbol_visible &&
879 gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.sidebar_notebook)) <= 2)
881 ui_prefs.sidebar_visible = FALSE;
884 widget = ui_lookup_widget(main_widgets.window, "menu_show_sidebar1");
885 if (ui_prefs.sidebar_visible != gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
887 ignore_callback = TRUE;
888 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), ui_prefs.sidebar_visible);
889 ignore_callback = FALSE;
892 ui_widget_show_hide(main_widgets.sidebar_notebook, ui_prefs.sidebar_visible);
894 ui_widget_show_hide(gtk_notebook_get_nth_page(
895 GTK_NOTEBOOK(main_widgets.sidebar_notebook), 0), interface_prefs.sidebar_symbol_visible);
896 ui_widget_show_hide(gtk_notebook_get_nth_page(
897 GTK_NOTEBOOK(main_widgets.sidebar_notebook), 1), interface_prefs.sidebar_openfiles_visible);
901 void ui_document_show_hide(GeanyDocument *doc)
903 const gchar *widget_name;
904 GtkWidget *item;
905 const GeanyIndentPrefs *iprefs;
907 g_return_if_fail(doc == NULL || doc->is_valid);
909 if (doc == NULL)
910 doc = document_get_current();
912 if (doc == NULL)
913 return;
915 ignore_callback = TRUE;
917 gtk_check_menu_item_set_active(
918 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_line_wrapping1")),
919 doc->editor->line_wrapping);
921 gtk_check_menu_item_set_active(
922 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "line_breaking1")),
923 doc->editor->line_breaking);
925 iprefs = editor_get_indent_prefs(doc->editor);
927 item = ui_lookup_widget(main_widgets.window, "menu_use_auto_indentation1");
928 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), doc->editor->auto_indent);
930 switch (iprefs->type)
932 case GEANY_INDENT_TYPE_SPACES:
933 widget_name = "spaces1"; break;
934 case GEANY_INDENT_TYPE_TABS:
935 widget_name = "tabs1"; break;
936 case GEANY_INDENT_TYPE_BOTH:
937 default:
938 widget_name = "tabs_and_spaces1"; break;
940 item = ui_lookup_widget(main_widgets.window, widget_name);
941 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
943 if (iprefs->width >= 1 && iprefs->width <= 8)
945 gchar *name;
947 name = g_strdup_printf("indent_width_%d", iprefs->width);
948 item = ui_lookup_widget(main_widgets.window, name);
949 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
950 g_free(name);
953 gtk_check_menu_item_set_active(
954 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "set_file_readonly1")),
955 doc->readonly);
957 item = ui_lookup_widget(main_widgets.window, "menu_write_unicode_bom1");
958 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), doc->has_bom);
959 ui_widget_set_sensitive(item, encodings_is_unicode_charset(doc->encoding));
961 switch (sci_get_eol_mode(doc->editor->sci))
963 case SC_EOL_CR: widget_name = "cr"; break;
964 case SC_EOL_LF: widget_name = "lf"; break;
965 default: widget_name = "crlf"; break;
967 gtk_check_menu_item_set_active(
968 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, widget_name)), TRUE);
970 encodings_select_radio_item(doc->encoding);
971 filetypes_select_radio_item(doc->file_type);
973 ignore_callback = FALSE;
977 void ui_set_search_entry_background(GtkWidget *widget, gboolean success)
979 gtk_widget_set_name(widget, success ? NULL : "geany-search-entry-no-match");
983 static void recent_create_menu(GeanyRecentFiles *grf)
985 GtkWidget *tmp;
986 guint i, len;
987 gchar *filename;
989 len = MIN(file_prefs.mru_length, g_queue_get_length(grf->recent_queue));
990 for (i = 0; i < len; i++)
992 filename = g_queue_peek_nth(grf->recent_queue, i);
993 /* create menu item for the recent files menu in the menu bar */
994 tmp = gtk_menu_item_new_with_label(filename);
995 gtk_widget_show(tmp);
996 gtk_container_add(GTK_CONTAINER(grf->menubar), tmp);
997 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
998 /* create menu item for the recent files menu in the toolbar */
999 if (grf->toolbar != NULL)
1001 tmp = gtk_menu_item_new_with_label(filename);
1002 gtk_widget_show(tmp);
1003 gtk_container_add(GTK_CONTAINER(grf->toolbar), tmp);
1004 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
1010 static GeanyRecentFiles *recent_get_recent_files(void)
1012 static GeanyRecentFiles grf = { RECENT_FILE_FILE, NULL, NULL, NULL, NULL };
1014 if (G_UNLIKELY(grf.recent_queue == NULL))
1016 grf.recent_queue = ui_prefs.recent_queue;
1017 grf.menubar = ui_widgets.recent_files_menu_menubar;
1018 grf.toolbar = geany_menu_button_action_get_menu(GEANY_MENU_BUTTON_ACTION(
1019 toolbar_get_action_by_name("Open")));
1020 grf.activate_cb = recent_file_activate_cb;
1022 return &grf;
1026 static GeanyRecentFiles *recent_get_recent_projects(void)
1028 static GeanyRecentFiles grf = { RECENT_FILE_PROJECT, NULL, NULL, NULL, NULL };
1030 if (G_UNLIKELY(grf.recent_queue == NULL))
1032 grf.recent_queue = ui_prefs.recent_projects_queue;
1033 grf.menubar = ui_widgets.recent_projects_menu_menubar;
1034 grf.toolbar = NULL;
1035 grf.activate_cb = recent_project_activate_cb;
1037 return &grf;
1041 void ui_create_recent_menus(void)
1043 recent_create_menu(recent_get_recent_files());
1044 recent_create_menu(recent_get_recent_projects());
1048 static void recent_file_activate_cb(GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
1050 gchar *utf8_filename = ui_menu_item_get_text(menuitem);
1051 gchar *locale_filename = utils_get_locale_from_utf8(utf8_filename);
1053 if (document_open_file(locale_filename, FALSE, NULL, NULL) != NULL)
1054 recent_file_loaded(utf8_filename, recent_get_recent_files());
1056 g_free(locale_filename);
1057 g_free(utf8_filename);
1061 static void recent_project_activate_cb(GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
1063 gchar *utf8_filename = ui_menu_item_get_text(menuitem);
1064 gchar *locale_filename = utils_get_locale_from_utf8(utf8_filename);
1066 if (project_ask_close() && project_load_file_with_session(locale_filename))
1067 recent_file_loaded(utf8_filename, recent_get_recent_projects());
1069 g_free(locale_filename);
1070 g_free(utf8_filename);
1074 static void add_recent_file(const gchar *utf8_filename, GeanyRecentFiles *grf,
1075 const GtkRecentData *rdata)
1077 if (g_queue_find_custom(grf->recent_queue, utf8_filename, (GCompareFunc) strcmp) == NULL)
1080 if (grf->type == RECENT_FILE_FILE && rdata)
1082 GtkRecentManager *manager = gtk_recent_manager_get_default();
1083 gchar *uri = g_filename_to_uri(utf8_filename, NULL, NULL);
1084 if (uri != NULL)
1086 gtk_recent_manager_add_full(manager, uri, rdata);
1087 g_free(uri);
1091 g_queue_push_head(grf->recent_queue, g_strdup(utf8_filename));
1092 if (g_queue_get_length(grf->recent_queue) > file_prefs.mru_length)
1094 g_free(g_queue_pop_tail(grf->recent_queue));
1096 update_recent_menu(grf);
1098 /* filename already in recent list */
1099 else
1100 recent_file_loaded(utf8_filename, grf);
1104 void ui_add_recent_document(GeanyDocument *doc)
1106 /* what are the groups for actually? */
1107 static const gchar *groups[2] = {
1108 "geany",
1109 NULL
1111 GtkRecentData rdata;
1113 /* Prepare the data for gtk_recent_manager_add_full() */
1114 rdata.display_name = NULL;
1115 rdata.description = NULL;
1116 rdata.mime_type = doc->file_type->mime_type;
1117 /* if we ain't got no mime-type, fallback to plain text */
1118 if (! rdata.mime_type)
1119 rdata.mime_type = (gchar *) "text/plain";
1120 rdata.app_name = (gchar *) "geany";
1121 rdata.app_exec = (gchar *) "geany %u";
1122 rdata.groups = (gchar **) groups;
1123 rdata.is_private = FALSE;
1125 add_recent_file(doc->file_name, recent_get_recent_files(), &rdata);
1129 void ui_add_recent_project_file(const gchar *utf8_filename)
1131 add_recent_file(utf8_filename, recent_get_recent_projects(), NULL);
1135 /* Returns: newly allocated string with the UTF-8 menu text. */
1136 gchar *ui_menu_item_get_text(GtkMenuItem *menu_item)
1138 const gchar *text = NULL;
1140 if (gtk_bin_get_child(GTK_BIN(menu_item)))
1142 GtkWidget *child = gtk_bin_get_child(GTK_BIN(menu_item));
1144 if (GTK_IS_LABEL(child))
1145 text = gtk_label_get_text(GTK_LABEL(child));
1147 /* GTK owns text so it's much safer to return a copy of it in case the memory is reallocated */
1148 return g_strdup(text);
1152 static gint find_recent_file_item(gconstpointer list_data, gconstpointer user_data)
1154 gchar *menu_text = ui_menu_item_get_text(GTK_MENU_ITEM(list_data));
1155 gint result;
1157 if (utils_str_equal(menu_text, user_data))
1158 result = 0;
1159 else
1160 result = 1;
1162 g_free(menu_text);
1163 return result;
1167 static void recent_file_loaded(const gchar *utf8_filename, GeanyRecentFiles *grf)
1169 GList *item, *children;
1170 void *data;
1171 GtkWidget *tmp;
1173 /* first reorder the queue */
1174 item = g_queue_find_custom(grf->recent_queue, utf8_filename, (GCompareFunc) strcmp);
1175 g_return_if_fail(item != NULL);
1177 data = item->data;
1178 g_queue_remove(grf->recent_queue, data);
1179 g_queue_push_head(grf->recent_queue, data);
1181 /* remove the old menuitem for the filename */
1182 children = gtk_container_get_children(GTK_CONTAINER(grf->menubar));
1183 item = g_list_find_custom(children, utf8_filename, (GCompareFunc) find_recent_file_item);
1184 if (item != NULL)
1185 gtk_widget_destroy(GTK_WIDGET(item->data));
1186 g_list_free(children);
1188 if (grf->toolbar != NULL)
1190 children = gtk_container_get_children(GTK_CONTAINER(grf->toolbar));
1191 item = g_list_find_custom(children, utf8_filename, (GCompareFunc) find_recent_file_item);
1192 if (item != NULL)
1193 gtk_widget_destroy(GTK_WIDGET(item->data));
1194 g_list_free(children);
1196 /* now prepend a new menuitem for the filename,
1197 * first for the recent files menu in the menu bar */
1198 tmp = gtk_menu_item_new_with_label(utf8_filename);
1199 gtk_widget_show(tmp);
1200 gtk_menu_shell_prepend(GTK_MENU_SHELL(grf->menubar), tmp);
1201 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
1202 /* then for the recent files menu in the tool bar */
1203 if (grf->toolbar != NULL)
1205 tmp = gtk_menu_item_new_with_label(utf8_filename);
1206 gtk_widget_show(tmp);
1207 gtk_container_add(GTK_CONTAINER(grf->toolbar), tmp);
1208 /* this is a bit ugly, but we need to use gtk_container_add(). Using
1209 * gtk_menu_shell_prepend() doesn't emit GtkContainer's "add" signal which we need in
1210 * GeanyMenubuttonAction */
1211 gtk_menu_reorder_child(GTK_MENU(grf->toolbar), tmp, 0);
1212 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
1217 static void update_recent_menu(GeanyRecentFiles *grf)
1219 GtkWidget *tmp;
1220 gchar *filename;
1221 GList *children, *item;
1223 filename = g_queue_peek_head(grf->recent_queue);
1225 /* clean the MRU list before adding an item (menubar) */
1226 children = gtk_container_get_children(GTK_CONTAINER(grf->menubar));
1227 if (g_list_length(children) > file_prefs.mru_length - 1)
1229 item = g_list_nth(children, file_prefs.mru_length - 1);
1230 while (item != NULL)
1232 if (GTK_IS_MENU_ITEM(item->data))
1233 gtk_widget_destroy(GTK_WIDGET(item->data));
1234 item = g_list_next(item);
1237 g_list_free(children);
1239 /* create item for the menu bar menu */
1240 tmp = gtk_menu_item_new_with_label(filename);
1241 gtk_widget_show(tmp);
1242 gtk_menu_shell_prepend(GTK_MENU_SHELL(grf->menubar), tmp);
1243 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
1245 /* clean the MRU list before adding an item (toolbar) */
1246 if (grf->toolbar != NULL)
1248 children = gtk_container_get_children(GTK_CONTAINER(grf->toolbar));
1249 if (g_list_length(children) > file_prefs.mru_length - 1)
1251 item = g_list_nth(children, file_prefs.mru_length - 1);
1252 while (item != NULL)
1254 if (GTK_IS_MENU_ITEM(item->data))
1255 gtk_widget_destroy(GTK_WIDGET(item->data));
1256 item = g_list_next(item);
1259 g_list_free(children);
1261 /* create item for the tool bar menu */
1262 tmp = gtk_menu_item_new_with_label(filename);
1263 gtk_widget_show(tmp);
1264 gtk_container_add(GTK_CONTAINER(grf->toolbar), tmp);
1265 gtk_menu_reorder_child(GTK_MENU(grf->toolbar), tmp, 0);
1266 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
1271 void ui_toggle_editor_features(GeanyUIEditorFeatures feature)
1273 guint i;
1275 foreach_document (i)
1277 GeanyDocument *doc = documents[i];
1279 switch (feature)
1281 case GEANY_EDITOR_SHOW_MARKERS_MARGIN:
1282 sci_set_symbol_margin(doc->editor->sci, editor_prefs.show_markers_margin);
1283 break;
1284 case GEANY_EDITOR_SHOW_LINE_NUMBERS:
1285 sci_set_line_numbers(doc->editor->sci, editor_prefs.show_linenumber_margin, 0);
1286 break;
1287 case GEANY_EDITOR_SHOW_WHITE_SPACE:
1288 sci_set_visible_white_spaces(doc->editor->sci, editor_prefs.show_white_space);
1289 break;
1290 case GEANY_EDITOR_SHOW_LINE_ENDINGS:
1291 sci_set_visible_eols(doc->editor->sci, editor_prefs.show_line_endings);
1292 break;
1293 case GEANY_EDITOR_SHOW_INDENTATION_GUIDES:
1294 editor_set_indentation_guides(doc->editor);
1295 break;
1301 void ui_update_view_editor_menu_items(void)
1303 ignore_callback = TRUE;
1304 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_markers_margin1")), editor_prefs.show_markers_margin);
1305 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_linenumber_margin1")), editor_prefs.show_linenumber_margin);
1306 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);
1307 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);
1308 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);
1309 ignore_callback = FALSE;
1313 /** Creates a GNOME HIG-style frame (with no border and indented child alignment).
1314 * @param label_text The label text.
1315 * @param alignment An address to store the alignment widget pointer.
1316 * @return The frame widget, setting the alignment container for packing child widgets. */
1317 GtkWidget *ui_frame_new_with_alignment(const gchar *label_text, GtkWidget **alignment)
1319 GtkWidget *label, *align;
1320 GtkWidget *frame = gtk_frame_new(NULL);
1322 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
1324 align = gtk_alignment_new(0.5, 0.5, 1, 1);
1325 gtk_container_add(GTK_CONTAINER(frame), align);
1326 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 0);
1328 label = ui_label_new_bold(label_text);
1329 gtk_frame_set_label_widget(GTK_FRAME(frame), label);
1331 *alignment = align;
1332 return frame;
1336 /** Makes a fixed border for dialogs without increasing the button box border.
1337 * @param dialog The parent container for the @c GtkVBox.
1338 * @return The packed @c GtkVBox. */
1339 GtkWidget *ui_dialog_vbox_new(GtkDialog *dialog)
1341 GtkWidget *vbox = gtk_vbox_new(FALSE, 12); /* need child vbox to set a separate border. */
1343 gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
1344 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), vbox, TRUE, TRUE, 0);
1345 return vbox;
1349 static GtkWidget *dialog_get_widget_for_response(GtkDialog *dialog, gint response_id)
1351 #if GTK_CHECK_VERSION(2, 20, 0)
1352 return gtk_dialog_get_widget_for_response(dialog, response_id);
1353 #else /* GTK < 2.20 */
1354 /* base logic stolen from GTK */
1355 GtkWidget *action_area = gtk_dialog_get_action_area(dialog);
1356 GtkWidget *widget = NULL;
1357 GList *children, *node;
1359 children = gtk_container_get_children(GTK_CONTAINER(action_area));
1360 for (node = children; node && ! widget; node = node->next)
1362 if (gtk_dialog_get_response_for_widget(dialog, node->data) == response_id)
1363 widget = node->data;
1365 g_list_free(children);
1367 return widget;
1368 #endif
1372 /* Reorders a dialog's buttons
1373 * @param dialog A dialog
1374 * @param response First response ID to reorder
1375 * @param ... more response IDs, terminated by -1
1377 * Like gtk_dialog_set_alternative_button_order(), but reorders the default
1378 * buttons layout, not the alternative one. This is useful if you e.g. added a
1379 * button to a dialog which already had some and need yours not to be on the
1380 * end.
1382 /* Heavily based on gtk_dialog_set_alternative_button_order().
1383 * This relies on the action area to be a GtkBox, but although not documented
1384 * the API expose it to be a GtkHButtonBox though GtkBuilder, so it should be
1385 * fine */
1386 void ui_dialog_set_primary_button_order(GtkDialog *dialog, gint response, ...)
1388 va_list ap;
1389 GtkWidget *action_area = gtk_dialog_get_action_area(dialog);
1390 gint position;
1392 va_start(ap, response);
1393 for (position = 0; response != -1; position++)
1395 GtkWidget *child = dialog_get_widget_for_response(dialog, response);
1396 if (child)
1397 gtk_box_reorder_child(GTK_BOX(action_area), child, position);
1398 else
1399 g_warning("%s: no child button with response id %d.", G_STRFUNC, response);
1401 response = va_arg(ap, gint);
1403 va_end(ap);
1407 /** Creates a @c GtkButton with custom text and a stock image similar to
1408 * @c gtk_button_new_from_stock().
1409 * @param stock_id A @c GTK_STOCK_NAME string.
1410 * @param text Button label text, can include mnemonics.
1411 * @return The new @c GtkButton.
1413 GtkWidget *ui_button_new_with_image(const gchar *stock_id, const gchar *text)
1415 GtkWidget *image, *button;
1417 button = gtk_button_new_with_mnemonic(text);
1418 gtk_widget_show(button);
1419 image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON);
1420 gtk_button_set_image(GTK_BUTTON(button), image);
1421 /* note: image is shown by gtk */
1422 return button;
1426 /** Creates a @c GtkImageMenuItem with a stock image and a custom label.
1427 * @param stock_id Stock image ID, e.g. @c GTK_STOCK_OPEN.
1428 * @param label Menu item label, can include mnemonics.
1429 * @return The new @c GtkImageMenuItem.
1431 * @since 0.16
1433 GtkWidget *
1434 ui_image_menu_item_new(const gchar *stock_id, const gchar *label)
1436 GtkWidget *item = gtk_image_menu_item_new_with_mnemonic(label);
1437 GtkWidget *image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_MENU);
1439 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
1440 gtk_widget_show(image);
1441 return item;
1445 static void entry_clear_icon_release_cb(GtkEntry *entry, gint icon_pos,
1446 GdkEvent *event, gpointer data)
1448 if (event->button.button == 1 && icon_pos == 1)
1450 gtk_entry_set_text(entry, "");
1451 gtk_widget_grab_focus(GTK_WIDGET(entry));
1456 /** Adds a small clear icon to the right end of the passed @a entry.
1457 * A callback to clear the contents of the GtkEntry is automatically added.
1459 * @param entry The GtkEntry object to which the icon should be attached.
1461 * @since 0.16
1463 void ui_entry_add_clear_icon(GtkEntry *entry)
1465 g_object_set(entry, "secondary-icon-stock", GTK_STOCK_CLEAR,
1466 "secondary-icon-activatable", TRUE, NULL);
1467 g_signal_connect(entry, "icon-release", G_CALLBACK(entry_clear_icon_release_cb), NULL);
1471 /* Adds a :activate-backwards signal emitted by default when <Shift>Return is pressed */
1472 void ui_entry_add_activate_backward_signal(GtkEntry *entry)
1474 static gboolean installed = FALSE;
1476 g_return_if_fail(GTK_IS_ENTRY(entry));
1478 if (G_UNLIKELY(! installed))
1480 GtkBindingSet *binding_set;
1482 installed = TRUE;
1484 /* try to handle the unexpected case where GTK would already have installed the signal */
1485 if (g_signal_lookup("activate-backward", G_TYPE_FROM_INSTANCE(entry)))
1487 g_warning("Signal GtkEntry:activate-backward is unexpectedly already installed");
1488 return;
1491 g_signal_new("activate-backward", G_TYPE_FROM_INSTANCE(entry),
1492 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, 0, NULL, NULL,
1493 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
1494 binding_set = gtk_binding_set_by_class(GTK_ENTRY_GET_CLASS(entry));
1495 gtk_binding_entry_add_signal(binding_set, GDK_Return, GDK_SHIFT_MASK, "activate-backward", 0);
1500 static void add_to_size_group(GtkWidget *widget, gpointer size_group)
1502 g_return_if_fail(GTK_IS_SIZE_GROUP(size_group));
1503 gtk_size_group_add_widget(GTK_SIZE_GROUP(size_group), widget);
1507 /* Copies the spacing and layout of the master GtkHButtonBox and synchronises
1508 * the width of each button box's children.
1509 * Should be called after all child widgets have been packed. */
1510 void ui_hbutton_box_copy_layout(GtkButtonBox *master, GtkButtonBox *copy)
1512 GtkSizeGroup *size_group;
1514 gtk_box_set_spacing(GTK_BOX(copy), 10);
1515 gtk_button_box_set_layout(copy, gtk_button_box_get_layout(master));
1517 /* now we need to put the widest widget from each button box in a size group,
1518 * but we don't know the width before they are drawn, and for different label
1519 * translations the widest widget can vary, so we just add all widgets. */
1520 size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1521 gtk_container_foreach(GTK_CONTAINER(master), add_to_size_group, size_group);
1522 gtk_container_foreach(GTK_CONTAINER(copy), add_to_size_group, size_group);
1523 g_object_unref(size_group);
1527 static gboolean tree_model_find_text(GtkTreeModel *model,
1528 GtkTreeIter *iter, gint column, const gchar *text)
1530 gchar *combo_text;
1531 gboolean found = FALSE;
1533 if (gtk_tree_model_get_iter_first(model, iter))
1537 gtk_tree_model_get(model, iter, 0, &combo_text, -1);
1538 found = utils_str_equal(combo_text, text);
1539 g_free(combo_text);
1541 if (found)
1542 return TRUE;
1544 while (gtk_tree_model_iter_next(model, iter));
1546 return FALSE;
1550 /** Prepends @a text to the drop down list, removing a duplicate element in
1551 * the list if found. Also ensures there are <= @a history_len elements.
1552 * @param combo_entry .
1553 * @param text Text to add, or @c NULL for current entry text.
1554 * @param history_len Max number of items, or @c 0 for default. */
1555 void ui_combo_box_add_to_history(GtkComboBoxText *combo_entry,
1556 const gchar *text, gint history_len)
1558 GtkComboBox *combo = GTK_COMBO_BOX(combo_entry);
1559 GtkTreeModel *model;
1560 GtkTreeIter iter;
1561 GtkTreePath *path;
1563 if (history_len <= 0)
1564 history_len = 10;
1565 if (!text)
1566 text = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))));
1568 model = gtk_combo_box_get_model(combo);
1570 if (tree_model_find_text(model, &iter, 0, text))
1572 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1574 gtk_combo_box_text_prepend_text(combo_entry, text);
1576 /* limit history */
1577 path = gtk_tree_path_new_from_indices(history_len, -1);
1578 if (gtk_tree_model_get_iter(model, &iter, path))
1580 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1582 gtk_tree_path_free(path);
1586 /* Same as gtk_combo_box_text_prepend_text(), except that text is only prepended if it not already
1587 * exists in the combo's model. */
1588 void ui_combo_box_prepend_text_once(GtkComboBoxText *combo, const gchar *text)
1590 GtkTreeModel *model;
1591 GtkTreeIter iter;
1593 model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
1594 if (tree_model_find_text(model, &iter, 0, text))
1595 return; /* don't prepend duplicate */
1597 gtk_combo_box_text_prepend_text(combo, text);
1601 /* Changes the color of the notebook tab text and open files items according to
1602 * document status. */
1603 void ui_update_tab_status(GeanyDocument *doc)
1605 gtk_widget_set_name(doc->priv->tab_label, document_get_status_widget_class(doc));
1607 sidebar_openfiles_update(doc);
1611 static gboolean tree_model_iter_get_next(GtkTreeModel *model, GtkTreeIter *iter,
1612 gboolean down)
1614 GtkTreePath *path;
1615 gboolean result;
1617 if (down)
1618 return gtk_tree_model_iter_next(model, iter);
1620 path = gtk_tree_model_get_path(model, iter);
1621 result = gtk_tree_path_prev(path) && gtk_tree_model_get_iter(model, iter, path);
1622 gtk_tree_path_free(path);
1623 return result;
1627 /* note: the while loop might be more efficient when searching upwards if it
1628 * used tree paths instead of tree iters, but in practice it probably doesn't matter much. */
1629 static gboolean tree_view_find(GtkTreeView *treeview, TVMatchCallback cb, gboolean down)
1631 GtkTreeSelection *treesel;
1632 GtkTreeIter iter;
1633 GtkTreeModel *model;
1635 treesel = gtk_tree_view_get_selection(treeview);
1636 if (gtk_tree_selection_get_selected(treesel, &model, &iter))
1638 /* get the next selected item */
1639 if (! tree_model_iter_get_next(model, &iter, down))
1640 return FALSE; /* no more items */
1642 else /* no selection */
1644 if (! gtk_tree_model_get_iter_first(model, &iter))
1645 return TRUE; /* no items */
1647 while (TRUE)
1649 gtk_tree_selection_select_iter(treesel, &iter);
1650 if (cb(FALSE))
1651 break; /* found next message */
1653 if (! tree_model_iter_get_next(model, &iter, down))
1654 return FALSE; /* no more items */
1656 /* scroll item in view */
1657 if (ui_prefs.msgwindow_visible)
1659 GtkTreePath *path = gtk_tree_model_get_path(
1660 gtk_tree_view_get_model(treeview), &iter);
1662 gtk_tree_view_scroll_to_cell(treeview, path, NULL, TRUE, 0.5, 0.5);
1663 gtk_tree_path_free(path);
1665 return TRUE;
1669 /* Returns FALSE if the treeview has items but no matching next item. */
1670 gboolean ui_tree_view_find_next(GtkTreeView *treeview, TVMatchCallback cb)
1672 return tree_view_find(treeview, cb, TRUE);
1676 /* Returns FALSE if the treeview has items but no matching next item. */
1677 gboolean ui_tree_view_find_previous(GtkTreeView *treeview, TVMatchCallback cb)
1679 return tree_view_find(treeview, cb, FALSE);
1684 * Modifies the font of a widget using gtk_widget_modify_font().
1686 * @param widget The widget.
1687 * @param str The font name as expected by pango_font_description_from_string().
1689 void ui_widget_modify_font_from_string(GtkWidget *widget, const gchar *str)
1691 PangoFontDescription *pfd;
1693 pfd = pango_font_description_from_string(str);
1694 gtk_widget_modify_font(widget, pfd);
1695 pango_font_description_free(pfd);
1699 /** Creates a @c GtkHBox with @a entry packed into it and an open button which runs a
1700 * file chooser, replacing entry text (if successful) with the path returned from the
1701 * @c GtkFileChooser.
1702 * @note @a entry can be the child of an unparented widget, such as @c GtkComboBoxEntry.
1703 * @param title The file chooser dialog title, or @c NULL.
1704 * @param action The mode of the file chooser.
1705 * @param entry Can be an unpacked @c GtkEntry, or the child of an unpacked widget,
1706 * such as @c GtkComboBoxEntry.
1707 * @return The @c GtkHBox.
1709 /* @see ui_setup_open_button_callback(). */
1710 GtkWidget *ui_path_box_new(const gchar *title, GtkFileChooserAction action, GtkEntry *entry)
1712 GtkWidget *vbox, *dirbtn, *openimg, *hbox, *path_entry;
1714 hbox = gtk_hbox_new(FALSE, 6);
1715 path_entry = GTK_WIDGET(entry);
1717 /* prevent path_entry being vertically stretched to the height of dirbtn */
1718 vbox = gtk_vbox_new(FALSE, 0);
1719 if (gtk_widget_get_parent(path_entry)) /* entry->parent may be a GtkComboBoxEntry */
1721 GtkWidget *parent = gtk_widget_get_parent(path_entry);
1723 gtk_box_pack_start(GTK_BOX(vbox), parent, TRUE, FALSE, 0);
1725 else
1726 gtk_box_pack_start(GTK_BOX(vbox), path_entry, TRUE, FALSE, 0);
1728 dirbtn = gtk_button_new();
1729 openimg = gtk_image_new_from_stock(GTK_STOCK_OPEN, GTK_ICON_SIZE_BUTTON);
1730 gtk_container_add(GTK_CONTAINER(dirbtn), openimg);
1731 ui_setup_open_button_callback(dirbtn, title, action, entry);
1733 gtk_box_pack_end(GTK_BOX(hbox), dirbtn, FALSE, FALSE, 0);
1734 gtk_box_pack_end(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
1735 return hbox;
1739 static void ui_path_box_open_clicked(GtkButton *button, gpointer user_data);
1742 /* Setup a GtkButton to run a GtkFileChooser, setting entry text if successful.
1743 * title can be NULL.
1744 * action is the file chooser mode to use. */
1745 void ui_setup_open_button_callback(GtkWidget *open_btn, const gchar *title,
1746 GtkFileChooserAction action, GtkEntry *entry)
1748 GtkWidget *path_entry = GTK_WIDGET(entry);
1750 if (title)
1751 g_object_set_data_full(G_OBJECT(open_btn), "title", g_strdup(title),
1752 (GDestroyNotify) g_free);
1753 g_object_set_data(G_OBJECT(open_btn), "action", GINT_TO_POINTER(action));
1754 g_signal_connect(open_btn, "clicked", G_CALLBACK(ui_path_box_open_clicked), path_entry);
1758 #ifndef G_OS_WIN32
1759 static gchar *run_file_chooser(const gchar *title, GtkFileChooserAction action,
1760 const gchar *utf8_path)
1762 GtkWidget *dialog = gtk_file_chooser_dialog_new(title,
1763 GTK_WINDOW(main_widgets.window), action,
1764 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1765 GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);
1766 gchar *locale_path;
1767 gchar *ret_path = NULL;
1769 gtk_widget_set_name(dialog, "GeanyDialog");
1770 locale_path = utils_get_locale_from_utf8(utf8_path);
1771 if (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
1773 if (g_path_is_absolute(locale_path) && g_file_test(locale_path, G_FILE_TEST_IS_DIR))
1774 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), locale_path);
1776 else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
1778 if (g_path_is_absolute(locale_path))
1779 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), locale_path);
1781 g_free(locale_path);
1783 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK)
1785 gchar *dir_locale;
1787 dir_locale = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1788 ret_path = utils_get_utf8_from_locale(dir_locale);
1789 g_free(dir_locale);
1791 gtk_widget_destroy(dialog);
1792 return ret_path;
1794 #endif
1797 static void ui_path_box_open_clicked(GtkButton *button, gpointer user_data)
1799 GtkFileChooserAction action = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "action"));
1800 GtkEntry *entry = user_data;
1801 const gchar *title = g_object_get_data(G_OBJECT(button), "title");
1802 gchar *utf8_path = NULL;
1804 /* TODO: extend for other actions */
1805 g_return_if_fail(action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
1806 action == GTK_FILE_CHOOSER_ACTION_OPEN);
1808 if (title == NULL)
1809 title = (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) ?
1810 _("Select Folder") : _("Select File");
1812 if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
1814 #ifdef G_OS_WIN32
1815 utf8_path = win32_show_file_dialog(GTK_WINDOW(ui_widgets.prefs_dialog), title,
1816 gtk_entry_get_text(GTK_ENTRY(entry)));
1817 #else
1818 utf8_path = run_file_chooser(title, action, gtk_entry_get_text(GTK_ENTRY(entry)));
1819 #endif
1821 else if (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
1823 gchar *path = g_path_get_dirname(gtk_entry_get_text(GTK_ENTRY(entry)));
1824 #ifdef G_OS_WIN32
1825 utf8_path = win32_show_folder_dialog(ui_widgets.prefs_dialog, title,
1826 gtk_entry_get_text(GTK_ENTRY(entry)));
1827 #else
1828 utf8_path = run_file_chooser(title, action, path);
1829 #endif
1830 g_free(path);
1833 if (utf8_path != NULL)
1835 gtk_entry_set_text(GTK_ENTRY(entry), utf8_path);
1836 g_free(utf8_path);
1841 void ui_statusbar_showhide(gboolean state)
1843 /* handle statusbar visibility */
1844 if (state)
1846 gtk_widget_show(ui_widgets.statusbar);
1847 ui_update_statusbar(NULL, -1);
1849 else
1850 gtk_widget_hide(ui_widgets.statusbar);
1854 /** Packs all @c GtkWidgets passed after the row argument into a table, using
1855 * one widget per cell. The first widget is not expanded as the table grows,
1856 * as this is usually a label.
1857 * @param table
1858 * @param row The row number of the table.
1860 void ui_table_add_row(GtkTable *table, gint row, ...)
1862 va_list args;
1863 guint i;
1864 GtkWidget *widget;
1866 va_start(args, row);
1867 for (i = 0; (widget = va_arg(args, GtkWidget*), widget != NULL); i++)
1869 gint options = (i == 0) ? GTK_FILL : GTK_EXPAND | GTK_FILL;
1871 gtk_table_attach(GTK_TABLE(table), widget, i, i + 1, row, row + 1,
1872 options, 0, 0, 0);
1874 va_end(args);
1878 static void on_config_file_clicked(GtkWidget *widget, gpointer user_data)
1880 const gchar *file_name = user_data;
1881 GeanyFiletype *ft = NULL;
1883 if (strstr(file_name, G_DIR_SEPARATOR_S "filetypes."))
1884 ft = filetypes[GEANY_FILETYPES_CONF];
1886 if (g_file_test(file_name, G_FILE_TEST_EXISTS))
1887 document_open_file(file_name, FALSE, ft, NULL);
1888 else
1890 gchar *utf8_filename = utils_get_utf8_from_locale(file_name);
1891 gchar *base_name = g_path_get_basename(file_name);
1892 gchar *global_file = g_build_filename(app->datadir, base_name, NULL);
1893 gchar *global_content = NULL;
1895 /* if the requested file doesn't exist in the user's config dir, try loading the file
1896 * from the global data directory and use its contents for the newly created file */
1897 if (g_file_test(global_file, G_FILE_TEST_EXISTS))
1898 g_file_get_contents(global_file, &global_content, NULL, NULL);
1900 document_new_file(utf8_filename, ft, global_content);
1902 utils_free_pointers(4, utf8_filename, base_name, global_file, global_content, NULL);
1907 static void free_on_closure_notify(gpointer data, GClosure *closure)
1909 g_free(data);
1913 /* @note You should connect to the "document-save" signal yourself to detect
1914 * if the user has just saved the config file, reloading it. */
1915 void ui_add_config_file_menu_item(const gchar *real_path, const gchar *label, GtkContainer *parent)
1917 GtkWidget *item;
1919 if (!parent)
1920 parent = GTK_CONTAINER(widgets.config_files_menu);
1922 if (!label)
1924 gchar *base_name;
1926 base_name = g_path_get_basename(real_path);
1927 item = gtk_menu_item_new_with_label(base_name);
1928 g_free(base_name);
1930 else
1931 item = gtk_menu_item_new_with_mnemonic(label);
1933 gtk_widget_show(item);
1934 gtk_container_add(parent, item);
1935 g_signal_connect_data(item, "activate", G_CALLBACK(on_config_file_clicked),
1936 g_strdup(real_path), free_on_closure_notify, 0);
1940 static gboolean sort_menu(gpointer data)
1942 ui_menu_sort_by_label(GTK_MENU(data));
1943 return FALSE;
1947 static void create_config_files_menu(void)
1949 GtkWidget *menu, *item;
1951 widgets.config_files_menu = menu = gtk_menu_new();
1953 item = ui_lookup_widget(main_widgets.window, "configuration_files1");
1954 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu);
1956 /* sort menu after all items added */
1957 g_idle_add(sort_menu, widgets.config_files_menu);
1961 /* adds factory icons with a named icon source using the stock items id */
1962 static void add_stock_icons(const GtkStockItem *items, gsize count)
1964 GtkIconFactory *factory = gtk_icon_factory_new();
1965 GtkIconSource *source = gtk_icon_source_new();
1966 gsize i;
1968 for (i = 0; i < count; i++)
1970 GtkIconSet *set = gtk_icon_set_new();
1972 gtk_icon_source_set_icon_name(source, items[i].stock_id);
1973 gtk_icon_set_add_source(set, source);
1974 gtk_icon_factory_add(factory, items[i].stock_id, set);
1975 gtk_icon_set_unref(set);
1977 gtk_icon_source_free(source);
1978 gtk_icon_factory_add_default(factory);
1979 g_object_unref(factory);
1983 void ui_init_stock_items(void)
1985 GtkStockItem items[] =
1987 { GEANY_STOCK_SAVE_ALL, N_("Save All"), 0, 0, GETTEXT_PACKAGE },
1988 { GEANY_STOCK_CLOSE_ALL, N_("Close All"), 0, 0, GETTEXT_PACKAGE },
1989 { GEANY_STOCK_BUILD, N_("Build"), 0, 0, GETTEXT_PACKAGE }
1992 gtk_stock_add(items, G_N_ELEMENTS(items));
1993 add_stock_icons(items, G_N_ELEMENTS(items));
1997 void ui_init_toolbar_widgets(void)
1999 widgets.save_buttons[1] = toolbar_get_widget_by_name("Save");
2000 widgets.save_buttons[3] = toolbar_get_widget_by_name("SaveAll");
2001 widgets.redo_items[2] = toolbar_get_widget_by_name("Redo");
2002 widgets.undo_items[2] = toolbar_get_widget_by_name("Undo");
2006 void ui_swap_sidebar_pos(void)
2008 GtkWidget *pane = ui_lookup_widget(main_widgets.window, "hpaned1");
2009 GtkWidget *left = gtk_paned_get_child1(GTK_PANED(pane));
2010 GtkWidget *right = gtk_paned_get_child2(GTK_PANED(pane));
2012 g_object_ref(left);
2013 g_object_ref(right);
2014 gtk_container_remove (GTK_CONTAINER (pane), left);
2015 gtk_container_remove (GTK_CONTAINER (pane), right);
2016 /* only scintilla notebook should expand */
2017 gtk_paned_pack1(GTK_PANED(pane), right, right == main_widgets.notebook, TRUE);
2018 gtk_paned_pack2(GTK_PANED(pane), left, left == main_widgets.notebook, TRUE);
2019 g_object_unref(left);
2020 g_object_unref(right);
2022 gtk_paned_set_position(GTK_PANED(pane), gtk_widget_get_allocated_width(pane)
2023 - gtk_paned_get_position(GTK_PANED(pane)));
2027 static void init_recent_files(void)
2029 GtkWidget *toolbar_recent_files_menu;
2031 /* add recent files to the File menu */
2032 ui_widgets.recent_files_menuitem = ui_lookup_widget(main_widgets.window, "recent_files1");
2033 ui_widgets.recent_files_menu_menubar = gtk_menu_new();
2034 gtk_menu_item_set_submenu(GTK_MENU_ITEM(ui_widgets.recent_files_menuitem),
2035 ui_widgets.recent_files_menu_menubar);
2037 /* add recent files to the toolbar Open button */
2038 toolbar_recent_files_menu = gtk_menu_new();
2039 g_object_ref(toolbar_recent_files_menu);
2040 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
2041 toolbar_get_action_by_name("Open")), toolbar_recent_files_menu);
2045 static void ui_menu_move(GtkWidget *menu, GtkWidget *old, GtkWidget *new)
2047 g_object_ref(menu);
2048 gtk_menu_item_set_submenu(GTK_MENU_ITEM(old), NULL);
2049 gtk_menu_item_set_submenu(GTK_MENU_ITEM(new), menu);
2050 g_object_unref(menu);
2054 typedef struct GeanySharedMenu
2056 const gchar *menu;
2057 const gchar *menubar_item;
2058 const gchar *popup_item;
2060 GeanySharedMenu;
2062 #define foreach_menu(item, array) \
2063 for (item = array; item->menu; item++)
2065 static void on_editor_menu_show(GtkWidget *widget, GeanySharedMenu *items)
2067 GeanySharedMenu *item;
2069 foreach_menu(item, items)
2071 GtkWidget *popup = ui_lookup_widget(main_widgets.editor_menu, item->popup_item);
2072 GtkWidget *bar = ui_lookup_widget(main_widgets.window, item->menubar_item);
2073 GtkWidget *menu = ui_lookup_widget(main_widgets.window, item->menu);
2075 ui_menu_move(menu, bar, popup);
2080 static void on_editor_menu_hide(GtkWidget *widget, GeanySharedMenu *items)
2082 GeanySharedMenu *item;
2084 foreach_menu(item, items)
2086 GtkWidget *popup = ui_lookup_widget(main_widgets.editor_menu, item->popup_item);
2087 GtkWidget *bar = ui_lookup_widget(main_widgets.window, item->menubar_item);
2088 GtkWidget *menu = ui_lookup_widget(main_widgets.window, item->menu);
2090 ui_menu_move(menu, popup, bar);
2095 /* Currently ui_init() is called before keyfile.c stash group code is initialized,
2096 * so this is called after that's done. */
2097 void ui_init_prefs(void)
2099 StashGroup *group = stash_group_new(PACKAGE);
2101 /* various prefs */
2102 configuration_add_various_pref_group(group);
2104 stash_group_add_boolean(group, &interface_prefs.show_symbol_list_expanders,
2105 "show_symbol_list_expanders", TRUE);
2106 stash_group_add_boolean(group, &interface_prefs.compiler_tab_autoscroll,
2107 "compiler_tab_autoscroll", TRUE);
2108 stash_group_add_boolean(group, &ui_prefs.allow_always_save,
2109 "allow_always_save", FALSE);
2110 stash_group_add_string(group, &ui_prefs.statusbar_template,
2111 "statusbar_template", _(DEFAULT_STATUSBAR_TEMPLATE));
2112 stash_group_add_boolean(group, &ui_prefs.new_document_after_close,
2113 "new_document_after_close", FALSE);
2114 stash_group_add_boolean(group, &interface_prefs.msgwin_status_visible,
2115 "msgwin_status_visible", TRUE);
2116 stash_group_add_boolean(group, &interface_prefs.msgwin_compiler_visible,
2117 "msgwin_compiler_visible", TRUE);
2118 stash_group_add_boolean(group, &interface_prefs.msgwin_messages_visible,
2119 "msgwin_messages_visible", TRUE);
2120 stash_group_add_boolean(group, &interface_prefs.msgwin_scribble_visible,
2121 "msgwin_scribble_visible", TRUE);
2125 /* Used to find out the name of the GtkBuilder retrieved object since
2126 * some objects will be GTK_IS_BUILDABLE() and use the GtkBuildable
2127 * 'name' property for that and those that don't implement GtkBuildable
2128 * will have a "gtk-builder-name" stored in the GObject's data list. */
2129 static const gchar *ui_guess_object_name(GObject *obj)
2131 const gchar *name = NULL;
2133 g_return_val_if_fail(G_IS_OBJECT(obj), NULL);
2135 if (GTK_IS_BUILDABLE(obj))
2136 name = gtk_buildable_get_name(GTK_BUILDABLE(obj));
2137 if (! name)
2138 name = g_object_get_data(obj, "gtk-builder-name");
2139 if (! name)
2140 return NULL;
2142 return name;
2146 /* Compatibility functions */
2147 GtkWidget *create_edit_menu1(void)
2149 return edit_menu1;
2153 GtkWidget *create_prefs_dialog(void)
2155 return prefs_dialog;
2159 GtkWidget *create_project_dialog(void)
2161 return project_dialog;
2165 GtkWidget *create_toolbar_popup_menu1(void)
2167 return toolbar_popup_menu1;
2171 GtkWidget *create_window1(void)
2173 return window1;
2177 static GtkWidget *ui_get_top_parent(GtkWidget *widget)
2179 GtkWidget *parent;
2181 g_return_val_if_fail(GTK_IS_WIDGET(widget), NULL);
2183 for (;;)
2185 if (GTK_IS_MENU(widget))
2186 parent = gtk_menu_get_attach_widget(GTK_MENU(widget));
2187 else
2188 parent = gtk_widget_get_parent(widget);
2189 if (parent == NULL)
2190 parent = (GtkWidget*) g_object_get_data(G_OBJECT(widget), "GladeParentKey");
2191 if (parent == NULL)
2192 break;
2193 widget = parent;
2196 return widget;
2200 void ui_init_builder(void)
2202 gchar *interface_file;
2203 const gchar *name;
2204 GError *error;
2205 GSList *iter, *all_objects;
2206 GtkWidget *widget, *toplevel;
2208 /* prevent function from being called twice */
2209 if (GTK_IS_BUILDER(builder))
2210 return;
2212 builder = gtk_builder_new();
2214 gtk_builder_set_translation_domain(builder, GETTEXT_PACKAGE);
2216 error = NULL;
2217 interface_file = g_build_filename(app->datadir, "geany.glade", NULL);
2218 if (! gtk_builder_add_from_file(builder, interface_file, &error))
2220 /* Show the user this message so they know WTF happened */
2221 dialogs_show_msgbox_with_secondary(GTK_MESSAGE_ERROR,
2222 _("Geany cannot start!"), error->message);
2223 /* Aborts */
2224 g_error("Cannot create user-interface: %s", error->message);
2225 g_error_free(error);
2226 g_free(interface_file);
2227 g_object_unref(builder);
2228 return;
2230 g_free(interface_file);
2232 gtk_builder_connect_signals(builder, NULL);
2234 edit_menu1 = GTK_WIDGET(gtk_builder_get_object(builder, "edit_menu1"));
2235 prefs_dialog = GTK_WIDGET(gtk_builder_get_object(builder, "prefs_dialog"));
2236 project_dialog = GTK_WIDGET(gtk_builder_get_object(builder, "project_dialog"));
2237 toolbar_popup_menu1 = GTK_WIDGET(gtk_builder_get_object(builder, "toolbar_popup_menu1"));
2238 window1 = GTK_WIDGET(gtk_builder_get_object(builder, "window1"));
2240 g_object_set_data(G_OBJECT(edit_menu1), "edit_menu1", edit_menu1);
2241 g_object_set_data(G_OBJECT(prefs_dialog), "prefs_dialog", prefs_dialog);
2242 g_object_set_data(G_OBJECT(project_dialog), "project_dialog", project_dialog);
2243 g_object_set_data(G_OBJECT(toolbar_popup_menu1), "toolbar_popup_menu1", toolbar_popup_menu1);
2244 g_object_set_data(G_OBJECT(window1), "window1", window1);
2246 all_objects = gtk_builder_get_objects(builder);
2247 for (iter = all_objects; iter != NULL; iter = g_slist_next(iter))
2249 if (! GTK_IS_WIDGET(iter->data))
2250 continue;
2252 widget = GTK_WIDGET(iter->data);
2254 name = ui_guess_object_name(G_OBJECT(widget));
2255 if (! name)
2257 g_warning("Unable to get name from GtkBuilder object");
2258 continue;
2261 toplevel = ui_get_top_parent(widget);
2262 if (toplevel)
2263 ui_hookup_widget(toplevel, widget, name);
2265 g_slist_free(all_objects);
2269 static void init_custom_style(void)
2271 #if GTK_CHECK_VERSION(3, 0, 0)
2272 gchar *css_file = g_build_filename(app->datadir, "geany.css", NULL);
2273 GtkCssProvider *css = gtk_css_provider_new();
2274 GError *error = NULL;
2276 if (! gtk_css_provider_load_from_path(css, css_file, &error))
2278 g_warning("Failed to load custom CSS: %s", error->message);
2279 g_error_free(error);
2281 else
2283 gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
2284 GTK_STYLE_PROVIDER(css), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2287 g_object_unref(css);
2288 g_free(css_file);
2289 #else
2290 /* see setup_gtk2_styles() in main.c */
2291 #endif
2295 void ui_init(void)
2297 init_custom_style();
2299 init_recent_files();
2301 ui_widgets.statusbar = ui_lookup_widget(main_widgets.window, "statusbar");
2302 ui_widgets.print_page_setup = ui_lookup_widget(main_widgets.window, "page_setup1");
2304 main_widgets.progressbar = progress_bar_create();
2306 /* current word sensitive items */
2307 widgets.popup_goto_items[0] = ui_lookup_widget(main_widgets.editor_menu, "goto_tag_definition2");
2308 widgets.popup_goto_items[1] = ui_lookup_widget(main_widgets.editor_menu, "context_action1");
2309 widgets.popup_goto_items[2] = ui_lookup_widget(main_widgets.editor_menu, "find_usage2");
2310 widgets.popup_goto_items[3] = ui_lookup_widget(main_widgets.editor_menu, "find_document_usage2");
2312 widgets.popup_copy_items[0] = ui_lookup_widget(main_widgets.editor_menu, "cut1");
2313 widgets.popup_copy_items[1] = ui_lookup_widget(main_widgets.editor_menu, "copy1");
2314 widgets.popup_copy_items[2] = ui_lookup_widget(main_widgets.editor_menu, "delete1");
2315 widgets.menu_copy_items[0] = ui_lookup_widget(main_widgets.window, "menu_cut1");
2316 widgets.menu_copy_items[1] = ui_lookup_widget(main_widgets.window, "menu_copy1");
2317 widgets.menu_copy_items[2] = ui_lookup_widget(main_widgets.window, "menu_delete1");
2318 widgets.menu_insert_include_items[0] = ui_lookup_widget(main_widgets.editor_menu, "insert_include1");
2319 widgets.menu_insert_include_items[1] = ui_lookup_widget(main_widgets.window, "insert_include2");
2320 widgets.save_buttons[0] = ui_lookup_widget(main_widgets.window, "menu_save1");
2321 widgets.save_buttons[2] = ui_lookup_widget(main_widgets.window, "menu_save_all1");
2322 widgets.redo_items[0] = ui_lookup_widget(main_widgets.editor_menu, "redo1");
2323 widgets.redo_items[1] = ui_lookup_widget(main_widgets.window, "menu_redo2");
2324 widgets.undo_items[0] = ui_lookup_widget(main_widgets.editor_menu, "undo1");
2325 widgets.undo_items[1] = ui_lookup_widget(main_widgets.window, "menu_undo2");
2327 /* reparent context submenus as needed */
2329 GeanySharedMenu arr[] = {
2330 {"commands2_menu", "commands2", "commands1"},
2331 {"menu_format1_menu", "menu_format1", "menu_format2"},
2332 {"more1_menu", "more1", "search2"},
2333 {NULL, NULL, NULL}
2335 static GeanySharedMenu items[G_N_ELEMENTS(arr)];
2337 memcpy(items, arr, sizeof(arr));
2338 g_signal_connect(main_widgets.editor_menu, "show", G_CALLBACK(on_editor_menu_show), items);
2339 g_signal_connect(main_widgets.editor_menu, "hide", G_CALLBACK(on_editor_menu_hide), items);
2342 ui_init_toolbar_widgets();
2343 init_document_widgets();
2344 create_config_files_menu();
2348 void ui_finalize_builder(void)
2350 if (GTK_IS_BUILDER(builder))
2351 g_object_unref(builder);
2353 /* cleanup refs lingering even after GtkBuilder is destroyed */
2354 if (GTK_IS_WIDGET(edit_menu1))
2355 gtk_widget_destroy(edit_menu1);
2356 if (GTK_IS_WIDGET(prefs_dialog))
2357 gtk_widget_destroy(prefs_dialog);
2358 if (GTK_IS_WIDGET(project_dialog))
2359 gtk_widget_destroy(project_dialog);
2360 if (GTK_IS_WIDGET(toolbar_popup_menu1))
2361 gtk_widget_destroy(toolbar_popup_menu1);
2362 if (GTK_IS_WIDGET(window1))
2363 gtk_widget_destroy(window1);
2367 static void auto_separator_update(GeanyAutoSeparator *autosep)
2369 g_return_if_fail(autosep->item_count >= 0);
2371 if (autosep->widget)
2373 if (autosep->item_count > 0)
2374 ui_widget_show_hide(autosep->widget, autosep->show_count > 0);
2375 else
2376 gtk_widget_destroy(autosep->widget);
2381 static void on_auto_separator_item_show_hide(GtkWidget *widget, gpointer user_data)
2383 GeanyAutoSeparator *autosep = user_data;
2385 if (gtk_widget_get_visible(widget))
2386 autosep->show_count++;
2387 else
2388 autosep->show_count--;
2389 auto_separator_update(autosep);
2393 static void on_auto_separator_item_destroy(GtkWidget *widget, gpointer user_data)
2395 GeanyAutoSeparator *autosep = user_data;
2397 autosep->item_count--;
2398 autosep->item_count = MAX(autosep->item_count, 0);
2399 /* gtk_widget_get_visible() won't work now the widget is being destroyed,
2400 * so assume widget was visible */
2401 autosep->show_count--;
2402 autosep->show_count = MAX(autosep->item_count, 0);
2403 auto_separator_update(autosep);
2407 /* Show the separator widget if @a item or another is visible. */
2408 /* Note: This would be neater taking a widget argument, setting a "visible-count"
2409 * property, and using reference counting to keep the widget alive whilst its visible group
2410 * is alive. */
2411 void ui_auto_separator_add_ref(GeanyAutoSeparator *autosep, GtkWidget *item)
2413 /* set widget ptr NULL when widget destroyed */
2414 if (autosep->item_count == 0)
2415 g_signal_connect(autosep->widget, "destroy",
2416 G_CALLBACK(gtk_widget_destroyed), &autosep->widget);
2418 if (gtk_widget_get_visible(item))
2419 autosep->show_count++;
2421 autosep->item_count++;
2422 auto_separator_update(autosep);
2424 g_signal_connect(item, "show", G_CALLBACK(on_auto_separator_item_show_hide), autosep);
2425 g_signal_connect(item, "hide", G_CALLBACK(on_auto_separator_item_show_hide), autosep);
2426 g_signal_connect(item, "destroy", G_CALLBACK(on_auto_separator_item_destroy), autosep);
2431 * Sets @a text as the contents of the tooltip for @a widget.
2433 * @param widget The widget the tooltip should be set for.
2434 * @param text The text for the tooltip.
2436 * @since 0.16
2437 * @deprecated 0.21 use gtk_widget_set_tooltip_text() instead
2439 void ui_widget_set_tooltip_text(GtkWidget *widget, const gchar *text)
2441 gtk_widget_set_tooltip_text(widget, text);
2445 /** Returns a widget from a name in a component, usually created by Glade.
2446 * Call it with the toplevel widget in the component (i.e. a window/dialog),
2447 * or alternatively any widget in the component, and the name of the widget
2448 * you want returned.
2449 * @param widget Widget with the @a widget_name property set.
2450 * @param widget_name Name to lookup.
2451 * @return The widget found.
2452 * @see ui_hookup_widget().
2454 * @since 0.16
2456 GtkWidget *ui_lookup_widget(GtkWidget *widget, const gchar *widget_name)
2458 GtkWidget *parent, *found_widget;
2460 g_return_val_if_fail(widget != NULL, NULL);
2461 g_return_val_if_fail(widget_name != NULL, NULL);
2463 for (;;)
2465 if (GTK_IS_MENU(widget))
2466 parent = gtk_menu_get_attach_widget(GTK_MENU(widget));
2467 else
2468 parent = gtk_widget_get_parent(widget);
2469 if (parent == NULL)
2470 parent = (GtkWidget*) g_object_get_data(G_OBJECT(widget), "GladeParentKey");
2471 if (parent == NULL)
2472 break;
2473 widget = parent;
2476 found_widget = (GtkWidget*) g_object_get_data(G_OBJECT(widget), widget_name);
2477 if (G_UNLIKELY(found_widget == NULL))
2478 g_warning("Widget not found: %s", widget_name);
2479 return found_widget;
2483 /* wraps gtk_builder_get_object()
2484 * unlike ui_lookup_widget(), it does only support getting object created from the main
2485 * UI file, but it can fetch any object, not only widgets */
2486 gpointer ui_builder_get_object (const gchar *name)
2488 return gtk_builder_get_object (builder, name);
2492 /* Progress Bar */
2493 static guint progress_bar_timer_id = 0;
2496 static GtkWidget *progress_bar_create(void)
2498 GtkWidget *bar = gtk_progress_bar_new();
2500 /* Set the progressbar's height to 1 to fit it in the statusbar */
2501 gtk_widget_set_size_request(bar, -1, 1);
2502 gtk_box_pack_start (GTK_BOX(ui_widgets.statusbar), bar, FALSE, FALSE, 3);
2504 return bar;
2508 static gboolean progress_bar_pulse(gpointer data)
2510 gtk_progress_bar_pulse(GTK_PROGRESS_BAR(main_widgets.progressbar));
2512 return TRUE;
2517 * Starts a constantly pulsing progressbar in the right corner of the statusbar
2518 * (if the statusbar is visible). This is a convenience function which adds a timer to
2519 * pulse the progressbar constantly until ui_progress_bar_stop() is called.
2520 * You can use this function when you have time consuming asynchronous operation and want to
2521 * display some activity in the GUI and when you don't know about detailed progress steps.
2522 * The progressbar widget is hidden by default when it is not active. This function and
2523 * ui_progress_bar_stop() will show and hide it automatically for you.
2525 * You can also access the progressbar widget directly using @c geany->main_widgets->progressbar
2526 * and use the GtkProgressBar API to set discrete fractions to display better progress information.
2527 * In this case, you need to show and hide the widget yourself. You can find some example code
2528 * in @c src/printing.c.
2530 * @param text The text to be shown as the progress bar label or NULL to leave it empty.
2532 * @since 0.16
2534 void ui_progress_bar_start(const gchar *text)
2536 g_return_if_fail(progress_bar_timer_id == 0);
2538 if (! interface_prefs.statusbar_visible)
2539 return;
2541 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(main_widgets.progressbar), text);
2543 progress_bar_timer_id = g_timeout_add(200, progress_bar_pulse, NULL);
2545 gtk_widget_show(GTK_WIDGET(main_widgets.progressbar));
2549 /** Stops a running progress bar and hides the widget again.
2551 * @since 0.16
2553 void ui_progress_bar_stop(void)
2555 gtk_widget_hide(GTK_WIDGET(main_widgets.progressbar));
2557 if (progress_bar_timer_id != 0)
2559 g_source_remove(progress_bar_timer_id);
2560 progress_bar_timer_id = 0;
2565 static gint compare_menu_item_labels(gconstpointer a, gconstpointer b)
2567 GtkMenuItem *item_a = GTK_MENU_ITEM(a);
2568 GtkMenuItem *item_b = GTK_MENU_ITEM(b);
2569 gchar *sa, *sb;
2570 gint result;
2572 sa = ui_menu_item_get_text(item_a);
2573 sb = ui_menu_item_get_text(item_b);
2574 result = utils_str_casecmp(sa, sb);
2575 g_free(sa);
2576 g_free(sb);
2577 return result;
2581 /* Currently @a menu should contain only GtkMenuItems with labels. */
2582 void ui_menu_sort_by_label(GtkMenu *menu)
2584 GList *list = gtk_container_get_children(GTK_CONTAINER(menu));
2585 GList *node;
2586 gint pos;
2588 list = g_list_sort(list, compare_menu_item_labels);
2589 pos = 0;
2590 foreach_list(node, list)
2592 gtk_menu_reorder_child(menu, node->data, pos);
2593 pos++;
2595 g_list_free(list);
2599 void ui_label_set_markup(GtkLabel *label, const gchar *format, ...)
2601 va_list a;
2602 gchar *text;
2604 va_start(a, format);
2605 text = g_strdup_vprintf(format, a);
2606 va_end(a);
2608 gtk_label_set_text(label, text);
2609 gtk_label_set_use_markup(label, TRUE);
2610 g_free(text);
2614 GtkWidget *ui_label_new_bold(const gchar *text)
2616 GtkWidget *label;
2617 gchar *label_text;
2619 label_text = g_markup_escape_text(text, -1);
2620 label = gtk_label_new(NULL);
2621 ui_label_set_markup(GTK_LABEL(label), "<b>%s</b>", label_text);
2622 g_free(label_text);
2623 return label;
2627 /** Adds a list of document items to @a menu.
2628 * @param menu Menu.
2629 * @param active Which document to highlight, or @c NULL.
2630 * @param callback is used for each menu item's @c "activate" signal and will be passed
2631 * the corresponding document pointer as @c user_data.
2632 * @warning You should check @c doc->is_valid in the callback.
2633 * @since 0.19 */
2634 void ui_menu_add_document_items(GtkMenu *menu, GeanyDocument *active, GCallback callback)
2636 ui_menu_add_document_items_sorted(menu, active, callback, NULL);
2640 /** Adds a list of document items to @a menu.
2642 * @a compare_func might be NULL to not sort the documents in the menu. In this case,
2643 * the order of the document tabs is used.
2645 * See document_compare_by_display_name() for an example sort function.
2647 * @param menu Menu.
2648 * @param active Which document to highlight, or @c NULL.
2649 * @param callback is used for each menu item's @c "activate" signal and will be passed
2650 * the corresponding document pointer as @c user_data.
2651 * @param compare_func is used to sort the list. Might be @c NULL to not sort the list.
2652 * @warning You should check @c doc->is_valid in the callback.
2653 * @since 0.21 */
2654 void ui_menu_add_document_items_sorted(GtkMenu *menu, GeanyDocument *active,
2655 GCallback callback, GCompareFunc compare_func)
2657 GtkWidget *menu_item, *menu_item_label, *image;
2658 GeanyDocument *doc;
2659 guint i, len;
2660 gchar *base_name, *label;
2661 GPtrArray *sorted_documents;
2663 len = (guint) gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook));
2665 sorted_documents = g_ptr_array_sized_new(len);
2666 /* copy the documents_array into the new one */
2667 foreach_document(i)
2669 g_ptr_array_add(sorted_documents, documents[i]);
2671 if (compare_func == NULL)
2672 compare_func = document_compare_by_tab_order;
2674 /* and now sort it */
2675 g_ptr_array_sort(sorted_documents, compare_func);
2677 for (i = 0; i < sorted_documents->len; i++)
2679 doc = g_ptr_array_index(sorted_documents, i);
2681 base_name = g_path_get_basename(DOC_FILENAME(doc));
2682 menu_item = gtk_image_menu_item_new_with_label(base_name);
2683 image = gtk_image_new_from_gicon(doc->file_type->icon, GTK_ICON_SIZE_MENU);
2684 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image);
2686 gtk_widget_show(menu_item);
2687 gtk_container_add(GTK_CONTAINER(menu), menu_item);
2688 g_signal_connect(menu_item, "activate", callback, doc);
2690 menu_item_label = gtk_bin_get_child(GTK_BIN(menu_item));
2691 gtk_widget_set_name(menu_item_label, document_get_status_widget_class(doc));
2693 if (doc == active)
2695 label = g_markup_escape_text(base_name, -1);
2696 ui_label_set_markup(GTK_LABEL(menu_item_label), "<b>%s</b>", label);
2697 g_free(label);
2700 g_free(base_name);
2702 g_ptr_array_free(sorted_documents, TRUE);
2706 /** Checks whether the passed @a keyval is the Enter or Return key.
2707 * There are three different Enter/Return key values
2708 * (@c GDK_Return, @c GDK_ISO_Enter, @c GDK_KP_Enter).
2709 * This is just a convenience function.
2710 * @param keyval A keyval.
2711 * @return @c TRUE if @a keyval is the one of the Enter/Return key values, otherwise @c FALSE.
2712 * @since 0.19 */
2713 gboolean ui_is_keyval_enter_or_return(guint keyval)
2715 return (keyval == GDK_Return || keyval == GDK_ISO_Enter|| keyval == GDK_KP_Enter);
2719 /** Reads an integer from the GTK default settings registry
2720 * (see http://library.gnome.org/devel/gtk/stable/GtkSettings.html).
2721 * @param property_name The property to read.
2722 * @param default_value The default value in case the value could not be read.
2723 * @return The value for the property if it exists, otherwise the @a default_value.
2724 * @since 0.19 */
2725 gint ui_get_gtk_settings_integer(const gchar *property_name, gint default_value)
2727 if (g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(
2728 gtk_settings_get_default())), property_name))
2730 gint value;
2731 g_object_get(G_OBJECT(gtk_settings_get_default()), property_name, &value, NULL);
2732 return value;
2734 else
2735 return default_value;
2739 void ui_editable_insert_text_callback(GtkEditable *editable, gchar *new_text,
2740 gint new_text_len, gint *position, gpointer data)
2742 gboolean first = position != NULL && *position == 0;
2743 gint i;
2745 if (new_text_len == -1)
2746 new_text_len = (gint) strlen(new_text);
2748 for (i = 0; i < new_text_len; i++, new_text++)
2750 if ((!first || !strchr("+-", *new_text)) && !isdigit(*new_text))
2752 g_signal_stop_emission_by_name(editable, "insert-text");
2753 break;
2755 first = FALSE;
2760 /* gets the icon that applies to a particular MIME type */
2761 GIcon *ui_get_mime_icon(const gchar *mime_type)
2763 GIcon *icon = NULL;
2764 gchar *ctype;
2766 ctype = g_content_type_from_mime_type(mime_type);
2767 if (ctype)
2769 icon = g_content_type_get_icon(ctype);
2770 g_free(ctype);
2773 /* fallback if icon lookup failed, like it might happen on Windows (?) */
2774 if (! icon)
2776 const gchar *stock_id = GTK_STOCK_FILE;
2778 if (strstr(mime_type, "directory"))
2779 stock_id = GTK_STOCK_DIRECTORY;
2781 icon = g_themed_icon_new(stock_id);
2783 return icon;
2787 void ui_focus_current_document(void)
2789 GeanyDocument *doc = document_get_current();
2791 if (doc != NULL)
2792 document_grab_focus(doc);
2796 /** Finds the label text associated with stock_id
2797 * @param stock_id stock_id to lookup e.g. @c GTK_STOCK_OPEN.
2798 * @return The label text for stock
2799 * @since Geany 1.22 */
2800 const gchar *ui_lookup_stock_label(const gchar *stock_id)
2802 GtkStockItem item;
2804 if (gtk_stock_lookup(stock_id, &item))
2805 return item.label;
2807 g_warning("No stock id '%s'!", stock_id);
2808 return NULL;
2812 /* finds the next iter at any level
2813 * @param iter in/out, the current iter, will be changed to the next one
2814 * @param down whether to try the child iter
2815 * @return TRUE if there @p iter was set, or FALSE if there is no next iter */
2816 gboolean ui_tree_model_iter_any_next(GtkTreeModel *model, GtkTreeIter *iter, gboolean down)
2818 GtkTreeIter guess;
2819 GtkTreeIter copy = *iter;
2821 /* go down if the item has children */
2822 if (down && gtk_tree_model_iter_children(model, &guess, iter))
2823 *iter = guess;
2824 /* or to the next item at the same level */
2825 else if (gtk_tree_model_iter_next(model, &copy))
2826 *iter = copy;
2827 /* or to the next item at a parent level */
2828 else if (gtk_tree_model_iter_parent(model, &guess, iter))
2830 copy = guess;
2831 while (TRUE)
2833 if (gtk_tree_model_iter_next(model, &copy))
2835 *iter = copy;
2836 return TRUE;
2838 else if (gtk_tree_model_iter_parent(model, &copy, &guess))
2839 guess = copy;
2840 else
2841 return FALSE;
2844 else
2845 return FALSE;
2847 return TRUE;
2851 GtkWidget *ui_create_encodings_combo_box(gboolean has_detect, gint default_enc)
2853 GtkCellRenderer *renderer;
2854 GtkTreeIter iter;
2855 GtkWidget *combo = gtk_combo_box_new();
2856 GtkTreeStore *store = encodings_encoding_store_new(has_detect);
2858 if (default_enc < 0 || default_enc >= GEANY_ENCODINGS_MAX)
2859 default_enc = has_detect ? GEANY_ENCODINGS_MAX : GEANY_ENCODING_NONE;
2861 gtk_combo_box_set_model(GTK_COMBO_BOX(combo), GTK_TREE_MODEL(store));
2862 if (encodings_encoding_store_get_iter(store, &iter, default_enc))
2863 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter);
2864 renderer = gtk_cell_renderer_text_new();
2865 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
2866 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo), renderer,
2867 encodings_encoding_store_cell_data_func, NULL, NULL);
2869 return combo;
2873 gint ui_encodings_combo_box_get_active_encoding(GtkComboBox *combo)
2875 GtkTreeIter iter;
2876 gint enc = GEANY_ENCODING_NONE;
2878 /* there should always be an active iter anyway, but we check just in case */
2879 if (gtk_combo_box_get_active_iter(combo, &iter))
2881 GtkTreeModel *model = gtk_combo_box_get_model(combo);
2882 enc = encodings_encoding_store_get_encoding(GTK_TREE_STORE(model), &iter);
2885 return enc;
2889 gboolean ui_encodings_combo_box_set_active_encoding(GtkComboBox *combo, gint enc)
2891 GtkTreeIter iter;
2892 GtkTreeModel *model = gtk_combo_box_get_model(combo);
2894 if (encodings_encoding_store_get_iter(GTK_TREE_STORE(model), &iter, enc))
2896 gtk_combo_box_set_active_iter(combo, &iter);
2897 return TRUE;
2899 return FALSE;