Fix ui_button_new_with_image() to call gtk_button_set_image() so
[geany-mirror.git] / src / ui_utils.c
blob543816b01cac1773f5622fa8a9dcbf8b093b5ce0
1 /*
2 * ui_utils.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2006-2010 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2010 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * $Id$
24 /** @file ui_utils.h
25 * User Interface general utility functions.
28 #include "geany.h"
30 #include <string.h>
31 #include <gdk/gdkkeysyms.h>
33 #include "ui_utils.h"
34 #include "prefs.h"
35 #include "sciwrappers.h"
36 #include "document.h"
37 #include "documentprivate.h"
38 #include "filetypes.h"
39 #include "support.h"
40 #include "msgwindow.h"
41 #include "utils.h"
42 #include "callbacks.h"
43 #include "encodings.h"
44 #include "images.c"
45 #include "sidebar.h"
46 #include "win32.h"
47 #include "project.h"
48 #include "editor.h"
49 #include "plugins.h"
50 #include "symbols.h"
51 #include "toolbar.h"
52 #include "geanymenubuttonaction.h"
55 GeanyInterfacePrefs interface_prefs;
56 GeanyMainWidgets main_widgets;
58 UIPrefs ui_prefs;
59 UIWidgets ui_widgets;
61 static struct
63 /* pointers to widgets only sensitive when there is at least one document, the pointers can
64 * also be GtkAction objects, so check each pointer before using it */
65 GPtrArray *document_buttons;
66 GtkWidget *menu_insert_include_items[2];
67 GtkWidget *popup_goto_items[4];
68 GtkWidget *popup_copy_items[3];
69 GtkWidget *menu_copy_items[3];
70 GtkWidget *redo_items[3];
71 GtkWidget *undo_items[3];
72 GtkWidget *save_buttons[4];
73 GtkWidget *config_files_menu;
74 GtkWidget *commands_menu;
75 GtkWidget *format_menu;
77 widgets;
79 enum
81 RECENT_FILE_FILE,
82 RECENT_FILE_PROJECT
85 typedef struct
87 gint type;
88 GQueue *recent_queue;
89 GtkWidget *menubar;
90 GtkWidget *toolbar;
91 void (*activate_cb)(GtkMenuItem *, gpointer);
92 } GeanyRecentFiles;
95 static void update_recent_menu(GeanyRecentFiles *grf);
96 static void recent_file_loaded(const gchar *utf8_filename, GeanyRecentFiles *grf);
97 static void recent_file_activate_cb(GtkMenuItem *menuitem, gpointer user_data);
98 static void recent_project_activate_cb(GtkMenuItem *menuitem, gpointer user_data);
99 static GtkWidget *progress_bar_create(void);
102 /* simple wrapper for gtk_widget_set_sensitive() to allow widget being NULL */
103 void ui_widget_set_sensitive(GtkWidget *widget, gboolean set)
105 if (widget != NULL)
106 gtk_widget_set_sensitive(widget, set);
110 /* allow_override is TRUE if text can be ignored when another message has been set
111 * that didn't use allow_override and has not timed out. */
112 static void set_statusbar(const gchar *text, gboolean allow_override)
114 static glong last_time = 0;
115 GTimeVal timeval;
116 const gint GEANY_STATUS_TIMEOUT = 1;
118 if (! interface_prefs.statusbar_visible)
119 return; /* just do nothing if statusbar is not visible */
121 g_get_current_time(&timeval);
123 if (! allow_override)
125 gtk_statusbar_pop(GTK_STATUSBAR(ui_widgets.statusbar), 1);
126 gtk_statusbar_push(GTK_STATUSBAR(ui_widgets.statusbar), 1, text);
127 last_time = timeval.tv_sec;
129 else
130 if (timeval.tv_sec > last_time + GEANY_STATUS_TIMEOUT)
132 gtk_statusbar_pop(GTK_STATUSBAR(ui_widgets.statusbar), 1);
133 gtk_statusbar_push(GTK_STATUSBAR(ui_widgets.statusbar), 1, text);
138 /** Display text on the statusbar.
139 * @param log Whether the message should be recorded in the Status window.
140 * @param format A @c printf -style string. */
141 void ui_set_statusbar(gboolean log, const gchar *format, ...)
143 gchar string[512];
144 va_list args;
146 va_start(args, format);
147 g_vsnprintf(string, 512, format, args);
148 va_end(args);
150 if (! prefs.suppress_status_messages)
151 set_statusbar(string, FALSE);
153 if (log || prefs.suppress_status_messages)
154 msgwin_status_add("%s", string);
158 /* updates the status bar document statistics */
159 void ui_update_statusbar(GeanyDocument *doc, gint pos)
161 if (! interface_prefs.statusbar_visible)
162 return; /* just do nothing if statusbar is not visible */
164 if (doc == NULL)
165 doc = document_get_current();
167 if (doc != NULL)
169 static GString *stats_str = NULL;
170 const gchar sp[] = " ";
171 guint line, col;
172 const gchar *cur_tag;
173 gchar *filetype_name = doc->file_type->name;
175 if (G_UNLIKELY(stats_str == NULL))
176 stats_str = g_string_sized_new(120);
178 if (pos == -1)
179 pos = sci_get_current_position(doc->editor->sci);
180 line = sci_get_line_from_position(doc->editor->sci, pos);
182 /* Add temporary fix for sci infinite loop in Document::GetColumn(int)
183 * when current pos is beyond document end (can occur when removing
184 * blocks of selected lines especially esp. brace sections near end of file). */
185 if (pos <= sci_get_length(doc->editor->sci))
186 col = sci_get_col_from_position(doc->editor->sci, pos);
187 else
188 col = 0;
190 /* Status bar statistics: col = column, sel = selection. */
191 g_string_printf(stats_str, _("line: %d / %d\t col: %d\t sel: %d\t "),
192 (line + 1), sci_get_line_count(doc->editor->sci), col,
193 sci_get_selected_text_length(doc->editor->sci) - 1);
195 g_string_append(stats_str,
196 /* RO = read-only */
197 (doc->readonly) ? _("RO ") :
198 /* OVR = overwrite/overtype, INS = insert */
199 (sci_get_overtype(doc->editor->sci) ? _("OVR") : _("INS")));
200 g_string_append(stats_str, sp);
202 switch (editor_get_indent_prefs(doc->editor)->type)
204 case GEANY_INDENT_TYPE_TABS:
205 g_string_append(stats_str, _("TAB"));
206 break;
207 case GEANY_INDENT_TYPE_SPACES:
208 g_string_append(stats_str, _("SP")); /* SP = space */
209 break;
210 case GEANY_INDENT_TYPE_BOTH:
211 g_string_append(stats_str, _("T/S")); /* T/S = tabs and spaces */
212 break;
214 g_string_append(stats_str, sp);
215 g_string_append_printf(stats_str, _("mode: %s"),
216 editor_get_eol_char_name(doc->editor));
217 g_string_append(stats_str, sp);
218 g_string_append_printf(stats_str, _("encoding: %s %s"),
219 (doc->encoding) ? doc->encoding : _("unknown"),
220 (encodings_is_unicode_charset(doc->encoding)) ?
221 /* BOM = byte order mark */
222 ((doc->has_bom) ? _("(with BOM)") : "") : "");
223 g_string_append(stats_str, sp);
224 g_string_append_printf(stats_str, _("filetype: %s"), filetype_name);
225 g_string_append(stats_str, sp);
226 if (doc->changed)
228 g_string_append(stats_str, _("MOD")); /* MOD = modified */
229 g_string_append(stats_str, sp);
232 symbols_get_current_function(doc, &cur_tag);
233 g_string_append_printf(stats_str, _("scope: %s"),
234 cur_tag);
236 #ifdef GEANY_DEBUG
237 g_string_append(stats_str, sp);
238 g_string_append_printf(stats_str, "pos: %d", pos);
239 g_string_append(stats_str, sp);
240 g_string_append_printf(stats_str, "style: %d", sci_get_style_at(doc->editor->sci, pos));
241 #endif
243 /* can be overridden by status messages */
244 set_statusbar(stats_str->str, TRUE);
246 else /* no documents */
248 set_statusbar("", TRUE); /* can be overridden by status messages */
253 /* This sets the window title according to the current filename. */
254 void ui_set_window_title(GeanyDocument *doc)
256 GString *str;
257 GeanyProject *project = app->project;
259 if (doc == NULL)
260 doc = document_get_current();
262 str = g_string_new(NULL);
264 if (doc != NULL)
266 g_string_append(str, doc->changed ? "*" : "");
268 if (doc->file_name == NULL)
269 g_string_append(str, DOC_FILENAME(doc));
270 else
272 gchar *short_name = document_get_basename_for_display(doc, 30);
273 gchar *dirname = g_path_get_dirname(DOC_FILENAME(doc));
275 g_string_append(str, short_name);
276 g_string_append(str, " - ");
277 g_string_append(str, dirname ? dirname : "");
278 g_free(short_name);
279 g_free(dirname);
281 g_string_append(str, " - ");
283 if (project)
285 g_string_append_c(str, '[');
286 g_string_append(str, project->name);
287 g_string_append(str, "] - ");
289 g_string_append(str, "Geany");
290 gtk_window_set_title(GTK_WINDOW(main_widgets.window), str->str);
291 g_string_free(str, TRUE);
295 void ui_set_editor_font(const gchar *font_name)
297 guint i;
299 g_return_if_fail(font_name != NULL);
301 /* do nothing if font has not changed */
302 if (interface_prefs.editor_font != NULL)
303 if (strcmp(font_name, interface_prefs.editor_font) == 0)
304 return;
306 g_free(interface_prefs.editor_font);
307 interface_prefs.editor_font = g_strdup(font_name);
309 /* We copy the current style, and update the font in all open tabs. */
310 for (i = 0; i < documents_array->len; i++)
312 if (documents[i]->editor)
314 editor_set_font(documents[i]->editor, interface_prefs.editor_font);
318 ui_set_statusbar(TRUE, _("Font updated (%s)."), interface_prefs.editor_font);
322 void ui_set_fullscreen(void)
324 if (ui_prefs.fullscreen)
326 gtk_window_fullscreen(GTK_WINDOW(main_widgets.window));
328 else
330 gtk_window_unfullscreen(GTK_WINDOW(main_widgets.window));
335 void ui_update_popup_reundo_items(GeanyDocument *doc)
337 gboolean enable_undo;
338 gboolean enable_redo;
339 guint i, len;
341 if (doc == NULL)
343 enable_undo = FALSE;
344 enable_redo = FALSE;
346 else
348 enable_undo = document_can_undo(doc);
349 enable_redo = document_can_redo(doc);
352 /* index 0 is the popup menu, 1 is the menubar, 2 is the toolbar */
353 len = G_N_ELEMENTS(widgets.undo_items);
354 for (i = 0; i < len; i++)
356 ui_widget_set_sensitive(widgets.undo_items[i], enable_undo);
358 len = G_N_ELEMENTS(widgets.redo_items);
359 for (i = 0; i < len; i++)
361 ui_widget_set_sensitive(widgets.redo_items[i], enable_redo);
366 void ui_update_popup_copy_items(GeanyDocument *doc)
368 gboolean enable;
369 guint i, len;
371 if (doc == NULL)
372 enable = FALSE;
373 else
374 enable = sci_has_selection(doc->editor->sci);
376 len = G_N_ELEMENTS(widgets.popup_copy_items);
377 for (i = 0; i < len; i++)
378 ui_widget_set_sensitive(widgets.popup_copy_items[i], enable);
382 void ui_update_popup_goto_items(gboolean enable)
384 guint i, len;
385 len = G_N_ELEMENTS(widgets.popup_goto_items);
386 for (i = 0; i < len; i++)
387 ui_widget_set_sensitive(widgets.popup_goto_items[i], enable);
391 void ui_update_menu_copy_items(GeanyDocument *doc)
393 gboolean enable = FALSE;
394 guint i, len;
395 GtkWidget *focusw = gtk_window_get_focus(GTK_WINDOW(main_widgets.window));
397 if (IS_SCINTILLA(focusw))
398 enable = (doc == NULL) ? FALSE : sci_has_selection(doc->editor->sci);
399 else
400 if (GTK_IS_EDITABLE(focusw))
401 enable = gtk_editable_get_selection_bounds(GTK_EDITABLE(focusw), NULL, NULL);
402 else
403 if (GTK_IS_TEXT_VIEW(focusw))
405 GtkTextBuffer *buffer = gtk_text_view_get_buffer(
406 GTK_TEXT_VIEW(focusw));
407 enable = gtk_text_buffer_get_selection_bounds(buffer, NULL, NULL);
410 len = G_N_ELEMENTS(widgets.menu_copy_items);
411 for (i = 0; i < len; i++)
412 ui_widget_set_sensitive(widgets.menu_copy_items[i], enable);
416 void ui_update_insert_include_item(GeanyDocument *doc, gint item)
418 gboolean enable = FALSE;
420 if (doc == NULL || doc->file_type == NULL)
421 enable = FALSE;
422 else if (doc->file_type->id == GEANY_FILETYPES_C || doc->file_type->id == GEANY_FILETYPES_CPP)
423 enable = TRUE;
425 ui_widget_set_sensitive(widgets.menu_insert_include_items[item], enable);
429 void ui_update_fold_items(void)
431 ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "menu_fold_all1"), editor_prefs.folding);
432 ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "menu_unfold_all1"), editor_prefs.folding);
433 ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "separator22"), editor_prefs.folding);
437 static void insert_include_items(GtkMenu *me, GtkMenu *mp, gchar **includes, gchar *label)
439 guint i = 0;
440 GtkWidget *tmp_menu;
441 GtkWidget *tmp_popup;
442 GtkWidget *edit_menu, *edit_menu_item;
443 GtkWidget *popup_menu, *popup_menu_item;
445 edit_menu = gtk_menu_new();
446 popup_menu = gtk_menu_new();
447 edit_menu_item = gtk_menu_item_new_with_label(label);
448 popup_menu_item = gtk_menu_item_new_with_label(label);
449 gtk_menu_item_set_submenu(GTK_MENU_ITEM(edit_menu_item), edit_menu);
450 gtk_menu_item_set_submenu(GTK_MENU_ITEM(popup_menu_item), popup_menu);
452 while (includes[i] != NULL)
454 tmp_menu = gtk_menu_item_new_with_label(includes[i]);
455 tmp_popup = gtk_menu_item_new_with_label(includes[i]);
456 gtk_container_add(GTK_CONTAINER(edit_menu), tmp_menu);
457 gtk_container_add(GTK_CONTAINER(popup_menu), tmp_popup);
458 g_signal_connect(tmp_menu, "activate",
459 G_CALLBACK(on_menu_insert_include_activate), (gpointer) includes[i]);
460 g_signal_connect(tmp_popup, "activate",
461 G_CALLBACK(on_insert_include_activate), (gpointer) includes[i]);
462 i++;
464 gtk_widget_show_all(edit_menu_item);
465 gtk_widget_show_all(popup_menu_item);
466 gtk_container_add(GTK_CONTAINER(me), edit_menu_item);
467 gtk_container_add(GTK_CONTAINER(mp), popup_menu_item);
471 void ui_create_insert_menu_items(void)
473 GtkMenu *menu_edit = GTK_MENU(ui_lookup_widget(main_widgets.window, "insert_include2_menu"));
474 GtkMenu *menu_popup = GTK_MENU(ui_lookup_widget(main_widgets.editor_menu, "insert_include1_menu"));
475 GtkWidget *blank;
476 const gchar *c_includes_stdlib[] = {
477 "assert.h", "ctype.h", "errno.h", "float.h", "limits.h", "locale.h", "math.h", "setjmp.h",
478 "signal.h", "stdarg.h", "stddef.h", "stdio.h", "stdlib.h", "string.h", "time.h", NULL
480 const gchar *c_includes_c99[] = {
481 "complex.h", "fenv.h", "inttypes.h", "iso646.h", "stdbool.h", "stdint.h",
482 "tgmath.h", "wchar.h", "wctype.h", NULL
484 const gchar *c_includes_cpp[] = {
485 "cstdio", "cstring", "cctype", "cmath", "ctime", "cstdlib", "cstdarg", NULL
487 const gchar *c_includes_cppstdlib[] = {
488 "iostream", "fstream", "iomanip", "sstream", "exception", "stdexcept",
489 "memory", "locale", NULL
491 const gchar *c_includes_stl[] = {
492 "bitset", "dequev", "list", "map", "set", "queue", "stack", "vector", "algorithm",
493 "iterator", "functional", "string", "complex", "valarray", NULL
496 blank = gtk_menu_item_new_with_label("#include \"...\"");
497 gtk_container_add(GTK_CONTAINER(menu_edit), blank);
498 gtk_widget_show(blank);
499 g_signal_connect(blank, "activate", G_CALLBACK(on_menu_insert_include_activate),
500 (gpointer) "blank");
501 blank = gtk_separator_menu_item_new ();
502 gtk_container_add(GTK_CONTAINER(menu_edit), blank);
503 gtk_widget_show(blank);
505 blank = gtk_menu_item_new_with_label("#include \"...\"");
506 gtk_container_add(GTK_CONTAINER(menu_popup), blank);
507 gtk_widget_show(blank);
508 g_signal_connect(blank, "activate", G_CALLBACK(on_insert_include_activate),
509 (gpointer) "blank");
510 blank = gtk_separator_menu_item_new();
511 gtk_container_add(GTK_CONTAINER(menu_popup), blank);
512 gtk_widget_show(blank);
514 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_stdlib, _("C Standard Library"));
515 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_c99, _("ISO C99"));
516 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_cpp, _("C++ (C Standard Library)"));
517 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_cppstdlib, _("C++ Standard Library"));
518 insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_stl, _("C++ STL"));
522 static void insert_date_items(GtkMenu *me, GtkMenu *mp, gchar *label)
524 GtkWidget *item;
526 item = gtk_menu_item_new_with_mnemonic(label);
527 gtk_container_add(GTK_CONTAINER(me), item);
528 gtk_widget_show(item);
529 g_signal_connect(item, "activate", G_CALLBACK(on_menu_insert_date_activate), label);
531 item = gtk_menu_item_new_with_mnemonic(label);
532 gtk_container_add(GTK_CONTAINER(mp), item);
533 gtk_widget_show(item);
534 g_signal_connect(item, "activate", G_CALLBACK(on_insert_date_activate), label);
538 void ui_create_insert_date_menu_items(void)
540 GtkMenu *menu_edit = GTK_MENU(ui_lookup_widget(main_widgets.window, "insert_date1_menu"));
541 GtkMenu *menu_popup = GTK_MENU(ui_lookup_widget(main_widgets.editor_menu, "insert_date2_menu"));
542 GtkWidget *item;
543 gchar *str;
545 insert_date_items(menu_edit, menu_popup, _("dd.mm.yyyy"));
546 insert_date_items(menu_edit, menu_popup, _("mm.dd.yyyy"));
547 insert_date_items(menu_edit, menu_popup, _("yyyy/mm/dd"));
549 item = gtk_separator_menu_item_new();
550 gtk_container_add(GTK_CONTAINER(menu_edit), item);
551 gtk_widget_show(item);
552 item = gtk_separator_menu_item_new();
553 gtk_container_add(GTK_CONTAINER(menu_popup), item);
554 gtk_widget_show(item);
556 insert_date_items(menu_edit, menu_popup, _("dd.mm.yyyy hh:mm:ss"));
557 insert_date_items(menu_edit, menu_popup, _("mm.dd.yyyy hh:mm:ss"));
558 insert_date_items(menu_edit, menu_popup, _("yyyy/mm/dd hh:mm:ss"));
560 item = gtk_separator_menu_item_new();
561 gtk_container_add(GTK_CONTAINER(menu_edit), item);
562 gtk_widget_show(item);
563 item = gtk_separator_menu_item_new();
564 gtk_container_add(GTK_CONTAINER(menu_popup), item);
565 gtk_widget_show(item);
567 str = _("_Use Custom Date Format");
568 item = gtk_menu_item_new_with_mnemonic(str);
569 gtk_container_add(GTK_CONTAINER(menu_edit), item);
570 gtk_widget_show(item);
571 g_signal_connect(item, "activate", G_CALLBACK(on_menu_insert_date_activate), str);
572 g_object_set_data_full(G_OBJECT(main_widgets.window),
573 "insert_date_custom1", g_object_ref(item), (GDestroyNotify)g_object_unref);
575 item = gtk_menu_item_new_with_mnemonic(str);
576 gtk_container_add(GTK_CONTAINER(menu_popup), item);
577 gtk_widget_show(item);
578 g_signal_connect(item, "activate", G_CALLBACK(on_insert_date_activate), str);
579 g_object_set_data_full(G_OBJECT(main_widgets.editor_menu),
580 "insert_date_custom2", g_object_ref(item), (GDestroyNotify)g_object_unref);
582 insert_date_items(menu_edit, menu_popup, _("_Set Custom Date Format"));
586 void ui_save_buttons_toggle(gboolean enable)
588 guint i;
589 gboolean dirty_tabs = FALSE;
591 if (ui_prefs.allow_always_save)
592 return;
594 ui_widget_set_sensitive(widgets.save_buttons[0], enable);
595 ui_widget_set_sensitive(widgets.save_buttons[1], enable);
597 /* save all menu item and tool button */
598 for (i = 0; i < documents_array->len; i++)
600 /* check whether there are files where changes were made and if there are some,
601 * we need the save all button / item */
602 if (documents[i]->is_valid && documents[i]->changed)
604 dirty_tabs = TRUE;
605 break;
609 ui_widget_set_sensitive(widgets.save_buttons[2], dirty_tabs);
610 ui_widget_set_sensitive(widgets.save_buttons[3], dirty_tabs);
614 #define add_doc_widget(widget_name) \
615 g_ptr_array_add(widgets.document_buttons, ui_lookup_widget(main_widgets.window, widget_name))
617 #define add_doc_toolitem(widget_name) \
618 g_ptr_array_add(widgets.document_buttons, toolbar_get_action_by_name(widget_name))
620 static void init_document_widgets(void)
622 widgets.document_buttons = g_ptr_array_new();
624 /* Cache the document-sensitive widgets so we don't have to keep looking them up
625 * when using ui_document_buttons_update(). */
626 add_doc_widget("menu_close1");
627 add_doc_widget("close_other_documents1");
628 add_doc_widget("menu_change_font1");
629 add_doc_widget("menu_close_all1");
630 add_doc_widget("menu_save1");
631 add_doc_widget("menu_save_all1");
632 add_doc_widget("menu_save_as1");
633 add_doc_widget("menu_count_words1");
634 add_doc_widget("menu_build1");
635 add_doc_widget("add_comments1");
636 add_doc_widget("menu_paste1");
637 add_doc_widget("menu_undo2");
638 add_doc_widget("preferences2");
639 add_doc_widget("menu_reload1");
640 add_doc_widget("menu_document1");
641 add_doc_widget("menu_choose_color1");
642 add_doc_widget("menu_zoom_in1");
643 add_doc_widget("menu_zoom_out1");
644 add_doc_widget("menu_view_editor1");
645 add_doc_widget("normal_size1");
646 add_doc_widget("treeview6");
647 add_doc_widget("print1");
648 add_doc_widget("menu_reload_as1");
649 add_doc_widget("menu_select_all1");
650 add_doc_widget("insert_date1");
651 add_doc_widget("menu_format1");
652 add_doc_widget("commands2");
653 add_doc_widget("menu_open_selected_file1");
654 add_doc_widget("page_setup1");
655 add_doc_widget("find1");
656 add_doc_widget("find_next1");
657 add_doc_widget("find_previous1");
658 add_doc_widget("replace1");
659 add_doc_widget("find_nextsel1");
660 add_doc_widget("find_prevsel1");
661 add_doc_widget("go_to_line1");
662 add_doc_toolitem("Close");
663 add_doc_toolitem("CloseAll");
664 add_doc_toolitem("Search");
665 add_doc_toolitem("SearchEntry");
666 add_doc_toolitem("NavBack");
667 add_doc_toolitem("NavFor");
668 add_doc_toolitem("ZoomIn");
669 add_doc_toolitem("ZoomOut");
670 add_doc_toolitem("Indent");
671 add_doc_toolitem("UnIndent");
672 add_doc_toolitem("Cut");
673 add_doc_toolitem("Copy");
674 add_doc_toolitem("Paste");
675 add_doc_toolitem("Delete");
676 add_doc_toolitem("Save");
677 add_doc_toolitem("SaveAll");
678 add_doc_toolitem("Compile");
679 add_doc_toolitem("Run");
680 add_doc_toolitem("Reload");
681 add_doc_toolitem("Color");
682 add_doc_toolitem("Goto");
683 add_doc_toolitem("GotoEntry");
687 void ui_document_buttons_update(void)
689 guint i;
690 gboolean enable = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)) ? TRUE : FALSE;
692 for (i = 0; i < widgets.document_buttons->len; i++)
694 GtkWidget *widget = g_ptr_array_index(widgets.document_buttons, i);
695 if (GTK_IS_ACTION(widget))
696 gtk_action_set_sensitive(GTK_ACTION(widget), enable);
697 else
698 ui_widget_set_sensitive(widget, enable);
703 static void on_doc_sensitive_widget_destroy(GtkWidget *widget, G_GNUC_UNUSED gpointer user_data)
705 g_ptr_array_remove_fast(widgets.document_buttons, widget);
709 /** Add a widget to the list of widgets that should be set sensitive/insensitive
710 * when some documents are present/no documents are open.
711 * It will be removed when the widget is destroyed.
712 * @param widget The widget to add.
714 * @since 0.15
716 void ui_add_document_sensitive(GtkWidget *widget)
718 gboolean enable = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)) ? TRUE : FALSE;
720 ui_widget_set_sensitive(widget, enable);
722 g_ptr_array_add(widgets.document_buttons, widget);
723 g_signal_connect(widget, "destroy", G_CALLBACK(on_doc_sensitive_widget_destroy), NULL);
727 void ui_widget_show_hide(GtkWidget *widget, gboolean show)
729 if (show)
731 gtk_widget_show(widget);
733 else
735 gtk_widget_hide(widget);
740 void ui_sidebar_show_hide(void)
742 GtkWidget *widget;
744 /* check that there are no other notebook pages before hiding the sidebar completely
745 * other pages could be e.g. the file browser plugin */
746 if (! interface_prefs.sidebar_openfiles_visible && ! interface_prefs.sidebar_symbol_visible &&
747 gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.sidebar_notebook)) <= 2)
749 ui_prefs.sidebar_visible = FALSE;
752 widget = ui_lookup_widget(main_widgets.window, "menu_show_sidebar1");
753 if (ui_prefs.sidebar_visible != gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
755 ignore_callback = TRUE;
756 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), ui_prefs.sidebar_visible);
757 ignore_callback = FALSE;
760 ui_widget_show_hide(main_widgets.sidebar_notebook, ui_prefs.sidebar_visible);
762 ui_widget_show_hide(gtk_notebook_get_nth_page(
763 GTK_NOTEBOOK(main_widgets.sidebar_notebook), 0), interface_prefs.sidebar_symbol_visible);
764 ui_widget_show_hide(gtk_notebook_get_nth_page(
765 GTK_NOTEBOOK(main_widgets.sidebar_notebook), 1), interface_prefs.sidebar_openfiles_visible);
769 void ui_document_show_hide(GeanyDocument *doc)
771 gchar *widget_name;
772 GtkWidget *item;
773 const GeanyIndentPrefs *iprefs;
775 if (doc == NULL)
776 doc = document_get_current();
778 if (doc == NULL)
779 return;
781 ignore_callback = TRUE;
783 gtk_check_menu_item_set_active(
784 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_line_wrapping1")),
785 doc->editor->line_wrapping);
787 gtk_check_menu_item_set_active(
788 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "line_breaking1")),
789 doc->editor->line_breaking);
791 iprefs = editor_get_indent_prefs(doc->editor);
793 item = ui_lookup_widget(main_widgets.window, "menu_use_auto_indentation1");
794 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), doc->editor->auto_indent);
796 switch (iprefs->type)
798 case GEANY_INDENT_TYPE_SPACES:
799 widget_name = "spaces1"; break;
800 case GEANY_INDENT_TYPE_TABS:
801 widget_name = "tabs1"; break;
802 case GEANY_INDENT_TYPE_BOTH:
803 default:
804 widget_name = "tabs_and_spaces1"; break;
806 item = ui_lookup_widget(main_widgets.window, widget_name);
807 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
809 gtk_check_menu_item_set_active(
810 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "set_file_readonly1")),
811 doc->readonly);
813 item = ui_lookup_widget(main_widgets.window, "menu_write_unicode_bom1");
814 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), doc->has_bom);
815 ui_widget_set_sensitive(item, encodings_is_unicode_charset(doc->encoding));
817 switch (sci_get_eol_mode(doc->editor->sci))
819 case SC_EOL_CR: widget_name = "cr"; break;
820 case SC_EOL_LF: widget_name = "lf"; break;
821 default: widget_name = "crlf"; break;
823 gtk_check_menu_item_set_active(
824 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, widget_name)), TRUE);
826 encodings_select_radio_item(doc->encoding);
827 filetypes_select_radio_item(doc->file_type);
829 ignore_callback = FALSE;
833 void ui_set_search_entry_background(GtkWidget *widget, gboolean success)
835 static const GdkColor red = {0, 0xffff, 0x6666, 0x6666};
836 static const GdkColor white = {0, 0xffff, 0xffff, 0xffff};
837 static gboolean old_value = TRUE;
839 g_return_if_fail(widget != NULL);
841 /* update only if really needed */
842 if (old_value != success)
844 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, success ? NULL : &red);
845 gtk_widget_modify_text(widget, GTK_STATE_NORMAL, success ? NULL : &white);
847 old_value = success;
852 static gboolean have_tango_icon_theme(void)
854 static gboolean result = FALSE;
855 static gboolean checked = FALSE;
857 if (! checked)
859 gchar *theme_name;
861 g_object_get(G_OBJECT(gtk_settings_get_default()), "gtk-icon-theme-name", &theme_name, NULL);
862 setptr(theme_name, g_utf8_strdown(theme_name, -1));
864 result = (strstr(theme_name, "tango") != NULL);
865 checked = TRUE;
867 g_free(theme_name);
870 return result;
874 /* Note: remember to unref the pixbuf once an image or window has added a reference. */
875 GdkPixbuf *ui_new_pixbuf_from_inline(gint img)
877 switch (img)
879 case GEANY_IMAGE_LOGO:
880 return gdk_pixbuf_new_from_inline(-1, aladin_inline, FALSE, NULL);
881 break;
882 case GEANY_IMAGE_SAVE_ALL:
884 /* check whether the icon theme looks like a Gnome icon theme, if so use the
885 * old Gnome based Save All icon, otherwise assume a Tango-like icon theme */
886 if (have_tango_icon_theme())
887 return gdk_pixbuf_new_from_inline(-1, save_all_tango_inline, FALSE, NULL);
888 else
889 return gdk_pixbuf_new_from_inline(-1, save_all_gnome_inline, FALSE, NULL);
890 break;
892 case GEANY_IMAGE_CLOSE_ALL:
894 return gdk_pixbuf_new_from_inline(-1, close_all_inline, FALSE, NULL);
895 break;
897 case GEANY_IMAGE_BUILD:
899 return gdk_pixbuf_new_from_inline(-1, build_inline, FALSE, NULL);
900 break;
902 default:
903 return NULL;
908 static GdkPixbuf *ui_new_pixbuf_from_stock(const gchar *stock_id)
910 if (utils_str_equal(stock_id, GEANY_STOCK_CLOSE_ALL))
911 return ui_new_pixbuf_from_inline(GEANY_IMAGE_CLOSE_ALL);
912 else if (utils_str_equal(stock_id, GEANY_STOCK_BUILD))
913 return ui_new_pixbuf_from_inline(GEANY_IMAGE_BUILD);
914 else if (utils_str_equal(stock_id, GEANY_STOCK_SAVE_ALL))
915 return ui_new_pixbuf_from_inline(GEANY_IMAGE_SAVE_ALL);
917 return NULL;
921 GtkWidget *ui_new_image_from_inline(gint img)
923 GtkWidget *wid;
924 GdkPixbuf *pb;
926 pb = ui_new_pixbuf_from_inline(img);
927 wid = gtk_image_new_from_pixbuf(pb);
928 g_object_unref(pb); /* the image doesn't adopt our reference, so remove our ref. */
929 return wid;
933 static void recent_create_menu(GeanyRecentFiles *grf)
935 GtkWidget *tmp;
936 guint i, len;
937 gchar *filename;
939 len = MIN(file_prefs.mru_length, g_queue_get_length(grf->recent_queue));
940 for (i = 0; i < len; i++)
942 filename = g_queue_peek_nth(grf->recent_queue, i);
943 /* create menu item for the recent files menu in the menu bar */
944 tmp = gtk_menu_item_new_with_label(filename);
945 gtk_widget_show(tmp);
946 gtk_container_add(GTK_CONTAINER(grf->menubar), tmp);
947 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
948 /* create menu item for the recent files menu in the toolbar */
949 if (grf->toolbar != NULL)
951 tmp = gtk_menu_item_new_with_label(filename);
952 gtk_widget_show(tmp);
953 gtk_container_add(GTK_CONTAINER(grf->toolbar), tmp);
954 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
960 static GeanyRecentFiles *recent_get_recent_files(void)
962 static GeanyRecentFiles grf = { RECENT_FILE_FILE, NULL, NULL, NULL, NULL };
964 if (G_UNLIKELY(grf.recent_queue == NULL))
966 grf.recent_queue = ui_prefs.recent_queue;
967 grf.menubar = ui_widgets.recent_files_menu_menubar;
968 grf.toolbar = geany_menu_button_action_get_menu(GEANY_MENU_BUTTON_ACTION(
969 toolbar_get_action_by_name("Open")));
970 grf.activate_cb = recent_file_activate_cb;
972 return &grf;
976 static GeanyRecentFiles *recent_get_recent_projects(void)
978 static GeanyRecentFiles grf = { RECENT_FILE_PROJECT, NULL, NULL, NULL, NULL };
980 if (G_UNLIKELY(grf.recent_queue == NULL))
982 grf.recent_queue = ui_prefs.recent_projects_queue;
983 grf.menubar = ui_widgets.recent_projects_menu_menubar;
984 grf.toolbar = NULL;
985 grf.activate_cb = recent_project_activate_cb;
987 return &grf;
991 void ui_create_recent_menus(void)
993 recent_create_menu(recent_get_recent_files());
994 recent_create_menu(recent_get_recent_projects());
998 static void recent_file_activate_cb(GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
1000 gchar *utf8_filename = ui_menu_item_get_text(menuitem);
1001 gchar *locale_filename = utils_get_locale_from_utf8(utf8_filename);
1003 if (document_open_file(locale_filename, FALSE, NULL, NULL) != NULL)
1004 recent_file_loaded(utf8_filename, recent_get_recent_files());
1006 g_free(locale_filename);
1007 g_free(utf8_filename);
1011 static void recent_project_activate_cb(GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
1013 gchar *utf8_filename = ui_menu_item_get_text(menuitem);
1014 gchar *locale_filename = utils_get_locale_from_utf8(utf8_filename);
1016 if (project_ask_close() && project_load_file_with_session(locale_filename))
1017 recent_file_loaded(utf8_filename, recent_get_recent_projects());
1019 g_free(locale_filename);
1020 g_free(utf8_filename);
1024 static void add_recent_file(const gchar *utf8_filename, GeanyRecentFiles *grf)
1026 if (g_queue_find_custom(grf->recent_queue, utf8_filename, (GCompareFunc) strcmp) == NULL)
1028 #if GTK_CHECK_VERSION(2, 10, 0)
1029 if (grf->type == RECENT_FILE_FILE)
1031 GtkRecentManager *manager = gtk_recent_manager_get_default();
1032 gchar *uri = g_filename_to_uri(utf8_filename, NULL, NULL);
1033 if (uri != NULL)
1035 gtk_recent_manager_add_item(manager, uri);
1036 g_free(uri);
1039 #endif
1040 g_queue_push_head(grf->recent_queue, g_strdup(utf8_filename));
1041 if (g_queue_get_length(grf->recent_queue) > file_prefs.mru_length)
1043 g_free(g_queue_pop_tail(grf->recent_queue));
1045 update_recent_menu(grf);
1047 /* filename already in recent list */
1048 else
1049 recent_file_loaded(utf8_filename, grf);
1053 void ui_add_recent_file(const gchar *utf8_filename)
1055 add_recent_file(utf8_filename, recent_get_recent_files());
1059 void ui_add_recent_project_file(const gchar *utf8_filename)
1061 add_recent_file(utf8_filename, recent_get_recent_projects());
1065 /* Returns: newly allocated string with the UTF-8 menu text. */
1066 gchar *ui_menu_item_get_text(GtkMenuItem *menu_item)
1068 const gchar *text = NULL;
1070 if (GTK_BIN(menu_item)->child)
1072 GtkWidget *child = GTK_BIN(menu_item)->child;
1074 if (GTK_IS_LABEL(child))
1075 text = gtk_label_get_text(GTK_LABEL(child));
1077 /* GTK owns text so it's much safer to return a copy of it in case the memory is reallocated */
1078 return g_strdup(text);
1082 static gint find_recent_file_item(gconstpointer list_data, gconstpointer user_data)
1084 gchar *menu_text = ui_menu_item_get_text(GTK_MENU_ITEM(list_data));
1085 gint result;
1087 if (utils_str_equal(menu_text, user_data))
1088 result = 0;
1089 else
1090 result = 1;
1092 g_free(menu_text);
1093 return result;
1097 static void recent_file_loaded(const gchar *utf8_filename, GeanyRecentFiles *grf)
1099 GList *item, *children;
1100 void *data;
1101 GtkWidget *tmp;
1103 /* first reorder the queue */
1104 item = g_queue_find_custom(grf->recent_queue, utf8_filename, (GCompareFunc) strcmp);
1105 g_return_if_fail(item != NULL);
1107 data = item->data;
1108 g_queue_remove(grf->recent_queue, data);
1109 g_queue_push_head(grf->recent_queue, data);
1111 /* remove the old menuitem for the filename */
1112 children = gtk_container_get_children(GTK_CONTAINER(grf->menubar));
1113 item = g_list_find_custom(children, utf8_filename, (GCompareFunc) find_recent_file_item);
1114 if (item != NULL)
1115 gtk_widget_destroy(GTK_WIDGET(item->data));
1116 g_list_free(children);
1118 if (grf->toolbar != NULL)
1120 children = gtk_container_get_children(GTK_CONTAINER(grf->toolbar));
1121 item = g_list_find_custom(children, utf8_filename, (GCompareFunc) find_recent_file_item);
1122 if (item != NULL)
1123 gtk_widget_destroy(GTK_WIDGET(item->data));
1124 g_list_free(children);
1126 /* now prepend a new menuitem for the filename,
1127 * first for the recent files menu in the menu bar */
1128 tmp = gtk_menu_item_new_with_label(utf8_filename);
1129 gtk_widget_show(tmp);
1130 gtk_menu_shell_prepend(GTK_MENU_SHELL(grf->menubar), tmp);
1131 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
1132 /* then for the recent files menu in the tool bar */
1133 if (grf->toolbar != NULL)
1135 tmp = gtk_menu_item_new_with_label(utf8_filename);
1136 gtk_widget_show(tmp);
1137 gtk_container_add(GTK_CONTAINER(grf->toolbar), tmp);
1138 /* this is a bit ugly, but we need to use gtk_container_add(). Using
1139 * gtk_menu_shell_prepend() doesn't emit GtkContainer's "add" signal which we need in
1140 * GeanyMenubuttonAction */
1141 gtk_menu_reorder_child(GTK_MENU(grf->toolbar), tmp, 0);
1142 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
1147 static void update_recent_menu(GeanyRecentFiles *grf)
1149 GtkWidget *tmp;
1150 gchar *filename;
1151 GList *children, *item;
1153 filename = g_queue_peek_head(grf->recent_queue);
1155 /* clean the MRU list before adding an item (menubar) */
1156 children = gtk_container_get_children(GTK_CONTAINER(grf->menubar));
1157 if (g_list_length(children) > file_prefs.mru_length - 1)
1159 item = g_list_nth(children, file_prefs.mru_length - 1);
1160 while (item != NULL)
1162 if (GTK_IS_MENU_ITEM(item->data))
1163 gtk_widget_destroy(GTK_WIDGET(item->data));
1164 item = g_list_next(item);
1167 g_list_free(children);
1169 /* create item for the menu bar menu */
1170 tmp = gtk_menu_item_new_with_label(filename);
1171 gtk_widget_show(tmp);
1172 gtk_menu_shell_prepend(GTK_MENU_SHELL(grf->menubar), tmp);
1173 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
1175 /* clean the MRU list before adding an item (toolbar) */
1176 if (grf->toolbar != NULL)
1178 children = gtk_container_get_children(GTK_CONTAINER(grf->toolbar));
1179 if (g_list_length(children) > file_prefs.mru_length - 1)
1181 item = g_list_nth(children, file_prefs.mru_length - 1);
1182 while (item != NULL)
1184 if (GTK_IS_MENU_ITEM(item->data))
1185 gtk_widget_destroy(GTK_WIDGET(item->data));
1186 item = g_list_next(item);
1189 g_list_free(children);
1191 /* create item for the tool bar menu */
1192 tmp = gtk_menu_item_new_with_label(filename);
1193 gtk_widget_show(tmp);
1194 gtk_container_add(GTK_CONTAINER(grf->toolbar), tmp);
1195 gtk_menu_reorder_child(GTK_MENU(grf->toolbar), tmp, 0);
1196 g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
1201 void ui_toggle_editor_features(GeanyUIEditorFeatures feature)
1203 gint i, max = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook));
1204 GeanyDocument *doc;
1206 for (i = 0; i < max; i++)
1208 doc = document_get_from_page(i);
1210 switch (feature)
1212 case GEANY_EDITOR_SHOW_MARKERS_MARGIN:
1213 sci_set_symbol_margin(doc->editor->sci, editor_prefs.show_markers_margin);
1214 break;
1215 case GEANY_EDITOR_SHOW_LINE_NUMBERS:
1216 sci_set_line_numbers(doc->editor->sci, editor_prefs.show_linenumber_margin, 0);
1217 break;
1218 case GEANY_EDITOR_SHOW_WHITE_SPACE:
1219 sci_set_visible_white_spaces(doc->editor->sci, editor_prefs.show_white_space);
1220 break;
1221 case GEANY_EDITOR_SHOW_LINE_ENDINGS:
1222 sci_set_visible_eols(doc->editor->sci, editor_prefs.show_line_endings);
1223 break;
1224 case GEANY_EDITOR_SHOW_INDENTATION_GUIDES:
1225 editor_set_indentation_guides(doc->editor);
1226 break;
1232 void ui_update_view_editor_menu_items(void)
1234 ignore_callback = TRUE;
1235 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_markers_margin1")), editor_prefs.show_markers_margin);
1236 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_linenumber_margin1")), editor_prefs.show_linenumber_margin);
1237 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);
1238 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);
1239 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);
1240 ignore_callback = FALSE;
1244 /** Creates a GNOME HIG-style frame (with no border and indented child alignment).
1245 * @param label_text The label text.
1246 * @param alignment An address to store the alignment widget pointer.
1247 * @return The frame widget, setting the alignment container for packing child widgets. */
1248 GtkWidget *ui_frame_new_with_alignment(const gchar *label_text, GtkWidget **alignment)
1250 GtkWidget *label, *align;
1251 GtkWidget *frame = gtk_frame_new(NULL);
1253 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
1255 align = gtk_alignment_new(0.5, 0.5, 1, 1);
1256 gtk_container_add(GTK_CONTAINER(frame), align);
1257 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 0);
1259 label = ui_label_new_bold(label_text);
1260 gtk_frame_set_label_widget(GTK_FRAME(frame), label);
1262 *alignment = align;
1263 return frame;
1267 /** Convenience function for getting a fixed border for dialogs that doesn't
1268 * increase the button box border.
1269 * @param dialog The parent container for the @c GtkVBox.
1270 * @return The packed @c GtkVBox. */
1271 GtkWidget *ui_dialog_vbox_new(GtkDialog *dialog)
1273 GtkWidget *vbox = gtk_vbox_new(FALSE, 12); /* need child vbox to set a separate border. */
1275 gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
1276 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
1277 return vbox;
1281 /** Creates a @c GtkButton with custom text and a stock image similar to
1282 * @c gtk_button_new_from_stock().
1283 * @param stock_id A @c GTK_STOCK_NAME string.
1284 * @param text Button label text, can include mnemonics.
1285 * @return The new @c GtkButton.
1287 GtkWidget *ui_button_new_with_image(const gchar *stock_id, const gchar *text)
1289 GtkWidget *image, *button;
1291 button = gtk_button_new_with_mnemonic(text);
1292 gtk_widget_show(button);
1293 image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON);
1294 gtk_button_set_image(GTK_BUTTON(button), image);
1295 /* note: image is shown by gtk */
1296 return button;
1300 /** Create a @c GtkImageMenuItem with a stock image and a custom label.
1301 * @param stock_id Stock image ID, e.g. @c GTK_STOCK_OPEN.
1302 * @param label Menu item label, can include mnemonics.
1303 * @return The new @c GtkImageMenuItem.
1305 * @since 0.16
1307 GtkWidget *
1308 ui_image_menu_item_new(const gchar *stock_id, const gchar *label)
1310 GtkWidget *item = gtk_image_menu_item_new_with_mnemonic(label);
1311 GtkWidget *image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_MENU);
1313 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
1314 gtk_widget_show(image);
1315 return item;
1319 static void entry_clear_icon_release_cb(GtkEntry *entry, gint icon_pos,
1320 GdkEvent *event, gpointer data)
1322 if (event->button.button == 1 && icon_pos == 1)
1324 gtk_entry_set_text(entry, "");
1325 gtk_widget_grab_focus(GTK_WIDGET(entry));
1330 /** Convenience function to add a small clear icon to the right end of the passed @a entry.
1331 * A callback to clear the contents of the GtkEntry is automatically added.
1333 * This feature is only available with GTK 2.16 but implemented as a runtime check,
1334 * so it is safe to just use this function, if the code is ran with older versions,
1335 * nothing happens. If ran with GTK 2.16 or newer, the icon is displayed.
1337 * @param entry The GtkEntry object to which the icon should be attached.
1339 * @since 0.16
1341 void ui_entry_add_clear_icon(GtkEntry *entry)
1343 if (gtk_check_version(2, 15, 2) == NULL)
1345 g_object_set(entry, "secondary-icon-stock", "gtk-clear", NULL);
1346 g_signal_connect(entry, "icon-release", G_CALLBACK(entry_clear_icon_release_cb), NULL);
1351 static void add_to_size_group(GtkWidget *widget, gpointer size_group)
1353 g_return_if_fail(GTK_IS_SIZE_GROUP(size_group));
1354 gtk_size_group_add_widget(GTK_SIZE_GROUP(size_group), widget);
1358 /* Copies the spacing and layout of the master GtkHButtonBox and synchronises
1359 * the width of each button box's children.
1360 * Should be called after all child widgets have been packed. */
1361 void ui_hbutton_box_copy_layout(GtkButtonBox *master, GtkButtonBox *copy)
1363 GtkSizeGroup *size_group;
1365 gtk_box_set_spacing(GTK_BOX(copy), 10);
1366 gtk_button_box_set_layout(copy, gtk_button_box_get_layout(master));
1368 /* now we need to put the widest widget from each button box in a size group,
1369 * but we don't know the width before they are drawn, and for different label
1370 * translations the widest widget can vary, so we just add all widgets. */
1371 size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1372 gtk_container_foreach(GTK_CONTAINER(master), add_to_size_group, size_group);
1373 gtk_container_foreach(GTK_CONTAINER(copy), add_to_size_group, size_group);
1374 g_object_unref(size_group);
1378 /* Prepends the active text to the drop down list, unless the first element in
1379 * the list is identical, ensuring there are <= history_len elements. */
1380 void ui_combo_box_add_to_history(GtkComboBox *combo, const gchar *text)
1382 const gint history_len = 30;
1383 GtkTreeModel *model;
1384 GtkTreeIter iter;
1385 gchar *combo_text;
1386 gboolean equal = FALSE;
1387 GtkTreePath *path;
1389 model = gtk_combo_box_get_model(combo);
1390 if (gtk_tree_model_get_iter_first(model, &iter))
1392 gtk_tree_model_get(model, &iter, 0, &combo_text, -1);
1393 equal = utils_str_equal(combo_text, text);
1394 g_free(combo_text);
1396 if (equal) return; /* don't prepend duplicate */
1398 gtk_combo_box_prepend_text(combo, text);
1400 /* limit history */
1401 path = gtk_tree_path_new_from_indices(history_len, -1);
1402 if (gtk_tree_model_get_iter(model, &iter, path))
1404 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1406 gtk_tree_path_free(path);
1410 /* Same as gtk_combo_box_prepend_text(), except that text is only prepended if it not already
1411 * exists in the combo's model. */
1412 void ui_combo_box_prepend_text_once(GtkComboBox *combo, const gchar *text)
1414 GtkTreeModel *model;
1415 GtkTreeIter iter;
1416 gchar *combo_text;
1417 gboolean found = FALSE;
1419 model = gtk_combo_box_get_model(combo);
1420 if (gtk_tree_model_get_iter_first(model, &iter))
1424 gtk_tree_model_get(model, &iter, 0, &combo_text, -1);
1425 found = utils_str_equal(combo_text, text);
1426 g_free(combo_text);
1428 while (!found && gtk_tree_model_iter_next(model, &iter));
1430 if (found)
1431 return; /* don't prepend duplicate */
1433 gtk_combo_box_prepend_text(combo, text);
1437 /* Changes the color of the notebook tab text and open files items according to
1438 * document status. */
1439 void ui_update_tab_status(GeanyDocument *doc)
1441 const GdkColor *color = document_get_status_color(doc);
1443 /* NULL color will reset to default */
1444 gtk_widget_modify_fg(doc->priv->tab_label, GTK_STATE_NORMAL, color);
1445 gtk_widget_modify_fg(doc->priv->tab_label, GTK_STATE_ACTIVE, color);
1447 sidebar_openfiles_update(doc);
1451 static gboolean tree_model_iter_get_next(GtkTreeModel *model, GtkTreeIter *iter,
1452 gboolean down)
1454 GtkTreePath *path;
1455 gboolean result;
1457 if (down)
1458 return gtk_tree_model_iter_next(model, iter);
1460 path = gtk_tree_model_get_path(model, iter);
1461 result = gtk_tree_path_prev(path) && gtk_tree_model_get_iter(model, iter, path);
1462 gtk_tree_path_free(path);
1463 return result;
1467 /* note: the while loop might be more efficient when searching upwards if it
1468 * used tree paths instead of tree iters, but in practice it probably doesn't matter much. */
1469 static gboolean tree_view_find(GtkTreeView *treeview, TVMatchCallback cb, gboolean down)
1471 GtkTreeSelection *treesel;
1472 GtkTreeIter iter;
1473 GtkTreeModel *model;
1475 treesel = gtk_tree_view_get_selection(treeview);
1476 if (gtk_tree_selection_get_selected(treesel, &model, &iter))
1478 /* get the next selected item */
1479 if (! tree_model_iter_get_next(model, &iter, down))
1480 return FALSE; /* no more items */
1482 else /* no selection */
1484 if (! gtk_tree_model_get_iter_first(model, &iter))
1485 return TRUE; /* no items */
1487 while (TRUE)
1489 gtk_tree_selection_select_iter(treesel, &iter);
1490 if (cb(0))
1491 break; /* found next message */
1493 if (! tree_model_iter_get_next(model, &iter, down))
1494 return FALSE; /* no more items */
1496 /* scroll item in view */
1497 if (ui_prefs.msgwindow_visible)
1499 GtkTreePath *path = gtk_tree_model_get_path(
1500 gtk_tree_view_get_model(treeview), &iter);
1502 gtk_tree_view_scroll_to_cell(treeview, path, NULL, TRUE, 0.5, 0.5);
1503 gtk_tree_path_free(path);
1505 return TRUE;
1509 /* Returns FALSE if the treeview has items but no matching next item. */
1510 gboolean ui_tree_view_find_next(GtkTreeView *treeview, TVMatchCallback cb)
1512 return tree_view_find(treeview, cb, TRUE);
1516 /* Returns FALSE if the treeview has items but no matching next item. */
1517 gboolean ui_tree_view_find_previous(GtkTreeView *treeview, TVMatchCallback cb)
1519 return tree_view_find(treeview, cb, FALSE);
1524 * Modifies the font of a widget using gtk_widget_modify_font().
1526 * @param widget The widget.
1527 * @param str The font name as expected by pango_font_description_from_string().
1529 void ui_widget_modify_font_from_string(GtkWidget *widget, const gchar *str)
1531 PangoFontDescription *pfd;
1533 pfd = pango_font_description_from_string(str);
1534 gtk_widget_modify_font(widget, pfd);
1535 pango_font_description_free(pfd);
1539 /** Creates a @c GtkHBox with @a entry packed into it and an open button which runs a
1540 * file chooser, replacing entry text (if successful) with the path returned from the
1541 * @c GtkFileChooser.
1542 * @note @a entry can be the child of an unparented widget, such as @c GtkComboBoxEntry.
1543 * @param title The file chooser dialog title, or @c NULL.
1544 * @param action The mode of the file chooser.
1545 * @param entry Can be an unpacked @c GtkEntry, or the child of an unpacked widget,
1546 * such as @c GtkComboBoxEntry.
1547 * @return The @c GtkHBox.
1549 /* @see ui_setup_open_button_callback(). */
1550 GtkWidget *ui_path_box_new(const gchar *title, GtkFileChooserAction action, GtkEntry *entry)
1552 GtkWidget *vbox, *dirbtn, *openimg, *hbox, *path_entry;
1554 hbox = gtk_hbox_new(FALSE, 6);
1555 path_entry = GTK_WIDGET(entry);
1557 /* prevent path_entry being vertically stretched to the height of dirbtn */
1558 vbox = gtk_vbox_new(FALSE, 0);
1559 if (gtk_widget_get_parent(path_entry)) /* entry->parent may be a GtkComboBoxEntry */
1561 GtkWidget *parent = gtk_widget_get_parent(path_entry);
1563 gtk_box_pack_start(GTK_BOX(vbox), parent, TRUE, FALSE, 0);
1565 else
1566 gtk_box_pack_start(GTK_BOX(vbox), path_entry, TRUE, FALSE, 0);
1568 dirbtn = gtk_button_new();
1569 openimg = gtk_image_new_from_stock(GTK_STOCK_OPEN, GTK_ICON_SIZE_BUTTON);
1570 gtk_container_add(GTK_CONTAINER(dirbtn), openimg);
1571 ui_setup_open_button_callback(dirbtn, title, action, entry);
1573 gtk_box_pack_end(GTK_BOX(hbox), dirbtn, FALSE, FALSE, 0);
1574 gtk_box_pack_end(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
1575 return hbox;
1579 static void ui_path_box_open_clicked(GtkButton *button, gpointer user_data);
1582 /* Setup a GtkButton to run a GtkFileChooser, setting entry text if successful.
1583 * title can be NULL.
1584 * action is the file chooser mode to use. */
1585 void ui_setup_open_button_callback(GtkWidget *open_btn, const gchar *title,
1586 GtkFileChooserAction action, GtkEntry *entry)
1588 GtkWidget *path_entry = GTK_WIDGET(entry);
1590 if (title)
1591 g_object_set_data_full(G_OBJECT(open_btn), "title", g_strdup(title),
1592 (GDestroyNotify) g_free);
1593 g_object_set_data(G_OBJECT(open_btn), "action", (gpointer) action);
1594 g_object_set_data_full(G_OBJECT(open_btn), "entry",
1595 g_object_ref(path_entry), (GDestroyNotify) g_object_unref);
1596 g_signal_connect(open_btn, "clicked", G_CALLBACK(ui_path_box_open_clicked), open_btn);
1600 #ifndef G_OS_WIN32
1601 static gchar *run_file_chooser(const gchar *title, GtkFileChooserAction action,
1602 const gchar *utf8_path)
1604 GtkWidget *dialog = gtk_file_chooser_dialog_new(title,
1605 GTK_WINDOW(main_widgets.window), action,
1606 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1607 GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);
1608 gchar *locale_path;
1609 gchar *ret_path = NULL;
1611 gtk_widget_set_name(dialog, "GeanyDialog");
1612 locale_path = utils_get_locale_from_utf8(utf8_path);
1613 if (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
1615 if (g_path_is_absolute(locale_path) && g_file_test(locale_path, G_FILE_TEST_IS_DIR))
1616 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), locale_path);
1618 else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
1620 if (g_path_is_absolute(locale_path))
1621 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), locale_path);
1623 g_free(locale_path);
1625 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK)
1627 gchar *dir_locale;
1629 dir_locale = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1630 ret_path = utils_get_utf8_from_locale(dir_locale);
1631 g_free(dir_locale);
1633 gtk_widget_destroy(dialog);
1634 return ret_path;
1636 #endif
1639 static void ui_path_box_open_clicked(GtkButton *button, gpointer user_data)
1641 GtkWidget *path_box = GTK_WIDGET(user_data);
1642 GtkFileChooserAction action =
1643 (GtkFileChooserAction) g_object_get_data(G_OBJECT(path_box), "action");
1644 GtkEntry *entry =
1645 (GtkEntry *) g_object_get_data(G_OBJECT(path_box), "entry");
1646 const gchar *title = g_object_get_data(G_OBJECT(path_box), "title");
1647 gchar *utf8_path = NULL;
1649 /* TODO: extend for other actions */
1650 g_return_if_fail(action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
1651 action == GTK_FILE_CHOOSER_ACTION_OPEN);
1653 if (title == NULL)
1654 title = (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) ?
1655 _("Select Folder") : _("Select File");
1657 if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
1659 #ifdef G_OS_WIN32
1660 utf8_path = win32_show_file_dialog(GTK_WINDOW(ui_widgets.prefs_dialog), title,
1661 gtk_entry_get_text(GTK_ENTRY(entry)));
1662 #else
1663 utf8_path = run_file_chooser(title, action, gtk_entry_get_text(GTK_ENTRY(entry)));
1664 #endif
1666 else if (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
1668 gchar *path = g_path_get_dirname(gtk_entry_get_text(GTK_ENTRY(entry)));
1669 #ifdef G_OS_WIN32
1670 utf8_path = win32_show_folder_dialog(ui_widgets.prefs_dialog, title,
1671 gtk_entry_get_text(GTK_ENTRY(entry)));
1672 #else
1673 utf8_path = run_file_chooser(title, action, path);
1674 #endif
1675 g_free(path);
1678 if (utf8_path != NULL)
1680 gtk_entry_set_text(GTK_ENTRY(entry), utf8_path);
1681 g_free(utf8_path);
1686 void ui_statusbar_showhide(gboolean state)
1688 /* handle statusbar visibility */
1689 if (state)
1691 gtk_widget_show(ui_widgets.statusbar);
1692 ui_update_statusbar(NULL, -1);
1694 else
1695 gtk_widget_hide(ui_widgets.statusbar);
1699 /** Pack all @c GtkWidgets passed after the row argument into a table, using
1700 * one widget per cell. The first widget is not expanded as the table grows,
1701 * as this is usually a label.
1702 * @param table
1703 * @param row The row number of the table.
1705 void ui_table_add_row(GtkTable *table, gint row, ...)
1707 va_list args;
1708 gint i;
1709 GtkWidget *widget;
1711 va_start(args, row);
1712 for (i = 0; (widget = va_arg(args, GtkWidget*), widget != NULL); i++)
1714 gint options = (i == 0) ? GTK_FILL : GTK_EXPAND | GTK_FILL;
1716 gtk_table_attach(GTK_TABLE(table), widget, i, i + 1, row, row + 1,
1717 options, 0, 0, 0);
1719 va_end(args);
1723 static void on_config_file_clicked(GtkWidget *widget, gpointer user_data)
1725 const gchar *file_name = user_data;
1726 GeanyFiletype *ft = NULL;
1728 if (strstr(file_name, G_DIR_SEPARATOR_S "filetypes."))
1729 ft = filetypes[GEANY_FILETYPES_CONF];
1731 if (g_file_test(file_name, G_FILE_TEST_EXISTS))
1732 document_open_file(file_name, FALSE, ft, NULL);
1733 else
1735 gchar *utf8_filename = utils_get_utf8_from_locale(file_name);
1736 gchar *base_name = g_path_get_basename(file_name);
1737 gchar *global_file = g_build_filename(app->datadir, base_name, NULL);
1738 gchar *global_content = NULL;
1739 GeanyDocument *doc;
1741 /* if the requested file doesn't exist in the user's config dir, try loading the file
1742 * from the global data directory and use its contents for the newly created file */
1743 if (g_file_test(global_file, G_FILE_TEST_EXISTS))
1744 g_file_get_contents(global_file, &global_content, NULL, NULL);
1746 doc = document_new_file(utf8_filename, ft, global_content);
1748 /* Enforce config file override policy by populating doc->real_path, which in turn
1749 * allows document to be saved directly to file_name location. */
1750 doc->real_path = g_strdup(utf8_filename);
1752 utils_free_pointers(4, utf8_filename, base_name, global_file, global_content, NULL);
1757 /* @note You should connect to the "document-save" signal yourself to detect
1758 * if the user has just saved the config file, reloading it. */
1759 void ui_add_config_file_menu_item(const gchar *real_path, const gchar *label, GtkContainer *parent)
1761 GtkWidget *item;
1763 if (!parent)
1764 parent = GTK_CONTAINER(widgets.config_files_menu);
1766 if (!label)
1768 gchar *base_name;
1770 base_name = g_path_get_basename(real_path);
1771 item = gtk_menu_item_new_with_label(base_name);
1772 g_free(base_name);
1774 else
1775 item = gtk_menu_item_new_with_mnemonic(label);
1777 gtk_widget_show(item);
1778 gtk_container_add(parent, item);
1779 g_signal_connect(item, "activate", G_CALLBACK(on_config_file_clicked),
1780 /* this memory is kept */
1781 g_strdup(real_path));
1785 static gboolean sort_menu(gpointer data)
1787 ui_menu_sort_by_label(GTK_MENU(data));
1788 return FALSE;
1792 static void create_config_files_menu(void)
1794 GtkWidget *menu, *item;
1796 widgets.config_files_menu = menu = gtk_menu_new();
1798 item = ui_lookup_widget(main_widgets.window, "configuration_files1");
1799 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu);
1801 /* sort menu after all items added */
1802 g_idle_add(sort_menu, widgets.config_files_menu);
1806 void ui_init_stock_items(void)
1808 GtkIconSet *icon_set;
1809 GtkIconFactory *factory = gtk_icon_factory_new();
1810 GdkPixbuf *pb;
1811 gsize i, len;
1812 GtkStockItem items[] =
1814 { GEANY_STOCK_SAVE_ALL, _("Save All"), 0, 0, GETTEXT_PACKAGE },
1815 { GEANY_STOCK_CLOSE_ALL, _("Close All"), 0, 0, GETTEXT_PACKAGE },
1816 { GEANY_STOCK_BUILD, _("Build"), 0, 0, GETTEXT_PACKAGE }
1819 len = G_N_ELEMENTS(items);
1820 for (i = 0; i < len; i++)
1822 pb = ui_new_pixbuf_from_stock(items[i].stock_id);
1823 icon_set = gtk_icon_set_new_from_pixbuf(pb);
1825 gtk_icon_factory_add(factory, items[i].stock_id, icon_set);
1827 gtk_icon_set_unref(icon_set);
1828 g_object_unref(pb);
1830 gtk_stock_add((GtkStockItem *) items, len);
1831 gtk_icon_factory_add_default(factory);
1832 g_object_unref(factory);
1836 void ui_init_toolbar_widgets(void)
1838 widgets.save_buttons[1] = toolbar_get_widget_by_name("Save");
1839 widgets.save_buttons[3] = toolbar_get_widget_by_name("SaveAll");
1840 widgets.redo_items[2] = toolbar_get_widget_by_name("Redo");
1841 widgets.undo_items[2] = toolbar_get_widget_by_name("Undo");
1845 void ui_swap_sidebar_pos(void)
1847 GtkWidget *pane = ui_lookup_widget(main_widgets.window, "hpaned1");
1848 GtkWidget *left = gtk_paned_get_child1(GTK_PANED(pane));
1849 GtkWidget *right = gtk_paned_get_child2(GTK_PANED(pane));
1850 GtkWidget *box = ui_lookup_widget(main_widgets.window, "vbox1");
1852 /* reparenting avoids scintilla problem with middle click paste */
1853 gtk_widget_reparent(left, box);
1854 gtk_widget_reparent(right, box);
1855 gtk_widget_reparent(right, pane);
1856 gtk_widget_reparent(left, pane);
1858 gtk_paned_set_position(GTK_PANED(pane), pane->allocation.width
1859 - gtk_paned_get_position(GTK_PANED(pane)));
1863 static void init_recent_files(void)
1865 GtkWidget *toolbar_recent_files_menu;
1867 /* add recent files to the File menu */
1868 ui_widgets.recent_files_menuitem = ui_lookup_widget(main_widgets.window, "recent_files1");
1869 ui_widgets.recent_files_menu_menubar = gtk_menu_new();
1870 gtk_menu_item_set_submenu(GTK_MENU_ITEM(ui_widgets.recent_files_menuitem),
1871 ui_widgets.recent_files_menu_menubar);
1873 /* add recent files to the toolbar Open button */
1874 toolbar_recent_files_menu = gtk_menu_new();
1875 g_object_ref(toolbar_recent_files_menu);
1876 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
1877 toolbar_get_action_by_name("Open")), toolbar_recent_files_menu);
1881 static void ui_menu_move(GtkWidget *menu, GtkWidget *old, GtkWidget *new)
1883 g_object_ref(menu);
1884 gtk_menu_item_set_submenu(GTK_MENU_ITEM(old), NULL);
1885 gtk_menu_item_set_submenu(GTK_MENU_ITEM(new), menu);
1886 g_object_unref(menu);
1890 static void on_editor_menu_show(GtkWidget *item)
1892 GtkWidget *popup = ui_lookup_widget(main_widgets.editor_menu, "commands1");
1893 GtkWidget *bar = ui_lookup_widget(main_widgets.window, "commands2");
1895 ui_menu_move(widgets.commands_menu, bar, popup);
1897 popup = ui_lookup_widget(main_widgets.editor_menu, "menu_format2");
1898 bar = ui_lookup_widget(main_widgets.window, "menu_format1");
1899 ui_menu_move(widgets.format_menu, bar, popup);
1903 static void on_editor_menu_hide(GtkWidget *item)
1905 GtkWidget *popup = ui_lookup_widget(main_widgets.editor_menu, "commands1");
1906 GtkWidget *bar = ui_lookup_widget(main_widgets.window, "commands2");
1908 ui_menu_move(widgets.commands_menu, popup, bar);
1910 popup = ui_lookup_widget(main_widgets.editor_menu, "menu_format2");
1911 bar = ui_lookup_widget(main_widgets.window, "menu_format1");
1912 ui_menu_move(widgets.format_menu, popup, bar);
1916 void ui_init(void)
1918 GtkWidget *item;
1920 init_recent_files();
1922 ui_widgets.statusbar = ui_lookup_widget(main_widgets.window, "statusbar");
1923 ui_widgets.print_page_setup = ui_lookup_widget(main_widgets.window, "page_setup1");
1925 main_widgets.progressbar = progress_bar_create();
1927 widgets.popup_goto_items[0] = ui_lookup_widget(main_widgets.editor_menu, "goto_tag_definition1");
1928 widgets.popup_goto_items[1] = ui_lookup_widget(main_widgets.editor_menu, "goto_tag_declaration1");
1929 widgets.popup_goto_items[2] = ui_lookup_widget(main_widgets.editor_menu, "find_usage1");
1930 widgets.popup_goto_items[3] = ui_lookup_widget(main_widgets.editor_menu, "find_document_usage1");
1931 widgets.popup_copy_items[0] = ui_lookup_widget(main_widgets.editor_menu, "cut1");
1932 widgets.popup_copy_items[1] = ui_lookup_widget(main_widgets.editor_menu, "copy1");
1933 widgets.popup_copy_items[2] = ui_lookup_widget(main_widgets.editor_menu, "delete1");
1934 widgets.menu_copy_items[0] = ui_lookup_widget(main_widgets.window, "menu_cut1");
1935 widgets.menu_copy_items[1] = ui_lookup_widget(main_widgets.window, "menu_copy1");
1936 widgets.menu_copy_items[2] = ui_lookup_widget(main_widgets.window, "menu_delete1");
1937 widgets.menu_insert_include_items[0] = ui_lookup_widget(main_widgets.editor_menu, "insert_include1");
1938 widgets.menu_insert_include_items[1] = ui_lookup_widget(main_widgets.window, "insert_include2");
1939 widgets.save_buttons[0] = ui_lookup_widget(main_widgets.window, "menu_save1");
1940 widgets.save_buttons[2] = ui_lookup_widget(main_widgets.window, "menu_save_all1");
1941 widgets.redo_items[0] = ui_lookup_widget(main_widgets.editor_menu, "redo1");
1942 widgets.redo_items[1] = ui_lookup_widget(main_widgets.window, "menu_redo2");
1943 widgets.undo_items[0] = ui_lookup_widget(main_widgets.editor_menu, "undo1");
1944 widgets.undo_items[1] = ui_lookup_widget(main_widgets.window, "menu_undo2");
1946 item = ui_lookup_widget(main_widgets.window, "menu_format1");
1947 widgets.format_menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(item));
1948 item = ui_lookup_widget(main_widgets.window, "commands2");
1949 widgets.commands_menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(item));
1951 /* reparent edit submenus as needed */
1952 item = main_widgets.editor_menu;
1953 g_signal_connect(item, "show", G_CALLBACK(on_editor_menu_show), NULL);
1954 g_signal_connect(item, "hide", G_CALLBACK(on_editor_menu_hide), NULL);
1956 ui_init_toolbar_widgets();
1957 init_document_widgets();
1958 create_config_files_menu();
1962 static void auto_separator_update(GeanyAutoSeparator *autosep)
1964 g_return_if_fail(autosep->ref_count >= 0);
1966 if (autosep->widget)
1967 ui_widget_show_hide(autosep->widget, autosep->ref_count > 0);
1971 static void on_auto_separator_item_show_hide(GtkWidget *widget, gpointer user_data)
1973 GeanyAutoSeparator *autosep = user_data;
1975 if (GTK_WIDGET_VISIBLE(widget))
1976 autosep->ref_count++;
1977 else
1978 autosep->ref_count--;
1979 auto_separator_update(autosep);
1983 static void on_auto_separator_item_destroy(GtkWidget *widget, gpointer user_data)
1985 GeanyAutoSeparator *autosep = user_data;
1987 /* GTK_WIDGET_VISIBLE won't work now the widget is being destroyed,
1988 * so assume widget was visible */
1989 autosep->ref_count--;
1990 autosep->ref_count = MAX(autosep->ref_count, 0);
1991 auto_separator_update(autosep);
1995 /* Show the separator widget if @a item or another is visible. */
1996 /* Note: This would be neater taking a widget argument, setting a "visible-count"
1997 * property, and using reference counting to keep the widget alive whilst its visible group
1998 * is alive. */
1999 void ui_auto_separator_add_ref(GeanyAutoSeparator *autosep, GtkWidget *item)
2001 /* set widget ptr NULL when widget destroyed */
2002 if (autosep->ref_count == 0)
2003 g_signal_connect(autosep->widget, "destroy",
2004 G_CALLBACK(gtk_widget_destroyed), &autosep->widget);
2006 if (GTK_WIDGET_VISIBLE(item))
2008 autosep->ref_count++;
2009 auto_separator_update(autosep);
2011 g_signal_connect(item, "show", G_CALLBACK(on_auto_separator_item_show_hide), autosep);
2012 g_signal_connect(item, "hide", G_CALLBACK(on_auto_separator_item_show_hide), autosep);
2013 g_signal_connect(item, "destroy", G_CALLBACK(on_auto_separator_item_destroy), autosep);
2018 * Sets @a text as the contents of the tooltip for @a widget.
2020 * @param widget The widget the tooltip should be set for.
2021 * @param text The text for the tooltip.
2023 * @since 0.16
2025 void ui_widget_set_tooltip_text(GtkWidget *widget, const gchar *text)
2027 #if GTK_CHECK_VERSION(2, 12, 0)
2028 gtk_widget_set_tooltip_text(widget, text);
2029 #else
2030 static GtkTooltips *tooltips = NULL;
2032 if (G_UNLIKELY(tooltips == NULL))
2033 tooltips = GTK_TOOLTIPS(ui_lookup_widget(main_widgets.window, "tooltips"));
2035 gtk_tooltips_set_tip(tooltips, widget, text, NULL);
2036 #endif
2040 /** This function returns a widget from a name in a component, usually created by Glade.
2041 * Call it with the toplevel widget in the component (i.e. a window/dialog),
2042 * or alternatively any widget in the component, and the name of the widget
2043 * you want returned.
2044 * @param widget Widget with the @a widget_name property set.
2045 * @param widget_name Name to lookup.
2046 * @return The widget found.
2047 * @see ui_hookup_widget().
2049 * @since 0.16
2051 GtkWidget *ui_lookup_widget(GtkWidget *widget, const gchar *widget_name)
2053 GtkWidget *parent, *found_widget;
2055 g_return_val_if_fail(widget != NULL, NULL);
2056 g_return_val_if_fail(widget_name != NULL, NULL);
2058 for (;;)
2060 if (GTK_IS_MENU(widget))
2061 parent = gtk_menu_get_attach_widget(GTK_MENU(widget));
2062 else
2063 parent = widget->parent;
2064 if (parent == NULL)
2065 parent = (GtkWidget*) g_object_get_data(G_OBJECT(widget), "GladeParentKey");
2066 if (parent == NULL)
2067 break;
2068 widget = parent;
2071 found_widget = (GtkWidget*) g_object_get_data(G_OBJECT(widget), widget_name);
2072 if (G_UNLIKELY(found_widget == NULL))
2073 g_warning("Widget not found: %s", widget_name);
2074 return found_widget;
2078 /* Progress Bar */
2079 static guint progress_bar_timer_id = (guint) -1;
2082 static GtkWidget *progress_bar_create(void)
2084 GtkWidget *bar = gtk_progress_bar_new();
2086 /* Set the progressbar's height to 1 to fit it in the statusbar */
2087 gtk_widget_set_size_request(bar, -1, 1);
2088 gtk_box_pack_start (GTK_BOX(ui_widgets.statusbar), bar, FALSE, FALSE, 3);
2090 return bar;
2094 static gboolean progress_bar_pulse(gpointer data)
2096 gtk_progress_bar_pulse(GTK_PROGRESS_BAR(main_widgets.progressbar));
2098 return TRUE;
2103 * Starts a constantly pulsing progressbar in the right corner of the statusbar
2104 * (if the statusbar is visible). This is a convenience function which adds a timer to
2105 * pulse the progressbar constantly until ui_progress_bar_stop() is called.
2106 * You can use this function when you have time consuming asynchronous operation and want to
2107 * display some activity in the GUI and when you don't know about detailed progress steps.
2108 * The progressbar widget is hidden by default when it is not active. This function and
2109 * ui_progress_bar_stop() will show and hide it automatically for you.
2111 * You can also access the progressbar widget directly using @c geany->main_widgets->progressbar
2112 * and use the GtkProgressBar API to set discrete fractions to display better progress information.
2113 * In this case, you need to show and hide the widget yourself. You can find some example code
2114 * in @c src/printing.c.
2116 * @param text The text to be shown as the progress bar label or NULL to leave it empty.
2118 * @since 0.16
2120 void ui_progress_bar_start(const gchar *text)
2122 g_return_if_fail(progress_bar_timer_id == (guint) -1);
2124 if (! interface_prefs.statusbar_visible)
2125 return;
2127 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(main_widgets.progressbar), text);
2129 progress_bar_timer_id = g_timeout_add(200, progress_bar_pulse, NULL);
2131 gtk_widget_show(GTK_WIDGET(main_widgets.progressbar));
2135 /** Stops a running progress bar and hides the widget again.
2137 * @since 0.16
2139 void ui_progress_bar_stop(void)
2141 gtk_widget_hide(GTK_WIDGET(main_widgets.progressbar));
2143 if (progress_bar_timer_id != (guint) -1)
2145 g_source_remove(progress_bar_timer_id);
2146 progress_bar_timer_id = (guint) -1;
2151 static gint compare_menu_item_labels(gconstpointer a, gconstpointer b)
2153 GtkMenuItem *item_a = GTK_MENU_ITEM(a);
2154 GtkMenuItem *item_b = GTK_MENU_ITEM(b);
2155 gchar *sa, *sb;
2156 gint result;
2158 sa = ui_menu_item_get_text(item_a);
2159 sb = ui_menu_item_get_text(item_b);
2160 result = utils_str_casecmp(sa, sb);
2161 g_free(sa);
2162 g_free(sb);
2163 return result;
2167 /* Currently @a menu should contain only GtkMenuItems with labels. */
2168 void ui_menu_sort_by_label(GtkMenu *menu)
2170 GList *list = gtk_container_get_children(GTK_CONTAINER(menu));
2171 GList *node;
2172 gint pos;
2174 list = g_list_sort(list, compare_menu_item_labels);
2175 pos = 0;
2176 foreach_list(node, list)
2178 gtk_menu_reorder_child(menu, node->data, pos);
2179 pos++;
2181 g_list_free(list);
2185 /* return value is for macros */
2186 GtkWidget *ui_label_set_markup(GtkLabel *label, const gchar *format, ...)
2188 va_list a;
2189 gchar *text;
2191 va_start(a, format);
2192 text = g_strdup_vprintf(format, a);
2193 va_end(a);
2195 gtk_label_set_text(label, text);
2196 gtk_label_set_use_markup(label, TRUE);
2197 g_free(text);
2198 return GTK_WIDGET(label);
2202 /** Add a list of document items to @a menu.
2203 * @param menu Menu.
2204 * @param active Which document to highlight, or @c NULL.
2205 * @param callback is used for each menu item's @c "activate" signal and will be passed
2206 * the corresponding document pointer as @c user_data.
2207 * @warning You should check @c doc->is_valid in the callback.
2208 * @since 0.19 */
2209 void ui_menu_add_document_items(GtkMenu *menu, GeanyDocument *active, GCallback callback)
2211 GtkWidget *menu_item, *menu_item_label;
2212 const GdkColor *color;
2213 GeanyDocument *doc;
2214 guint i, len;
2215 gchar *base_name;
2217 len = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook));
2218 for (i = 0; i < len; i++)
2220 doc = document_get_from_page(i);
2221 if (! DOC_VALID(doc))
2222 continue;
2224 base_name = g_path_get_basename(DOC_FILENAME(doc));
2225 menu_item = gtk_menu_item_new_with_label(base_name);
2226 gtk_widget_show(menu_item);
2227 gtk_container_add(GTK_CONTAINER(menu), menu_item);
2228 g_signal_connect(menu_item, "activate", callback, doc);
2230 color = document_get_status_color(doc);
2231 menu_item_label = gtk_bin_get_child(GTK_BIN(menu_item));
2232 gtk_widget_modify_fg(menu_item_label, GTK_STATE_NORMAL, color);
2233 gtk_widget_modify_fg(menu_item_label, GTK_STATE_ACTIVE, color);
2235 if (doc == active)
2236 ui_label_set_markup(GTK_LABEL(menu_item_label), "<b>%s</b>", base_name);
2238 g_free(base_name);
2243 /** Check whether the passed @a keyval is the Enter or Return key.
2244 * There are three different Enter/Return key values
2245 * (@c GDK_Return, @c GDK_ISO_Enter, @c GDK_KP_Enter).
2246 * This is just a convenience function.
2247 * @param keyval A keyval.
2248 * @return @c TRUE if @a keyval is the one of the Enter/Return key values, otherwise @c FALSE.
2249 * @since 0.19 */
2250 gboolean ui_is_keyval_enter_or_return(guint keyval)
2252 return (keyval == GDK_Return || keyval == GDK_ISO_Enter|| keyval == GDK_KP_Enter);