"Fix" some GCC -Wparentheses warnings
[geany-mirror.git] / src / ui_utils.c
blob18919b7553f446eb2a9f2032c68666935c404031
1 /*
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.
21 /** @file ui_utils.h
22 * User Interface general utility functions.
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
29 #include "ui_utils.h"
31 #include "app.h"
32 #include "callbacks.h"
33 #include "dialogs.h"
34 #include "documentprivate.h"
35 #include "encodingsprivate.h"
36 #include "filetypes.h"
37 #include "geanymenubuttonaction.h"
38 #include "keyfile.h"
39 #include "keybindings.h"
40 #include "main.h"
41 #include "msgwindow.h"
42 #include "prefs.h"
43 #include "project.h"
44 #include "sciwrappers.h"
45 #include "sidebar.h"
46 #include "stash.h"
47 #include "support.h"
48 #include "symbols.h"
49 #include "toolbar.h"
50 #include "utils.h"
51 #include "win32.h"
52 #include "osx.h"
54 #include <string.h>
55 #include <ctype.h>
56 #include <stdarg.h>
57 #include <gtk/gtk.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 "EOL: %M " \
67 "encoding: %e " \
68 "filetype: %f " \
69 "scope: %S")
71 GEANY_EXPORT_SYMBOL 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 GEANY_API_SYMBOL
167 void ui_set_statusbar(gboolean log, const gchar *format, ...)
169 gchar *string;
170 va_list args;
172 va_start(args, format);
173 string = g_strdup_vprintf(format, args);
174 va_end(args);
176 if (! prefs.suppress_status_messages)
177 set_statusbar(string, FALSE);
179 if (log || prefs.suppress_status_messages)
180 msgwin_status_add("%s", string);
182 g_free(string);
186 /* note: some comments below are for translators */
187 static gchar *create_statusbar_statistics(GeanyDocument *doc,
188 guint line, guint vcol, guint pos)
190 const gchar *cur_tag;
191 const gchar *fmt;
192 const gchar *expos; /* % expansion position */
193 const gchar sp[] = " ";
194 GString *stats_str;
195 ScintillaObject *sci = doc->editor->sci;
197 if (!EMPTY(ui_prefs.statusbar_template))
198 fmt = ui_prefs.statusbar_template;
199 else
200 fmt = _(DEFAULT_STATUSBAR_TEMPLATE);
202 stats_str = g_string_sized_new(120);
204 while ((expos = strchr(fmt, '%')) != NULL)
206 /* append leading text before % char */
207 g_string_append_len(stats_str, fmt, expos - fmt);
209 switch (*++expos)
211 case 'l':
212 g_string_append_printf(stats_str, "%d", line + 1);
213 break;
214 case 'L':
215 g_string_append_printf(stats_str, "%d",
216 sci_get_line_count(doc->editor->sci));
217 break;
218 case 'c':
219 g_string_append_printf(stats_str, "%d", vcol);
220 break;
221 case 'C':
222 g_string_append_printf(stats_str, "%d", vcol + 1);
223 break;
224 case 'p':
225 g_string_append_printf(stats_str, "%u", pos);
226 break;
227 case 's':
229 gint len = sci_get_selected_text_length2(sci);
230 /* check if whole lines are selected */
231 if (!len || sci_get_col_from_position(sci,
232 sci_get_selection_start(sci)) != 0 ||
233 sci_get_col_from_position(sci,
234 sci_get_selection_end(sci)) != 0)
235 g_string_append_printf(stats_str, "%d", len);
236 else /* L = lines */
237 g_string_append_printf(stats_str, _("%dL"),
238 sci_get_lines_selected(doc->editor->sci) - 1);
239 break;
241 case 'n' :
242 g_string_append_printf(stats_str, "%d",
243 sci_get_selected_text_length2(doc->editor->sci));
244 break;
245 case 'w':
246 /* RO = read-only */
247 g_string_append(stats_str, (doc->readonly) ? _("RO ") :
248 /* OVR = overwrite/overtype, INS = insert */
249 (sci_get_overtype(doc->editor->sci) ? _("OVR") : _("INS")));
250 break;
251 case 'r':
252 if (doc->readonly)
254 g_string_append(stats_str, _("RO ")); /* RO = read-only */
255 g_string_append(stats_str, sp + 1);
257 break;
258 case 't':
260 switch (editor_get_indent_prefs(doc->editor)->type)
262 case GEANY_INDENT_TYPE_TABS:
263 g_string_append(stats_str, _("TAB"));
264 break;
265 case GEANY_INDENT_TYPE_SPACES: /* SP = space */
266 g_string_append(stats_str, _("SP"));
267 break;
268 case GEANY_INDENT_TYPE_BOTH: /* T/S = tabs and spaces */
269 g_string_append(stats_str, _("T/S"));
270 break;
272 break;
274 case 'm':
275 if (doc->changed)
277 g_string_append(stats_str, _("MOD")); /* MOD = modified */
278 g_string_append(stats_str, sp);
280 break;
281 case 'M':
282 g_string_append(stats_str, utils_get_eol_short_name(sci_get_eol_mode(doc->editor->sci)));
283 break;
284 case 'e':
285 g_string_append(stats_str,
286 doc->encoding ? doc->encoding : _("unknown"));
287 if (encodings_is_unicode_charset(doc->encoding) && (doc->has_bom))
289 g_string_append_c(stats_str, ' ');
290 g_string_append(stats_str, _("(with BOM)")); /* BOM = byte order mark */
292 break;
293 case 'f':
294 g_string_append(stats_str, filetypes_get_display_name(doc->file_type));
295 break;
296 case 'S':
297 symbols_get_current_scope(doc, &cur_tag);
298 g_string_append(stats_str, cur_tag);
299 break;
300 case 'Y':
301 g_string_append_c(stats_str, ' ');
302 g_string_append_printf(stats_str, "%d",
303 sci_get_style_at(doc->editor->sci, pos));
304 break;
305 default:
306 g_string_append_len(stats_str, expos, 1);
309 /* skip past %c chars */
310 if (*expos)
311 fmt = expos + 1;
312 else
313 break;
315 /* add any remaining text */
316 g_string_append(stats_str, fmt);
318 return g_string_free(stats_str, FALSE);
322 /* updates the status bar document statistics */
323 void ui_update_statusbar(GeanyDocument *doc, gint pos)
325 g_return_if_fail(doc == NULL || doc->is_valid);
327 if (! interface_prefs.statusbar_visible)
328 return; /* just do nothing if statusbar is not visible */
330 if (doc == NULL)
331 doc = document_get_current();
333 if (doc != NULL)
335 guint line, vcol;
336 gchar *stats_str;
338 if (pos == -1)
339 pos = sci_get_current_position(doc->editor->sci);
340 line = sci_get_line_from_position(doc->editor->sci, pos);
342 /* Add temporary fix for sci infinite loop in Document::GetColumn(int)
343 * when current pos is beyond document end (can occur when removing
344 * blocks of selected lines especially esp. brace sections near end of file). */
345 if (pos <= sci_get_length(doc->editor->sci))
346 vcol = sci_get_col_from_position(doc->editor->sci, pos);
347 else
348 vcol = 0;
349 vcol += sci_get_cursor_virtual_space(doc->editor->sci);
351 stats_str = create_statusbar_statistics(doc, line, vcol, pos);
353 /* can be overridden by status messages */
354 set_statusbar(stats_str, TRUE);
355 g_free(stats_str);
357 else /* no documents */
359 set_statusbar("", TRUE); /* can be overridden by status messages */
364 /* This sets the window title according to the current filename. */
365 void ui_set_window_title(GeanyDocument *doc)
367 GString *str;
368 GeanyProject *project = app->project;
370 g_return_if_fail(doc == NULL || doc->is_valid);
372 if (doc == NULL)
373 doc = document_get_current();
375 str = g_string_new(NULL);
377 if (doc != NULL)
379 g_string_append(str, doc->changed ? "*" : "");
381 if (doc->file_name == NULL)
382 g_string_append(str, DOC_FILENAME(doc));
383 else
385 gchar *short_name = document_get_basename_for_display(doc, interface_prefs.tab_label_len);
386 gchar *dirname = g_path_get_dirname(DOC_FILENAME(doc));
388 g_string_append(str, short_name);
389 g_string_append(str, " - ");
390 g_string_append(str, dirname ? dirname : "");
391 g_free(short_name);
392 g_free(dirname);
394 g_string_append(str, " - ");
396 if (project)
398 g_string_append_c(str, '[');
399 g_string_append(str, project->name);
400 g_string_append(str, "] - ");
402 g_string_append(str, "Geany");
403 if (cl_options.new_instance)
405 g_string_append(str, _(" (new instance)"));
407 gtk_window_set_title(GTK_WINDOW(main_widgets.window), str->str);
408 g_string_free(str, TRUE);
412 void ui_set_editor_font(const gchar *font_name)
414 guint i;
416 g_return_if_fail(font_name != NULL);
418 /* do nothing if font has not changed */
419 if (interface_prefs.editor_font != NULL)
420 if (strcmp(font_name, interface_prefs.editor_font) == 0)
421 return;
423 g_free(interface_prefs.editor_font);
424 interface_prefs.editor_font = g_strdup(font_name);
426 /* We copy the current style, and update the font in all open tabs. */
427 for (i = 0; i < documents_array->len; i++)
429 if (documents[i]->editor)
431 editor_set_font(documents[i]->editor, interface_prefs.editor_font);
435 ui_set_statusbar(TRUE, _("Font updated (%s)."), interface_prefs.editor_font);
439 void ui_set_fullscreen(void)
441 if (ui_prefs.fullscreen)
443 gtk_window_fullscreen(GTK_WINDOW(main_widgets.window));
445 else
447 gtk_window_unfullscreen(GTK_WINDOW(main_widgets.window));
452 void ui_update_popup_reundo_items(GeanyDocument *doc)
454 gboolean enable_undo;
455 gboolean enable_redo;
456 guint i, len;
458 g_return_if_fail(doc == NULL || doc->is_valid);
460 if (doc == NULL)
462 enable_undo = FALSE;
463 enable_redo = FALSE;
465 else
467 enable_undo = document_can_undo(doc);
468 enable_redo = document_can_redo(doc);
471 /* index 0 is the popup menu, 1 is the menubar, 2 is the toolbar */
472 len = G_N_ELEMENTS(widgets.undo_items);
473 for (i = 0; i < len; i++)
475 ui_widget_set_sensitive(widgets.undo_items[i], enable_undo);
477 len = G_N_ELEMENTS(widgets.redo_items);
478 for (i = 0; i < len; i++)
480 ui_widget_set_sensitive(widgets.redo_items[i], enable_redo);
485 void ui_update_popup_copy_items(GeanyDocument *doc)
487 gboolean enable;
488 guint i, len;
490 g_return_if_fail(doc == NULL || doc->is_valid);
492 if (doc == NULL)
493 enable = FALSE;
494 else
495 enable = sci_has_selection(doc->editor->sci);
497 len = G_N_ELEMENTS(widgets.popup_copy_items);
498 for (i = 0; i < len; i++)
499 ui_widget_set_sensitive(widgets.popup_copy_items[i], enable);
503 void ui_update_popup_goto_items(gboolean enable)
505 guint i, len;
506 len = G_N_ELEMENTS(widgets.popup_goto_items);
507 for (i = 0; i < len; i++)
508 ui_widget_set_sensitive(widgets.popup_goto_items[i], enable);
512 void ui_menu_copy_items_set_sensitive(gboolean sensitive)
514 guint i, len;
516 len = G_N_ELEMENTS(widgets.menu_copy_items);
517 for (i = 0; i < len; i++)
518 ui_widget_set_sensitive(widgets.menu_copy_items[i], sensitive);
522 void ui_update_menu_copy_items(GeanyDocument *doc)
524 gboolean enable = FALSE;
525 GtkWidget *focusw = gtk_window_get_focus(GTK_WINDOW(main_widgets.window));
527 g_return_if_fail(doc == NULL || doc->is_valid);
529 if (IS_SCINTILLA(focusw))
530 enable = (doc == NULL) ? FALSE : sci_has_selection(doc->editor->sci);
531 else
532 if (GTK_IS_EDITABLE(focusw))
533 enable = gtk_editable_get_selection_bounds(GTK_EDITABLE(focusw), NULL, NULL);
534 else
535 if (GTK_IS_TEXT_VIEW(focusw))
537 GtkTextBuffer *buffer = gtk_text_view_get_buffer(
538 GTK_TEXT_VIEW(focusw));
539 enable = gtk_text_buffer_get_selection_bounds(buffer, NULL, NULL);
542 ui_menu_copy_items_set_sensitive(enable);
546 void ui_update_insert_include_item(GeanyDocument *doc, gint item)
548 gboolean enable = FALSE;
550 g_return_if_fail(doc == NULL || doc->is_valid);
552 if (doc == NULL || doc->file_type == NULL)
553 enable = FALSE;
554 else if (doc->file_type->id == GEANY_FILETYPES_C || doc->file_type->id == GEANY_FILETYPES_CPP)
555 enable = TRUE;
557 ui_widget_set_sensitive(widgets.menu_insert_include_items[item], enable);
561 void ui_update_fold_items(void)
563 ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "menu_fold_all1"), editor_prefs.folding);
564 ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "menu_unfold_all1"), editor_prefs.folding);
565 ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "separator22"), editor_prefs.folding);
569 /* @include include name or NULL for empty with cursor ready for typing it */
570 static void insert_include(GeanyDocument *doc, gint pos, const gchar *include)
572 gint pos_after = -1;
573 gchar *text;
575 g_return_if_fail(doc != NULL);
576 g_return_if_fail(pos == -1 || pos >= 0);
578 if (pos == -1)
579 pos = sci_get_current_position(doc->editor->sci);
581 if (! include)
583 text = g_strdup("#include \"\"\n");
584 pos_after = pos + 10;
586 else
588 text = g_strconcat("#include <", include, ">\n", NULL);
591 sci_start_undo_action(doc->editor->sci);
592 sci_insert_text(doc->editor->sci, pos, text);
593 sci_end_undo_action(doc->editor->sci);
594 g_free(text);
595 if (pos_after >= 0)
596 sci_goto_pos(doc->editor->sci, pos_after, FALSE);
600 static void on_popup_insert_include_activate(GtkMenuItem *menuitem, gpointer user_data)
602 insert_include(document_get_current(), editor_info.click_pos, user_data);
606 static void on_menu_insert_include_activate(GtkMenuItem *menuitem, gpointer user_data)
608 insert_include(document_get_current(), -1, user_data);
612 static void insert_include_items(GtkMenu *me, GtkMenu *mp, gchar **includes, gchar *label)
614 guint i = 0;
615 GtkWidget *edit_menu, *edit_menu_item;
616 GtkWidget *popup_menu, *popup_menu_item;
618 edit_menu = gtk_menu_new();
619 popup_menu = gtk_menu_new();
620 edit_menu_item = gtk_menu_item_new_with_label(label);
621 popup_menu_item = gtk_menu_item_new_with_label(label);
622 gtk_menu_item_set_submenu(GTK_MENU_ITEM(edit_menu_item), edit_menu);
623 gtk_menu_item_set_submenu(GTK_MENU_ITEM(popup_menu_item), popup_menu);
625 while (includes[i] != NULL)
627 GtkWidget *tmp_menu = gtk_menu_item_new_with_label(includes[i]);
628 GtkWidget *tmp_popup = gtk_menu_item_new_with_label(includes[i]);
630 gtk_container_add(GTK_CONTAINER(edit_menu), tmp_menu);
631 gtk_container_add(GTK_CONTAINER(popup_menu), tmp_popup);
632 g_signal_connect(tmp_menu, "activate",
633 G_CALLBACK(on_menu_insert_include_activate), (gpointer) includes[i]);
634 g_signal_connect(tmp_popup, "activate",
635 G_CALLBACK(on_popup_insert_include_activate), (gpointer) includes[i]);
636 i++;
638 gtk_widget_show_all(edit_menu_item);
639 gtk_widget_show_all(popup_menu_item);
640 gtk_container_add(GTK_CONTAINER(me), edit_menu_item);
641 gtk_container_add(GTK_CONTAINER(mp), popup_menu_item);
645 void ui_create_insert_menu_items(void)
647 GtkMenu *menu_edit = GTK_MENU(ui_lookup_widget(main_widgets.window, "insert_include2_menu"));
648 GtkMenu *menu_popup = GTK_MENU(ui_lookup_widget(main_widgets.editor_menu, "insert_include1_menu"));
649 GtkWidget *blank;
650 const gchar *c_includes_stdlib[] = {
651 "assert.h", "ctype.h", "errno.h", "float.h", "limits.h", "locale.h", "math.h", "setjmp.h",
652 "signal.h", "stdarg.h", "stddef.h", "stdio.h", "stdlib.h", "string.h", "time.h", NULL
654 const gchar *c_includes_c99[] = {
655 "complex.h", "fenv.h", "inttypes.h", "iso646.h", "stdbool.h", "stdint.h",
656 "tgmath.h", "wchar.h", "wctype.h", NULL
658 const gchar *c_includes_cpp[] = {
659 "cstdio", "cstring", "cctype", "cmath", "ctime", "cstdlib", "cstdarg", NULL
661 const gchar *c_includes_cppstdlib[] = {
662 "iostream", "fstream", "iomanip", "sstream", "exception", "stdexcept",
663 "memory", "locale", NULL
665 const gchar *c_includes_stl[] = {
666 "bitset", "deque", "list", "map", "set", "queue", "stack", "vector", "algorithm",
667 "iterator", "functional", "string", "complex", "valarray", NULL
670 blank = gtk_menu_item_new_with_label("#include \"...\"");
671 gtk_container_add(GTK_CONTAINER(menu_edit), blank);
672 gtk_widget_show(blank);
673 g_signal_connect(blank, "activate", G_CALLBACK(on_menu_insert_include_activate), NULL);
674 blank = gtk_separator_menu_item_new ();
675 gtk_container_add(GTK_CONTAINER(menu_edit), blank);
676 gtk_widget_show(blank);
678 blank = gtk_menu_item_new_with_label("#include \"...\"");
679 gtk_container_add(GTK_CONTAINER(menu_popup), blank);
680 gtk_widget_show(blank);
681 g_signal_connect(blank, "activate", G_CALLBACK(on_popup_insert_include_activate), NULL);
682 blank = gtk_separator_menu_item_new();
683 gtk_container_add(GTK_CONTAINER(menu_popup), blank);
684 gtk_widget_show(blank);
686 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_stdlib, _("C Standard Library"));
687 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_c99, _("ISO C99"));
688 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_cpp, _("C++ (C Standard Library)"));
689 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_cppstdlib, _("C++ Standard Library"));
690 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_stl, _("C++ STL"));
694 static void insert_date(GeanyDocument *doc, gint pos, const gchar *date_style)
696 const gchar *format = NULL;
697 gchar *time_str;
699 g_return_if_fail(doc != NULL);
700 g_return_if_fail(pos == -1 || pos >= 0);
702 if (pos == -1)
703 pos = sci_get_current_position(doc->editor->sci);
705 /* set default value */
706 if (utils_str_equal("", ui_prefs.custom_date_format))
708 g_free(ui_prefs.custom_date_format);
709 ui_prefs.custom_date_format = g_strdup("%d.%m.%Y");
712 if (utils_str_equal(_("dd.mm.yyyy"), date_style))
713 format = "%d.%m.%Y";
714 else if (utils_str_equal(_("mm.dd.yyyy"), date_style))
715 format = "%m.%d.%Y";
716 else if (utils_str_equal(_("yyyy/mm/dd"), date_style))
717 format = "%Y/%m/%d";
718 else if (utils_str_equal(_("dd.mm.yyyy hh:mm:ss"), date_style))
719 format = "%d.%m.%Y %H:%M:%S";
720 else if (utils_str_equal(_("mm.dd.yyyy hh:mm:ss"), date_style))
721 format = "%m.%d.%Y %H:%M:%S";
722 else if (utils_str_equal(_("yyyy/mm/dd hh:mm:ss"), date_style))
723 format = "%Y/%m/%d %H:%M:%S";
724 else if (utils_str_equal(_("_Use Custom Date Format"), date_style))
725 format = ui_prefs.custom_date_format;
726 else
728 gchar *str = dialogs_show_input(_("Custom Date Format"), GTK_WINDOW(main_widgets.window),
729 _("Enter here a custom date and time format. "
730 "For a list of available conversion specifiers see https://docs.gtk.org/glib/method.DateTime.format.html."),
731 ui_prefs.custom_date_format);
732 if (str)
733 SETPTR(ui_prefs.custom_date_format, str);
734 return;
737 time_str = utils_get_date_time(format, NULL);
738 if (time_str != NULL)
740 sci_start_undo_action(doc->editor->sci);
741 sci_insert_text(doc->editor->sci, pos, time_str);
742 sci_goto_pos(doc->editor->sci, pos + strlen(time_str), FALSE);
743 sci_end_undo_action(doc->editor->sci);
744 g_free(time_str);
746 else
748 utils_beep();
749 ui_set_statusbar(TRUE,
750 _("Date format string could not be converted (possibly too long)."));
755 static void on_popup_insert_date_activate(GtkMenuItem *menuitem, gpointer user_data)
757 insert_date(document_get_current(), editor_info.click_pos, user_data);
761 static void on_menu_insert_date_activate(GtkMenuItem *menuitem, gpointer user_data)
763 insert_date(document_get_current(), -1, user_data);
767 static void insert_date_items(GtkMenu *me, GtkMenu *mp, gchar *label)
769 GtkWidget *item;
771 item = gtk_menu_item_new_with_mnemonic(label);
772 gtk_container_add(GTK_CONTAINER(me), item);
773 gtk_widget_show(item);
774 g_signal_connect(item, "activate", G_CALLBACK(on_menu_insert_date_activate), label);
776 item = gtk_menu_item_new_with_mnemonic(label);
777 gtk_container_add(GTK_CONTAINER(mp), item);
778 gtk_widget_show(item);
779 g_signal_connect(item, "activate", G_CALLBACK(on_popup_insert_date_activate), label);
783 void ui_create_insert_date_menu_items(void)
785 GtkMenu *menu_edit = GTK_MENU(ui_lookup_widget(main_widgets.window, "insert_date1_menu"));
786 GtkMenu *menu_popup = GTK_MENU(ui_lookup_widget(main_widgets.editor_menu, "insert_date2_menu"));
787 GtkWidget *item;
788 gchar *str;
790 insert_date_items(menu_edit, menu_popup, _("dd.mm.yyyy"));
791 insert_date_items(menu_edit, menu_popup, _("mm.dd.yyyy"));
792 insert_date_items(menu_edit, menu_popup, _("yyyy/mm/dd"));
794 item = gtk_separator_menu_item_new();
795 gtk_container_add(GTK_CONTAINER(menu_edit), item);
796 gtk_widget_show(item);
797 item = gtk_separator_menu_item_new();
798 gtk_container_add(GTK_CONTAINER(menu_popup), item);
799 gtk_widget_show(item);
801 insert_date_items(menu_edit, menu_popup, _("dd.mm.yyyy hh:mm:ss"));
802 insert_date_items(menu_edit, menu_popup, _("mm.dd.yyyy hh:mm:ss"));
803 insert_date_items(menu_edit, menu_popup, _("yyyy/mm/dd hh:mm:ss"));
805 item = gtk_separator_menu_item_new();
806 gtk_container_add(GTK_CONTAINER(menu_edit), item);
807 gtk_widget_show(item);
808 item = gtk_separator_menu_item_new();
809 gtk_container_add(GTK_CONTAINER(menu_popup), item);
810 gtk_widget_show(item);
812 str = _("_Use Custom Date Format");
813 item = gtk_menu_item_new_with_mnemonic(str);
814 gtk_container_add(GTK_CONTAINER(menu_edit), item);
815 gtk_widget_show(item);
816 g_signal_connect(item, "activate", G_CALLBACK(on_menu_insert_date_activate), str);
817 ui_hookup_widget(main_widgets.window, item, "insert_date_custom1");
819 item = gtk_menu_item_new_with_mnemonic(str);
820 gtk_container_add(GTK_CONTAINER(menu_popup), item);
821 gtk_widget_show(item);
822 g_signal_connect(item, "activate", G_CALLBACK(on_popup_insert_date_activate), str);
823 ui_hookup_widget(main_widgets.editor_menu, item, "insert_date_custom2");
825 insert_date_items(menu_edit, menu_popup, _("_Set Custom Date Format"));
829 void ui_save_buttons_toggle(gboolean enable)
831 guint i;
832 gboolean dirty_tabs = FALSE;
834 if (ui_prefs.allow_always_save)
835 enable = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)) > 0;
837 ui_widget_set_sensitive(widgets.save_buttons[0], enable);
838 ui_widget_set_sensitive(widgets.save_buttons[1], enable);
840 /* save all menu item and tool button */
841 for (i = 0; i < documents_array->len; i++)
843 /* check whether there are files where changes were made and if there are some,
844 * we need the save all button / item */
845 if (documents[i]->is_valid && documents[i]->changed)
847 dirty_tabs = TRUE;
848 break;
852 ui_widget_set_sensitive(widgets.save_buttons[2], dirty_tabs);
853 ui_widget_set_sensitive(widgets.save_buttons[3], dirty_tabs);
857 #define add_doc_widget(widget_name) \
858 g_ptr_array_add(widgets.document_buttons, ui_lookup_widget(main_widgets.window, widget_name))
860 #define add_doc_toolitem(widget_name) \
861 g_ptr_array_add(widgets.document_buttons, toolbar_get_action_by_name(widget_name))
863 static void init_document_widgets(void)
865 widgets.document_buttons = g_ptr_array_new();
867 /* Cache the document-sensitive widgets so we don't have to keep looking them up
868 * when using ui_document_buttons_update(). */
869 add_doc_widget("menu_close1");
870 add_doc_widget("close_other_documents1");
871 add_doc_widget("menu_change_font1");
872 add_doc_widget("menu_close_all1");
873 add_doc_widget("menu_save1");
874 add_doc_widget("menu_save_all1");
875 add_doc_widget("menu_save_as1");
876 add_doc_widget("menu_count_words1");
877 add_doc_widget("menu_build1");
878 add_doc_widget("add_comments1");
879 add_doc_widget("menu_paste1");
880 add_doc_widget("menu_undo2");
881 add_doc_widget("properties1");
882 add_doc_widget("menu_reload1");
883 add_doc_widget("menu_document1");
884 add_doc_widget("menu_choose_color1");
885 add_doc_widget("menu_color_schemes");
886 add_doc_widget("menu_markers_margin1");
887 add_doc_widget("menu_linenumber_margin1");
888 add_doc_widget("menu_show_white_space1");
889 add_doc_widget("menu_show_line_endings1");
890 add_doc_widget("menu_show_indentation_guides1");
891 add_doc_widget("menu_zoom_in1");
892 add_doc_widget("menu_zoom_out1");
893 add_doc_widget("normal_size1");
894 add_doc_widget("treeview6");
895 add_doc_widget("print1");
896 add_doc_widget("menu_reload_as1");
897 add_doc_widget("menu_select_all1");
898 add_doc_widget("insert_date1");
899 add_doc_widget("insert_alternative_white_space1");
900 add_doc_widget("menu_format1");
901 add_doc_widget("commands2");
902 add_doc_widget("menu_open_selected_file1");
903 add_doc_widget("page_setup1");
904 add_doc_widget("find1");
905 add_doc_widget("find_next1");
906 add_doc_widget("find_previous1");
907 add_doc_widget("go_to_next_marker1");
908 add_doc_widget("go_to_previous_marker1");
909 add_doc_widget("replace1");
910 add_doc_widget("find_nextsel1");
911 add_doc_widget("find_prevsel1");
912 add_doc_widget("find_usage1");
913 add_doc_widget("find_document_usage1");
914 add_doc_widget("mark_all1");
915 add_doc_widget("go_to_line1");
916 add_doc_widget("goto_tag_definition1");
917 add_doc_widget("goto_tag_declaration1");
918 add_doc_widget("reset_indentation1");
919 add_doc_toolitem("Close");
920 add_doc_toolitem("CloseAll");
921 add_doc_toolitem("Search");
922 add_doc_toolitem("SearchEntry");
923 add_doc_toolitem("ZoomIn");
924 add_doc_toolitem("ZoomOut");
925 add_doc_toolitem("Indent");
926 add_doc_toolitem("UnIndent");
927 add_doc_toolitem("Cut");
928 add_doc_toolitem("Copy");
929 add_doc_toolitem("Paste");
930 add_doc_toolitem("Delete");
931 add_doc_toolitem("Save");
932 add_doc_toolitem("SaveAs");
933 add_doc_toolitem("SaveAll");
934 add_doc_toolitem("Compile");
935 add_doc_toolitem("Run");
936 add_doc_toolitem("Reload");
937 add_doc_toolitem("Color");
938 add_doc_toolitem("Goto");
939 add_doc_toolitem("GotoEntry");
940 add_doc_toolitem("Replace");
941 add_doc_toolitem("Print");
945 void ui_document_buttons_update(void)
947 guint i;
948 gboolean enable = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)) > 0;
950 for (i = 0; i < widgets.document_buttons->len; i++)
952 GtkWidget *widget = g_ptr_array_index(widgets.document_buttons, i);
953 if (GTK_IS_ACTION(widget))
954 gtk_action_set_sensitive(GTK_ACTION(widget), enable);
955 else
956 ui_widget_set_sensitive(widget, enable);
961 static void on_doc_sensitive_widget_destroy(GtkWidget *widget, G_GNUC_UNUSED gpointer user_data)
963 g_ptr_array_remove_fast(widgets.document_buttons, widget);
967 /** Adds a widget to the list of widgets that should be set sensitive/insensitive
968 * when some documents are present/no documents are open.
969 * It will be removed when the widget is destroyed.
970 * @param widget The widget to add.
972 * @since 0.15
974 GEANY_API_SYMBOL
975 void ui_add_document_sensitive(GtkWidget *widget)
977 gboolean enable = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)) > 0;
979 ui_widget_set_sensitive(widget, enable);
981 g_ptr_array_add(widgets.document_buttons, widget);
982 g_signal_connect(widget, "destroy", G_CALLBACK(on_doc_sensitive_widget_destroy), NULL);
986 void ui_widget_show_hide(GtkWidget *widget, gboolean show)
988 if (show)
990 gtk_widget_show(widget);
992 else
994 gtk_widget_hide(widget);
999 void ui_sidebar_show_hide(void)
1001 GtkWidget *widget;
1003 /* check that there are no other notebook pages before hiding the sidebar completely
1004 * other pages could be e.g. the file browser plugin */
1005 if (! interface_prefs.sidebar_openfiles_visible && ! interface_prefs.sidebar_symbol_visible &&
1006 gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.sidebar_notebook)) <= 2)
1008 ui_prefs.sidebar_visible = FALSE;
1011 widget = ui_lookup_widget(main_widgets.window, "menu_show_sidebar1");
1012 if (ui_prefs.sidebar_visible != gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
1014 ignore_callback = TRUE;
1015 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), ui_prefs.sidebar_visible);
1016 ignore_callback = FALSE;
1019 ui_widget_show_hide(main_widgets.sidebar_notebook, ui_prefs.sidebar_visible);
1021 ui_widget_show_hide(gtk_notebook_get_nth_page(
1022 GTK_NOTEBOOK(main_widgets.sidebar_notebook), 0), interface_prefs.sidebar_symbol_visible);
1023 ui_widget_show_hide(gtk_notebook_get_nth_page(
1024 GTK_NOTEBOOK(main_widgets.sidebar_notebook), 1), interface_prefs.sidebar_openfiles_visible);
1028 void ui_document_show_hide(GeanyDocument *doc)
1030 const gchar *widget_name;
1031 GtkWidget *item;
1032 const GeanyIndentPrefs *iprefs;
1034 g_return_if_fail(doc == NULL || doc->is_valid);
1036 if (doc == NULL)
1037 doc = document_get_current();
1039 if (doc == NULL)
1040 return;
1042 ignore_callback = TRUE;
1044 gtk_check_menu_item_set_active(
1045 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_line_wrapping1")),
1046 doc->editor->line_wrapping);
1048 gtk_check_menu_item_set_active(
1049 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "line_breaking1")),
1050 doc->editor->line_breaking);
1052 iprefs = editor_get_indent_prefs(doc->editor);
1054 item = ui_lookup_widget(main_widgets.window, "menu_use_auto_indentation1");
1055 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), doc->editor->auto_indent);
1057 switch (iprefs->type)
1059 case GEANY_INDENT_TYPE_SPACES:
1060 widget_name = "spaces1"; break;
1061 case GEANY_INDENT_TYPE_TABS:
1062 widget_name = "tabs1"; break;
1063 case GEANY_INDENT_TYPE_BOTH:
1064 default:
1065 widget_name = "tabs_and_spaces1"; break;
1067 item = ui_lookup_widget(main_widgets.window, widget_name);
1068 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1070 if (iprefs->width >= 1 && iprefs->width <= 8)
1072 gchar *name;
1074 name = g_strdup_printf("indent_width_%d", iprefs->width);
1075 item = ui_lookup_widget(main_widgets.window, name);
1076 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1077 g_free(name);
1080 gtk_check_menu_item_set_active(
1081 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "set_file_readonly1")),
1082 doc->readonly);
1084 item = ui_lookup_widget(main_widgets.window, "menu_write_unicode_bom1");
1085 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), doc->has_bom);
1086 ui_widget_set_sensitive(item, encodings_is_unicode_charset(doc->encoding));
1088 switch (sci_get_eol_mode(doc->editor->sci))
1090 case SC_EOL_CR: widget_name = "cr"; break;
1091 case SC_EOL_LF: widget_name = "lf"; break;
1092 default: widget_name = "crlf"; break;
1094 gtk_check_menu_item_set_active(
1095 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, widget_name)), TRUE);
1097 encodings_select_radio_item(doc->encoding);
1098 filetypes_select_radio_item(doc->file_type);
1100 ignore_callback = FALSE;
1104 void ui_set_search_entry_background(GtkWidget *widget, gboolean success)
1106 gtk_widget_set_name(widget, success ? NULL : "geany-search-entry-no-match");
1110 static void recent_create_menu(GeanyRecentFiles *grf)
1112 guint i, len;
1114 len = MIN(file_prefs.mru_length, g_queue_get_length(grf->recent_queue));
1115 for (i = 0; i < len; i++)
1117 /* create menu item for the recent files menu in the menu bar */
1118 const gchar *filename = g_queue_peek_nth(grf->recent_queue, i);
1119 GtkWidget *tmp = gtk_menu_item_new_with_label(filename);
1121 gtk_widget_show(tmp);
1122 gtk_container_add(GTK_CONTAINER(grf->menubar), tmp);
1123 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
1124 /* create menu item for the recent files menu in the toolbar */
1125 if (grf->toolbar != NULL)
1127 tmp = gtk_menu_item_new_with_label(filename);
1128 gtk_widget_show(tmp);
1129 gtk_container_add(GTK_CONTAINER(grf->toolbar), tmp);
1130 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
1136 static GeanyRecentFiles *recent_get_recent_files(void)
1138 static GeanyRecentFiles grf = { RECENT_FILE_FILE, NULL, NULL, NULL, NULL };
1140 if (G_UNLIKELY(grf.recent_queue == NULL))
1142 grf.recent_queue = ui_prefs.recent_queue;
1143 grf.menubar = ui_widgets.recent_files_menu_menubar;
1144 grf.toolbar = geany_menu_button_action_get_menu(GEANY_MENU_BUTTON_ACTION(
1145 toolbar_get_action_by_name("Open")));
1146 grf.activate_cb = recent_file_activate_cb;
1148 return &grf;
1152 static GeanyRecentFiles *recent_get_recent_projects(void)
1154 static GeanyRecentFiles grf = { RECENT_FILE_PROJECT, NULL, NULL, NULL, NULL };
1156 if (G_UNLIKELY(grf.recent_queue == NULL))
1158 grf.recent_queue = ui_prefs.recent_projects_queue;
1159 grf.menubar = ui_widgets.recent_projects_menu_menubar;
1160 grf.toolbar = NULL;
1161 grf.activate_cb = recent_project_activate_cb;
1163 return &grf;
1167 void ui_create_recent_menus(void)
1169 recent_create_menu(recent_get_recent_files());
1170 recent_create_menu(recent_get_recent_projects());
1174 static void recent_file_activate_cb(GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
1176 gchar *utf8_filename = ui_menu_item_get_text(menuitem);
1177 gchar *locale_filename = utils_get_locale_from_utf8(utf8_filename);
1179 if (document_open_file(locale_filename, FALSE, NULL, NULL) != NULL)
1180 recent_file_loaded(utf8_filename, recent_get_recent_files());
1182 g_free(locale_filename);
1183 g_free(utf8_filename);
1187 static void recent_project_activate_cb(GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
1189 gchar *utf8_filename = ui_menu_item_get_text(menuitem);
1190 gchar *locale_filename = utils_get_locale_from_utf8(utf8_filename);
1192 if (app->project && !project_close(FALSE)) {}
1193 else if (project_load_file_with_session(locale_filename))
1194 recent_file_loaded(utf8_filename, recent_get_recent_projects());
1196 g_free(locale_filename);
1197 g_free(utf8_filename);
1201 static void add_recent_file(const gchar *utf8_filename, GeanyRecentFiles *grf,
1202 const GtkRecentData *rdata)
1204 if (g_queue_find_custom(grf->recent_queue, utf8_filename, (GCompareFunc) strcmp) == NULL)
1207 if (grf->type == RECENT_FILE_FILE && rdata)
1209 GtkRecentManager *manager = gtk_recent_manager_get_default();
1210 gchar *uri = g_filename_to_uri(utf8_filename, NULL, NULL);
1211 if (uri != NULL)
1213 gtk_recent_manager_add_full(manager, uri, rdata);
1214 g_free(uri);
1218 g_queue_push_head(grf->recent_queue, g_strdup(utf8_filename));
1219 if (g_queue_get_length(grf->recent_queue) > file_prefs.mru_length)
1221 g_free(g_queue_pop_tail(grf->recent_queue));
1223 update_recent_menu(grf);
1225 /* filename already in recent list */
1226 else
1227 recent_file_loaded(utf8_filename, grf);
1231 void ui_add_recent_document(GeanyDocument *doc)
1233 /* what are the groups for actually? */
1234 static const gchar *groups[2] = {
1235 "geany",
1236 NULL
1238 GtkRecentData rdata;
1240 /* Prepare the data for gtk_recent_manager_add_full() */
1241 rdata.display_name = NULL;
1242 rdata.description = NULL;
1243 rdata.mime_type = doc->file_type->mime_type;
1244 /* if we ain't got no mime-type, fallback to plain text */
1245 if (! rdata.mime_type)
1246 rdata.mime_type = (gchar *) "text/plain";
1247 rdata.app_name = (gchar *) "geany";
1248 rdata.app_exec = (gchar *) "geany %u";
1249 rdata.groups = (gchar **) groups;
1250 rdata.is_private = FALSE;
1252 add_recent_file(doc->file_name, recent_get_recent_files(), &rdata);
1256 void ui_add_recent_project_file(const gchar *utf8_filename)
1258 add_recent_file(utf8_filename, recent_get_recent_projects(), NULL);
1262 /* Returns: newly allocated string with the UTF-8 menu text. */
1263 gchar *ui_menu_item_get_text(GtkMenuItem *menu_item)
1265 const gchar *text = NULL;
1267 if (gtk_bin_get_child(GTK_BIN(menu_item)))
1269 GtkWidget *child = gtk_bin_get_child(GTK_BIN(menu_item));
1271 if (GTK_IS_LABEL(child))
1272 text = gtk_label_get_text(GTK_LABEL(child));
1274 /* GTK owns text so it's much safer to return a copy of it in case the memory is reallocated */
1275 return g_strdup(text);
1279 static gint find_recent_file_item(gconstpointer list_data, gconstpointer user_data)
1281 gchar *menu_text = ui_menu_item_get_text(GTK_MENU_ITEM(list_data));
1282 gint result;
1284 if (utils_str_equal(menu_text, user_data))
1285 result = 0;
1286 else
1287 result = 1;
1289 g_free(menu_text);
1290 return result;
1294 /* update the project menu item's sensitivity */
1295 void ui_update_recent_project_menu(void)
1297 GeanyRecentFiles *grf = recent_get_recent_projects();
1298 GList *children, *item;
1300 /* only need to update the menubar menu, the project doesn't have a toolbar item */
1301 children = gtk_container_get_children(GTK_CONTAINER(grf->menubar));
1302 for (item = children; item; item = item->next)
1304 gboolean sensitive = TRUE;
1306 if (app->project)
1308 const gchar *filename = gtk_menu_item_get_label(item->data);
1309 sensitive = g_strcmp0(app->project->file_name, filename) != 0;
1311 gtk_widget_set_sensitive(item->data, sensitive);
1313 g_list_free(children);
1317 /* Use instead of gtk_menu_reorder_child() to update the menubar properly on OS X */
1318 static void menu_reorder_child(GtkMenu *menu, GtkWidget *child, gint position)
1320 gtk_menu_reorder_child(menu, child, position);
1321 #ifdef MAC_INTEGRATION
1322 /* On OS X GtkMenuBar is kept in sync with the native OS X menubar using
1323 * signals. Unfortunately gtk_menu_reorder_child() doesn't emit anything
1324 * so we have to update the OS X menubar manually. */
1325 gtkosx_application_sync_menubar(gtkosx_application_get());
1326 #endif
1330 static void add_recent_file_menu_item(const gchar *utf8_filename, GeanyRecentFiles *grf, GtkWidget *menu)
1332 GtkWidget *child = gtk_menu_item_new_with_label(utf8_filename);
1334 gtk_widget_show(child);
1335 if (menu != grf->toolbar)
1336 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), child);
1337 else
1339 /* this is a bit ugly, but we need to use gtk_container_add(). Using
1340 * gtk_menu_shell_prepend() doesn't emit GtkContainer's "add" signal
1341 * which we need in GeanyMenubuttonAction */
1342 gtk_container_add(GTK_CONTAINER(menu), child);
1343 menu_reorder_child(GTK_MENU(menu), child, 0);
1345 g_signal_connect(child, "activate", G_CALLBACK(grf->activate_cb), NULL);
1349 static void recent_file_loaded(const gchar *utf8_filename, GeanyRecentFiles *grf)
1351 GList *item;
1352 GtkWidget *parents[] = { grf->menubar, grf->toolbar };
1353 guint i;
1355 /* first reorder the queue */
1356 item = g_queue_find_custom(grf->recent_queue, utf8_filename, (GCompareFunc) strcmp);
1357 g_return_if_fail(item != NULL);
1359 g_queue_unlink(grf->recent_queue, item);
1360 g_queue_push_head_link(grf->recent_queue, item);
1362 for (i = 0; i < G_N_ELEMENTS(parents); i++)
1364 GList *children;
1366 if (! parents[i])
1367 continue;
1369 children = gtk_container_get_children(GTK_CONTAINER(parents[i]));
1370 item = g_list_find_custom(children, utf8_filename, (GCompareFunc) find_recent_file_item);
1371 /* either reorder or prepend a new one */
1372 if (item)
1373 menu_reorder_child(GTK_MENU(parents[i]), item->data, 0);
1374 else
1375 add_recent_file_menu_item(utf8_filename, grf, parents[i]);
1376 g_list_free(children);
1379 if (grf->type == RECENT_FILE_PROJECT)
1380 ui_update_recent_project_menu();
1384 static void update_recent_menu(GeanyRecentFiles *grf)
1386 gchar *filename;
1387 GtkWidget *parents[] = { grf->menubar, grf->toolbar };
1388 guint i;
1390 filename = g_queue_peek_head(grf->recent_queue);
1392 for (i = 0; i < G_N_ELEMENTS(parents); i++)
1394 GList *children;
1396 if (! parents[i])
1397 continue;
1399 /* clean the MRU list before adding an item */
1400 children = gtk_container_get_children(GTK_CONTAINER(parents[i]));
1401 if (g_list_length(children) > file_prefs.mru_length - 1)
1403 GList *item = g_list_nth(children, file_prefs.mru_length - 1);
1404 while (item != NULL)
1406 if (GTK_IS_MENU_ITEM(item->data))
1407 gtk_widget_destroy(GTK_WIDGET(item->data));
1408 item = g_list_next(item);
1411 g_list_free(children);
1413 /* create the new item */
1414 add_recent_file_menu_item(filename, grf, parents[i]);
1417 if (grf->type == RECENT_FILE_PROJECT)
1418 ui_update_recent_project_menu();
1422 void ui_toggle_editor_features(GeanyUIEditorFeatures feature)
1424 guint i;
1426 foreach_document (i)
1428 GeanyDocument *doc = documents[i];
1430 switch (feature)
1432 case GEANY_EDITOR_SHOW_MARKERS_MARGIN:
1433 sci_set_symbol_margin(doc->editor->sci, editor_prefs.show_markers_margin);
1434 break;
1435 case GEANY_EDITOR_SHOW_LINE_NUMBERS:
1436 sci_set_line_numbers(doc->editor->sci, editor_prefs.show_linenumber_margin);
1437 break;
1438 case GEANY_EDITOR_SHOW_WHITE_SPACE:
1439 sci_set_visible_white_spaces(doc->editor->sci, editor_prefs.show_white_space);
1440 break;
1441 case GEANY_EDITOR_SHOW_LINE_ENDINGS:
1442 sci_set_visible_eols(doc->editor->sci, editor_prefs.show_line_endings);
1443 break;
1444 case GEANY_EDITOR_SHOW_INDENTATION_GUIDES:
1445 editor_set_indentation_guides(doc->editor);
1446 break;
1452 void ui_update_view_editor_menu_items(void)
1454 ignore_callback = TRUE;
1455 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_markers_margin1")), editor_prefs.show_markers_margin);
1456 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_linenumber_margin1")), editor_prefs.show_linenumber_margin);
1457 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);
1458 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);
1459 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);
1460 ignore_callback = FALSE;
1464 /** Creates a GNOME HIG-style frame (with no border and indented child alignment).
1465 * @param label_text The label text.
1466 * @param alignment An address to store the alignment widget pointer.
1468 * @return @transfer{floating} The frame widget, setting the alignment container for
1469 * packing child widgets.
1471 * @deprecated 1.29: Use GTK API directly
1473 GEANY_API_SYMBOL
1474 GtkWidget *ui_frame_new_with_alignment(const gchar *label_text, GtkWidget **alignment)
1476 GtkWidget *label, *align;
1477 GtkWidget *frame = gtk_frame_new(NULL);
1479 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
1481 align = gtk_alignment_new(0.5, 0.5, 1, 1);
1482 gtk_container_add(GTK_CONTAINER(frame), align);
1483 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 0);
1485 label = ui_label_new_bold(label_text);
1486 gtk_frame_set_label_widget(GTK_FRAME(frame), label);
1488 *alignment = align;
1489 return frame;
1493 /** Makes a fixed border for dialogs without increasing the button box border.
1494 * @param dialog The parent container for the @c GtkVBox.
1496 * @return @transfer{none} The packed @c GtkVBox. */
1497 GEANY_API_SYMBOL
1498 GtkWidget *ui_dialog_vbox_new(GtkDialog *dialog)
1500 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); /* need child vbox to set a separate border. */
1502 gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
1503 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), vbox, TRUE, TRUE, 0);
1504 return vbox;
1508 /* Reorders a dialog's buttons
1509 * @param dialog A dialog
1510 * @param response First response ID to reorder
1511 * @param ... more response IDs, terminated by -1
1513 * Like gtk_dialog_set_alternative_button_order(), but reorders the default
1514 * buttons layout, not the alternative one. This is useful if you e.g. added a
1515 * button to a dialog which already had some and need yours not to be on the
1516 * end.
1518 /* Heavily based on gtk_dialog_set_alternative_button_order().
1519 * This relies on the action area to be a GtkBox, but although not documented
1520 * the API expose it to be a GtkHButtonBox though GtkBuilder, so it should be
1521 * fine */
1522 void ui_dialog_set_primary_button_order(GtkDialog *dialog, gint response, ...)
1524 va_list ap;
1525 GtkWidget *action_area = gtk_dialog_get_action_area(dialog);
1526 gint position;
1528 va_start(ap, response);
1529 for (position = 0; response != -1; position++)
1531 GtkWidget *child = gtk_dialog_get_widget_for_response(dialog, response);
1532 if (child)
1533 gtk_box_reorder_child(GTK_BOX(action_area), child, position);
1534 else
1535 g_warning("%s: no child button with response id %d.", G_STRFUNC, response);
1537 response = va_arg(ap, gint);
1539 va_end(ap);
1543 /** Creates a @c GtkButton with custom text and a stock image similar to
1544 * @c gtk_button_new_from_stock().
1545 * @param stock_id A @c GTK_STOCK_NAME string.
1546 * @param text Button label text, can include mnemonics.
1548 * @return @transfer{floating} The new @c GtkButton.
1550 GEANY_API_SYMBOL
1551 GtkWidget *ui_button_new_with_image(const gchar *stock_id, const gchar *text)
1553 GtkWidget *image, *button;
1555 button = gtk_button_new_with_mnemonic(text);
1556 gtk_widget_show(button);
1557 image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON);
1558 gtk_button_set_image(GTK_BUTTON(button), image);
1559 /* note: image is shown by gtk */
1560 return button;
1564 /** Creates a @c GtkImageMenuItem with a stock image and a custom label.
1565 * @param stock_id Stock image ID, e.g. @c GTK_STOCK_OPEN.
1566 * @param label Menu item label, can include mnemonics.
1567 * @return @transfer{floating} The new @c GtkImageMenuItem.
1569 * @since 0.16
1571 GEANY_API_SYMBOL
1572 GtkWidget *
1573 ui_image_menu_item_new(const gchar *stock_id, const gchar *label)
1575 GtkWidget *item = gtk_image_menu_item_new_with_mnemonic(label);
1576 GtkWidget *image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_MENU);
1578 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
1579 gtk_widget_show(image);
1580 return item;
1584 static void entry_clear_icon_release_cb(GtkEntry *entry, gint icon_pos,
1585 GdkEvent *event, gpointer data)
1587 if (event->button.button == 1 && icon_pos == 1)
1589 gtk_entry_set_text(entry, "");
1590 gtk_widget_grab_focus(GTK_WIDGET(entry));
1595 /** Adds a small clear icon to the right end of the passed @a entry.
1596 * A callback to clear the contents of the GtkEntry is automatically added.
1598 * @param entry The GtkEntry object to which the icon should be attached.
1600 * @since 0.16
1602 GEANY_API_SYMBOL
1603 void ui_entry_add_clear_icon(GtkEntry *entry)
1605 g_object_set(entry, "secondary-icon-stock", GTK_STOCK_CLEAR,
1606 "secondary-icon-activatable", TRUE, NULL);
1607 g_signal_connect(entry, "icon-release", G_CALLBACK(entry_clear_icon_release_cb), NULL);
1611 /* Adds a :activate-backwards signal emitted by default when <Shift>Return is pressed */
1612 void ui_entry_add_activate_backward_signal(GtkEntry *entry)
1614 static gboolean installed = FALSE;
1616 g_return_if_fail(GTK_IS_ENTRY(entry));
1618 if (G_UNLIKELY(! installed))
1620 GtkBindingSet *binding_set;
1622 installed = TRUE;
1624 /* try to handle the unexpected case where GTK would already have installed the signal */
1625 if (g_signal_lookup("activate-backward", G_TYPE_FROM_INSTANCE(entry)))
1627 g_warning("Signal GtkEntry:activate-backward is unexpectedly already installed");
1628 return;
1631 g_signal_new("activate-backward", G_TYPE_FROM_INSTANCE(entry),
1632 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, 0, NULL, NULL,
1633 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
1634 binding_set = gtk_binding_set_by_class(GTK_ENTRY_GET_CLASS(entry));
1635 gtk_binding_entry_add_signal(binding_set, GDK_KEY_Return, GDK_SHIFT_MASK, "activate-backward", 0);
1640 static void add_to_size_group(GtkWidget *widget, gpointer size_group)
1642 g_return_if_fail(GTK_IS_SIZE_GROUP(size_group));
1643 gtk_size_group_add_widget(GTK_SIZE_GROUP(size_group), widget);
1647 /* Copies the spacing and layout of the master GtkHButtonBox and synchronises
1648 * the width of each button box's children.
1649 * Should be called after all child widgets have been packed. */
1650 void ui_hbutton_box_copy_layout(GtkButtonBox *master, GtkButtonBox *copy)
1652 GtkSizeGroup *size_group;
1654 gtk_box_set_spacing(GTK_BOX(copy), 10);
1655 gtk_button_box_set_layout(copy, gtk_button_box_get_layout(master));
1657 /* now we need to put the widest widget from each button box in a size group,
1658 * but we don't know the width before they are drawn, and for different label
1659 * translations the widest widget can vary, so we just add all widgets. */
1660 size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1661 gtk_container_foreach(GTK_CONTAINER(master), add_to_size_group, size_group);
1662 gtk_container_foreach(GTK_CONTAINER(copy), add_to_size_group, size_group);
1663 g_object_unref(size_group);
1667 static gboolean tree_model_find_text(GtkTreeModel *model,
1668 GtkTreeIter *iter, gint column, const gchar *text)
1670 gchar *combo_text;
1671 gboolean found = FALSE;
1673 if (gtk_tree_model_get_iter_first(model, iter))
1677 gtk_tree_model_get(model, iter, 0, &combo_text, -1);
1678 found = utils_str_equal(combo_text, text);
1679 g_free(combo_text);
1681 if (found)
1682 return TRUE;
1684 while (gtk_tree_model_iter_next(model, iter));
1686 return FALSE;
1690 /** Prepends @a text to the drop down list, removing a duplicate element in
1691 * the list if found. Also ensures there are <= @a history_len elements.
1692 * @param combo_entry .
1693 * @param text @nullable Text to add, or @c NULL for current entry text.
1694 * @param history_len Max number of items, or @c 0 for default. */
1695 GEANY_API_SYMBOL
1696 void ui_combo_box_add_to_history(GtkComboBoxText *combo_entry,
1697 const gchar *text, gint history_len)
1699 GtkComboBox *combo = GTK_COMBO_BOX(combo_entry);
1700 GtkTreeModel *model;
1701 GtkTreeIter iter;
1702 GtkTreePath *path;
1704 if (history_len <= 0)
1705 history_len = 10;
1706 if (!text)
1707 text = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))));
1709 model = gtk_combo_box_get_model(combo);
1711 if (tree_model_find_text(model, &iter, 0, text))
1713 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1715 gtk_combo_box_text_prepend_text(combo_entry, text);
1717 /* limit history */
1718 path = gtk_tree_path_new_from_indices(history_len, -1);
1719 if (gtk_tree_model_get_iter(model, &iter, path))
1721 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1723 gtk_tree_path_free(path);
1727 /* Same as gtk_combo_box_text_prepend_text(), except that text is only prepended if it not already
1728 * exists in the combo's model. */
1729 void ui_combo_box_prepend_text_once(GtkComboBoxText *combo, const gchar *text)
1731 GtkTreeModel *model;
1732 GtkTreeIter iter;
1734 model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
1735 if (tree_model_find_text(model, &iter, 0, text))
1736 return; /* don't prepend duplicate */
1738 gtk_combo_box_text_prepend_text(combo, text);
1742 /* Changes the color of the notebook tab text and open files items according to
1743 * document status. */
1744 void ui_update_tab_status(GeanyDocument *doc)
1746 gtk_widget_set_name(doc->priv->tab_label, document_get_status_widget_class(doc));
1748 sidebar_openfiles_update(doc);
1752 static gboolean tree_model_iter_get_next(GtkTreeModel *model, GtkTreeIter *iter,
1753 gboolean down)
1755 GtkTreePath *path;
1756 gboolean result;
1758 if (down)
1759 return gtk_tree_model_iter_next(model, iter);
1761 path = gtk_tree_model_get_path(model, iter);
1762 result = gtk_tree_path_prev(path) && gtk_tree_model_get_iter(model, iter, path);
1763 gtk_tree_path_free(path);
1764 return result;
1768 /* note: the while loop might be more efficient when searching upwards if it
1769 * used tree paths instead of tree iters, but in practice it probably doesn't matter much. */
1770 static gboolean tree_view_find(GtkTreeView *treeview, TVMatchCallback cb, gboolean down)
1772 GtkTreeSelection *treesel;
1773 GtkTreeIter iter;
1774 GtkTreeModel *model;
1776 treesel = gtk_tree_view_get_selection(treeview);
1777 if (gtk_tree_selection_get_selected(treesel, &model, &iter))
1779 /* get the next selected item */
1780 if (! tree_model_iter_get_next(model, &iter, down))
1781 return FALSE; /* no more items */
1783 else /* no selection */
1785 if (! gtk_tree_model_get_iter_first(model, &iter))
1786 return TRUE; /* no items */
1788 while (TRUE)
1790 gtk_tree_selection_select_iter(treesel, &iter);
1791 if (cb(FALSE))
1792 break; /* found next message */
1794 if (! tree_model_iter_get_next(model, &iter, down))
1795 return FALSE; /* no more items */
1797 /* scroll item in view */
1798 if (ui_prefs.msgwindow_visible)
1800 GtkTreePath *path = gtk_tree_model_get_path(
1801 gtk_tree_view_get_model(treeview), &iter);
1803 gtk_tree_view_scroll_to_cell(treeview, path, NULL, TRUE, 0.5, 0.5);
1804 gtk_tree_path_free(path);
1806 return TRUE;
1810 /* Returns FALSE if the treeview has items but no matching next item. */
1811 gboolean ui_tree_view_find_next(GtkTreeView *treeview, TVMatchCallback cb)
1813 return tree_view_find(treeview, cb, TRUE);
1817 /* Returns FALSE if the treeview has items but no matching next item. */
1818 gboolean ui_tree_view_find_previous(GtkTreeView *treeview, TVMatchCallback cb)
1820 return tree_view_find(treeview, cb, FALSE);
1824 /* Shamelessly stolen from GTK */
1825 static gboolean ui_tree_view_query_tooltip_cb(GtkWidget *widget, gint x, gint y,
1826 gboolean keyboard_tip, GtkTooltip *tooltip, gpointer data)
1828 GValue value = { 0 };
1829 GValue transformed = { 0 };
1830 GtkTreeIter iter;
1831 GtkTreePath *path;
1832 GtkTreeModel *model;
1833 GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
1834 gint column = GPOINTER_TO_INT(data);
1835 gboolean tootlip_set = FALSE;
1837 if (! gtk_tree_view_get_tooltip_context(tree_view, &x, &y, keyboard_tip, &model, &path, &iter))
1838 return FALSE;
1840 gtk_tree_model_get_value(model, &iter, column, &value);
1842 g_value_init(&transformed, G_TYPE_STRING);
1843 if (g_value_transform(&value, &transformed) && g_value_get_string(&transformed))
1845 gtk_tooltip_set_text(tooltip, g_value_get_string(&transformed));
1846 gtk_tree_view_set_tooltip_row(tree_view, tooltip, path);
1847 tootlip_set = TRUE;
1850 g_value_unset(&transformed);
1851 g_value_unset(&value);
1852 gtk_tree_path_free(path);
1854 return tootlip_set;
1858 /** Adds text tooltips to a tree view.
1860 * This is similar to gtk_tree_view_set_tooltip_column() but considers the column contents to be
1861 * text, not markup -- it uses gtk_tooltip_set_text() rather than gtk_tooltip_set_markup() to set
1862 * the tooltip's value.
1864 * @warning Unlike gtk_tree_view_set_tooltip_column() you currently cannot change or remove the
1865 * tooltip column after it has been added. Trying to do so will probably give funky results.
1867 * @param tree_view The tree view
1868 * @param column The column to get the tooltip from
1870 * @since 1.25 (API 223)
1872 /* Note: @p column is int and not uint both to match gtk_tree_view_set_tooltip_column() signature
1873 * and to allow future support of -1 to unset if ever wanted */
1874 GEANY_API_SYMBOL
1875 void ui_tree_view_set_tooltip_text_column(GtkTreeView *tree_view, gint column)
1877 g_return_if_fail(column >= 0);
1878 g_return_if_fail(GTK_IS_TREE_VIEW(tree_view));
1880 g_signal_connect(tree_view, "query-tooltip",
1881 G_CALLBACK(ui_tree_view_query_tooltip_cb), GINT_TO_POINTER(column));
1882 gtk_widget_set_has_tooltip(GTK_WIDGET(tree_view), TRUE);
1887 * Modifies the font of a widget using gtk_widget_modify_font().
1889 * @param widget The widget.
1890 * @param str The font name as expected by pango_font_description_from_string().
1892 GEANY_API_SYMBOL
1893 void ui_widget_modify_font_from_string(GtkWidget *widget, const gchar *str)
1895 PangoFontDescription *pfd;
1897 pfd = pango_font_description_from_string(str);
1898 gtk_widget_modify_font(widget, pfd);
1899 pango_font_description_free(pfd);
1903 /** Creates a @c GtkHBox with @a entry packed into it and an open button which runs a
1904 * file chooser, replacing entry text (if successful) with the path returned from the
1905 * @c GtkFileChooser.
1906 * @note @a entry can be the child of an unparented widget, such as @c GtkComboBoxEntry.
1907 * @param title @nullable The file chooser dialog title, or @c NULL.
1908 * @param action The mode of the file chooser.
1909 * @param entry Can be an unpacked @c GtkEntry, or the child of an unpacked widget,
1910 * such as @c GtkComboBoxEntry.
1912 * @return @transfer{floating} The @c GtkHBox.
1914 /* @see ui_setup_open_button_callback(). */
1915 GEANY_API_SYMBOL
1916 GtkWidget *ui_path_box_new(const gchar *title, GtkFileChooserAction action, GtkEntry *entry)
1918 GtkWidget *vbox, *dirbtn, *openimg, *hbox, *path_entry, *parent, *next_parent;
1920 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
1921 path_entry = GTK_WIDGET(entry);
1923 /* prevent path_entry being vertically stretched to the height of dirbtn */
1924 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1926 parent = path_entry;
1927 while ((next_parent = gtk_widget_get_parent(parent)) != NULL)
1928 parent = next_parent;
1930 gtk_box_pack_start(GTK_BOX(vbox), parent, TRUE, FALSE, 0);
1932 dirbtn = gtk_button_new();
1933 openimg = gtk_image_new_from_stock(GTK_STOCK_OPEN, GTK_ICON_SIZE_BUTTON);
1934 gtk_container_add(GTK_CONTAINER(dirbtn), openimg);
1935 ui_setup_open_button_callback(dirbtn, title, action, entry);
1937 gtk_box_pack_end(GTK_BOX(hbox), dirbtn, FALSE, FALSE, 0);
1938 gtk_box_pack_end(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
1939 return hbox;
1943 static void ui_path_box_open_clicked(GtkButton *button, gpointer user_data);
1946 /* Setup a GtkButton to run a GtkFileChooser, setting entry text if successful.
1947 * title can be NULL.
1948 * action is the file chooser mode to use. */
1949 void ui_setup_open_button_callback(GtkWidget *open_btn, const gchar *title,
1950 GtkFileChooserAction action, GtkEntry *entry)
1952 GtkWidget *path_entry = GTK_WIDGET(entry);
1954 if (title)
1955 g_object_set_data_full(G_OBJECT(open_btn), "title", g_strdup(title),
1956 (GDestroyNotify) g_free);
1957 g_object_set_data(G_OBJECT(open_btn), "action", GINT_TO_POINTER(action));
1958 g_signal_connect(open_btn, "clicked", G_CALLBACK(ui_path_box_open_clicked), path_entry);
1962 #ifndef G_OS_WIN32
1963 static gchar *run_file_chooser(const gchar *title, GtkFileChooserAction action,
1964 const gchar *utf8_path)
1966 GtkWidget *dialog = gtk_file_chooser_dialog_new(title,
1967 GTK_WINDOW(main_widgets.window), action,
1968 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1969 GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);
1970 gchar *locale_path;
1971 gchar *ret_path = NULL;
1973 gtk_widget_set_name(dialog, "GeanyDialog");
1974 locale_path = utils_get_locale_from_utf8(utf8_path);
1975 if (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
1977 if (g_path_is_absolute(locale_path) && g_file_test(locale_path, G_FILE_TEST_IS_DIR))
1978 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), locale_path);
1980 else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
1982 if (g_path_is_absolute(locale_path))
1983 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), locale_path);
1985 g_free(locale_path);
1987 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK)
1989 gchar *dir_locale;
1991 dir_locale = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1992 ret_path = utils_get_utf8_from_locale(dir_locale);
1993 g_free(dir_locale);
1995 gtk_widget_destroy(dialog);
1996 return ret_path;
1998 #endif
2001 gchar *ui_get_project_directory(const gchar *path)
2003 gchar *utf8_path;
2004 const gchar *title = _("Select Project Base Path");
2006 #ifdef G_OS_WIN32
2007 utf8_path = win32_show_folder_dialog(ui_widgets.prefs_dialog, title, path);
2008 #else
2009 utf8_path = run_file_chooser(title, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, path);
2010 #endif
2012 return utf8_path;
2016 static void ui_path_box_open_clicked(GtkButton *button, gpointer user_data)
2018 GtkFileChooserAction action = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "action"));
2019 GtkEntry *entry = user_data;
2020 const gchar *title = g_object_get_data(G_OBJECT(button), "title");
2021 gchar *utf8_path = NULL;
2023 /* TODO: extend for other actions */
2024 g_return_if_fail(action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
2025 action == GTK_FILE_CHOOSER_ACTION_OPEN);
2027 if (title == NULL)
2028 title = (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) ?
2029 _("Select Folder") : _("Select File");
2031 if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
2033 #ifdef G_OS_WIN32
2034 utf8_path = win32_show_file_dialog(GTK_WINDOW(ui_widgets.prefs_dialog), title,
2035 gtk_entry_get_text(GTK_ENTRY(entry)));
2036 #else
2037 utf8_path = run_file_chooser(title, action, gtk_entry_get_text(GTK_ENTRY(entry)));
2038 #endif
2040 else if (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
2042 gchar *path = g_path_get_dirname(gtk_entry_get_text(GTK_ENTRY(entry)));
2043 #ifdef G_OS_WIN32
2044 utf8_path = win32_show_folder_dialog(ui_widgets.prefs_dialog, title,
2045 gtk_entry_get_text(GTK_ENTRY(entry)));
2046 #else
2047 utf8_path = run_file_chooser(title, action, path);
2048 #endif
2049 g_free(path);
2052 if (utf8_path != NULL)
2054 gtk_entry_set_text(GTK_ENTRY(entry), utf8_path);
2055 g_free(utf8_path);
2060 void ui_statusbar_showhide(gboolean state)
2062 /* handle statusbar visibility */
2063 if (state)
2065 gtk_widget_show(ui_widgets.statusbar);
2066 ui_update_statusbar(NULL, -1);
2068 else
2069 gtk_widget_hide(ui_widgets.statusbar);
2073 /** Packs all @c GtkWidgets passed after the row argument into a table, using
2074 * one widget per cell. The first widget is not expanded as the table grows,
2075 * as this is usually a label.
2076 * @param table
2077 * @param row The row number of the table.
2079 GEANY_API_SYMBOL
2080 void ui_table_add_row(GtkTable *table, gint row, ...)
2082 va_list args;
2083 guint i;
2084 GtkWidget *widget;
2086 va_start(args, row);
2087 for (i = 0; (widget = va_arg(args, GtkWidget*), widget != NULL); i++)
2089 gint options = (i == 0) ? GTK_FILL : GTK_EXPAND | GTK_FILL;
2091 gtk_table_attach(GTK_TABLE(table), widget, i, i + 1, row, row + 1,
2092 options, 0, 0, 0);
2094 va_end(args);
2098 /* comment-out all lines that are not already commented out except sections */
2099 static void comment_conf_files(ScintillaObject *sci)
2101 gint line, line_count;
2103 line_count = sci_get_line_count(sci);
2104 for (line = 0; line < line_count; line++)
2106 gint pos_start = sci_get_position_from_line(sci, line);
2107 gint pos_end = sci_get_line_end_position(sci, line);
2108 gint pos;
2110 for (pos = pos_start; pos < pos_end; pos++)
2112 gchar c = sci_get_char_at(sci, pos);
2113 if (c == '[' || c == '#')
2114 break;
2115 if (!isspace(c))
2117 sci_insert_text(sci, pos_start, "#");
2118 break;
2125 static void on_config_file_clicked(GtkWidget *widget, gpointer user_data)
2127 const gchar *file_name = user_data;
2128 GeanyFiletype *ft = NULL;
2130 if (strstr(file_name, G_DIR_SEPARATOR_S "filetypes."))
2131 ft = filetypes[GEANY_FILETYPES_CONF];
2133 if (g_file_test(file_name, G_FILE_TEST_EXISTS))
2134 document_open_file(file_name, FALSE, ft, NULL);
2135 else
2137 gchar *utf8_filename = utils_get_utf8_from_locale(file_name);
2138 gchar *global_file;
2139 gchar *base_name = NULL;
2140 gchar *global_content = NULL;
2141 GeanyDocument *doc = NULL;
2143 /* get the path inside app->configdir - can contain subdirectories */
2144 if (g_str_has_prefix(file_name, app->configdir))
2146 gsize len = strlen(app->configdir);
2147 if (file_name[len] == G_DIR_SEPARATOR)
2148 base_name = g_strdup(file_name + len + 1);
2151 if (!base_name)
2152 base_name = g_path_get_basename(file_name);
2154 global_file = g_build_filename(app->datadir, base_name, NULL);
2156 /* if the requested file doesn't exist in the user's config dir, try loading the file
2157 * from the global data directory and use its contents for the newly created file */
2158 if (g_file_test(global_file, G_FILE_TEST_EXISTS))
2159 g_file_get_contents(global_file, &global_content, NULL, NULL);
2161 doc = document_new_file(utf8_filename, ft, global_content);
2162 if (global_content && doc->file_type->id == GEANY_FILETYPES_CONF)
2164 comment_conf_files(doc->editor->sci);
2165 sci_set_current_line(doc->editor->sci, 0);
2166 document_set_text_changed(doc, FALSE);
2167 sci_empty_undo_buffer(doc->editor->sci);
2168 ui_document_show_hide(doc); /* update the document menu */
2170 utils_free_pointers(4, utf8_filename, base_name, global_file, global_content, NULL);
2175 static void free_on_closure_notify(gpointer data, GClosure *closure)
2177 g_free(data);
2181 /* @note You should connect to the "document-save" signal yourself to detect
2182 * if the user has just saved the config file, reloading it. */
2183 void ui_add_config_file_menu_item(const gchar *real_path, const gchar *label, GtkContainer *parent)
2185 GtkWidget *item;
2187 if (!parent)
2188 parent = GTK_CONTAINER(widgets.config_files_menu);
2190 if (!label)
2192 gchar *base_name;
2194 base_name = g_path_get_basename(real_path);
2195 item = gtk_menu_item_new_with_label(base_name);
2196 g_free(base_name);
2198 else
2199 item = gtk_menu_item_new_with_mnemonic(label);
2201 gtk_widget_show(item);
2202 gtk_container_add(parent, item);
2203 g_signal_connect_data(item, "activate", G_CALLBACK(on_config_file_clicked),
2204 g_strdup(real_path), free_on_closure_notify, 0);
2208 static gboolean sort_menu(gpointer data)
2210 ui_menu_sort_by_label(GTK_MENU(data));
2211 return FALSE;
2215 static void create_config_files_menu(void)
2217 GtkWidget *menu, *item;
2219 widgets.config_files_menu = menu = gtk_menu_new();
2221 item = ui_lookup_widget(main_widgets.window, "configuration_files1");
2222 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu);
2224 item = gtk_menu_item_new_with_mnemonic(_("_Filetype Configuration"));
2225 gtk_container_add(GTK_CONTAINER(menu), item);
2226 ui_widgets.config_files_filetype_menu = gtk_menu_new();
2227 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), ui_widgets.config_files_filetype_menu);
2228 gtk_widget_show(item);
2230 /* sort menu after all items added */
2231 g_idle_add(sort_menu, widgets.config_files_menu);
2235 /* adds factory icons with a named icon source using the stock items id */
2236 static void add_stock_icons(const GtkStockItem *items, gsize count)
2238 GtkIconFactory *factory = gtk_icon_factory_new();
2239 GtkIconSource *source = gtk_icon_source_new();
2240 gsize i;
2242 for (i = 0; i < count; i++)
2244 GtkIconSet *set = gtk_icon_set_new();
2246 gtk_icon_source_set_icon_name(source, items[i].stock_id);
2247 gtk_icon_set_add_source(set, source);
2248 gtk_icon_factory_add(factory, items[i].stock_id, set);
2249 gtk_icon_set_unref(set);
2251 gtk_icon_source_free(source);
2252 gtk_icon_factory_add_default(factory);
2253 g_object_unref(factory);
2257 void ui_init_stock_items(void)
2259 GtkStockItem items[] =
2261 { GEANY_STOCK_SAVE_ALL, N_("Save All"), 0, 0, GETTEXT_PACKAGE },
2262 { GEANY_STOCK_CLOSE_ALL, N_("Close All"), 0, 0, GETTEXT_PACKAGE },
2263 { GEANY_STOCK_BUILD, N_("Build"), 0, 0, GETTEXT_PACKAGE }
2266 gtk_stock_add(items, G_N_ELEMENTS(items));
2267 add_stock_icons(items, G_N_ELEMENTS(items));
2271 void ui_init_toolbar_widgets(void)
2273 widgets.save_buttons[1] = toolbar_get_widget_by_name("Save");
2274 widgets.save_buttons[3] = toolbar_get_widget_by_name("SaveAll");
2275 widgets.redo_items[2] = toolbar_get_widget_by_name("Redo");
2276 widgets.undo_items[2] = toolbar_get_widget_by_name("Undo");
2280 void ui_swap_sidebar_pos(void)
2282 GtkWidget *pane = ui_lookup_widget(main_widgets.window, "hpaned1");
2283 GtkWidget *left = gtk_paned_get_child1(GTK_PANED(pane));
2284 GtkWidget *right = gtk_paned_get_child2(GTK_PANED(pane));
2286 g_object_ref(left);
2287 g_object_ref(right);
2288 gtk_container_remove (GTK_CONTAINER (pane), left);
2289 gtk_container_remove (GTK_CONTAINER (pane), right);
2290 /* only scintilla notebook should expand */
2291 gtk_paned_pack1(GTK_PANED(pane), right, right == main_widgets.notebook, TRUE);
2292 gtk_paned_pack2(GTK_PANED(pane), left, left == main_widgets.notebook, TRUE);
2293 g_object_unref(left);
2294 g_object_unref(right);
2296 gtk_paned_set_position(GTK_PANED(pane), gtk_widget_get_allocated_width(pane)
2297 - gtk_paned_get_position(GTK_PANED(pane)));
2301 static void init_recent_files(void)
2303 GtkWidget *toolbar_recent_files_menu;
2305 /* add recent files to the File menu */
2306 ui_widgets.recent_files_menuitem = ui_lookup_widget(main_widgets.window, "recent_files1");
2307 ui_widgets.recent_files_menu_menubar = gtk_menu_new();
2308 gtk_menu_item_set_submenu(GTK_MENU_ITEM(ui_widgets.recent_files_menuitem),
2309 ui_widgets.recent_files_menu_menubar);
2311 /* add recent files to the toolbar Open button */
2312 toolbar_recent_files_menu = gtk_menu_new();
2313 g_object_ref(toolbar_recent_files_menu);
2314 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
2315 toolbar_get_action_by_name("Open")), toolbar_recent_files_menu);
2319 static void ui_menu_move(GtkWidget *menu, GtkWidget *old, GtkWidget *new)
2321 g_object_ref(menu);
2322 gtk_menu_item_set_submenu(GTK_MENU_ITEM(old), NULL);
2323 gtk_menu_item_set_submenu(GTK_MENU_ITEM(new), menu);
2324 g_object_unref(menu);
2328 typedef struct GeanySharedMenu
2330 const gchar *menu;
2331 const gchar *menubar_item;
2332 const gchar *popup_item;
2334 GeanySharedMenu;
2336 #define foreach_menu(item, array) \
2337 for (item = array; item->menu; item++)
2339 static void on_editor_menu_show(GtkWidget *widget, GeanySharedMenu *items)
2341 GeanySharedMenu *item;
2343 foreach_menu(item, items)
2345 GtkWidget *popup = ui_lookup_widget(main_widgets.editor_menu, item->popup_item);
2346 GtkWidget *bar = ui_lookup_widget(main_widgets.window, item->menubar_item);
2347 GtkWidget *menu = ui_lookup_widget(main_widgets.window, item->menu);
2349 ui_menu_move(menu, bar, popup);
2354 static void on_editor_menu_hide(GtkWidget *widget, GeanySharedMenu *items)
2356 GeanySharedMenu *item;
2358 foreach_menu(item, items)
2360 GtkWidget *popup = ui_lookup_widget(main_widgets.editor_menu, item->popup_item);
2361 GtkWidget *bar = ui_lookup_widget(main_widgets.window, item->menubar_item);
2362 GtkWidget *menu = ui_lookup_widget(main_widgets.window, item->menu);
2364 ui_menu_move(menu, popup, bar);
2369 /* Currently ui_init() is called before keyfile.c stash group code is initialized,
2370 * so this is called after that's done. */
2371 void ui_init_prefs(void)
2373 StashGroup *group = stash_group_new(PACKAGE);
2375 /* various prefs */
2376 configuration_add_various_pref_group(group, "interface");
2378 stash_group_add_boolean(group, &interface_prefs.show_symbol_list_expanders,
2379 "show_symbol_list_expanders", TRUE);
2380 stash_group_add_boolean(group, &interface_prefs.compiler_tab_autoscroll,
2381 "compiler_tab_autoscroll", TRUE);
2382 stash_group_add_string(group, &ui_prefs.statusbar_template,
2383 "statusbar_template", _(DEFAULT_STATUSBAR_TEMPLATE));
2384 stash_group_add_boolean(group, &ui_prefs.new_document_after_close,
2385 "new_document_after_close", FALSE);
2386 stash_group_add_boolean(group, &interface_prefs.msgwin_status_visible,
2387 "msgwin_status_visible", TRUE);
2388 stash_group_add_boolean(group, &interface_prefs.msgwin_compiler_visible,
2389 "msgwin_compiler_visible", TRUE);
2390 stash_group_add_boolean(group, &interface_prefs.msgwin_messages_visible,
2391 "msgwin_messages_visible", TRUE);
2392 stash_group_add_boolean(group, &interface_prefs.msgwin_scribble_visible,
2393 "msgwin_scribble_visible", TRUE);
2394 stash_group_add_boolean(group, &interface_prefs.warn_on_project_close,
2395 "warn_on_project_close", TRUE);
2396 stash_group_add_spin_button_integer(group, &interface_prefs.tab_label_len,
2397 "tab_label_length", 99999, "spin_tab_label_len");
2401 /* Used to find out the name of the GtkBuilder retrieved object since
2402 * some objects will be GTK_IS_BUILDABLE() and use the GtkBuildable
2403 * 'name' property for that and those that don't implement GtkBuildable
2404 * will have a "gtk-builder-name" stored in the GObject's data list. */
2405 static const gchar *ui_guess_object_name(GObject *obj)
2407 const gchar *name = NULL;
2409 g_return_val_if_fail(G_IS_OBJECT(obj), NULL);
2411 if (GTK_IS_BUILDABLE(obj))
2412 name = gtk_buildable_get_name(GTK_BUILDABLE(obj));
2413 if (! name)
2414 name = g_object_get_data(obj, "gtk-builder-name");
2415 if (! name)
2416 return NULL;
2418 return name;
2422 /* Compatibility functions */
2423 GtkWidget *create_edit_menu1(void)
2425 return edit_menu1;
2429 GtkWidget *create_prefs_dialog(void)
2431 return prefs_dialog;
2435 GtkWidget *create_project_dialog(void)
2437 return project_dialog;
2441 GtkWidget *create_toolbar_popup_menu1(void)
2443 return toolbar_popup_menu1;
2447 GtkWidget *create_window1(void)
2449 return window1;
2453 static GtkWidget *ui_get_top_parent(GtkWidget *widget)
2455 GtkWidget *parent;
2457 g_return_val_if_fail(GTK_IS_WIDGET(widget), NULL);
2459 for (;;)
2461 if (GTK_IS_MENU(widget))
2462 parent = gtk_menu_get_attach_widget(GTK_MENU(widget));
2463 else
2464 parent = gtk_widget_get_parent(widget);
2465 if (parent == NULL)
2466 parent = (GtkWidget*) g_object_get_data(G_OBJECT(widget), "GladeParentKey");
2467 if (parent == NULL)
2468 break;
2469 widget = parent;
2472 return widget;
2476 void ui_init_builder(void)
2478 gchar *interface_file;
2479 const gchar *name;
2480 GError *error;
2481 GSList *iter, *all_objects;
2482 GtkWidget *widget, *toplevel;
2484 /* prevent function from being called twice */
2485 if (GTK_IS_BUILDER(builder))
2486 return;
2488 builder = gtk_builder_new();
2490 gtk_builder_set_translation_domain(builder, GETTEXT_PACKAGE);
2492 error = NULL;
2493 interface_file = g_build_filename(app->datadir, "geany.glade", NULL);
2494 if (! gtk_builder_add_from_file(builder, interface_file, &error))
2496 /* Show the user this message so they know WTF happened */
2497 dialogs_show_msgbox_with_secondary(GTK_MESSAGE_ERROR,
2498 _("Geany cannot start!"), error->message);
2499 /* Aborts */
2500 g_error("Cannot create user-interface: %s", error->message);
2501 g_error_free(error);
2502 g_free(interface_file);
2503 g_object_unref(builder);
2504 return;
2506 g_free(interface_file);
2508 callbacks_connect(builder);
2510 edit_menu1 = GTK_WIDGET(gtk_builder_get_object(builder, "edit_menu1"));
2511 prefs_dialog = GTK_WIDGET(gtk_builder_get_object(builder, "prefs_dialog"));
2512 project_dialog = GTK_WIDGET(gtk_builder_get_object(builder, "project_dialog"));
2513 toolbar_popup_menu1 = GTK_WIDGET(gtk_builder_get_object(builder, "toolbar_popup_menu1"));
2514 window1 = GTK_WIDGET(gtk_builder_get_object(builder, "window1"));
2516 g_object_set_data(G_OBJECT(edit_menu1), "edit_menu1", edit_menu1);
2517 g_object_set_data(G_OBJECT(prefs_dialog), "prefs_dialog", prefs_dialog);
2518 g_object_set_data(G_OBJECT(project_dialog), "project_dialog", project_dialog);
2519 g_object_set_data(G_OBJECT(toolbar_popup_menu1), "toolbar_popup_menu1", toolbar_popup_menu1);
2520 g_object_set_data(G_OBJECT(window1), "window1", window1);
2522 all_objects = gtk_builder_get_objects(builder);
2523 for (iter = all_objects; iter != NULL; iter = g_slist_next(iter))
2525 if (! GTK_IS_WIDGET(iter->data))
2526 continue;
2528 widget = GTK_WIDGET(iter->data);
2530 name = ui_guess_object_name(G_OBJECT(widget));
2531 if (! name)
2533 g_warning("Unable to get name from GtkBuilder object");
2534 continue;
2536 else
2538 /* Set a default name for use from CSS by name */
2539 gtk_widget_set_name(widget, name);
2542 toplevel = ui_get_top_parent(widget);
2543 if (toplevel)
2544 ui_hookup_widget(toplevel, widget, name);
2546 g_slist_free(all_objects);
2550 static void load_css_theme(const gchar *fn, guint priority)
2552 GtkCssProvider *provider = gtk_css_provider_new();
2553 GError *error = NULL;
2555 if (! gtk_css_provider_load_from_path(provider, fn, &error))
2557 g_warning("Failed to load custom CSS: %s", error->message);
2558 g_error_free(error);
2559 return;
2562 gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
2563 GTK_STYLE_PROVIDER(provider), priority);
2564 geany_debug("Loaded GTK+ CSS theme '%s'", fn);
2566 g_object_unref(provider);
2570 static void init_css_styles(void)
2572 gchar *theme_fn;
2574 // load the main geany.css file from system data dir
2575 theme_fn = g_build_filename(app->datadir, "geany.css", NULL);
2576 load_css_theme(theme_fn, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2577 g_free(theme_fn);
2579 // load themes to handle breakage between various GTK+ versions
2580 const struct
2582 guint min_version;
2583 guint max_version;
2584 const gchar *file;
2586 css_files[] =
2588 { 20, G_MAXUINT, "geany-3.20.css" },
2589 { 0, 19, "geany-3.0.css" },
2592 guint gtk_version = gtk_get_minor_version();
2593 for (guint i = 0; i < G_N_ELEMENTS(css_files); i++)
2595 if (gtk_version >= css_files[i].min_version &&
2596 gtk_version <= css_files[i].max_version)
2598 theme_fn = g_build_filename(app->datadir, css_files[i].file, NULL);
2599 load_css_theme(theme_fn, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2600 g_free(theme_fn);
2604 // if the user provided a geany.css file in their config dir, try and load that
2605 theme_fn = g_build_filename(app->configdir, "geany.css", NULL);
2606 if (g_file_test(theme_fn, G_FILE_TEST_EXISTS))
2607 load_css_theme(theme_fn, GTK_STYLE_PROVIDER_PRIORITY_USER);
2608 g_free(theme_fn);
2612 static void add_css_config_file_item(void)
2614 gchar *theme_fn;
2615 theme_fn = g_build_filename(app->configdir, "geany.css", NULL);
2616 ui_add_config_file_menu_item(theme_fn, NULL, NULL);
2617 g_free(theme_fn);
2621 void ui_init(void)
2623 init_css_styles();
2624 init_recent_files();
2626 ui_widgets.statusbar = ui_lookup_widget(main_widgets.window, "statusbar");
2627 ui_widgets.print_page_setup = ui_lookup_widget(main_widgets.window, "page_setup1");
2629 main_widgets.progressbar = progress_bar_create();
2631 /* current word sensitive items */
2632 widgets.popup_goto_items[0] = ui_lookup_widget(main_widgets.editor_menu, "goto_tag_definition2");
2633 widgets.popup_goto_items[1] = ui_lookup_widget(main_widgets.editor_menu, "context_action1");
2634 widgets.popup_goto_items[2] = ui_lookup_widget(main_widgets.editor_menu, "find_usage2");
2635 widgets.popup_goto_items[3] = ui_lookup_widget(main_widgets.editor_menu, "find_document_usage2");
2637 widgets.popup_copy_items[0] = ui_lookup_widget(main_widgets.editor_menu, "cut1");
2638 widgets.popup_copy_items[1] = ui_lookup_widget(main_widgets.editor_menu, "copy1");
2639 widgets.popup_copy_items[2] = ui_lookup_widget(main_widgets.editor_menu, "delete1");
2640 widgets.menu_copy_items[0] = ui_lookup_widget(main_widgets.window, "menu_cut1");
2641 widgets.menu_copy_items[1] = ui_lookup_widget(main_widgets.window, "menu_copy1");
2642 widgets.menu_copy_items[2] = ui_lookup_widget(main_widgets.window, "menu_delete1");
2643 widgets.menu_insert_include_items[0] = ui_lookup_widget(main_widgets.editor_menu, "insert_include1");
2644 widgets.menu_insert_include_items[1] = ui_lookup_widget(main_widgets.window, "insert_include2");
2645 widgets.save_buttons[0] = ui_lookup_widget(main_widgets.window, "menu_save1");
2646 widgets.save_buttons[2] = ui_lookup_widget(main_widgets.window, "menu_save_all1");
2647 widgets.redo_items[0] = ui_lookup_widget(main_widgets.editor_menu, "redo1");
2648 widgets.redo_items[1] = ui_lookup_widget(main_widgets.window, "menu_redo2");
2649 widgets.undo_items[0] = ui_lookup_widget(main_widgets.editor_menu, "undo1");
2650 widgets.undo_items[1] = ui_lookup_widget(main_widgets.window, "menu_undo2");
2652 /* reparent context submenus as needed */
2654 GeanySharedMenu arr[] = {
2655 {"commands2_menu", "commands2", "commands1"},
2656 {"menu_format1_menu", "menu_format1", "menu_format2"},
2657 {"more1_menu", "more1", "search2"},
2658 {NULL, NULL, NULL}
2660 static GeanySharedMenu items[G_N_ELEMENTS(arr)];
2662 memcpy(items, arr, sizeof(arr));
2663 g_signal_connect(main_widgets.editor_menu, "show", G_CALLBACK(on_editor_menu_show), items);
2664 g_signal_connect(main_widgets.editor_menu, "hide", G_CALLBACK(on_editor_menu_hide), items);
2667 ui_init_toolbar_widgets();
2668 init_document_widgets();
2670 create_config_files_menu();
2671 add_css_config_file_item();
2675 void ui_finalize_builder(void)
2677 if (GTK_IS_BUILDER(builder))
2678 g_object_unref(builder);
2680 /* cleanup refs lingering even after GtkBuilder is destroyed */
2681 if (GTK_IS_WIDGET(edit_menu1))
2682 gtk_widget_destroy(edit_menu1);
2683 if (GTK_IS_WIDGET(prefs_dialog))
2684 gtk_widget_destroy(prefs_dialog);
2685 if (GTK_IS_WIDGET(project_dialog))
2686 gtk_widget_destroy(project_dialog);
2687 if (GTK_IS_WIDGET(toolbar_popup_menu1))
2688 gtk_widget_destroy(toolbar_popup_menu1);
2689 if (GTK_IS_WIDGET(window1))
2690 gtk_widget_destroy(window1);
2694 static void auto_separator_update(GeanyAutoSeparator *autosep)
2696 g_return_if_fail(autosep->item_count >= 0);
2698 if (autosep->widget)
2700 if (autosep->item_count > 0)
2701 ui_widget_show_hide(autosep->widget, autosep->show_count > 0);
2702 else
2703 gtk_widget_destroy(autosep->widget);
2708 static void on_auto_separator_item_show_hide(GtkWidget *widget, gpointer user_data)
2710 GeanyAutoSeparator *autosep = user_data;
2712 if (gtk_widget_get_visible(widget))
2713 autosep->show_count++;
2714 else
2715 autosep->show_count--;
2716 auto_separator_update(autosep);
2720 static void on_auto_separator_item_destroy(GtkWidget *widget, gpointer user_data)
2722 GeanyAutoSeparator *autosep = user_data;
2724 autosep->item_count--;
2725 autosep->item_count = MAX(autosep->item_count, 0);
2726 /* gtk_widget_get_visible() won't work now the widget is being destroyed,
2727 * so assume widget was visible */
2728 autosep->show_count--;
2729 autosep->show_count = MAX(autosep->item_count, 0);
2730 auto_separator_update(autosep);
2734 /* Show the separator widget if @a item or another is visible. */
2735 /* Note: This would be neater taking a widget argument, setting a "visible-count"
2736 * property, and using reference counting to keep the widget alive whilst its visible group
2737 * is alive. */
2738 void ui_auto_separator_add_ref(GeanyAutoSeparator *autosep, GtkWidget *item)
2740 /* set widget ptr NULL when widget destroyed */
2741 if (autosep->item_count == 0)
2742 g_signal_connect(autosep->widget, "destroy",
2743 G_CALLBACK(gtk_widget_destroyed), &autosep->widget);
2745 if (gtk_widget_get_visible(item))
2746 autosep->show_count++;
2748 autosep->item_count++;
2749 auto_separator_update(autosep);
2751 g_signal_connect(item, "show", G_CALLBACK(on_auto_separator_item_show_hide), autosep);
2752 g_signal_connect(item, "hide", G_CALLBACK(on_auto_separator_item_show_hide), autosep);
2753 g_signal_connect(item, "destroy", G_CALLBACK(on_auto_separator_item_destroy), autosep);
2758 * Sets @a text as the contents of the tooltip for @a widget.
2760 * @param widget The widget the tooltip should be set for.
2761 * @param text The text for the tooltip.
2763 * @since 0.16
2764 * @deprecated 0.21 use gtk_widget_set_tooltip_text() instead
2766 GEANY_API_SYMBOL
2767 void ui_widget_set_tooltip_text(GtkWidget *widget, const gchar *text)
2769 gtk_widget_set_tooltip_text(widget, text);
2773 /** Returns a widget from a name in a component, usually created by Glade.
2774 * Call it with the toplevel widget in the component (i.e. a window/dialog),
2775 * or alternatively any widget in the component, and the name of the widget
2776 * you want returned.
2777 * @param widget Widget with the @a widget_name property set.
2778 * @param widget_name Name to lookup.
2780 * @return @transfer{none} The widget found.
2781 * @see ui_hookup_widget().
2783 * @since 0.16
2785 GEANY_API_SYMBOL
2786 GtkWidget *ui_lookup_widget(GtkWidget *widget, const gchar *widget_name)
2788 GtkWidget *parent, *found_widget;
2790 g_return_val_if_fail(widget != NULL, NULL);
2791 g_return_val_if_fail(widget_name != NULL, NULL);
2793 for (;;)
2795 if (GTK_IS_MENU(widget))
2796 parent = gtk_menu_get_attach_widget(GTK_MENU(widget));
2797 else
2798 parent = gtk_widget_get_parent(widget);
2799 if (parent == NULL)
2800 parent = (GtkWidget*) g_object_get_data(G_OBJECT(widget), "GladeParentKey");
2801 if (parent == NULL)
2802 break;
2803 widget = parent;
2806 found_widget = (GtkWidget*) g_object_get_data(G_OBJECT(widget), widget_name);
2807 if (G_UNLIKELY(found_widget == NULL))
2808 g_warning("Widget not found: %s", widget_name);
2809 return found_widget;
2813 /* wraps gtk_builder_get_object()
2814 * unlike ui_lookup_widget(), it does only support getting object created from the main
2815 * UI file, but it can fetch any object, not only widgets */
2816 gpointer ui_builder_get_object (const gchar *name)
2818 return gtk_builder_get_object (builder, name);
2822 /* Progress Bar */
2823 static guint progress_bar_timer_id = 0;
2826 static GtkWidget *progress_bar_create(void)
2828 GtkWidget *bar = gtk_progress_bar_new();
2830 /* Set the progressbar's height to 1 to fit it in the statusbar */
2831 gtk_widget_set_size_request(bar, -1, 1);
2832 gtk_box_pack_start (GTK_BOX(ui_widgets.statusbar), bar, FALSE, FALSE, 3);
2834 return bar;
2838 static gboolean progress_bar_pulse(gpointer data)
2840 gtk_progress_bar_pulse(GTK_PROGRESS_BAR(main_widgets.progressbar));
2842 return TRUE;
2847 * Starts a constantly pulsing progressbar in the right corner of the statusbar
2848 * (if the statusbar is visible). This is a convenience function which adds a timer to
2849 * pulse the progressbar constantly until ui_progress_bar_stop() is called.
2850 * You can use this function when you have time consuming asynchronous operation and want to
2851 * display some activity in the GUI and when you don't know about detailed progress steps.
2852 * The progressbar widget is hidden by default when it is not active. This function and
2853 * ui_progress_bar_stop() will show and hide it automatically for you.
2855 * You can also access the progressbar widget directly using @c geany->main_widgets->progressbar
2856 * and use the GtkProgressBar API to set discrete fractions to display better progress information.
2857 * In this case, you need to show and hide the widget yourself. You can find some example code
2858 * in @c src/printing.c.
2860 * @param text @nullable The text to be shown as the progress bar label or @c NULL to leave it empty.
2862 * @since 0.16
2864 GEANY_API_SYMBOL
2865 void ui_progress_bar_start(const gchar *text)
2867 g_return_if_fail(progress_bar_timer_id == 0);
2869 if (! interface_prefs.statusbar_visible)
2870 return;
2872 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(main_widgets.progressbar), text);
2874 progress_bar_timer_id = g_timeout_add(200, progress_bar_pulse, NULL);
2876 gtk_widget_show(GTK_WIDGET(main_widgets.progressbar));
2880 /** Stops a running progress bar and hides the widget again.
2882 * @since 0.16
2884 GEANY_API_SYMBOL
2885 void ui_progress_bar_stop(void)
2887 gtk_widget_hide(GTK_WIDGET(main_widgets.progressbar));
2889 if (progress_bar_timer_id != 0)
2891 g_source_remove(progress_bar_timer_id);
2892 progress_bar_timer_id = 0;
2897 static gint compare_menu_item_labels(gconstpointer a, gconstpointer b)
2899 GtkMenuItem *item_a = GTK_MENU_ITEM(a);
2900 GtkMenuItem *item_b = GTK_MENU_ITEM(b);
2901 gchar *sa, *sb;
2902 gint result;
2904 /* put entries with submenus at the start of the menu */
2905 if (gtk_menu_item_get_submenu(item_a) && !gtk_menu_item_get_submenu(item_b))
2906 return -1;
2907 else if (!gtk_menu_item_get_submenu(item_a) && gtk_menu_item_get_submenu(item_b))
2908 return 1;
2910 sa = ui_menu_item_get_text(item_a);
2911 sb = ui_menu_item_get_text(item_b);
2912 result = utils_str_casecmp(sa, sb);
2913 g_free(sa);
2914 g_free(sb);
2915 return result;
2919 /* Currently @a menu should contain only GtkMenuItems with labels. */
2920 void ui_menu_sort_by_label(GtkMenu *menu)
2922 GList *list = gtk_container_get_children(GTK_CONTAINER(menu));
2923 GList *node;
2924 gint pos;
2926 list = g_list_sort(list, compare_menu_item_labels);
2927 pos = 0;
2928 foreach_list(node, list)
2930 menu_reorder_child(menu, node->data, pos);
2931 pos++;
2933 g_list_free(list);
2937 void ui_label_set_markup(GtkLabel *label, const gchar *format, ...)
2939 va_list a;
2940 gchar *text;
2942 va_start(a, format);
2943 text = g_markup_vprintf_escaped(format, a);
2944 va_end(a);
2946 gtk_label_set_text(label, text);
2947 gtk_label_set_use_markup(label, TRUE);
2948 g_free(text);
2952 GtkWidget *ui_label_new_bold(const gchar *text)
2954 GtkWidget *label;
2956 label = gtk_label_new(NULL);
2957 ui_label_set_markup(GTK_LABEL(label), "<b>%s</b>", text);
2958 return label;
2962 /** @girskip
2963 * Adds a list of document items to @a menu.
2964 * @param menu Menu.
2965 * @param active @nullable Which document to highlight, or @c NULL.
2966 * @param callback is used for each menu item's @c "activate" signal and will be
2967 * passed the corresponding document pointer as @c user_data.
2968 * @warning You should check @c doc->is_valid in the callback.
2969 * @since 0.19
2971 GEANY_API_SYMBOL
2972 void ui_menu_add_document_items(GtkMenu *menu, GeanyDocument *active, GCallback callback)
2974 ui_menu_add_document_items_sorted(menu, active, callback, NULL);
2978 /** @girskip
2979 * Adds a list of document items to @a menu.
2981 * @a compare_func might be NULL to not sort the documents in the menu. In this case,
2982 * the order of the document tabs is used.
2984 * See document_compare_by_display_name() for an example sort function.
2986 * @param menu Menu.
2987 * @param active @nullable Which document to highlight, or @c NULL.
2988 * @param callback is used for each menu item's @c "activate" signal and will be passed
2989 * the corresponding document pointer as @c user_data.
2990 * @param compare_func is used to sort the list. Might be @c NULL to not sort the list.
2991 * @warning You should check @c doc->is_valid in the callback.
2992 * @since 0.21
2994 GEANY_API_SYMBOL
2995 void ui_menu_add_document_items_sorted(GtkMenu *menu, GeanyDocument *active,
2996 GCallback callback, GCompareFunc compare_func)
2998 GtkWidget *menu_item, *menu_item_label, *image;
2999 GeanyDocument *doc;
3000 guint i, len;
3001 gchar *base_name;
3002 GPtrArray *sorted_documents;
3004 len = (guint) gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook));
3006 sorted_documents = g_ptr_array_sized_new(len);
3007 /* copy the documents_array into the new one */
3008 foreach_document(i)
3010 g_ptr_array_add(sorted_documents, documents[i]);
3012 if (compare_func == NULL)
3013 compare_func = document_compare_by_tab_order;
3015 /* and now sort it */
3016 g_ptr_array_sort(sorted_documents, compare_func);
3018 for (i = 0; i < sorted_documents->len; i++)
3020 doc = g_ptr_array_index(sorted_documents, i);
3022 base_name = g_path_get_basename(DOC_FILENAME(doc));
3023 menu_item = gtk_image_menu_item_new_with_label(base_name);
3024 image = gtk_image_new_from_gicon(doc->file_type->icon, GTK_ICON_SIZE_MENU);
3025 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image);
3026 gtk_widget_set_tooltip_text(menu_item, DOC_FILENAME(doc));
3028 gtk_widget_show(menu_item);
3029 gtk_container_add(GTK_CONTAINER(menu), menu_item);
3030 g_signal_connect(menu_item, "activate", callback, doc);
3032 menu_item_label = gtk_bin_get_child(GTK_BIN(menu_item));
3033 gtk_widget_set_name(menu_item_label, document_get_status_widget_class(doc));
3035 if (doc == active)
3036 ui_label_set_markup(GTK_LABEL(menu_item_label), "<b>%s</b>", base_name);
3038 g_free(base_name);
3040 g_ptr_array_free(sorted_documents, TRUE);
3044 /** Checks whether the passed @a keyval is the Enter or Return key.
3045 * There are three different Enter/Return key values
3046 * (@c GDK_KEY_Return, @c GDK_KEY_ISO_Enter, @c GDK_KEY_KP_Enter).
3047 * This is just a convenience function.
3048 * @param keyval A keyval.
3049 * @return @c TRUE if @a keyval is the one of the Enter/Return key values, otherwise @c FALSE.
3050 * @since 0.19 */
3051 GEANY_API_SYMBOL
3052 gboolean ui_is_keyval_enter_or_return(guint keyval)
3054 return (keyval == GDK_KEY_Return || keyval == GDK_KEY_ISO_Enter|| keyval == GDK_KEY_KP_Enter);
3058 /** Reads an integer from the GTK default settings registry
3059 * (see http://library.gnome.org/devel/gtk/stable/GtkSettings.html).
3060 * @param property_name The property to read.
3061 * @param default_value The default value in case the value could not be read.
3062 * @return The value for the property if it exists, otherwise the @a default_value.
3063 * @since 0.19 */
3064 GEANY_API_SYMBOL
3065 gint ui_get_gtk_settings_integer(const gchar *property_name, gint default_value)
3067 if (g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(
3068 gtk_settings_get_default())), property_name))
3070 gint value;
3071 g_object_get(G_OBJECT(gtk_settings_get_default()), property_name, &value, NULL);
3072 return value;
3074 else
3075 return default_value;
3079 void ui_editable_insert_text_callback(GtkEditable *editable, gchar *new_text,
3080 gint new_text_len, gint *position, gpointer data)
3082 gboolean first = position != NULL && *position == 0;
3083 gint i;
3085 if (new_text_len == -1)
3086 new_text_len = (gint) strlen(new_text);
3088 for (i = 0; i < new_text_len; i++, new_text++)
3090 if ((!first || !strchr("+-", *new_text)) && !isdigit(*new_text))
3092 g_signal_stop_emission_by_name(editable, "insert-text");
3093 break;
3095 first = FALSE;
3100 /* gets the icon that applies to a particular MIME type */
3101 GIcon *ui_get_mime_icon(const gchar *mime_type)
3103 GIcon *icon = NULL;
3104 gchar *ctype;
3106 ctype = g_content_type_from_mime_type(mime_type);
3107 if (ctype)
3109 GdkScreen *screen = gdk_screen_get_default();
3111 icon = g_content_type_get_icon(ctype);
3112 if (screen && icon)
3114 GtkIconInfo *icon_info;
3116 icon_info = gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_for_screen(screen), icon, 16, 0);
3117 if (!icon_info)
3119 g_object_unref(icon);
3120 icon = NULL;
3122 else
3123 gtk_icon_info_free(icon_info);
3126 g_free(ctype);
3129 /* fallback if icon lookup failed, like it might happen on Windows (?) */
3130 if (! icon)
3132 const gchar *icon_name = "text-x-generic";
3134 if (strstr(mime_type, "directory"))
3135 icon_name = "folder";
3137 icon = g_themed_icon_new(icon_name);
3139 return icon;
3143 void ui_focus_current_document(void)
3145 GeanyDocument *doc = document_get_current();
3147 if (doc != NULL)
3148 document_grab_focus(doc);
3152 /** Finds the label text associated with stock_id
3153 * @param stock_id stock_id to lookup e.g. @c GTK_STOCK_OPEN.
3154 * @return The label text for stock
3155 * @since Geany 1.22 */
3156 GEANY_API_SYMBOL
3157 const gchar *ui_lookup_stock_label(const gchar *stock_id)
3159 GtkStockItem item;
3161 if (gtk_stock_lookup(stock_id, &item))
3162 return item.label;
3164 g_warning("No stock id '%s'!", stock_id);
3165 return NULL;
3169 /* finds the next iter at any level
3170 * @param iter in/out, the current iter, will be changed to the next one
3171 * @param down whether to try the child iter
3172 * @return TRUE if there @p iter was set, or FALSE if there is no next iter */
3173 gboolean ui_tree_model_iter_any_next(GtkTreeModel *model, GtkTreeIter *iter, gboolean down)
3175 GtkTreeIter guess;
3176 GtkTreeIter copy = *iter;
3178 /* go down if the item has children */
3179 if (down && gtk_tree_model_iter_children(model, &guess, iter))
3180 *iter = guess;
3181 /* or to the next item at the same level */
3182 else if (gtk_tree_model_iter_next(model, &copy))
3183 *iter = copy;
3184 /* or to the next item at a parent level */
3185 else if (gtk_tree_model_iter_parent(model, &guess, iter))
3187 copy = guess;
3188 while (TRUE)
3190 if (gtk_tree_model_iter_next(model, &copy))
3192 *iter = copy;
3193 return TRUE;
3195 else if (gtk_tree_model_iter_parent(model, &copy, &guess))
3196 guess = copy;
3197 else
3198 return FALSE;
3201 else
3202 return FALSE;
3204 return TRUE;
3208 GtkWidget *ui_create_encodings_combo_box(gboolean has_detect, gint default_enc)
3210 GtkCellRenderer *renderer;
3211 GtkTreeIter iter;
3212 GtkWidget *combo = gtk_combo_box_new();
3213 GtkTreeStore *store = encodings_encoding_store_new(has_detect);
3215 if (default_enc < 0 || default_enc >= GEANY_ENCODINGS_MAX)
3216 default_enc = has_detect ? GEANY_ENCODINGS_MAX : GEANY_ENCODING_NONE;
3218 gtk_combo_box_set_model(GTK_COMBO_BOX(combo), GTK_TREE_MODEL(store));
3219 if (encodings_encoding_store_get_iter(store, &iter, default_enc))
3220 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter);
3221 renderer = gtk_cell_renderer_text_new();
3222 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
3223 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo), renderer,
3224 encodings_encoding_store_cell_data_func, NULL, NULL);
3226 return combo;
3230 gint ui_encodings_combo_box_get_active_encoding(GtkComboBox *combo)
3232 GtkTreeIter iter;
3233 gint enc = GEANY_ENCODING_NONE;
3235 /* there should always be an active iter anyway, but we check just in case */
3236 if (gtk_combo_box_get_active_iter(combo, &iter))
3238 GtkTreeModel *model = gtk_combo_box_get_model(combo);
3239 enc = encodings_encoding_store_get_encoding(GTK_TREE_STORE(model), &iter);
3242 return enc;
3246 gboolean ui_encodings_combo_box_set_active_encoding(GtkComboBox *combo, gint enc)
3248 GtkTreeIter iter;
3249 GtkTreeModel *model = gtk_combo_box_get_model(combo);
3251 if (encodings_encoding_store_get_iter(GTK_TREE_STORE(model), &iter, enc))
3253 gtk_combo_box_set_active_iter(combo, &iter);
3254 return TRUE;
3256 return FALSE;
3259 void ui_menu_popup(GtkMenu* menu, GtkMenuPositionFunc func, gpointer data, guint button, guint32 activate_time)
3261 /* Use appropriate function for menu popup:
3262 - gtk_menu_popup_at_pointer is not available on GTK older than 3.22
3263 - gtk_menu_popup is deprecated and causes issues on multimonitor wayland setups */
3264 #if GTK_CHECK_VERSION(3,22,0)
3265 gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
3266 #else
3267 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, func, data, button, activate_time);
3268 #endif