1 /*
2  *      ui_utils.c - this file is part of Geany, a fast and lightweight IDE
3  *
4  *      Copyright 2006 The Geany contributors
5  *
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.
10  *
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.
15  *
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.
19  */
20 
21 /** @file ui_utils.h
22  * User Interface general utility functions.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28 
29 #include "ui_utils.h"
30 
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"
53 
54 #include <string.h>
55 #include <ctype.h>
56 #include <stdarg.h>
57 #include <gtk/gtk.h>
58 #include <gdk/gdkkeysyms.h>
59 
60 
61 #define DEFAULT_STATUSBAR_TEMPLATE N_(\
62 	"line: %l / %L\t "   \
63 	"col: %c\t "         \
64 	"sel: %s\t "         \
65 	"%w      %t      %m" \
66 	"mode: %M      "     \
67 	"encoding: %e      " \
68 	"filetype: %f      " \
69 	"scope: %S")
70 
71 GeanyInterfacePrefs	interface_prefs;
72 GeanyMainWidgets	main_widgets;
73 
74 UIPrefs			ui_prefs;
75 UIWidgets		ui_widgets;
76 
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;
83 
84 static struct
85 {
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;
97 }
98 widgets;
99 
100 enum
101 {
102 	RECENT_FILE_FILE,
103 	RECENT_FILE_PROJECT
104 };
105 
106 typedef struct
107 {
108 	gint type;
109 	GQueue *recent_queue;
110 	GtkWidget *menubar;
111 	GtkWidget *toolbar;
112 	void (*activate_cb)(GtkMenuItem *, gpointer);
113 } GeanyRecentFiles;
114 
115 
116 static void update_recent_menu(GeanyRecentFiles *grf);
117 static void recent_file_loaded(const gchar *utf8_filename, GeanyRecentFiles *grf);
118 static void recent_file_activate_cb(GtkMenuItem *menuitem, gpointer user_data);
119 static void recent_project_activate_cb(GtkMenuItem *menuitem, gpointer user_data);
120 static GtkWidget *progress_bar_create(void);
121 static void ui_menu_sort_by_label(GtkMenu *menu);
122 
123 
124 /* simple wrapper for gtk_widget_set_sensitive() to allow widget being NULL */
ui_widget_set_sensitive(GtkWidget * widget,gboolean set)125 void ui_widget_set_sensitive(GtkWidget *widget, gboolean set)
126 {
127 	if (widget != NULL)
128 		gtk_widget_set_sensitive(widget, set);
129 }
130 
131 
132 /* allow_override is TRUE if text can be ignored when another message has been set
133  * that didn't use allow_override and has not timed out. */
set_statusbar(const gchar * text,gboolean allow_override)134 static void set_statusbar(const gchar *text, gboolean allow_override)
135 {
136 	static guint id = 0;
137 	static glong last_time = 0;
138 	GTimeVal timeval;
139 	const gint GEANY_STATUS_TIMEOUT = 1;
140 
141 	if (! interface_prefs.statusbar_visible)
142 		return; /* just do nothing if statusbar is not visible */
143 
144 	if (id == 0)
145 		id = gtk_statusbar_get_context_id(GTK_STATUSBAR(ui_widgets.statusbar), "geany-main");
146 
147 	g_get_current_time(&timeval);
148 
149 	if (! allow_override)
150 	{
151 		gtk_statusbar_pop(GTK_STATUSBAR(ui_widgets.statusbar), id);
152 		gtk_statusbar_push(GTK_STATUSBAR(ui_widgets.statusbar), id, text);
153 		last_time = timeval.tv_sec;
154 	}
155 	else
156 	if (timeval.tv_sec > last_time + GEANY_STATUS_TIMEOUT)
157 	{
158 		gtk_statusbar_pop(GTK_STATUSBAR(ui_widgets.statusbar), id);
159 		gtk_statusbar_push(GTK_STATUSBAR(ui_widgets.statusbar), id, text);
160 	}
161 }
162 
163 
164 /** Displays text on the statusbar.
165  * @param log Whether the message should be recorded in the Status window.
166  * @param format A @c printf -style string. */
167 GEANY_API_SYMBOL
ui_set_statusbar(gboolean log,const gchar * format,...)168 void ui_set_statusbar(gboolean log, const gchar *format, ...)
169 {
170 	gchar *string;
171 	va_list args;
172 
173 	va_start(args, format);
174 	string = g_strdup_vprintf(format, args);
175 	va_end(args);
176 
177 	if (! prefs.suppress_status_messages)
178 		set_statusbar(string, FALSE);
179 
180 	if (log || prefs.suppress_status_messages)
181 		msgwin_status_add("%s", string);
182 
183 	g_free(string);
184 }
185 
186 
187 /* note: some comments below are for translators */
create_statusbar_statistics(GeanyDocument * doc,guint line,guint vcol,guint pos)188 static gchar *create_statusbar_statistics(GeanyDocument *doc,
189 	guint line, guint vcol, guint pos)
190 {
191 	const gchar *cur_tag;
192 	const gchar *fmt;
193 	const gchar *expos;	/* % expansion position */
194 	const gchar sp[] = "      ";
195 	GString *stats_str;
196 	ScintillaObject *sci = doc->editor->sci;
197 
198 	if (!EMPTY(ui_prefs.statusbar_template))
199 		fmt = ui_prefs.statusbar_template;
200 	else
201 		fmt = _(DEFAULT_STATUSBAR_TEMPLATE);
202 
203 	stats_str = g_string_sized_new(120);
204 
205 	while ((expos = strchr(fmt, '%')) != NULL)
206 	{
207 		/* append leading text before % char */
208 		g_string_append_len(stats_str, fmt, expos - fmt);
209 
210 		switch (*++expos)
211 		{
212 			case 'l':
213 				g_string_append_printf(stats_str, "%d", line + 1);
214 				break;
215 			case 'L':
216 				g_string_append_printf(stats_str, "%d",
217 					sci_get_line_count(doc->editor->sci));
218 				break;
219 			case 'c':
220 				g_string_append_printf(stats_str, "%d", vcol);
221 				break;
222 			case 'C':
223 				g_string_append_printf(stats_str, "%d", vcol + 1);
224 				break;
225 			case 'p':
226 				g_string_append_printf(stats_str, "%u", pos);
227 				break;
228 			case 's':
229 			{
230 				gint len = sci_get_selected_text_length(sci) - 1;
231 				/* check if whole lines are selected */
232 				if (!len || sci_get_col_from_position(sci,
233 						sci_get_selection_start(sci)) != 0 ||
234 					sci_get_col_from_position(sci,
235 						sci_get_selection_end(sci)) != 0)
236 					g_string_append_printf(stats_str, "%d", len);
237 				else /* L = lines */
238 					g_string_append_printf(stats_str, _("%dL"),
239 						sci_get_lines_selected(doc->editor->sci) - 1);
240 				break;
241 			}
242 			case 'n' :
243 				g_string_append_printf(stats_str, "%d",
244 					sci_get_selected_text_length(doc->editor->sci) - 1);
245 				break;
246 			case 'w':
247 				/* RO = read-only */
248 				g_string_append(stats_str, (doc->readonly) ? _("RO ") :
249 					/* OVR = overwrite/overtype, INS = insert */
250 					(sci_get_overtype(doc->editor->sci) ? _("OVR") : _("INS")));
251 				break;
252 			case 'r':
253 				if (doc->readonly)
254 				{
255 					g_string_append(stats_str, _("RO "));	/* RO = read-only */
256 					g_string_append(stats_str, sp + 1);
257 				}
258 				break;
259 			case 't':
260 			{
261 				switch (editor_get_indent_prefs(doc->editor)->type)
262 				{
263 					case GEANY_INDENT_TYPE_TABS:
264 						g_string_append(stats_str, _("TAB"));
265 						break;
266 					case GEANY_INDENT_TYPE_SPACES:	/* SP = space */
267 						g_string_append(stats_str, _("SP"));
268 						break;
269 					case GEANY_INDENT_TYPE_BOTH:	/* T/S = tabs and spaces */
270 						g_string_append(stats_str, _("T/S"));
271 						break;
272 				}
273 				break;
274 			}
275 			case 'm':
276 				if (doc->changed)
277 				{
278 					g_string_append(stats_str, _("MOD"));	/* MOD = modified */
279 					g_string_append(stats_str, sp);
280 				}
281 				break;
282 			case 'M':
283 				g_string_append(stats_str, utils_get_eol_short_name(sci_get_eol_mode(doc->editor->sci)));
284 				break;
285 			case 'e':
286 				g_string_append(stats_str,
287 					doc->encoding ? doc->encoding : _("unknown"));
288 				if (encodings_is_unicode_charset(doc->encoding) && (doc->has_bom))
289 				{
290 					g_string_append_c(stats_str, ' ');
291 					g_string_append(stats_str, _("(with BOM)"));	/* BOM = byte order mark */
292 				}
293 				break;
294 			case 'f':
295 				g_string_append(stats_str, filetypes_get_display_name(doc->file_type));
296 				break;
297 			case 'S':
298 				symbols_get_current_scope(doc, &cur_tag);
299 				g_string_append(stats_str, cur_tag);
300 				break;
301 			case 'Y':
302 				g_string_append_c(stats_str, ' ');
303 				g_string_append_printf(stats_str, "%d",
304 					sci_get_style_at(doc->editor->sci, pos));
305 				break;
306 			default:
307 				g_string_append_len(stats_str, expos, 1);
308 		}
309 
310 		/* skip past %c chars */
311 		if (*expos)
312 			fmt = expos + 1;
313 		else
314 			break;
315 	}
316 	/* add any remaining text */
317 	g_string_append(stats_str, fmt);
318 
319 	return g_string_free(stats_str, FALSE);
320 }
321 
322 
323 /* updates the status bar document statistics */
ui_update_statusbar(GeanyDocument * doc,gint pos)324 void ui_update_statusbar(GeanyDocument *doc, gint pos)
325 {
326 	g_return_if_fail(doc == NULL || doc->is_valid);
327 
328 	if (! interface_prefs.statusbar_visible)
329 		return; /* just do nothing if statusbar is not visible */
330 
331 	if (doc == NULL)
332 		doc = document_get_current();
333 
334 	if (doc != NULL)
335 	{
336 		guint line, vcol;
337 		gchar *stats_str;
338 
339 		if (pos == -1)
340 			pos = sci_get_current_position(doc->editor->sci);
341 		line = sci_get_line_from_position(doc->editor->sci, pos);
342 
343 		/* Add temporary fix for sci infinite loop in Document::GetColumn(int)
344 		 * when current pos is beyond document end (can occur when removing
345 		 * blocks of selected lines especially esp. brace sections near end of file). */
346 		if (pos <= sci_get_length(doc->editor->sci))
347 			vcol = sci_get_col_from_position(doc->editor->sci, pos);
348 		else
349 			vcol = 0;
350 		vcol += sci_get_cursor_virtual_space(doc->editor->sci);
351 
352 		stats_str = create_statusbar_statistics(doc, line, vcol, pos);
353 
354 		/* can be overridden by status messages */
355 		set_statusbar(stats_str, TRUE);
356 		g_free(stats_str);
357 	}
358 	else	/* no documents */
359 	{
360 		set_statusbar("", TRUE);	/* can be overridden by status messages */
361 	}
362 }
363 
364 
365 /* This sets the window title according to the current filename. */
ui_set_window_title(GeanyDocument * doc)366 void ui_set_window_title(GeanyDocument *doc)
367 {
368 	GString *str;
369 	GeanyProject *project = app->project;
370 
371 	g_return_if_fail(doc == NULL || doc->is_valid);
372 
373 	if (doc == NULL)
374 		doc = document_get_current();
375 
376 	str = g_string_new(NULL);
377 
378 	if (doc != NULL)
379 	{
380 		g_string_append(str, doc->changed ? "*" : "");
381 
382 		if (doc->file_name == NULL)
383 			g_string_append(str, DOC_FILENAME(doc));
384 		else
385 		{
386 			gchar *short_name = document_get_basename_for_display(doc, 30);
387 			gchar *dirname = g_path_get_dirname(DOC_FILENAME(doc));
388 
389 			g_string_append(str, short_name);
390 			g_string_append(str, " - ");
391 			g_string_append(str, dirname ? dirname : "");
392 			g_free(short_name);
393 			g_free(dirname);
394 		}
395 		g_string_append(str, " - ");
396 	}
397 	if (project)
398 	{
399 		g_string_append_c(str, '[');
400 		g_string_append(str, project->name);
401 		g_string_append(str, "] - ");
402 	}
403 	g_string_append(str, "Geany");
404 	if (cl_options.new_instance)
405 	{
406 		g_string_append(str, _(" (new instance)"));
407 	}
408 	gtk_window_set_title(GTK_WINDOW(main_widgets.window), str->str);
409 	g_string_free(str, TRUE);
410 }
411 
412 
ui_set_editor_font(const gchar * font_name)413 void ui_set_editor_font(const gchar *font_name)
414 {
415 	guint i;
416 
417 	g_return_if_fail(font_name != NULL);
418 
419 	/* do nothing if font has not changed */
420 	if (interface_prefs.editor_font != NULL)
421 		if (strcmp(font_name, interface_prefs.editor_font) == 0)
422 			return;
423 
424 	g_free(interface_prefs.editor_font);
425 	interface_prefs.editor_font = g_strdup(font_name);
426 
427 	/* We copy the current style, and update the font in all open tabs. */
428 	for (i = 0; i < documents_array->len; i++)
429 	{
430 		if (documents[i]->editor)
431 		{
432 			editor_set_font(documents[i]->editor, interface_prefs.editor_font);
433 		}
434 	}
435 
436 	ui_set_statusbar(TRUE, _("Font updated (%s)."), interface_prefs.editor_font);
437 }
438 
439 
ui_set_fullscreen(void)440 void ui_set_fullscreen(void)
441 {
442 	if (ui_prefs.fullscreen)
443 	{
444 		gtk_window_fullscreen(GTK_WINDOW(main_widgets.window));
445 	}
446 	else
447 	{
448 		gtk_window_unfullscreen(GTK_WINDOW(main_widgets.window));
449 	}
450 }
451 
452 
ui_update_popup_reundo_items(GeanyDocument * doc)453 void ui_update_popup_reundo_items(GeanyDocument *doc)
454 {
455 	gboolean enable_undo;
456 	gboolean enable_redo;
457 	guint i, len;
458 
459 	g_return_if_fail(doc == NULL || doc->is_valid);
460 
461 	if (doc == NULL)
462 	{
463 		enable_undo = FALSE;
464 		enable_redo = FALSE;
465 	}
466 	else
467 	{
468 		enable_undo = document_can_undo(doc);
469 		enable_redo = document_can_redo(doc);
470 	}
471 
472 	/* index 0 is the popup menu, 1 is the menubar, 2 is the toolbar */
473 	len = G_N_ELEMENTS(widgets.undo_items);
474 	for (i = 0; i < len; i++)
475 	{
476 		ui_widget_set_sensitive(widgets.undo_items[i], enable_undo);
477 	}
478 	len = G_N_ELEMENTS(widgets.redo_items);
479 	for (i = 0; i < len; i++)
480 	{
481 		ui_widget_set_sensitive(widgets.redo_items[i], enable_redo);
482 	}
483 }
484 
485 
ui_update_popup_copy_items(GeanyDocument * doc)486 void ui_update_popup_copy_items(GeanyDocument *doc)
487 {
488 	gboolean enable;
489 	guint i, len;
490 
491 	g_return_if_fail(doc == NULL || doc->is_valid);
492 
493 	if (doc == NULL)
494 		enable = FALSE;
495 	else
496 		enable = sci_has_selection(doc->editor->sci);
497 
498 	len = G_N_ELEMENTS(widgets.popup_copy_items);
499 	for (i = 0; i < len; i++)
500 		ui_widget_set_sensitive(widgets.popup_copy_items[i], enable);
501 }
502 
503 
ui_update_popup_goto_items(gboolean enable)504 void ui_update_popup_goto_items(gboolean enable)
505 {
506 	guint i, len;
507 	len = G_N_ELEMENTS(widgets.popup_goto_items);
508 	for (i = 0; i < len; i++)
509 		ui_widget_set_sensitive(widgets.popup_goto_items[i], enable);
510 }
511 
512 
ui_menu_copy_items_set_sensitive(gboolean sensitive)513 void ui_menu_copy_items_set_sensitive(gboolean sensitive)
514 {
515 	guint i, len;
516 
517 	len = G_N_ELEMENTS(widgets.menu_copy_items);
518 	for (i = 0; i < len; i++)
519 		ui_widget_set_sensitive(widgets.menu_copy_items[i], sensitive);
520 }
521 
522 
ui_update_menu_copy_items(GeanyDocument * doc)523 void ui_update_menu_copy_items(GeanyDocument *doc)
524 {
525 	gboolean enable = FALSE;
526 	GtkWidget *focusw = gtk_window_get_focus(GTK_WINDOW(main_widgets.window));
527 
528 	g_return_if_fail(doc == NULL || doc->is_valid);
529 
530 	if (IS_SCINTILLA(focusw))
531 		enable = (doc == NULL) ? FALSE : sci_has_selection(doc->editor->sci);
532 	else
533 	if (GTK_IS_EDITABLE(focusw))
534 		enable = gtk_editable_get_selection_bounds(GTK_EDITABLE(focusw), NULL, NULL);
535 	else
536 	if (GTK_IS_TEXT_VIEW(focusw))
537 	{
538 		GtkTextBuffer *buffer = gtk_text_view_get_buffer(
539 			GTK_TEXT_VIEW(focusw));
540 		enable = gtk_text_buffer_get_selection_bounds(buffer, NULL, NULL);
541 	}
542 
543 	ui_menu_copy_items_set_sensitive(enable);
544 }
545 
546 
ui_update_insert_include_item(GeanyDocument * doc,gint item)547 void ui_update_insert_include_item(GeanyDocument *doc, gint item)
548 {
549 	gboolean enable = FALSE;
550 
551 	g_return_if_fail(doc == NULL || doc->is_valid);
552 
553 	if (doc == NULL || doc->file_type == NULL)
554 		enable = FALSE;
555 	else if (doc->file_type->id == GEANY_FILETYPES_C ||  doc->file_type->id == GEANY_FILETYPES_CPP)
556 		enable = TRUE;
557 
558 	ui_widget_set_sensitive(widgets.menu_insert_include_items[item], enable);
559 }
560 
561 
ui_update_fold_items(void)562 void ui_update_fold_items(void)
563 {
564 	ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "menu_fold_all1"), editor_prefs.folding);
565 	ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "menu_unfold_all1"), editor_prefs.folding);
566 	ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "separator22"), editor_prefs.folding);
567 }
568 
569 
570 /* @include include name or NULL for empty with cursor ready for typing it */
insert_include(GeanyDocument * doc,gint pos,const gchar * include)571 static void insert_include(GeanyDocument *doc, gint pos, const gchar *include)
572 {
573 	gint pos_after = -1;
574 	gchar *text;
575 
576 	g_return_if_fail(doc != NULL);
577 	g_return_if_fail(pos == -1 || pos >= 0);
578 
579 	if (pos == -1)
580 		pos = sci_get_current_position(doc->editor->sci);
581 
582 	if (! include)
583 	{
584 		text = g_strdup("#include \"\"\n");
585 		pos_after = pos + 10;
586 	}
587 	else
588 	{
589 		text = g_strconcat("#include <", include, ">\n", NULL);
590 	}
591 
592 	sci_start_undo_action(doc->editor->sci);
593 	sci_insert_text(doc->editor->sci, pos, text);
594 	sci_end_undo_action(doc->editor->sci);
595 	g_free(text);
596 	if (pos_after >= 0)
597 		sci_goto_pos(doc->editor->sci, pos_after, FALSE);
598 }
599 
600 
on_popup_insert_include_activate(GtkMenuItem * menuitem,gpointer user_data)601 static void on_popup_insert_include_activate(GtkMenuItem *menuitem, gpointer user_data)
602 {
603 	insert_include(document_get_current(), editor_info.click_pos, user_data);
604 }
605 
606 
on_menu_insert_include_activate(GtkMenuItem * menuitem,gpointer user_data)607 static void on_menu_insert_include_activate(GtkMenuItem *menuitem, gpointer user_data)
608 {
609 	insert_include(document_get_current(), -1, user_data);
610 }
611 
612 
insert_include_items(GtkMenu * me,GtkMenu * mp,gchar ** includes,gchar * label)613 static void insert_include_items(GtkMenu *me, GtkMenu *mp, gchar **includes, gchar *label)
614 {
615 	guint i = 0;
616 	GtkWidget *edit_menu, *edit_menu_item;
617 	GtkWidget *popup_menu, *popup_menu_item;
618 
619 	edit_menu = gtk_menu_new();
620 	popup_menu = gtk_menu_new();
621 	edit_menu_item = gtk_menu_item_new_with_label(label);
622 	popup_menu_item = gtk_menu_item_new_with_label(label);
623 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(edit_menu_item), edit_menu);
624 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(popup_menu_item), popup_menu);
625 
626 	while (includes[i] != NULL)
627 	{
628 		GtkWidget *tmp_menu = gtk_menu_item_new_with_label(includes[i]);
629 		GtkWidget *tmp_popup = gtk_menu_item_new_with_label(includes[i]);
630 
631 		gtk_container_add(GTK_CONTAINER(edit_menu), tmp_menu);
632 		gtk_container_add(GTK_CONTAINER(popup_menu), tmp_popup);
633 		g_signal_connect(tmp_menu, "activate",
634 					G_CALLBACK(on_menu_insert_include_activate), (gpointer) includes[i]);
635 		g_signal_connect(tmp_popup, "activate",
636 					G_CALLBACK(on_popup_insert_include_activate), (gpointer) includes[i]);
637 		i++;
638 	}
639 	gtk_widget_show_all(edit_menu_item);
640 	gtk_widget_show_all(popup_menu_item);
641 	gtk_container_add(GTK_CONTAINER(me), edit_menu_item);
642 	gtk_container_add(GTK_CONTAINER(mp), popup_menu_item);
643 }
644 
645 
ui_create_insert_menu_items(void)646 void ui_create_insert_menu_items(void)
647 {
648 	GtkMenu *menu_edit = GTK_MENU(ui_lookup_widget(main_widgets.window, "insert_include2_menu"));
649 	GtkMenu *menu_popup = GTK_MENU(ui_lookup_widget(main_widgets.editor_menu, "insert_include1_menu"));
650 	GtkWidget *blank;
651 	const gchar *c_includes_stdlib[] = {
652 		"assert.h", "ctype.h", "errno.h", "float.h", "limits.h", "locale.h", "math.h", "setjmp.h",
653 		"signal.h", "stdarg.h", "stddef.h", "stdio.h", "stdlib.h", "string.h", "time.h", NULL
654 	};
655 	const gchar *c_includes_c99[] = {
656 		"complex.h", "fenv.h", "inttypes.h", "iso646.h", "stdbool.h", "stdint.h",
657 		"tgmath.h", "wchar.h", "wctype.h", NULL
658 	};
659 	const gchar *c_includes_cpp[] = {
660 		"cstdio", "cstring", "cctype", "cmath", "ctime", "cstdlib", "cstdarg", NULL
661 	};
662 	const gchar *c_includes_cppstdlib[] = {
663 		"iostream", "fstream", "iomanip", "sstream", "exception", "stdexcept",
664 		"memory", "locale", NULL
665 	};
666 	const gchar *c_includes_stl[] = {
667 		"bitset", "deque", "list", "map", "set", "queue", "stack", "vector", "algorithm",
668 		"iterator", "functional", "string", "complex", "valarray", NULL
669 	};
670 
671 	blank = gtk_menu_item_new_with_label("#include \"...\"");
672 	gtk_container_add(GTK_CONTAINER(menu_edit), blank);
673 	gtk_widget_show(blank);
674 	g_signal_connect(blank, "activate", G_CALLBACK(on_menu_insert_include_activate), NULL);
675 	blank = gtk_separator_menu_item_new ();
676 	gtk_container_add(GTK_CONTAINER(menu_edit), blank);
677 	gtk_widget_show(blank);
678 
679 	blank = gtk_menu_item_new_with_label("#include \"...\"");
680 	gtk_container_add(GTK_CONTAINER(menu_popup), blank);
681 	gtk_widget_show(blank);
682 	g_signal_connect(blank, "activate", G_CALLBACK(on_popup_insert_include_activate), NULL);
683 	blank = gtk_separator_menu_item_new();
684 	gtk_container_add(GTK_CONTAINER(menu_popup), blank);
685 	gtk_widget_show(blank);
686 
687 	insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_stdlib, _("C Standard Library"));
688 	insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_c99, _("ISO C99"));
689 	insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_cpp, _("C++ (C Standard Library)"));
690 	insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_cppstdlib, _("C++ Standard Library"));
691 	insert_include_items(menu_edit, menu_popup, (gchar**) c_includes_stl, _("C++ STL"));
692 }
693 
694 
insert_date(GeanyDocument * doc,gint pos,const gchar * date_style)695 static void insert_date(GeanyDocument *doc, gint pos, const gchar *date_style)
696 {
697 	const gchar *format = NULL;
698 	gchar *time_str;
699 
700 	g_return_if_fail(doc != NULL);
701 	g_return_if_fail(pos == -1 || pos >= 0);
702 
703 	if (pos == -1)
704 		pos = sci_get_current_position(doc->editor->sci);
705 
706 	/* set default value */
707 	if (utils_str_equal("", ui_prefs.custom_date_format))
708 	{
709 		g_free(ui_prefs.custom_date_format);
710 		ui_prefs.custom_date_format = g_strdup("%d.%m.%Y");
711 	}
712 
713 	if (utils_str_equal(_("dd.mm.yyyy"), date_style))
714 		format = "%d.%m.%Y";
715 	else if (utils_str_equal(_("mm.dd.yyyy"), date_style))
716 		format = "%m.%d.%Y";
717 	else if (utils_str_equal(_("yyyy/mm/dd"), date_style))
718 		format = "%Y/%m/%d";
719 	else if (utils_str_equal(_("dd.mm.yyyy hh:mm:ss"), date_style))
720 		format = "%d.%m.%Y %H:%M:%S";
721 	else if (utils_str_equal(_("mm.dd.yyyy hh:mm:ss"), date_style))
722 		format = "%m.%d.%Y %H:%M:%S";
723 	else if (utils_str_equal(_("yyyy/mm/dd hh:mm:ss"), date_style))
724 		format = "%Y/%m/%d %H:%M:%S";
725 	else if (utils_str_equal(_("_Use Custom Date Format"), date_style))
726 		format = ui_prefs.custom_date_format;
727 	else
728 	{
729 		gchar *str = dialogs_show_input(_("Custom Date Format"), GTK_WINDOW(main_widgets.window),
730 				_("Enter here a custom date and time format. "
731 				"You can use any conversion specifiers which can be used with the ANSI C strftime function."),
732 				ui_prefs.custom_date_format);
733 		if (str)
734 			SETPTR(ui_prefs.custom_date_format, str);
735 		return;
736 	}
737 
738 	time_str = utils_get_date_time(format, NULL);
739 	if (time_str != NULL)
740 	{
741 		sci_start_undo_action(doc->editor->sci);
742 		sci_insert_text(doc->editor->sci, pos, time_str);
743 		sci_goto_pos(doc->editor->sci, pos + strlen(time_str), FALSE);
744 		sci_end_undo_action(doc->editor->sci);
745 		g_free(time_str);
746 	}
747 	else
748 	{
749 		utils_beep();
750 		ui_set_statusbar(TRUE,
751 				_("Date format string could not be converted (possibly too long)."));
752 	}
753 }
754 
755 
on_popup_insert_date_activate(GtkMenuItem * menuitem,gpointer user_data)756 static void on_popup_insert_date_activate(GtkMenuItem *menuitem, gpointer user_data)
757 {
758 	insert_date(document_get_current(), editor_info.click_pos, user_data);
759 }
760 
761 
on_menu_insert_date_activate(GtkMenuItem * menuitem,gpointer user_data)762 static void on_menu_insert_date_activate(GtkMenuItem *menuitem, gpointer user_data)
763 {
764 	insert_date(document_get_current(), -1, user_data);
765 }
766 
767 
insert_date_items(GtkMenu * me,GtkMenu * mp,gchar * label)768 static void insert_date_items(GtkMenu *me, GtkMenu *mp, gchar *label)
769 {
770 	GtkWidget *item;
771 
772 	item = gtk_menu_item_new_with_mnemonic(label);
773 	gtk_container_add(GTK_CONTAINER(me), item);
774 	gtk_widget_show(item);
775 	g_signal_connect(item, "activate", G_CALLBACK(on_menu_insert_date_activate), label);
776 
777 	item = gtk_menu_item_new_with_mnemonic(label);
778 	gtk_container_add(GTK_CONTAINER(mp), item);
779 	gtk_widget_show(item);
780 	g_signal_connect(item, "activate", G_CALLBACK(on_popup_insert_date_activate), label);
781 }
782 
783 
ui_create_insert_date_menu_items(void)784 void ui_create_insert_date_menu_items(void)
785 {
786 	GtkMenu *menu_edit = GTK_MENU(ui_lookup_widget(main_widgets.window, "insert_date1_menu"));
787 	GtkMenu *menu_popup = GTK_MENU(ui_lookup_widget(main_widgets.editor_menu, "insert_date2_menu"));
788 	GtkWidget *item;
789 	gchar *str;
790 
791 	insert_date_items(menu_edit, menu_popup, _("dd.mm.yyyy"));
792 	insert_date_items(menu_edit, menu_popup, _("mm.dd.yyyy"));
793 	insert_date_items(menu_edit, menu_popup, _("yyyy/mm/dd"));
794 
795 	item = gtk_separator_menu_item_new();
796 	gtk_container_add(GTK_CONTAINER(menu_edit), item);
797 	gtk_widget_show(item);
798 	item = gtk_separator_menu_item_new();
799 	gtk_container_add(GTK_CONTAINER(menu_popup), item);
800 	gtk_widget_show(item);
801 
802 	insert_date_items(menu_edit, menu_popup, _("dd.mm.yyyy hh:mm:ss"));
803 	insert_date_items(menu_edit, menu_popup, _("mm.dd.yyyy hh:mm:ss"));
804 	insert_date_items(menu_edit, menu_popup, _("yyyy/mm/dd hh:mm:ss"));
805 
806 	item = gtk_separator_menu_item_new();
807 	gtk_container_add(GTK_CONTAINER(menu_edit), item);
808 	gtk_widget_show(item);
809 	item = gtk_separator_menu_item_new();
810 	gtk_container_add(GTK_CONTAINER(menu_popup), item);
811 	gtk_widget_show(item);
812 
813 	str = _("_Use Custom Date Format");
814 	item = gtk_menu_item_new_with_mnemonic(str);
815 	gtk_container_add(GTK_CONTAINER(menu_edit), item);
816 	gtk_widget_show(item);
817 	g_signal_connect(item, "activate", G_CALLBACK(on_menu_insert_date_activate), str);
818 	ui_hookup_widget(main_widgets.window, item, "insert_date_custom1");
819 
820 	item = gtk_menu_item_new_with_mnemonic(str);
821 	gtk_container_add(GTK_CONTAINER(menu_popup), item);
822 	gtk_widget_show(item);
823 	g_signal_connect(item, "activate", G_CALLBACK(on_popup_insert_date_activate), str);
824 	ui_hookup_widget(main_widgets.editor_menu, item, "insert_date_custom2");
825 
826 	insert_date_items(menu_edit, menu_popup, _("_Set Custom Date Format"));
827 }
828 
829 
ui_save_buttons_toggle(gboolean enable)830 void ui_save_buttons_toggle(gboolean enable)
831 {
832 	guint i;
833 	gboolean dirty_tabs = FALSE;
834 
835 	if (ui_prefs.allow_always_save)
836 		enable = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)) > 0;
837 
838 	ui_widget_set_sensitive(widgets.save_buttons[0], enable);
839 	ui_widget_set_sensitive(widgets.save_buttons[1], enable);
840 
841 	/* save all menu item and tool button */
842 	for (i = 0; i < documents_array->len; i++)
843 	{
844 		/* check whether there are files where changes were made and if there are some,
845 		 * we need the save all button / item */
846 		if (documents[i]->is_valid && documents[i]->changed)
847 		{
848 			dirty_tabs = TRUE;
849 			break;
850 		}
851 	}
852 
853 	ui_widget_set_sensitive(widgets.save_buttons[2], dirty_tabs);
854 	ui_widget_set_sensitive(widgets.save_buttons[3], dirty_tabs);
855 }
856 
857 
858 #define add_doc_widget(widget_name) \
859 	g_ptr_array_add(widgets.document_buttons, ui_lookup_widget(main_widgets.window, widget_name))
860 
861 #define add_doc_toolitem(widget_name) \
862 	g_ptr_array_add(widgets.document_buttons, toolbar_get_action_by_name(widget_name))
863 
init_document_widgets(void)864 static void init_document_widgets(void)
865 {
866 	widgets.document_buttons = g_ptr_array_new();
867 
868 	/* Cache the document-sensitive widgets so we don't have to keep looking them up
869 	 * when using ui_document_buttons_update(). */
870 	add_doc_widget("menu_close1");
871 	add_doc_widget("close_other_documents1");
872 	add_doc_widget("menu_change_font1");
873 	add_doc_widget("menu_close_all1");
874 	add_doc_widget("menu_save1");
875 	add_doc_widget("menu_save_all1");
876 	add_doc_widget("menu_save_as1");
877 	add_doc_widget("menu_count_words1");
878 	add_doc_widget("menu_build1");
879 	add_doc_widget("add_comments1");
880 	add_doc_widget("menu_paste1");
881 	add_doc_widget("menu_undo2");
882 	add_doc_widget("properties1");
883 	add_doc_widget("menu_reload1");
884 	add_doc_widget("menu_document1");
885 	add_doc_widget("menu_choose_color1");
886 	add_doc_widget("menu_color_schemes");
887 	add_doc_widget("menu_markers_margin1");
888 	add_doc_widget("menu_linenumber_margin1");
889 	add_doc_widget("menu_show_white_space1");
890 	add_doc_widget("menu_show_line_endings1");
891 	add_doc_widget("menu_show_indentation_guides1");
892 	add_doc_widget("menu_zoom_in1");
893 	add_doc_widget("menu_zoom_out1");
894 	add_doc_widget("normal_size1");
895 	add_doc_widget("treeview6");
896 	add_doc_widget("print1");
897 	add_doc_widget("menu_reload_as1");
898 	add_doc_widget("menu_select_all1");
899 	add_doc_widget("insert_date1");
900 	add_doc_widget("insert_alternative_white_space1");
901 	add_doc_widget("menu_format1");
902 	add_doc_widget("commands2");
903 	add_doc_widget("menu_open_selected_file1");
904 	add_doc_widget("page_setup1");
905 	add_doc_widget("find1");
906 	add_doc_widget("find_next1");
907 	add_doc_widget("find_previous1");
908 	add_doc_widget("go_to_next_marker1");
909 	add_doc_widget("go_to_previous_marker1");
910 	add_doc_widget("replace1");
911 	add_doc_widget("find_nextsel1");
912 	add_doc_widget("find_prevsel1");
913 	add_doc_widget("find_usage1");
914 	add_doc_widget("find_document_usage1");
915 	add_doc_widget("mark_all1");
916 	add_doc_widget("go_to_line1");
917 	add_doc_widget("goto_tag_definition1");
918 	add_doc_widget("goto_tag_declaration1");
919 	add_doc_widget("reset_indentation1");
920 	add_doc_toolitem("Close");
921 	add_doc_toolitem("CloseAll");
922 	add_doc_toolitem("Search");
923 	add_doc_toolitem("SearchEntry");
924 	add_doc_toolitem("ZoomIn");
925 	add_doc_toolitem("ZoomOut");
926 	add_doc_toolitem("Indent");
927 	add_doc_toolitem("UnIndent");
928 	add_doc_toolitem("Cut");
929 	add_doc_toolitem("Copy");
930 	add_doc_toolitem("Paste");
931 	add_doc_toolitem("Delete");
932 	add_doc_toolitem("Save");
933 	add_doc_toolitem("SaveAs");
934 	add_doc_toolitem("SaveAll");
935 	add_doc_toolitem("Compile");
936 	add_doc_toolitem("Run");
937 	add_doc_toolitem("Reload");
938 	add_doc_toolitem("Color");
939 	add_doc_toolitem("Goto");
940 	add_doc_toolitem("GotoEntry");
941 	add_doc_toolitem("Replace");
942 	add_doc_toolitem("Print");
943 }
944 
945 
ui_document_buttons_update(void)946 void ui_document_buttons_update(void)
947 {
948 	guint i;
949 	gboolean enable = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)) > 0;
950 
951 	for (i = 0; i < widgets.document_buttons->len; i++)
952 	{
953 		GtkWidget *widget = g_ptr_array_index(widgets.document_buttons, i);
954 		if (GTK_IS_ACTION(widget))
955 			gtk_action_set_sensitive(GTK_ACTION(widget), enable);
956 		else
957 			ui_widget_set_sensitive(widget, enable);
958 	}
959 }
960 
961 
on_doc_sensitive_widget_destroy(GtkWidget * widget,G_GNUC_UNUSED gpointer user_data)962 static void on_doc_sensitive_widget_destroy(GtkWidget *widget, G_GNUC_UNUSED gpointer user_data)
963 {
964 	g_ptr_array_remove_fast(widgets.document_buttons, widget);
965 }
966 
967 
968 /** Adds a widget to the list of widgets that should be set sensitive/insensitive
969  * when some documents are present/no documents are open.
970  * It will be removed when the widget is destroyed.
971  * @param widget The widget to add.
972  *
973  * @since 0.15
974  **/
975 GEANY_API_SYMBOL
ui_add_document_sensitive(GtkWidget * widget)976 void ui_add_document_sensitive(GtkWidget *widget)
977 {
978 	gboolean enable = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)) > 0;
979 
980 	ui_widget_set_sensitive(widget, enable);
981 
982 	g_ptr_array_add(widgets.document_buttons, widget);
983 	g_signal_connect(widget, "destroy", G_CALLBACK(on_doc_sensitive_widget_destroy), NULL);
984 }
985 
986 
ui_widget_show_hide(GtkWidget * widget,gboolean show)987 void ui_widget_show_hide(GtkWidget *widget, gboolean show)
988 {
989 	if (show)
990 	{
991 		gtk_widget_show(widget);
992 	}
993 	else
994 	{
995 		gtk_widget_hide(widget);
996 	}
997 }
998 
999 
ui_sidebar_show_hide(void)1000 void ui_sidebar_show_hide(void)
1001 {
1002 	GtkWidget *widget;
1003 
1004 	/* check that there are no other notebook pages before hiding the sidebar completely
1005 	 * other pages could be e.g. the file browser plugin */
1006 	if (! interface_prefs.sidebar_openfiles_visible && ! interface_prefs.sidebar_symbol_visible &&
1007 		gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.sidebar_notebook)) <= 2)
1008 	{
1009 		ui_prefs.sidebar_visible = FALSE;
1010 	}
1011 
1012 	widget = ui_lookup_widget(main_widgets.window, "menu_show_sidebar1");
1013 	if (ui_prefs.sidebar_visible != gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
1014 	{
1015 		ignore_callback = TRUE;
1016 		gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), ui_prefs.sidebar_visible);
1017 		ignore_callback = FALSE;
1018 	}
1019 
1020 	ui_widget_show_hide(main_widgets.sidebar_notebook, ui_prefs.sidebar_visible);
1021 
1022 	ui_widget_show_hide(gtk_notebook_get_nth_page(
1023 		GTK_NOTEBOOK(main_widgets.sidebar_notebook), 0), interface_prefs.sidebar_symbol_visible);
1024 	ui_widget_show_hide(gtk_notebook_get_nth_page(
1025 		GTK_NOTEBOOK(main_widgets.sidebar_notebook), 1), interface_prefs.sidebar_openfiles_visible);
1026 }
1027 
1028 
ui_document_show_hide(GeanyDocument * doc)1029 void ui_document_show_hide(GeanyDocument *doc)
1030 {
1031 	const gchar *widget_name;
1032 	GtkWidget *item;
1033 	const GeanyIndentPrefs *iprefs;
1034 
1035 	g_return_if_fail(doc == NULL || doc->is_valid);
1036 
1037 	if (doc == NULL)
1038 		doc = document_get_current();
1039 
1040 	if (doc == NULL)
1041 		return;
1042 
1043 	ignore_callback = TRUE;
1044 
1045 	gtk_check_menu_item_set_active(
1046 			GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_line_wrapping1")),
1047 			doc->editor->line_wrapping);
1048 
1049 	gtk_check_menu_item_set_active(
1050 			GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "line_breaking1")),
1051 			doc->editor->line_breaking);
1052 
1053 	iprefs = editor_get_indent_prefs(doc->editor);
1054 
1055 	item = ui_lookup_widget(main_widgets.window, "menu_use_auto_indentation1");
1056 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), doc->editor->auto_indent);
1057 
1058 	switch (iprefs->type)
1059 	{
1060 		case GEANY_INDENT_TYPE_SPACES:
1061 			widget_name = "spaces1"; break;
1062 		case GEANY_INDENT_TYPE_TABS:
1063 			widget_name = "tabs1"; break;
1064 		case GEANY_INDENT_TYPE_BOTH:
1065 		default:
1066 			widget_name = "tabs_and_spaces1"; break;
1067 	}
1068 	item = ui_lookup_widget(main_widgets.window, widget_name);
1069 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1070 
1071 	if (iprefs->width >= 1 && iprefs->width <= 8)
1072 	{
1073 		gchar *name;
1074 
1075 		name = g_strdup_printf("indent_width_%d", iprefs->width);
1076 		item = ui_lookup_widget(main_widgets.window, name);
1077 		gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1078 		g_free(name);
1079 	}
1080 
1081 	gtk_check_menu_item_set_active(
1082 			GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "set_file_readonly1")),
1083 			doc->readonly);
1084 
1085 	item = ui_lookup_widget(main_widgets.window, "menu_write_unicode_bom1");
1086 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), doc->has_bom);
1087 	ui_widget_set_sensitive(item, encodings_is_unicode_charset(doc->encoding));
1088 
1089 	switch (sci_get_eol_mode(doc->editor->sci))
1090 	{
1091 		case SC_EOL_CR: widget_name = "cr"; break;
1092 		case SC_EOL_LF: widget_name = "lf"; break;
1093 		default: widget_name = "crlf"; break;
1094 	}
1095 	gtk_check_menu_item_set_active(
1096 		GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, widget_name)), TRUE);
1097 
1098 	encodings_select_radio_item(doc->encoding);
1099 	filetypes_select_radio_item(doc->file_type);
1100 
1101 	ignore_callback = FALSE;
1102 }
1103 
1104 
ui_set_search_entry_background(GtkWidget * widget,gboolean success)1105 void ui_set_search_entry_background(GtkWidget *widget, gboolean success)
1106 {
1107 	gtk_widget_set_name(widget, success ? NULL : "geany-search-entry-no-match");
1108 }
1109 
1110 
recent_create_menu(GeanyRecentFiles * grf)1111 static void recent_create_menu(GeanyRecentFiles *grf)
1112 {
1113 	guint i, len;
1114 
1115 	len = MIN(file_prefs.mru_length, g_queue_get_length(grf->recent_queue));
1116 	for (i = 0; i < len; i++)
1117 	{
1118 		/* create menu item for the recent files menu in the menu bar */
1119 		const gchar *filename = g_queue_peek_nth(grf->recent_queue, i);
1120 		GtkWidget *tmp = gtk_menu_item_new_with_label(filename);
1121 
1122 		gtk_widget_show(tmp);
1123 		gtk_container_add(GTK_CONTAINER(grf->menubar), tmp);
1124 		g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
1125 		/* create menu item for the recent files menu in the toolbar */
1126 		if (grf->toolbar != NULL)
1127 		{
1128 			tmp = gtk_menu_item_new_with_label(filename);
1129 			gtk_widget_show(tmp);
1130 			gtk_container_add(GTK_CONTAINER(grf->toolbar), tmp);
1131 			g_signal_connect(tmp, "activate", G_CALLBACK(grf->activate_cb), NULL);
1132 		}
1133 	}
1134 }
1135 
1136 
recent_get_recent_files(void)1137 static GeanyRecentFiles *recent_get_recent_files(void)
1138 {
1139 	static GeanyRecentFiles grf = { RECENT_FILE_FILE, NULL, NULL, NULL, NULL };
1140 
1141 	if (G_UNLIKELY(grf.recent_queue == NULL))
1142 	{
1143 		grf.recent_queue = ui_prefs.recent_queue;
1144 		grf.menubar = ui_widgets.recent_files_menu_menubar;
1145 		grf.toolbar = geany_menu_button_action_get_menu(GEANY_MENU_BUTTON_ACTION(
1146 						toolbar_get_action_by_name("Open")));
1147 		grf.activate_cb = recent_file_activate_cb;
1148 	}
1149 	return &grf;
1150 }
1151 
1152 
recent_get_recent_projects(void)1153 static GeanyRecentFiles *recent_get_recent_projects(void)
1154 {
1155 	static GeanyRecentFiles grf = { RECENT_FILE_PROJECT, NULL, NULL, NULL, NULL };
1156 
1157 	if (G_UNLIKELY(grf.recent_queue == NULL))
1158 	{
1159 		grf.recent_queue = ui_prefs.recent_projects_queue;
1160 		grf.menubar = ui_widgets.recent_projects_menu_menubar;
1161 		grf.toolbar = NULL;
1162 		grf.activate_cb = recent_project_activate_cb;
1163 	}
1164 	return &grf;
1165 }
1166 
1167 
ui_create_recent_menus(void)1168 void ui_create_recent_menus(void)
1169 {
1170 	recent_create_menu(recent_get_recent_files());
1171 	recent_create_menu(recent_get_recent_projects());
1172 }
1173 
1174 
recent_file_activate_cb(GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)1175 static void recent_file_activate_cb(GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
1176 {
1177 	gchar *utf8_filename = ui_menu_item_get_text(menuitem);
1178 	gchar *locale_filename = utils_get_locale_from_utf8(utf8_filename);
1179 
1180 	if (document_open_file(locale_filename, FALSE, NULL, NULL) != NULL)
1181 		recent_file_loaded(utf8_filename, recent_get_recent_files());
1182 
1183 	g_free(locale_filename);
1184 	g_free(utf8_filename);
1185 }
1186 
1187 
recent_project_activate_cb(GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)1188 static void recent_project_activate_cb(GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
1189 {
1190 	gchar *utf8_filename = ui_menu_item_get_text(menuitem);
1191 	gchar *locale_filename = utils_get_locale_from_utf8(utf8_filename);
1192 
1193 	if (app->project && !project_close(FALSE)) {}
1194 	else if (project_load_file_with_session(locale_filename))
1195 		recent_file_loaded(utf8_filename, recent_get_recent_projects());
1196 
1197 	g_free(locale_filename);
1198 	g_free(utf8_filename);
1199 }
1200 
1201 
add_recent_file(const gchar * utf8_filename,GeanyRecentFiles * grf,const GtkRecentData * rdata)1202 static void add_recent_file(const gchar *utf8_filename, GeanyRecentFiles *grf,
1203 		const GtkRecentData *rdata)
1204 {
1205 	if (g_queue_find_custom(grf->recent_queue, utf8_filename, (GCompareFunc) strcmp) == NULL)
1206 	{
1207 
1208 		if (grf->type == RECENT_FILE_FILE && rdata)
1209 		{
1210 			GtkRecentManager *manager = gtk_recent_manager_get_default();
1211 			gchar *uri = g_filename_to_uri(utf8_filename, NULL, NULL);
1212 			if (uri != NULL)
1213 			{
1214 				gtk_recent_manager_add_full(manager, uri, rdata);
1215 				g_free(uri);
1216 			}
1217 		}
1218 
1219 		g_queue_push_head(grf->recent_queue, g_strdup(utf8_filename));
1220 		if (g_queue_get_length(grf->recent_queue) > file_prefs.mru_length)
1221 		{
1222 			g_free(g_queue_pop_tail(grf->recent_queue));
1223 		}
1224 		update_recent_menu(grf);
1225 	}
1226 	/* filename already in recent list */
1227 	else
1228 		recent_file_loaded(utf8_filename, grf);
1229 }
1230 
1231 
ui_add_recent_document(GeanyDocument * doc)1232 void ui_add_recent_document(GeanyDocument *doc)
1233 {
1234 	/* what are the groups for actually? */
1235 	static const gchar *groups[2] = {
1236 		"geany",
1237 		NULL
1238 	};
1239 	GtkRecentData rdata;
1240 
1241 	/* Prepare the data for gtk_recent_manager_add_full() */
1242 	rdata.display_name = NULL;
1243 	rdata.description = NULL;
1244 	rdata.mime_type = doc->file_type->mime_type;
1245 	/* if we ain't got no mime-type, fallback to plain text */
1246 	if (! rdata.mime_type)
1247 		rdata.mime_type = (gchar *) "text/plain";
1248 	rdata.app_name = (gchar *) "geany";
1249 	rdata.app_exec = (gchar *) "geany %u";
1250 	rdata.groups = (gchar **) groups;
1251 	rdata.is_private = FALSE;
1252 
1253 	add_recent_file(doc->file_name, recent_get_recent_files(), &rdata);
1254 }
1255 
1256 
ui_add_recent_project_file(const gchar * utf8_filename)1257 void ui_add_recent_project_file(const gchar *utf8_filename)
1258 {
1259 	add_recent_file(utf8_filename, recent_get_recent_projects(), NULL);
1260 }
1261 
1262 
1263 /* Returns: newly allocated string with the UTF-8 menu text. */
ui_menu_item_get_text(GtkMenuItem * menu_item)1264 gchar *ui_menu_item_get_text(GtkMenuItem *menu_item)
1265 {
1266 	const gchar *text = NULL;
1267 
1268 	if (gtk_bin_get_child(GTK_BIN(menu_item)))
1269 	{
1270 		GtkWidget *child = gtk_bin_get_child(GTK_BIN(menu_item));
1271 
1272 		if (GTK_IS_LABEL(child))
1273 			text = gtk_label_get_text(GTK_LABEL(child));
1274 	}
1275 	/* GTK owns text so it's much safer to return a copy of it in case the memory is reallocated */
1276 	return g_strdup(text);
1277 }
1278 
1279 
find_recent_file_item(gconstpointer list_data,gconstpointer user_data)1280 static gint find_recent_file_item(gconstpointer list_data, gconstpointer user_data)
1281 {
1282 	gchar *menu_text = ui_menu_item_get_text(GTK_MENU_ITEM(list_data));
1283 	gint result;
1284 
1285 	if (utils_str_equal(menu_text, user_data))
1286 		result = 0;
1287 	else
1288 		result = 1;
1289 
1290 	g_free(menu_text);
1291 	return result;
1292 }
1293 
1294 
1295 /* update the project menu item's sensitivity */
ui_update_recent_project_menu(void)1296 void ui_update_recent_project_menu(void)
1297 {
1298 	GeanyRecentFiles *grf = recent_get_recent_projects();
1299 	GList *children, *item;
1300 
1301 	/* only need to update the menubar menu, the project doesn't have a toolbar item */
1302 	children = gtk_container_get_children(GTK_CONTAINER(grf->menubar));
1303 	for (item = children; item; item = item->next)
1304 	{
1305 		gboolean sensitive = TRUE;
1306 
1307 		if (app->project)
1308 		{
1309 			const gchar *filename = gtk_menu_item_get_label(item->data);
1310 			sensitive = g_strcmp0(app->project->file_name, filename) != 0;
1311 		}
1312 		gtk_widget_set_sensitive(item->data, sensitive);
1313 	}
1314 	g_list_free(children);
1315 }
1316 
1317 
1318 /* Use instead of gtk_menu_reorder_child() to update the menubar properly on OS X */
menu_reorder_child(GtkMenu * menu,GtkWidget * child,gint position)1319 static void menu_reorder_child(GtkMenu *menu, GtkWidget *child, gint position)
1320 {
1321 	gtk_menu_reorder_child(menu, child, position);
1322 #ifdef MAC_INTEGRATION
1323 	/* On OS X GtkMenuBar is kept in sync with the native OS X menubar using
1324 	 * signals. Unfortunately gtk_menu_reorder_child() doesn't emit anything
1325 	 * so we have to update the OS X menubar manually. */
1326 	gtkosx_application_sync_menubar(gtkosx_application_get());
1327 #endif
1328 }
1329 
1330 
add_recent_file_menu_item(const gchar * utf8_filename,GeanyRecentFiles * grf,GtkWidget * menu)1331 static void add_recent_file_menu_item(const gchar *utf8_filename, GeanyRecentFiles *grf, GtkWidget *menu)
1332 {
1333 	GtkWidget *child = gtk_menu_item_new_with_label(utf8_filename);
1334 
1335 	gtk_widget_show(child);
1336 	if (menu != grf->toolbar)
1337 		gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), child);
1338 	else
1339 	{
1340 		/* this is a bit ugly, but we need to use gtk_container_add(). Using
1341 		 * gtk_menu_shell_prepend() doesn't emit GtkContainer's "add" signal
1342 		 * which we need in GeanyMenubuttonAction */
1343 		gtk_container_add(GTK_CONTAINER(menu), child);
1344 		menu_reorder_child(GTK_MENU(menu), child, 0);
1345 	}
1346 	g_signal_connect(child, "activate", G_CALLBACK(grf->activate_cb), NULL);
1347 }
1348 
1349 
recent_file_loaded(const gchar * utf8_filename,GeanyRecentFiles * grf)1350 static void recent_file_loaded(const gchar *utf8_filename, GeanyRecentFiles *grf)
1351 {
1352 	GList *item;
1353 	GtkWidget *parents[] = { grf->menubar, grf->toolbar };
1354 	guint i;
1355 
1356 	/* first reorder the queue */
1357 	item = g_queue_find_custom(grf->recent_queue, utf8_filename, (GCompareFunc) strcmp);
1358 	g_return_if_fail(item != NULL);
1359 
1360 	g_queue_unlink(grf->recent_queue, item);
1361 	g_queue_push_head_link(grf->recent_queue, item);
1362 
1363 	for (i = 0; i < G_N_ELEMENTS(parents); i++)
1364 	{
1365 		GList *children;
1366 
1367 		if (! parents[i])
1368 			continue;
1369 
1370 		children = gtk_container_get_children(GTK_CONTAINER(parents[i]));
1371 		item = g_list_find_custom(children, utf8_filename, (GCompareFunc) find_recent_file_item);
1372 		/* either reorder or prepend a new one */
1373 		if (item)
1374 			menu_reorder_child(GTK_MENU(parents[i]), item->data, 0);
1375 		else
1376 			add_recent_file_menu_item(utf8_filename, grf, parents[i]);
1377 		g_list_free(children);
1378 	}
1379 
1380 	if (grf->type == RECENT_FILE_PROJECT)
1381 		ui_update_recent_project_menu();
1382 }
1383 
1384 
update_recent_menu(GeanyRecentFiles * grf)1385 static void update_recent_menu(GeanyRecentFiles *grf)
1386 {
1387 	gchar *filename;
1388 	GtkWidget *parents[] = { grf->menubar, grf->toolbar };
1389 	guint i;
1390 
1391 	filename = g_queue_peek_head(grf->recent_queue);
1392 
1393 	for (i = 0; i < G_N_ELEMENTS(parents); i++)
1394 	{
1395 		GList *children;
1396 
1397 		if (! parents[i])
1398 			continue;
1399 
1400 		/* clean the MRU list before adding an item */
1401 		children = gtk_container_get_children(GTK_CONTAINER(parents[i]));
1402 		if (g_list_length(children) > file_prefs.mru_length - 1)
1403 		{
1404 			GList *item = g_list_nth(children, file_prefs.mru_length - 1);
1405 			while (item != NULL)
1406 			{
1407 				if (GTK_IS_MENU_ITEM(item->data))
1408 					gtk_widget_destroy(GTK_WIDGET(item->data));
1409 				item = g_list_next(item);
1410 			}
1411 		}
1412 		g_list_free(children);
1413 
1414 		/* create the new item */
1415 		add_recent_file_menu_item(filename, grf, parents[i]);
1416 	}
1417 
1418 	if (grf->type == RECENT_FILE_PROJECT)
1419 		ui_update_recent_project_menu();
1420 }
1421 
1422 
ui_toggle_editor_features(GeanyUIEditorFeatures feature)1423 void ui_toggle_editor_features(GeanyUIEditorFeatures feature)
1424 {
1425 	guint i;
1426 
1427 	foreach_document (i)
1428 	{
1429 		GeanyDocument *doc = documents[i];
1430 
1431 		switch (feature)
1432 		{
1433 			case GEANY_EDITOR_SHOW_MARKERS_MARGIN:
1434 				sci_set_symbol_margin(doc->editor->sci, editor_prefs.show_markers_margin);
1435 				break;
1436 			case GEANY_EDITOR_SHOW_LINE_NUMBERS:
1437 				sci_set_line_numbers(doc->editor->sci, editor_prefs.show_linenumber_margin);
1438 				break;
1439 			case GEANY_EDITOR_SHOW_WHITE_SPACE:
1440 				sci_set_visible_white_spaces(doc->editor->sci, editor_prefs.show_white_space);
1441 				break;
1442 			case GEANY_EDITOR_SHOW_LINE_ENDINGS:
1443 				sci_set_visible_eols(doc->editor->sci, editor_prefs.show_line_endings);
1444 				break;
1445 			case GEANY_EDITOR_SHOW_INDENTATION_GUIDES:
1446 				editor_set_indentation_guides(doc->editor);
1447 				break;
1448 		}
1449 	}
1450 }
1451 
1452 
ui_update_view_editor_menu_items(void)1453 void ui_update_view_editor_menu_items(void)
1454 {
1455 	ignore_callback = TRUE;
1456 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_markers_margin1")), editor_prefs.show_markers_margin);
1457 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_linenumber_margin1")), editor_prefs.show_linenumber_margin);
1458 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_show_white_space1")), editor_prefs.show_white_space);
1459 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_show_line_endings1")), editor_prefs.show_line_endings);
1460 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_show_indentation_guides1")), editor_prefs.show_indent_guide);
1461 	ignore_callback = FALSE;
1462 }
1463 
1464 
1465 /** Creates a GNOME HIG-style frame (with no border and indented child alignment).
1466  * @param label_text The label text.
1467  * @param alignment An address to store the alignment widget pointer.
1468  *
1469  * @return @transfer{floating} The frame widget, setting the alignment container for
1470  * packing child widgets.
1471  *
1472  * @deprecated 1.29: Use GTK API directly
1473  **/
1474 GEANY_API_SYMBOL
ui_frame_new_with_alignment(const gchar * label_text,GtkWidget ** alignment)1475 GtkWidget *ui_frame_new_with_alignment(const gchar *label_text, GtkWidget **alignment)
1476 {
1477 	GtkWidget *label, *align;
1478 	GtkWidget *frame = gtk_frame_new(NULL);
1479 
1480 	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
1481 
1482 	align = gtk_alignment_new(0.5, 0.5, 1, 1);
1483 	gtk_container_add(GTK_CONTAINER(frame), align);
1484 	gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 0);
1485 
1486 	label = ui_label_new_bold(label_text);
1487 	gtk_frame_set_label_widget(GTK_FRAME(frame), label);
1488 
1489 	*alignment = align;
1490 	return frame;
1491 }
1492 
1493 
1494 /** Makes a fixed border for dialogs without increasing the button box border.
1495  * @param dialog The parent container for the @c GtkVBox.
1496  *
1497  * @return @transfer{none} The packed @c GtkVBox. */
1498 GEANY_API_SYMBOL
ui_dialog_vbox_new(GtkDialog * dialog)1499 GtkWidget *ui_dialog_vbox_new(GtkDialog *dialog)
1500 {
1501 	GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);	/* need child vbox to set a separate border. */
1502 
1503 	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
1504 	gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), vbox, TRUE, TRUE, 0);
1505 	return vbox;
1506 }
1507 
1508 
1509 /* Reorders a dialog's buttons
1510  * @param dialog A dialog
1511  * @param response First response ID to reorder
1512  * @param ... more response IDs, terminated by -1
1513  *
1514  * Like gtk_dialog_set_alternative_button_order(), but reorders the default
1515  * buttons layout, not the alternative one.  This is useful if you e.g. added a
1516  * button to a dialog which already had some and need yours not to be on the
1517  * end.
1518  */
1519 /* Heavily based on gtk_dialog_set_alternative_button_order().
1520  * This relies on the action area to be a GtkBox, but although not documented
1521  * the API expose it to be a GtkHButtonBox though GtkBuilder, so it should be
1522  * fine */
ui_dialog_set_primary_button_order(GtkDialog * dialog,gint response,...)1523 void ui_dialog_set_primary_button_order(GtkDialog *dialog, gint response, ...)
1524 {
1525 	va_list ap;
1526 	GtkWidget *action_area = gtk_dialog_get_action_area(dialog);
1527 	gint position;
1528 
1529 	va_start(ap, response);
1530 	for (position = 0; response != -1; position++)
1531 	{
1532 		GtkWidget *child = gtk_dialog_get_widget_for_response(dialog, response);
1533 		if (child)
1534 			gtk_box_reorder_child(GTK_BOX(action_area), child, position);
1535 		else
1536 			g_warning("%s: no child button with response id %d.", G_STRFUNC, response);
1537 
1538 		response = va_arg(ap, gint);
1539 	}
1540 	va_end(ap);
1541 }
1542 
1543 
1544 /** Creates a @c GtkButton with custom text and a stock image similar to
1545  * @c gtk_button_new_from_stock().
1546  * @param stock_id A @c GTK_STOCK_NAME string.
1547  * @param text Button label text, can include mnemonics.
1548  *
1549  * @return @transfer{floating} The new @c GtkButton.
1550  */
1551 GEANY_API_SYMBOL
ui_button_new_with_image(const gchar * stock_id,const gchar * text)1552 GtkWidget *ui_button_new_with_image(const gchar *stock_id, const gchar *text)
1553 {
1554 	GtkWidget *image, *button;
1555 
1556 	button = gtk_button_new_with_mnemonic(text);
1557 	gtk_widget_show(button);
1558 	image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON);
1559 	gtk_button_set_image(GTK_BUTTON(button), image);
1560 	/* note: image is shown by gtk */
1561 	return button;
1562 }
1563 
1564 
1565 /** Creates a @c GtkImageMenuItem with a stock image and a custom label.
1566  * @param stock_id Stock image ID, e.g. @c GTK_STOCK_OPEN.
1567  * @param label Menu item label, can include mnemonics.
1568  * @return @transfer{floating} The new @c GtkImageMenuItem.
1569  *
1570  *  @since 0.16
1571  */
1572 GEANY_API_SYMBOL
1573 GtkWidget *
ui_image_menu_item_new(const gchar * stock_id,const gchar * label)1574 ui_image_menu_item_new(const gchar *stock_id, const gchar *label)
1575 {
1576 	GtkWidget *item = gtk_image_menu_item_new_with_mnemonic(label);
1577 	GtkWidget *image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_MENU);
1578 
1579 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
1580 	gtk_widget_show(image);
1581 	return item;
1582 }
1583 
1584 
entry_clear_icon_release_cb(GtkEntry * entry,gint icon_pos,GdkEvent * event,gpointer data)1585 static void entry_clear_icon_release_cb(GtkEntry *entry, gint icon_pos,
1586 										GdkEvent *event, gpointer data)
1587 {
1588 	if (event->button.button == 1 && icon_pos == 1)
1589 	{
1590 		gtk_entry_set_text(entry, "");
1591 		gtk_widget_grab_focus(GTK_WIDGET(entry));
1592 	}
1593 }
1594 
1595 
1596 /** Adds a small clear icon to the right end of the passed @a entry.
1597  *  A callback to clear the contents of the GtkEntry is automatically added.
1598  *
1599  * @param entry The GtkEntry object to which the icon should be attached.
1600  *
1601  *  @since 0.16
1602  */
1603 GEANY_API_SYMBOL
ui_entry_add_clear_icon(GtkEntry * entry)1604 void ui_entry_add_clear_icon(GtkEntry *entry)
1605 {
1606 	g_object_set(entry, "secondary-icon-stock", GTK_STOCK_CLEAR,
1607 		"secondary-icon-activatable", TRUE, NULL);
1608 	g_signal_connect(entry, "icon-release", G_CALLBACK(entry_clear_icon_release_cb), NULL);
1609 }
1610 
1611 
1612 /* Adds a :activate-backwards signal emitted by default when <Shift>Return is pressed */
ui_entry_add_activate_backward_signal(GtkEntry * entry)1613 void ui_entry_add_activate_backward_signal(GtkEntry *entry)
1614 {
1615 	static gboolean installed = FALSE;
1616 
1617 	g_return_if_fail(GTK_IS_ENTRY(entry));
1618 
1619 	if (G_UNLIKELY(! installed))
1620 	{
1621 		GtkBindingSet *binding_set;
1622 
1623 		installed = TRUE;
1624 
1625 		/* try to handle the unexpected case where GTK would already have installed the signal */
1626 		if (g_signal_lookup("activate-backward", G_TYPE_FROM_INSTANCE(entry)))
1627 		{
1628 			g_warning("Signal GtkEntry:activate-backward is unexpectedly already installed");
1629 			return;
1630 		}
1631 
1632 		g_signal_new("activate-backward", G_TYPE_FROM_INSTANCE(entry),
1633 			G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, 0, NULL, NULL,
1634 			g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
1635 		binding_set = gtk_binding_set_by_class(GTK_ENTRY_GET_CLASS(entry));
1636 		gtk_binding_entry_add_signal(binding_set, GDK_KEY_Return, GDK_SHIFT_MASK, "activate-backward", 0);
1637 	}
1638 }
1639 
1640 
add_to_size_group(GtkWidget * widget,gpointer size_group)1641 static void add_to_size_group(GtkWidget *widget, gpointer size_group)
1642 {
1643 	g_return_if_fail(GTK_IS_SIZE_GROUP(size_group));
1644 	gtk_size_group_add_widget(GTK_SIZE_GROUP(size_group), widget);
1645 }
1646 
1647 
1648 /* Copies the spacing and layout of the master GtkHButtonBox and synchronises
1649  * the width of each button box's children.
1650  * Should be called after all child widgets have been packed. */
ui_hbutton_box_copy_layout(GtkButtonBox * master,GtkButtonBox * copy)1651 void ui_hbutton_box_copy_layout(GtkButtonBox *master, GtkButtonBox *copy)
1652 {
1653 	GtkSizeGroup *size_group;
1654 
1655 	gtk_box_set_spacing(GTK_BOX(copy), 10);
1656 	gtk_button_box_set_layout(copy, gtk_button_box_get_layout(master));
1657 
1658 	/* now we need to put the widest widget from each button box in a size group,
1659 	* but we don't know the width before they are drawn, and for different label
1660 	* translations the widest widget can vary, so we just add all widgets. */
1661 	size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1662 	gtk_container_foreach(GTK_CONTAINER(master), add_to_size_group, size_group);
1663 	gtk_container_foreach(GTK_CONTAINER(copy), add_to_size_group, size_group);
1664 	g_object_unref(size_group);
1665 }
1666 
1667 
tree_model_find_text(GtkTreeModel * model,GtkTreeIter * iter,gint column,const gchar * text)1668 static gboolean tree_model_find_text(GtkTreeModel *model,
1669 		GtkTreeIter *iter, gint column, const gchar *text)
1670 {
1671 	gchar *combo_text;
1672 	gboolean found = FALSE;
1673 
1674 	if (gtk_tree_model_get_iter_first(model, iter))
1675 	{
1676 		do
1677 		{
1678 			gtk_tree_model_get(model, iter, 0, &combo_text, -1);
1679 			found = utils_str_equal(combo_text, text);
1680 			g_free(combo_text);
1681 
1682 			if (found)
1683 				return TRUE;
1684 		}
1685 		while (gtk_tree_model_iter_next(model, iter));
1686 	}
1687 	return FALSE;
1688 }
1689 
1690 
1691 /** Prepends @a text to the drop down list, removing a duplicate element in
1692  * the list if found. Also ensures there are <= @a history_len elements.
1693  * @param combo_entry .
1694  * @param text @nullable Text to add, or @c NULL for current entry text.
1695  * @param history_len Max number of items, or @c 0 for default. */
1696 GEANY_API_SYMBOL
ui_combo_box_add_to_history(GtkComboBoxText * combo_entry,const gchar * text,gint history_len)1697 void ui_combo_box_add_to_history(GtkComboBoxText *combo_entry,
1698 		const gchar *text, gint history_len)
1699 {
1700 	GtkComboBox *combo = GTK_COMBO_BOX(combo_entry);
1701 	GtkTreeModel *model;
1702 	GtkTreeIter iter;
1703 	GtkTreePath *path;
1704 
1705 	if (history_len <= 0)
1706 		history_len = 10;
1707 	if (!text)
1708 		text = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))));
1709 
1710 	model = gtk_combo_box_get_model(combo);
1711 
1712 	if (tree_model_find_text(model, &iter, 0, text))
1713 	{
1714 		gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1715 	}
1716 	gtk_combo_box_text_prepend_text(combo_entry, text);
1717 
1718 	/* limit history */
1719 	path = gtk_tree_path_new_from_indices(history_len, -1);
1720 	if (gtk_tree_model_get_iter(model, &iter, path))
1721 	{
1722 		gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1723 	}
1724 	gtk_tree_path_free(path);
1725 }
1726 
1727 
1728 /* Same as gtk_combo_box_text_prepend_text(), except that text is only prepended if it not already
1729  * exists in the combo's model. */
ui_combo_box_prepend_text_once(GtkComboBoxText * combo,const gchar * text)1730 void ui_combo_box_prepend_text_once(GtkComboBoxText *combo, const gchar *text)
1731 {
1732 	GtkTreeModel *model;
1733 	GtkTreeIter iter;
1734 
1735 	model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
1736 	if (tree_model_find_text(model, &iter, 0, text))
1737 		return;	/* don't prepend duplicate */
1738 
1739 	gtk_combo_box_text_prepend_text(combo, text);
1740 }
1741 
1742 
1743 /* Changes the color of the notebook tab text and open files items according to
1744  * document status. */
ui_update_tab_status(GeanyDocument * doc)1745 void ui_update_tab_status(GeanyDocument *doc)
1746 {
1747 	gtk_widget_set_name(doc->priv->tab_label, document_get_status_widget_class(doc));
1748 
1749 	sidebar_openfiles_update(doc);
1750 }
1751 
1752 
tree_model_iter_get_next(GtkTreeModel * model,GtkTreeIter * iter,gboolean down)1753 static gboolean tree_model_iter_get_next(GtkTreeModel *model, GtkTreeIter *iter,
1754 		gboolean down)
1755 {
1756 	GtkTreePath *path;
1757 	gboolean result;
1758 
1759 	if (down)
1760 		return gtk_tree_model_iter_next(model, iter);
1761 
1762 	path = gtk_tree_model_get_path(model, iter);
1763 	result = gtk_tree_path_prev(path) && gtk_tree_model_get_iter(model, iter, path);
1764 	gtk_tree_path_free(path);
1765 	return result;
1766 }
1767 
1768 
1769 /* note: the while loop might be more efficient when searching upwards if it
1770  * used tree paths instead of tree iters, but in practice it probably doesn't matter much. */
tree_view_find(GtkTreeView * treeview,TVMatchCallback cb,gboolean down)1771 static gboolean tree_view_find(GtkTreeView *treeview, TVMatchCallback cb, gboolean down)
1772 {
1773 	GtkTreeSelection *treesel;
1774 	GtkTreeIter iter;
1775 	GtkTreeModel *model;
1776 
1777 	treesel = gtk_tree_view_get_selection(treeview);
1778 	if (gtk_tree_selection_get_selected(treesel, &model, &iter))
1779 	{
1780 		/* get the next selected item */
1781 		if (! tree_model_iter_get_next(model, &iter, down))
1782 			return FALSE;	/* no more items */
1783 	}
1784 	else	/* no selection */
1785 	{
1786 		if (! gtk_tree_model_get_iter_first(model, &iter))
1787 			return TRUE;	/* no items */
1788 	}
1789 	while (TRUE)
1790 	{
1791 		gtk_tree_selection_select_iter(treesel, &iter);
1792 		if (cb(FALSE))
1793 			break;	/* found next message */
1794 
1795 		if (! tree_model_iter_get_next(model, &iter, down))
1796 			return FALSE;	/* no more items */
1797 	}
1798 	/* scroll item in view */
1799 	if (ui_prefs.msgwindow_visible)
1800 	{
1801 		GtkTreePath *path = gtk_tree_model_get_path(
1802 			gtk_tree_view_get_model(treeview), &iter);
1803 
1804 		gtk_tree_view_scroll_to_cell(treeview, path, NULL, TRUE, 0.5, 0.5);
1805 		gtk_tree_path_free(path);
1806 	}
1807 	return TRUE;
1808 }
1809 
1810 
1811 /* Returns FALSE if the treeview has items but no matching next item. */
ui_tree_view_find_next(GtkTreeView * treeview,TVMatchCallback cb)1812 gboolean ui_tree_view_find_next(GtkTreeView *treeview, TVMatchCallback cb)
1813 {
1814 	return tree_view_find(treeview, cb, TRUE);
1815 }
1816 
1817 
1818 /* Returns FALSE if the treeview has items but no matching next item. */
ui_tree_view_find_previous(GtkTreeView * treeview,TVMatchCallback cb)1819 gboolean ui_tree_view_find_previous(GtkTreeView *treeview, TVMatchCallback cb)
1820 {
1821 	return tree_view_find(treeview, cb, FALSE);
1822 }
1823 
1824 
1825 /* Shamelessly stolen from GTK */
ui_tree_view_query_tooltip_cb(GtkWidget * widget,gint x,gint y,gboolean keyboard_tip,GtkTooltip * tooltip,gpointer data)1826 static gboolean ui_tree_view_query_tooltip_cb(GtkWidget *widget, gint x, gint y,
1827 		gboolean keyboard_tip, GtkTooltip *tooltip, gpointer data)
1828 {
1829 	GValue value = { 0 };
1830 	GValue transformed = { 0 };
1831 	GtkTreeIter iter;
1832 	GtkTreePath *path;
1833 	GtkTreeModel *model;
1834 	GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
1835 	gint column = GPOINTER_TO_INT(data);
1836 	gboolean tootlip_set = FALSE;
1837 
1838 	if (! gtk_tree_view_get_tooltip_context(tree_view, &x, &y, keyboard_tip, &model, &path, &iter))
1839 		return FALSE;
1840 
1841 	gtk_tree_model_get_value(model, &iter, column, &value);
1842 
1843 	g_value_init(&transformed, G_TYPE_STRING);
1844 	if (g_value_transform(&value, &transformed) && g_value_get_string(&transformed))
1845 	{
1846 		gtk_tooltip_set_text(tooltip, g_value_get_string(&transformed));
1847 		gtk_tree_view_set_tooltip_row(tree_view, tooltip, path);
1848 		tootlip_set = TRUE;
1849 	}
1850 
1851 	g_value_unset(&transformed);
1852 	g_value_unset(&value);
1853 	gtk_tree_path_free(path);
1854 
1855 	return tootlip_set;
1856 }
1857 
1858 
1859 /** Adds text tooltips to a tree view.
1860  *
1861  * This is similar to gtk_tree_view_set_tooltip_column() but considers the column contents to be
1862  * text, not markup -- it uses gtk_tooltip_set_text() rather than gtk_tooltip_set_markup() to set
1863  * the tooltip's value.
1864  *
1865  * @warning Unlike gtk_tree_view_set_tooltip_column() you currently cannot change or remove the
1866  * tooltip column after it has been added.  Trying to do so will probably give funky results.
1867  *
1868  * @param tree_view The tree view
1869  * @param column The column to get the tooltip from
1870  *
1871  * @since 1.25 (API 223)
1872  */
1873 /* Note: @p column is int and not uint both to match gtk_tree_view_set_tooltip_column() signature
1874  * and to allow future support of -1 to unset if ever wanted */
1875 GEANY_API_SYMBOL
ui_tree_view_set_tooltip_text_column(GtkTreeView * tree_view,gint column)1876 void ui_tree_view_set_tooltip_text_column(GtkTreeView *tree_view, gint column)
1877 {
1878 	g_return_if_fail(column >= 0);
1879 	g_return_if_fail(GTK_IS_TREE_VIEW(tree_view));
1880 
1881 	g_signal_connect(tree_view, "query-tooltip",
1882 			G_CALLBACK(ui_tree_view_query_tooltip_cb), GINT_TO_POINTER(column));
1883 	gtk_widget_set_has_tooltip(GTK_WIDGET(tree_view), TRUE);
1884 }
1885 
1886 
1887 /**
1888  * Modifies the font of a widget using gtk_widget_modify_font().
1889  *
1890  * @param widget The widget.
1891  * @param str The font name as expected by pango_font_description_from_string().
1892  */
1893 GEANY_API_SYMBOL
ui_widget_modify_font_from_string(GtkWidget * widget,const gchar * str)1894 void ui_widget_modify_font_from_string(GtkWidget *widget, const gchar *str)
1895 {
1896 	PangoFontDescription *pfd;
1897 
1898 	pfd = pango_font_description_from_string(str);
1899 	gtk_widget_modify_font(widget, pfd);
1900 	pango_font_description_free(pfd);
1901 }
1902 
1903 
1904 /** Creates a @c GtkHBox with @a entry packed into it and an open button which runs a
1905  * file chooser, replacing entry text (if successful) with the path returned from the
1906  * @c GtkFileChooser.
1907  * @note @a entry can be the child of an unparented widget, such as @c GtkComboBoxEntry.
1908  * @param title @nullable The file chooser dialog title, or @c NULL.
1909  * @param action The mode of the file chooser.
1910  * @param entry Can be an unpacked @c GtkEntry, or the child of an unpacked widget,
1911  * such as @c GtkComboBoxEntry.
1912  *
1913  * @return @transfer{floating} The @c GtkHBox.
1914  */
1915 /* @see ui_setup_open_button_callback(). */
1916 GEANY_API_SYMBOL
ui_path_box_new(const gchar * title,GtkFileChooserAction action,GtkEntry * entry)1917 GtkWidget *ui_path_box_new(const gchar *title, GtkFileChooserAction action, GtkEntry *entry)
1918 {
1919 	GtkWidget *vbox, *dirbtn, *openimg, *hbox, *path_entry, *parent, *next_parent;
1920 
1921 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
1922 	path_entry = GTK_WIDGET(entry);
1923 
1924 	/* prevent path_entry being vertically stretched to the height of dirbtn */
1925 	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1926 
1927 	parent = path_entry;
1928 	while ((next_parent = gtk_widget_get_parent(parent)) != NULL)
1929 		parent = next_parent;
1930 
1931 	gtk_box_pack_start(GTK_BOX(vbox), parent, TRUE, FALSE, 0);
1932 
1933 	dirbtn = gtk_button_new();
1934 	openimg = gtk_image_new_from_stock(GTK_STOCK_OPEN, GTK_ICON_SIZE_BUTTON);
1935 	gtk_container_add(GTK_CONTAINER(dirbtn), openimg);
1936 	ui_setup_open_button_callback(dirbtn, title, action, entry);
1937 
1938 	gtk_box_pack_end(GTK_BOX(hbox), dirbtn, FALSE, FALSE, 0);
1939 	gtk_box_pack_end(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
1940 	return hbox;
1941 }
1942 
1943 
1944 static void ui_path_box_open_clicked(GtkButton *button, gpointer user_data);
1945 
1946 
1947 /* Setup a GtkButton to run a GtkFileChooser, setting entry text if successful.
1948  * title can be NULL.
1949  * action is the file chooser mode to use. */
ui_setup_open_button_callback(GtkWidget * open_btn,const gchar * title,GtkFileChooserAction action,GtkEntry * entry)1950 void ui_setup_open_button_callback(GtkWidget *open_btn, const gchar *title,
1951 		GtkFileChooserAction action, GtkEntry *entry)
1952 {
1953 	GtkWidget *path_entry = GTK_WIDGET(entry);
1954 
1955 	if (title)
1956 		g_object_set_data_full(G_OBJECT(open_btn), "title", g_strdup(title),
1957 				(GDestroyNotify) g_free);
1958 	g_object_set_data(G_OBJECT(open_btn), "action", GINT_TO_POINTER(action));
1959 	g_signal_connect(open_btn, "clicked", G_CALLBACK(ui_path_box_open_clicked), path_entry);
1960 }
1961 
1962 
1963 #ifndef G_OS_WIN32
run_file_chooser(const gchar * title,GtkFileChooserAction action,const gchar * utf8_path)1964 static gchar *run_file_chooser(const gchar *title, GtkFileChooserAction action,
1965 		const gchar *utf8_path)
1966 {
1967 	GtkWidget *dialog = gtk_file_chooser_dialog_new(title,
1968 		GTK_WINDOW(main_widgets.window), action,
1969 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1970 		GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);
1971 	gchar *locale_path;
1972 	gchar *ret_path = NULL;
1973 
1974 	gtk_widget_set_name(dialog, "GeanyDialog");
1975 	locale_path = utils_get_locale_from_utf8(utf8_path);
1976 	if (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
1977 	{
1978 		if (g_path_is_absolute(locale_path) && g_file_test(locale_path, G_FILE_TEST_IS_DIR))
1979 			gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), locale_path);
1980 	}
1981 	else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
1982 	{
1983 		if (g_path_is_absolute(locale_path))
1984 			gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), locale_path);
1985 	}
1986 	g_free(locale_path);
1987 
1988 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK)
1989 	{
1990 		gchar *dir_locale;
1991 
1992 		dir_locale = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1993 		ret_path = utils_get_utf8_from_locale(dir_locale);
1994 		g_free(dir_locale);
1995 	}
1996 	gtk_widget_destroy(dialog);
1997 	return ret_path;
1998 }
1999 #endif
2000 
2001 
ui_path_box_open_clicked(GtkButton * button,gpointer user_data)2002 static void ui_path_box_open_clicked(GtkButton *button, gpointer user_data)
2003 {
2004 	GtkFileChooserAction action = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "action"));
2005 	GtkEntry *entry = user_data;
2006 	const gchar *title = g_object_get_data(G_OBJECT(button), "title");
2007 	gchar *utf8_path = NULL;
2008 
2009 	/* TODO: extend for other actions */
2010 	g_return_if_fail(action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
2011 					 action == GTK_FILE_CHOOSER_ACTION_OPEN);
2012 
2013 	if (title == NULL)
2014 		title = (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) ?
2015 			_("Select Folder") : _("Select File");
2016 
2017 	if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
2018 	{
2019 #ifdef G_OS_WIN32
2020 		utf8_path = win32_show_file_dialog(GTK_WINDOW(ui_widgets.prefs_dialog), title,
2021 						gtk_entry_get_text(GTK_ENTRY(entry)));
2022 #else
2023 		utf8_path = run_file_chooser(title, action, gtk_entry_get_text(GTK_ENTRY(entry)));
2024 #endif
2025 	}
2026 	else if (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
2027 	{
2028 		gchar *path = g_path_get_dirname(gtk_entry_get_text(GTK_ENTRY(entry)));
2029 #ifdef G_OS_WIN32
2030 		utf8_path = win32_show_folder_dialog(ui_widgets.prefs_dialog, title,
2031 						gtk_entry_get_text(GTK_ENTRY(entry)));
2032 #else
2033 		utf8_path = run_file_chooser(title, action, path);
2034 #endif
2035 		g_free(path);
2036 	}
2037 
2038 	if (utf8_path != NULL)
2039 	{
2040 		gtk_entry_set_text(GTK_ENTRY(entry), utf8_path);
2041 		g_free(utf8_path);
2042 	}
2043 }
2044 
2045 
ui_statusbar_showhide(gboolean state)2046 void ui_statusbar_showhide(gboolean state)
2047 {
2048 	/* handle statusbar visibility */
2049 	if (state)
2050 	{
2051 		gtk_widget_show(ui_widgets.statusbar);
2052 		ui_update_statusbar(NULL, -1);
2053 	}
2054 	else
2055 		gtk_widget_hide(ui_widgets.statusbar);
2056 }
2057 
2058 
2059 /** Packs all @c GtkWidgets passed after the row argument into a table, using
2060  * one widget per cell. The first widget is not expanded as the table grows,
2061  * as this is usually a label.
2062  * @param table
2063  * @param row The row number of the table.
2064  */
2065 GEANY_API_SYMBOL
ui_table_add_row(GtkTable * table,gint row,...)2066 void ui_table_add_row(GtkTable *table, gint row, ...)
2067 {
2068 	va_list args;
2069 	guint i;
2070 	GtkWidget *widget;
2071 
2072 	va_start(args, row);
2073 	for (i = 0; (widget = va_arg(args, GtkWidget*), widget != NULL); i++)
2074 	{
2075 		gint options = (i == 0) ? GTK_FILL : GTK_EXPAND | GTK_FILL;
2076 
2077 		gtk_table_attach(GTK_TABLE(table), widget, i, i + 1, row, row + 1,
2078 			options, 0, 0, 0);
2079 	}
2080 	va_end(args);
2081 }
2082 
2083 
on_config_file_clicked(GtkWidget * widget,gpointer user_data)2084 static void on_config_file_clicked(GtkWidget *widget, gpointer user_data)
2085 {
2086 	const gchar *file_name = user_data;
2087 	GeanyFiletype *ft = NULL;
2088 
2089 	if (strstr(file_name, G_DIR_SEPARATOR_S "filetypes."))
2090 		ft = filetypes[GEANY_FILETYPES_CONF];
2091 
2092 	if (g_file_test(file_name, G_FILE_TEST_EXISTS))
2093 		document_open_file(file_name, FALSE, ft, NULL);
2094 	else
2095 	{
2096 		gchar *utf8_filename = utils_get_utf8_from_locale(file_name);
2097 		gchar *global_file;
2098 		gchar *base_name = NULL;
2099 		gchar *global_content = NULL;
2100 		GeanyDocument *doc = NULL;
2101 
2102 		/* get the path inside app->configdir - can contain subdirectories */
2103 		if (g_str_has_prefix(file_name, app->configdir))
2104 		{
2105 			gsize len = strlen(app->configdir);
2106 			if (file_name[len] == G_DIR_SEPARATOR)
2107 				base_name = g_strdup(file_name + len + 1);
2108 		}
2109 
2110 		if (!base_name)
2111 			base_name = g_path_get_basename(file_name);
2112 
2113 		global_file = g_build_filename(app->datadir, base_name, NULL);
2114 
2115 		/* if the requested file doesn't exist in the user's config dir, try loading the file
2116 		 * from the global data directory and use its contents for the newly created file */
2117 		if (g_file_test(global_file, G_FILE_TEST_EXISTS))
2118 			g_file_get_contents(global_file, &global_content, NULL, NULL);
2119 
2120 		doc = document_new_file(utf8_filename, ft, global_content);
2121 		if (global_content)
2122 		{
2123 			sci_select_all(doc->editor->sci);
2124 			keybindings_send_command(GEANY_KEY_GROUP_FORMAT,
2125 				GEANY_KEYS_FORMAT_COMMENTLINETOGGLE);
2126 			sci_set_current_line(doc->editor->sci, 0);
2127 			document_set_text_changed(doc, FALSE);
2128 			sci_empty_undo_buffer(doc->editor->sci);
2129 			ui_document_show_hide(doc); /* update the document menu */
2130 		}
2131 		utils_free_pointers(4, utf8_filename, base_name, global_file, global_content, NULL);
2132 	}
2133 }
2134 
2135 
free_on_closure_notify(gpointer data,GClosure * closure)2136 static void free_on_closure_notify(gpointer data, GClosure *closure)
2137 {
2138 	g_free(data);
2139 }
2140 
2141 
2142 /* @note You should connect to the "document-save" signal yourself to detect
2143  * if the user has just saved the config file, reloading it. */
ui_add_config_file_menu_item(const gchar * real_path,const gchar * label,GtkContainer * parent)2144 void ui_add_config_file_menu_item(const gchar *real_path, const gchar *label, GtkContainer *parent)
2145 {
2146 	GtkWidget *item;
2147 
2148 	if (!parent)
2149 		parent = GTK_CONTAINER(widgets.config_files_menu);
2150 
2151 	if (!label)
2152 	{
2153 		gchar *base_name;
2154 
2155 		base_name = g_path_get_basename(real_path);
2156 		item = gtk_menu_item_new_with_label(base_name);
2157 		g_free(base_name);
2158 	}
2159 	else
2160 		item = gtk_menu_item_new_with_mnemonic(label);
2161 
2162 	gtk_widget_show(item);
2163 	gtk_container_add(parent, item);
2164 	g_signal_connect_data(item, "activate", G_CALLBACK(on_config_file_clicked),
2165 			g_strdup(real_path), free_on_closure_notify, 0);
2166 }
2167 
2168 
sort_menu(gpointer data)2169 static gboolean sort_menu(gpointer data)
2170 {
2171 	ui_menu_sort_by_label(GTK_MENU(data));
2172 	return FALSE;
2173 }
2174 
2175 
create_config_files_menu(void)2176 static void create_config_files_menu(void)
2177 {
2178 	GtkWidget *menu, *item;
2179 
2180 	widgets.config_files_menu = menu = gtk_menu_new();
2181 
2182 	item = ui_lookup_widget(main_widgets.window, "configuration_files1");
2183 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu);
2184 
2185 	item = gtk_menu_item_new_with_mnemonic(_("_Filetype Configuration"));
2186 	gtk_container_add(GTK_CONTAINER(menu), item);
2187 	ui_widgets.config_files_filetype_menu = gtk_menu_new();
2188 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), ui_widgets.config_files_filetype_menu);
2189 	gtk_widget_show(item);
2190 
2191 	/* sort menu after all items added */
2192 	g_idle_add(sort_menu, widgets.config_files_menu);
2193 }
2194 
2195 
2196 /* adds factory icons with a named icon source using the stock items id */
add_stock_icons(const GtkStockItem * items,gsize count)2197 static void add_stock_icons(const GtkStockItem *items, gsize count)
2198 {
2199 	GtkIconFactory *factory = gtk_icon_factory_new();
2200 	GtkIconSource *source = gtk_icon_source_new();
2201 	gsize i;
2202 
2203 	for (i = 0; i < count; i++)
2204 	{
2205 		GtkIconSet *set = gtk_icon_set_new();
2206 
2207 		gtk_icon_source_set_icon_name(source, items[i].stock_id);
2208 		gtk_icon_set_add_source(set, source);
2209 		gtk_icon_factory_add(factory, items[i].stock_id, set);
2210 		gtk_icon_set_unref(set);
2211 	}
2212 	gtk_icon_source_free(source);
2213 	gtk_icon_factory_add_default(factory);
2214 	g_object_unref(factory);
2215 }
2216 
2217 
ui_init_stock_items(void)2218 void ui_init_stock_items(void)
2219 {
2220 	GtkStockItem items[] =
2221 	{
2222 		{ GEANY_STOCK_SAVE_ALL, N_("Save All"), 0, 0, GETTEXT_PACKAGE },
2223 		{ GEANY_STOCK_CLOSE_ALL, N_("Close All"), 0, 0, GETTEXT_PACKAGE },
2224 		{ GEANY_STOCK_BUILD, N_("Build"), 0, 0, GETTEXT_PACKAGE }
2225 	};
2226 
2227 	gtk_stock_add(items, G_N_ELEMENTS(items));
2228 	add_stock_icons(items, G_N_ELEMENTS(items));
2229 }
2230 
2231 
ui_init_toolbar_widgets(void)2232 void ui_init_toolbar_widgets(void)
2233 {
2234 	widgets.save_buttons[1] = toolbar_get_widget_by_name("Save");
2235 	widgets.save_buttons[3] = toolbar_get_widget_by_name("SaveAll");
2236 	widgets.redo_items[2] = toolbar_get_widget_by_name("Redo");
2237 	widgets.undo_items[2] = toolbar_get_widget_by_name("Undo");
2238 }
2239 
2240 
ui_swap_sidebar_pos(void)2241 void ui_swap_sidebar_pos(void)
2242 {
2243 	GtkWidget *pane = ui_lookup_widget(main_widgets.window, "hpaned1");
2244 	GtkWidget *left = gtk_paned_get_child1(GTK_PANED(pane));
2245 	GtkWidget *right = gtk_paned_get_child2(GTK_PANED(pane));
2246 
2247 	g_object_ref(left);
2248 	g_object_ref(right);
2249 	gtk_container_remove (GTK_CONTAINER (pane), left);
2250 	gtk_container_remove (GTK_CONTAINER (pane), right);
2251 	/* only scintilla notebook should expand */
2252 	gtk_paned_pack1(GTK_PANED(pane), right, right == main_widgets.notebook, TRUE);
2253 	gtk_paned_pack2(GTK_PANED(pane), left, left == main_widgets.notebook, TRUE);
2254 	g_object_unref(left);
2255 	g_object_unref(right);
2256 
2257 	gtk_paned_set_position(GTK_PANED(pane), gtk_widget_get_allocated_width(pane)
2258 		- gtk_paned_get_position(GTK_PANED(pane)));
2259 }
2260 
2261 
init_recent_files(void)2262 static void init_recent_files(void)
2263 {
2264 	GtkWidget *toolbar_recent_files_menu;
2265 
2266 	/* add recent files to the File menu */
2267 	ui_widgets.recent_files_menuitem = ui_lookup_widget(main_widgets.window, "recent_files1");
2268 	ui_widgets.recent_files_menu_menubar = gtk_menu_new();
2269 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(ui_widgets.recent_files_menuitem),
2270 							ui_widgets.recent_files_menu_menubar);
2271 
2272 	/* add recent files to the toolbar Open button */
2273 	toolbar_recent_files_menu = gtk_menu_new();
2274 	g_object_ref(toolbar_recent_files_menu);
2275 	geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
2276 		toolbar_get_action_by_name("Open")), toolbar_recent_files_menu);
2277 }
2278 
2279 
ui_menu_move(GtkWidget * menu,GtkWidget * old,GtkWidget * new)2280 static void ui_menu_move(GtkWidget *menu, GtkWidget *old, GtkWidget *new)
2281 {
2282 	g_object_ref(menu);
2283 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(old), NULL);
2284 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(new), menu);
2285 	g_object_unref(menu);
2286 }
2287 
2288 
2289 typedef struct GeanySharedMenu
2290 {
2291 	const gchar *menu;
2292 	const gchar *menubar_item;
2293 	const gchar *popup_item;
2294 }
2295 GeanySharedMenu;
2296 
2297 #define foreach_menu(item, array) \
2298 	for (item = array; item->menu; item++)
2299 
on_editor_menu_show(GtkWidget * widget,GeanySharedMenu * items)2300 static void on_editor_menu_show(GtkWidget *widget, GeanySharedMenu *items)
2301 {
2302 	GeanySharedMenu *item;
2303 
2304 	foreach_menu(item, items)
2305 	{
2306 		GtkWidget *popup = ui_lookup_widget(main_widgets.editor_menu, item->popup_item);
2307 		GtkWidget *bar = ui_lookup_widget(main_widgets.window, item->menubar_item);
2308 		GtkWidget *menu = ui_lookup_widget(main_widgets.window, item->menu);
2309 
2310 		ui_menu_move(menu, bar, popup);
2311 	}
2312 }
2313 
2314 
on_editor_menu_hide(GtkWidget * widget,GeanySharedMenu * items)2315 static void on_editor_menu_hide(GtkWidget *widget, GeanySharedMenu *items)
2316 {
2317 	GeanySharedMenu *item;
2318 
2319 	foreach_menu(item, items)
2320 	{
2321 		GtkWidget *popup = ui_lookup_widget(main_widgets.editor_menu, item->popup_item);
2322 		GtkWidget *bar = ui_lookup_widget(main_widgets.window, item->menubar_item);
2323 		GtkWidget *menu = ui_lookup_widget(main_widgets.window, item->menu);
2324 
2325 		ui_menu_move(menu, popup, bar);
2326 	}
2327 }
2328 
2329 
2330 /* Currently ui_init() is called before keyfile.c stash group code is initialized,
2331  * so this is called after that's done. */
ui_init_prefs(void)2332 void ui_init_prefs(void)
2333 {
2334 	StashGroup *group = stash_group_new(PACKAGE);
2335 
2336 	/* various prefs */
2337 	configuration_add_various_pref_group(group, "interface");
2338 
2339 	stash_group_add_boolean(group, &interface_prefs.show_symbol_list_expanders,
2340 		"show_symbol_list_expanders", TRUE);
2341 	stash_group_add_boolean(group, &interface_prefs.compiler_tab_autoscroll,
2342 		"compiler_tab_autoscroll", TRUE);
2343 	stash_group_add_string(group, &ui_prefs.statusbar_template,
2344 		"statusbar_template", _(DEFAULT_STATUSBAR_TEMPLATE));
2345 	stash_group_add_boolean(group, &ui_prefs.new_document_after_close,
2346 		"new_document_after_close", FALSE);
2347 	stash_group_add_boolean(group, &interface_prefs.msgwin_status_visible,
2348 		"msgwin_status_visible", TRUE);
2349 	stash_group_add_boolean(group, &interface_prefs.msgwin_compiler_visible,
2350 		"msgwin_compiler_visible", TRUE);
2351 	stash_group_add_boolean(group, &interface_prefs.msgwin_messages_visible,
2352 		"msgwin_messages_visible", TRUE);
2353 	stash_group_add_boolean(group, &interface_prefs.msgwin_scribble_visible,
2354 		"msgwin_scribble_visible", TRUE);
2355 }
2356 
2357 
2358 /* Used to find out the name of the GtkBuilder retrieved object since
2359  * some objects will be GTK_IS_BUILDABLE() and use the GtkBuildable
2360  * 'name' property for that and those that don't implement GtkBuildable
2361  * will have a "gtk-builder-name" stored in the GObject's data list. */
ui_guess_object_name(GObject * obj)2362 static const gchar *ui_guess_object_name(GObject *obj)
2363 {
2364 	const gchar *name = NULL;
2365 
2366 	g_return_val_if_fail(G_IS_OBJECT(obj), NULL);
2367 
2368 	if (GTK_IS_BUILDABLE(obj))
2369 		name = gtk_buildable_get_name(GTK_BUILDABLE(obj));
2370 	if (! name)
2371 		name = g_object_get_data(obj, "gtk-builder-name");
2372 	if (! name)
2373 		return NULL;
2374 
2375 	return name;
2376 }
2377 
2378 
2379 /* Compatibility functions */
create_edit_menu1(void)2380 GtkWidget *create_edit_menu1(void)
2381 {
2382 	return edit_menu1;
2383 }
2384 
2385 
create_prefs_dialog(void)2386 GtkWidget *create_prefs_dialog(void)
2387 {
2388 	return prefs_dialog;
2389 }
2390 
2391 
create_project_dialog(void)2392 GtkWidget *create_project_dialog(void)
2393 {
2394 	return project_dialog;
2395 }
2396 
2397 
create_toolbar_popup_menu1(void)2398 GtkWidget *create_toolbar_popup_menu1(void)
2399 {
2400 	return toolbar_popup_menu1;
2401 }
2402 
2403 
create_window1(void)2404 GtkWidget *create_window1(void)
2405 {
2406 	return window1;
2407 }
2408 
2409 
ui_get_top_parent(GtkWidget * widget)2410 static GtkWidget *ui_get_top_parent(GtkWidget *widget)
2411 {
2412 	GtkWidget *parent;
2413 
2414 	g_return_val_if_fail(GTK_IS_WIDGET(widget), NULL);
2415 
2416 	for (;;)
2417 	{
2418 		if (GTK_IS_MENU(widget))
2419 			parent = gtk_menu_get_attach_widget(GTK_MENU(widget));
2420 		else
2421 			parent = gtk_widget_get_parent(widget);
2422 		if (parent == NULL)
2423 			parent = (GtkWidget*) g_object_get_data(G_OBJECT(widget), "GladeParentKey");
2424 		if (parent == NULL)
2425 			break;
2426 		widget = parent;
2427 	}
2428 
2429 	return widget;
2430 }
2431 
2432 
ui_init_builder(void)2433 void ui_init_builder(void)
2434 {
2435 	gchar *interface_file;
2436 	const gchar *name;
2437 	GError *error;
2438 	GSList *iter, *all_objects;
2439 	GtkWidget *widget, *toplevel;
2440 
2441 	/* prevent function from being called twice */
2442 	if (GTK_IS_BUILDER(builder))
2443 		return;
2444 
2445 	builder = gtk_builder_new();
2446 
2447 	gtk_builder_set_translation_domain(builder, GETTEXT_PACKAGE);
2448 
2449 	error = NULL;
2450 	interface_file = g_build_filename(app->datadir, "geany.glade", NULL);
2451 	if (! gtk_builder_add_from_file(builder, interface_file, &error))
2452 	{
2453 		/* Show the user this message so they know WTF happened */
2454 		dialogs_show_msgbox_with_secondary(GTK_MESSAGE_ERROR,
2455 			_("Geany cannot start!"), error->message);
2456 		/* Aborts */
2457 		g_error("Cannot create user-interface: %s", error->message);
2458 		g_error_free(error);
2459 		g_free(interface_file);
2460 		g_object_unref(builder);
2461 		return;
2462 	}
2463 	g_free(interface_file);
2464 
2465 	callbacks_connect(builder);
2466 
2467 	edit_menu1 = GTK_WIDGET(gtk_builder_get_object(builder, "edit_menu1"));
2468 	prefs_dialog = GTK_WIDGET(gtk_builder_get_object(builder, "prefs_dialog"));
2469 	project_dialog = GTK_WIDGET(gtk_builder_get_object(builder, "project_dialog"));
2470 	toolbar_popup_menu1 = GTK_WIDGET(gtk_builder_get_object(builder, "toolbar_popup_menu1"));
2471 	window1 = GTK_WIDGET(gtk_builder_get_object(builder, "window1"));
2472 
2473 	g_object_set_data(G_OBJECT(edit_menu1), "edit_menu1", edit_menu1);
2474 	g_object_set_data(G_OBJECT(prefs_dialog), "prefs_dialog", prefs_dialog);
2475 	g_object_set_data(G_OBJECT(project_dialog), "project_dialog", project_dialog);
2476 	g_object_set_data(G_OBJECT(toolbar_popup_menu1), "toolbar_popup_menu1", toolbar_popup_menu1);
2477 	g_object_set_data(G_OBJECT(window1), "window1", window1);
2478 
2479 	all_objects = gtk_builder_get_objects(builder);
2480 	for (iter = all_objects; iter != NULL; iter = g_slist_next(iter))
2481 	{
2482 		if (! GTK_IS_WIDGET(iter->data))
2483 			continue;
2484 
2485 		widget = GTK_WIDGET(iter->data);
2486 
2487 		name = ui_guess_object_name(G_OBJECT(widget));
2488 		if (! name)
2489 		{
2490 			g_warning("Unable to get name from GtkBuilder object");
2491 			continue;
2492 		}
2493 		else
2494 		{
2495 			/* Set a default name for use from CSS by name */
2496 			gtk_widget_set_name(widget, name);
2497 		}
2498 
2499 		toplevel = ui_get_top_parent(widget);
2500 		if (toplevel)
2501 			ui_hookup_widget(toplevel, widget, name);
2502 	}
2503 	g_slist_free(all_objects);
2504 }
2505 
2506 
load_css_theme(const gchar * fn,guint priority)2507 static void load_css_theme(const gchar *fn, guint priority)
2508 {
2509 	GtkCssProvider *provider = gtk_css_provider_new();
2510 	GError *error = NULL;
2511 
2512 	if (! gtk_css_provider_load_from_path(provider, fn, &error))
2513 	{
2514 		g_warning("Failed to load custom CSS: %s", error->message);
2515 		g_error_free(error);
2516 		return;
2517 	}
2518 
2519 	gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
2520 		GTK_STYLE_PROVIDER(provider), priority);
2521 	geany_debug("Loaded GTK+ CSS theme '%s'", fn);
2522 
2523 	g_object_unref(provider);
2524 }
2525 
2526 
init_css_styles(void)2527 static void init_css_styles(void)
2528 {
2529 	gchar *theme_fn;
2530 
2531 	// load the main geany.css file from system data dir
2532 	theme_fn = g_build_filename(app->datadir, "geany.css", NULL);
2533 	load_css_theme(theme_fn, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2534 	g_free(theme_fn);
2535 
2536 	// load themes to handle breakage between various GTK+ versions
2537 	const struct
2538 	{
2539 		guint min_version;
2540 		guint max_version;
2541 		const gchar *file;
2542 	}
2543 	css_files[] =
2544 	{
2545 		{ 20, G_MAXUINT, "geany-3.20.css" },
2546 		{ 0, 19, "geany-3.0.css" },
2547 	};
2548 
2549 	guint gtk_version = gtk_get_minor_version();
2550 	for (guint i = 0; i < G_N_ELEMENTS(css_files); i++)
2551 	{
2552 		if (gtk_version >= css_files[i].min_version &&
2553 			gtk_version <= css_files[i].max_version)
2554 		{
2555 			theme_fn = g_build_filename(app->datadir, css_files[i].file, NULL);
2556 			load_css_theme(theme_fn, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2557 			g_free(theme_fn);
2558 		}
2559 	}
2560 
2561 	// if the user provided a geany.css file in their config dir, try and load that
2562 	theme_fn = g_build_filename(app->configdir, "geany.css", NULL);
2563 	if (g_file_test(theme_fn, G_FILE_TEST_EXISTS))
2564 		load_css_theme(theme_fn, GTK_STYLE_PROVIDER_PRIORITY_USER);
2565 	g_free(theme_fn);
2566 }
2567 
2568 
add_css_config_file_item(void)2569 static void add_css_config_file_item(void)
2570 {
2571 	gchar *theme_fn;
2572 	theme_fn = g_build_filename(app->configdir, "geany.css", NULL);
2573 	ui_add_config_file_menu_item(theme_fn, NULL, NULL);
2574 	g_free(theme_fn);
2575 }
2576 
2577 
ui_init(void)2578 void ui_init(void)
2579 {
2580 	init_css_styles();
2581 	init_recent_files();
2582 
2583 	ui_widgets.statusbar = ui_lookup_widget(main_widgets.window, "statusbar");
2584 	ui_widgets.print_page_setup = ui_lookup_widget(main_widgets.window, "page_setup1");
2585 
2586 	main_widgets.progressbar = progress_bar_create();
2587 
2588 	/* current word sensitive items */
2589 	widgets.popup_goto_items[0] = ui_lookup_widget(main_widgets.editor_menu, "goto_tag_definition2");
2590 	widgets.popup_goto_items[1] = ui_lookup_widget(main_widgets.editor_menu, "context_action1");
2591 	widgets.popup_goto_items[2] = ui_lookup_widget(main_widgets.editor_menu, "find_usage2");
2592 	widgets.popup_goto_items[3] = ui_lookup_widget(main_widgets.editor_menu, "find_document_usage2");
2593 
2594 	widgets.popup_copy_items[0] = ui_lookup_widget(main_widgets.editor_menu, "cut1");
2595 	widgets.popup_copy_items[1] = ui_lookup_widget(main_widgets.editor_menu, "copy1");
2596 	widgets.popup_copy_items[2] = ui_lookup_widget(main_widgets.editor_menu, "delete1");
2597 	widgets.menu_copy_items[0] = ui_lookup_widget(main_widgets.window, "menu_cut1");
2598 	widgets.menu_copy_items[1] = ui_lookup_widget(main_widgets.window, "menu_copy1");
2599 	widgets.menu_copy_items[2] = ui_lookup_widget(main_widgets.window, "menu_delete1");
2600 	widgets.menu_insert_include_items[0] = ui_lookup_widget(main_widgets.editor_menu, "insert_include1");
2601 	widgets.menu_insert_include_items[1] = ui_lookup_widget(main_widgets.window, "insert_include2");
2602 	widgets.save_buttons[0] = ui_lookup_widget(main_widgets.window, "menu_save1");
2603 	widgets.save_buttons[2] = ui_lookup_widget(main_widgets.window, "menu_save_all1");
2604 	widgets.redo_items[0] = ui_lookup_widget(main_widgets.editor_menu, "redo1");
2605 	widgets.redo_items[1] = ui_lookup_widget(main_widgets.window, "menu_redo2");
2606 	widgets.undo_items[0] = ui_lookup_widget(main_widgets.editor_menu, "undo1");
2607 	widgets.undo_items[1] = ui_lookup_widget(main_widgets.window, "menu_undo2");
2608 
2609 	/* reparent context submenus as needed */
2610 	{
2611 		GeanySharedMenu arr[] = {
2612 			{"commands2_menu", "commands2", "commands1"},
2613 			{"menu_format1_menu", "menu_format1", "menu_format2"},
2614 			{"more1_menu", "more1", "search2"},
2615 			{NULL, NULL, NULL}
2616 		};
2617 		static GeanySharedMenu items[G_N_ELEMENTS(arr)];
2618 
2619 		memcpy(items, arr, sizeof(arr));
2620 		g_signal_connect(main_widgets.editor_menu, "show", G_CALLBACK(on_editor_menu_show), items);
2621 		g_signal_connect(main_widgets.editor_menu, "hide", G_CALLBACK(on_editor_menu_hide), items);
2622 	}
2623 
2624 	ui_init_toolbar_widgets();
2625 	init_document_widgets();
2626 
2627 	create_config_files_menu();
2628 	add_css_config_file_item();
2629 }
2630 
2631 
ui_finalize_builder(void)2632 void ui_finalize_builder(void)
2633 {
2634 	if (GTK_IS_BUILDER(builder))
2635 		g_object_unref(builder);
2636 
2637 	/* cleanup refs lingering even after GtkBuilder is destroyed */
2638 	if (GTK_IS_WIDGET(edit_menu1))
2639 		gtk_widget_destroy(edit_menu1);
2640 	if (GTK_IS_WIDGET(prefs_dialog))
2641 		gtk_widget_destroy(prefs_dialog);
2642 	if (GTK_IS_WIDGET(project_dialog))
2643 		gtk_widget_destroy(project_dialog);
2644 	if (GTK_IS_WIDGET(toolbar_popup_menu1))
2645 		gtk_widget_destroy(toolbar_popup_menu1);
2646 	if (GTK_IS_WIDGET(window1))
2647 		gtk_widget_destroy(window1);
2648 }
2649 
2650 
auto_separator_update(GeanyAutoSeparator * autosep)2651 static void auto_separator_update(GeanyAutoSeparator *autosep)
2652 {
2653 	g_return_if_fail(autosep->item_count >= 0);
2654 
2655 	if (autosep->widget)
2656 	{
2657 		if (autosep->item_count > 0)
2658 			ui_widget_show_hide(autosep->widget, autosep->show_count > 0);
2659 		else
2660 			gtk_widget_destroy(autosep->widget);
2661 	}
2662 }
2663 
2664 
on_auto_separator_item_show_hide(GtkWidget * widget,gpointer user_data)2665 static void on_auto_separator_item_show_hide(GtkWidget *widget, gpointer user_data)
2666 {
2667 	GeanyAutoSeparator *autosep = user_data;
2668 
2669 	if (gtk_widget_get_visible(widget))
2670 		autosep->show_count++;
2671 	else
2672 		autosep->show_count--;
2673 	auto_separator_update(autosep);
2674 }
2675 
2676 
on_auto_separator_item_destroy(GtkWidget * widget,gpointer user_data)2677 static void on_auto_separator_item_destroy(GtkWidget *widget, gpointer user_data)
2678 {
2679 	GeanyAutoSeparator *autosep = user_data;
2680 
2681 	autosep->item_count--;
2682 	autosep->item_count = MAX(autosep->item_count, 0);
2683 	/* gtk_widget_get_visible() won't work now the widget is being destroyed,
2684 	 * so assume widget was visible */
2685 	autosep->show_count--;
2686 	autosep->show_count = MAX(autosep->item_count, 0);
2687 	auto_separator_update(autosep);
2688 }
2689 
2690 
2691 /* Show the separator widget if @a item or another is visible. */
2692 /* Note: This would be neater taking a widget argument, setting a "visible-count"
2693  * property, and using reference counting to keep the widget alive whilst its visible group
2694  * is alive. */
ui_auto_separator_add_ref(GeanyAutoSeparator * autosep,GtkWidget * item)2695 void ui_auto_separator_add_ref(GeanyAutoSeparator *autosep, GtkWidget *item)
2696 {
2697 	/* set widget ptr NULL when widget destroyed */
2698 	if (autosep->item_count == 0)
2699 		g_signal_connect(autosep->widget, "destroy",
2700 			G_CALLBACK(gtk_widget_destroyed), &autosep->widget);
2701 
2702 	if (gtk_widget_get_visible(item))
2703 		autosep->show_count++;
2704 
2705 	autosep->item_count++;
2706 	auto_separator_update(autosep);
2707 
2708 	g_signal_connect(item, "show", G_CALLBACK(on_auto_separator_item_show_hide), autosep);
2709 	g_signal_connect(item, "hide", G_CALLBACK(on_auto_separator_item_show_hide), autosep);
2710 	g_signal_connect(item, "destroy", G_CALLBACK(on_auto_separator_item_destroy), autosep);
2711 }
2712 
2713 
2714 /**
2715  * Sets @a text as the contents of the tooltip for @a widget.
2716  *
2717  * @param widget The widget the tooltip should be set for.
2718  * @param text The text for the tooltip.
2719  *
2720  * @since 0.16
2721  * @deprecated 0.21 use gtk_widget_set_tooltip_text() instead
2722  */
2723 GEANY_API_SYMBOL
ui_widget_set_tooltip_text(GtkWidget * widget,const gchar * text)2724 void ui_widget_set_tooltip_text(GtkWidget *widget, const gchar *text)
2725 {
2726 	gtk_widget_set_tooltip_text(widget, text);
2727 }
2728 
2729 
2730 /** Returns a widget from a name in a component, usually created by Glade.
2731  * Call it with the toplevel widget in the component (i.e. a window/dialog),
2732  * or alternatively any widget in the component, and the name of the widget
2733  * you want returned.
2734  * @param widget Widget with the @a widget_name property set.
2735  * @param widget_name Name to lookup.
2736  *
2737  * @return @transfer{none} The widget found.
2738  * @see ui_hookup_widget().
2739  *
2740  *  @since 0.16
2741  */
2742 GEANY_API_SYMBOL
ui_lookup_widget(GtkWidget * widget,const gchar * widget_name)2743 GtkWidget *ui_lookup_widget(GtkWidget *widget, const gchar *widget_name)
2744 {
2745 	GtkWidget *parent, *found_widget;
2746 
2747 	g_return_val_if_fail(widget != NULL, NULL);
2748 	g_return_val_if_fail(widget_name != NULL, NULL);
2749 
2750 	for (;;)
2751 	{
2752 		if (GTK_IS_MENU(widget))
2753 			parent = gtk_menu_get_attach_widget(GTK_MENU(widget));
2754 		else
2755 			parent = gtk_widget_get_parent(widget);
2756 		if (parent == NULL)
2757 			parent = (GtkWidget*) g_object_get_data(G_OBJECT(widget), "GladeParentKey");
2758 		if (parent == NULL)
2759 			break;
2760 		widget = parent;
2761 	}
2762 
2763 	found_widget = (GtkWidget*) g_object_get_data(G_OBJECT(widget), widget_name);
2764 	if (G_UNLIKELY(found_widget == NULL))
2765 		g_warning("Widget not found: %s", widget_name);
2766 	return found_widget;
2767 }
2768 
2769 
2770 /* wraps gtk_builder_get_object()
2771  * unlike ui_lookup_widget(), it does only support getting object created from the main
2772  * UI file, but it can fetch any object, not only widgets */
ui_builder_get_object(const gchar * name)2773 gpointer ui_builder_get_object (const gchar *name)
2774 {
2775 	return gtk_builder_get_object (builder, name);
2776 }
2777 
2778 
2779 /* Progress Bar */
2780 static guint progress_bar_timer_id = 0;
2781 
2782 
progress_bar_create(void)2783 static GtkWidget *progress_bar_create(void)
2784 {
2785 	GtkWidget *bar = gtk_progress_bar_new();
2786 
2787 	/* Set the progressbar's height to 1 to fit it in the statusbar */
2788 	gtk_widget_set_size_request(bar, -1, 1);
2789 	gtk_box_pack_start (GTK_BOX(ui_widgets.statusbar), bar, FALSE, FALSE, 3);
2790 
2791 	return bar;
2792 }
2793 
2794 
progress_bar_pulse(gpointer data)2795 static gboolean progress_bar_pulse(gpointer data)
2796 {
2797 	gtk_progress_bar_pulse(GTK_PROGRESS_BAR(main_widgets.progressbar));
2798 
2799 	return TRUE;
2800 }
2801 
2802 
2803 /**
2804  * Starts a constantly pulsing progressbar in the right corner of the statusbar
2805  * (if the statusbar is visible). This is a convenience function which adds a timer to
2806  * pulse the progressbar constantly until ui_progress_bar_stop() is called.
2807  * You can use this function when you have time consuming asynchronous operation and want to
2808  * display some activity in the GUI and when you don't know about detailed progress steps.
2809  * The progressbar widget is hidden by default when it is not active. This function and
2810  * ui_progress_bar_stop() will show and hide it automatically for you.
2811  *
2812  * You can also access the progressbar widget directly using @c geany->main_widgets->progressbar
2813  * and use the GtkProgressBar API to set discrete fractions to display better progress information.
2814  * In this case, you need to show and hide the widget yourself. You can find some example code
2815  * in @c src/printing.c.
2816  *
2817  * @param text @nullable The text to be shown as the progress bar label or @c NULL to leave it empty.
2818  *
2819  *  @since 0.16
2820  **/
2821 GEANY_API_SYMBOL
ui_progress_bar_start(const gchar * text)2822 void ui_progress_bar_start(const gchar *text)
2823 {
2824 	g_return_if_fail(progress_bar_timer_id == 0);
2825 
2826 	if (! interface_prefs.statusbar_visible)
2827 		return;
2828 
2829 	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(main_widgets.progressbar), text);
2830 
2831 	progress_bar_timer_id = g_timeout_add(200, progress_bar_pulse, NULL);
2832 
2833 	gtk_widget_show(GTK_WIDGET(main_widgets.progressbar));
2834 }
2835 
2836 
2837 /** Stops a running progress bar and hides the widget again.
2838  *
2839  *  @since 0.16
2840  **/
2841 GEANY_API_SYMBOL
ui_progress_bar_stop(void)2842 void ui_progress_bar_stop(void)
2843 {
2844 	gtk_widget_hide(GTK_WIDGET(main_widgets.progressbar));
2845 
2846 	if (progress_bar_timer_id != 0)
2847 	{
2848 		g_source_remove(progress_bar_timer_id);
2849 		progress_bar_timer_id = 0;
2850 	}
2851 }
2852 
2853 
compare_menu_item_labels(gconstpointer a,gconstpointer b)2854 static gint compare_menu_item_labels(gconstpointer a, gconstpointer b)
2855 {
2856 	GtkMenuItem *item_a = GTK_MENU_ITEM(a);
2857 	GtkMenuItem *item_b = GTK_MENU_ITEM(b);
2858 	gchar *sa, *sb;
2859 	gint result;
2860 
2861 	/* put entries with submenus at the end of the menu */
2862 	if (gtk_menu_item_get_submenu(item_a) && !gtk_menu_item_get_submenu(item_b))
2863 		return 1;
2864 	else if (!gtk_menu_item_get_submenu(item_a) && gtk_menu_item_get_submenu(item_b))
2865 		return -1;
2866 
2867 	sa = ui_menu_item_get_text(item_a);
2868 	sb = ui_menu_item_get_text(item_b);
2869 	result = utils_str_casecmp(sa, sb);
2870 	g_free(sa);
2871 	g_free(sb);
2872 	return result;
2873 }
2874 
2875 
2876 /* Currently @a menu should contain only GtkMenuItems with labels. */
ui_menu_sort_by_label(GtkMenu * menu)2877 static void ui_menu_sort_by_label(GtkMenu *menu)
2878 {
2879 	GList *list = gtk_container_get_children(GTK_CONTAINER(menu));
2880 	GList *node;
2881 	gint pos;
2882 
2883 	list = g_list_sort(list, compare_menu_item_labels);
2884 	pos = 0;
2885 	foreach_list(node, list)
2886 	{
2887 		menu_reorder_child(menu, node->data, pos);
2888 		pos++;
2889 	}
2890 	g_list_free(list);
2891 }
2892 
2893 
ui_label_set_markup(GtkLabel * label,const gchar * format,...)2894 void ui_label_set_markup(GtkLabel *label, const gchar *format, ...)
2895 {
2896 	va_list a;
2897 	gchar *text;
2898 
2899 	va_start(a, format);
2900 	text = g_markup_vprintf_escaped(format, a);
2901 	va_end(a);
2902 
2903 	gtk_label_set_text(label, text);
2904 	gtk_label_set_use_markup(label, TRUE);
2905 	g_free(text);
2906 }
2907 
2908 
ui_label_new_bold(const gchar * text)2909 GtkWidget *ui_label_new_bold(const gchar *text)
2910 {
2911 	GtkWidget *label;
2912 
2913 	label = gtk_label_new(NULL);
2914 	ui_label_set_markup(GTK_LABEL(label), "<b>%s</b>", text);
2915 	return label;
2916 }
2917 
2918 
2919 /** @girskip
2920  * Adds a list of document items to @a menu.
2921  * @param menu Menu.
2922  * @param active @nullable Which document to highlight, or @c NULL.
2923  * @param callback is used for each menu item's @c "activate" signal and will be
2924  * passed the corresponding document pointer as @c user_data.
2925  * @warning You should check @c doc->is_valid in the callback.
2926  * @since 0.19
2927  **/
2928 GEANY_API_SYMBOL
ui_menu_add_document_items(GtkMenu * menu,GeanyDocument * active,GCallback callback)2929 void ui_menu_add_document_items(GtkMenu *menu, GeanyDocument *active, GCallback callback)
2930 {
2931 	ui_menu_add_document_items_sorted(menu, active, callback, NULL);
2932 }
2933 
2934 
2935 /** @girskip
2936  * Adds a list of document items to @a menu.
2937  *
2938  * @a compare_func might be NULL to not sort the documents in the menu. In this case,
2939  * the order of the document tabs is used.
2940  *
2941  * See document_compare_by_display_name() for an example sort function.
2942  *
2943  * @param menu Menu.
2944  * @param active @nullable Which document to highlight, or @c NULL.
2945  * @param callback is used for each menu item's @c "activate" signal and will be passed
2946  * the corresponding document pointer as @c user_data.
2947  * @param compare_func is used to sort the list. Might be @c NULL to not sort the list.
2948  * @warning You should check @c doc->is_valid in the callback.
2949  * @since 0.21
2950  **/
2951 GEANY_API_SYMBOL
ui_menu_add_document_items_sorted(GtkMenu * menu,GeanyDocument * active,GCallback callback,GCompareFunc compare_func)2952 void ui_menu_add_document_items_sorted(GtkMenu *menu, GeanyDocument *active,
2953 	GCallback callback, GCompareFunc compare_func)
2954 {
2955 	GtkWidget *menu_item, *menu_item_label, *image;
2956 	GeanyDocument *doc;
2957 	guint i, len;
2958 	gchar *base_name;
2959 	GPtrArray *sorted_documents;
2960 
2961 	len = (guint) gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook));
2962 
2963 	sorted_documents = g_ptr_array_sized_new(len);
2964 	/* copy the documents_array into the new one */
2965 	foreach_document(i)
2966 	{
2967 		g_ptr_array_add(sorted_documents, documents[i]);
2968 	}
2969 	if (compare_func == NULL)
2970 		compare_func = document_compare_by_tab_order;
2971 
2972 	/* and now sort it */
2973 	g_ptr_array_sort(sorted_documents, compare_func);
2974 
2975 	for (i = 0; i < sorted_documents->len; i++)
2976 	{
2977 		doc = g_ptr_array_index(sorted_documents, i);
2978 
2979 		base_name = g_path_get_basename(DOC_FILENAME(doc));
2980 		menu_item = gtk_image_menu_item_new_with_label(base_name);
2981 		image = gtk_image_new_from_gicon(doc->file_type->icon, GTK_ICON_SIZE_MENU);
2982 		gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image);
2983 		gtk_widget_set_tooltip_text(menu_item, DOC_FILENAME(doc));
2984 
2985 		gtk_widget_show(menu_item);
2986 		gtk_container_add(GTK_CONTAINER(menu), menu_item);
2987 		g_signal_connect(menu_item, "activate", callback, doc);
2988 
2989 		menu_item_label = gtk_bin_get_child(GTK_BIN(menu_item));
2990 		gtk_widget_set_name(menu_item_label, document_get_status_widget_class(doc));
2991 
2992 		if (doc == active)
2993 			ui_label_set_markup(GTK_LABEL(menu_item_label), "<b>%s</b>", base_name);
2994 
2995 		g_free(base_name);
2996 	}
2997 	g_ptr_array_free(sorted_documents, TRUE);
2998 }
2999 
3000 
3001 /** Checks whether the passed @a keyval is the Enter or Return key.
3002  * There are three different Enter/Return key values
3003  * (@c GDK_KEY_Return, @c GDK_KEY_ISO_Enter, @c GDK_KEY_KP_Enter).
3004  * This is just a convenience function.
3005  * @param keyval A keyval.
3006  * @return @c TRUE if @a keyval is the one of the Enter/Return key values, otherwise @c FALSE.
3007  * @since 0.19 */
3008 GEANY_API_SYMBOL
ui_is_keyval_enter_or_return(guint keyval)3009 gboolean ui_is_keyval_enter_or_return(guint keyval)
3010 {
3011 	return (keyval == GDK_KEY_Return || keyval == GDK_KEY_ISO_Enter|| keyval == GDK_KEY_KP_Enter);
3012 }
3013 
3014 
3015 /** Reads an integer from the GTK default settings registry
3016  * (see http://library.gnome.org/devel/gtk/stable/GtkSettings.html).
3017  * @param property_name The property to read.
3018  * @param default_value The default value in case the value could not be read.
3019  * @return The value for the property if it exists, otherwise the @a default_value.
3020  * @since 0.19 */
3021 GEANY_API_SYMBOL
ui_get_gtk_settings_integer(const gchar * property_name,gint default_value)3022 gint ui_get_gtk_settings_integer(const gchar *property_name, gint default_value)
3023 {
3024 	if (g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(
3025 		gtk_settings_get_default())), property_name))
3026 	{
3027 		gint value;
3028 		g_object_get(G_OBJECT(gtk_settings_get_default()), property_name, &value, NULL);
3029 		return value;
3030 	}
3031 	else
3032 		return default_value;
3033 }
3034 
3035 
ui_editable_insert_text_callback(GtkEditable * editable,gchar * new_text,gint new_text_len,gint * position,gpointer data)3036 void ui_editable_insert_text_callback(GtkEditable *editable, gchar *new_text,
3037 									  gint new_text_len, gint *position, gpointer data)
3038 {
3039 	gboolean first = position != NULL && *position == 0;
3040 	gint i;
3041 
3042 	if (new_text_len == -1)
3043 		new_text_len = (gint) strlen(new_text);
3044 
3045 	for (i = 0; i < new_text_len; i++, new_text++)
3046 	{
3047 		if ((!first || !strchr("+-", *new_text)) && !isdigit(*new_text))
3048 		{
3049 			g_signal_stop_emission_by_name(editable, "insert-text");
3050 			break;
3051 		}
3052 		first = FALSE;
3053 	}
3054 }
3055 
3056 
3057 /* gets the icon that applies to a particular MIME type */
ui_get_mime_icon(const gchar * mime_type)3058 GIcon *ui_get_mime_icon(const gchar *mime_type)
3059 {
3060 	GIcon *icon = NULL;
3061 	gchar *ctype;
3062 
3063 	ctype = g_content_type_from_mime_type(mime_type);
3064 	if (ctype)
3065 	{
3066 		GdkScreen *screen = gdk_screen_get_default();
3067 
3068 		icon = g_content_type_get_icon(ctype);
3069 		if (screen && icon)
3070 		{
3071 			GtkIconInfo *icon_info;
3072 
3073 			icon_info = gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_for_screen(screen), icon, 16, 0);
3074 			if (!icon_info)
3075 			{
3076 				g_object_unref(icon);
3077 				icon = NULL;
3078 			}
3079 			else
3080 				gtk_icon_info_free(icon_info);
3081 		}
3082 
3083 		g_free(ctype);
3084 	}
3085 
3086 	/* fallback if icon lookup failed, like it might happen on Windows (?) */
3087 	if (! icon)
3088 	{
3089 		const gchar *icon_name = "text-x-generic";
3090 
3091 		if (strstr(mime_type, "directory"))
3092 			icon_name = "folder";
3093 
3094 		icon = g_themed_icon_new(icon_name);
3095 	}
3096 	return icon;
3097 }
3098 
3099 
ui_focus_current_document(void)3100 void ui_focus_current_document(void)
3101 {
3102 	GeanyDocument *doc = document_get_current();
3103 
3104 	if (doc != NULL)
3105 		document_grab_focus(doc);
3106 }
3107 
3108 
3109 /** Finds the label text associated with stock_id
3110  * @param stock_id stock_id to lookup e.g. @c GTK_STOCK_OPEN.
3111  * @return The label text for stock
3112  * @since Geany 1.22 */
3113 GEANY_API_SYMBOL
ui_lookup_stock_label(const gchar * stock_id)3114 const gchar *ui_lookup_stock_label(const gchar *stock_id)
3115 {
3116 	GtkStockItem item;
3117 
3118 	if (gtk_stock_lookup(stock_id, &item))
3119 		return item.label;
3120 
3121 	g_warning("No stock id '%s'!", stock_id);
3122 	return NULL;
3123 }
3124 
3125 
3126 /* finds the next iter at any level
3127  * @param iter in/out, the current iter, will be changed to the next one
3128  * @param down whether to try the child iter
3129  * @return TRUE if there @p iter was set, or FALSE if there is no next iter */
ui_tree_model_iter_any_next(GtkTreeModel * model,GtkTreeIter * iter,gboolean down)3130 gboolean ui_tree_model_iter_any_next(GtkTreeModel *model, GtkTreeIter *iter, gboolean down)
3131 {
3132 	GtkTreeIter guess;
3133 	GtkTreeIter copy = *iter;
3134 
3135 	/* go down if the item has children */
3136 	if (down && gtk_tree_model_iter_children(model, &guess, iter))
3137 		*iter = guess;
3138 	/* or to the next item at the same level */
3139 	else if (gtk_tree_model_iter_next(model, &copy))
3140 		*iter = copy;
3141 	/* or to the next item at a parent level */
3142 	else if (gtk_tree_model_iter_parent(model, &guess, iter))
3143 	{
3144 		copy = guess;
3145 		while (TRUE)
3146 		{
3147 			if (gtk_tree_model_iter_next(model, &copy))
3148 			{
3149 				*iter = copy;
3150 				return TRUE;
3151 			}
3152 			else if (gtk_tree_model_iter_parent(model, &copy, &guess))
3153 				guess = copy;
3154 			else
3155 				return FALSE;
3156 		}
3157 	}
3158 	else
3159 		return FALSE;
3160 
3161 	return TRUE;
3162 }
3163 
3164 
ui_create_encodings_combo_box(gboolean has_detect,gint default_enc)3165 GtkWidget *ui_create_encodings_combo_box(gboolean has_detect, gint default_enc)
3166 {
3167 	GtkCellRenderer *renderer;
3168 	GtkTreeIter iter;
3169 	GtkWidget *combo = gtk_combo_box_new();
3170 	GtkTreeStore *store = encodings_encoding_store_new(has_detect);
3171 
3172 	if (default_enc < 0 || default_enc >= GEANY_ENCODINGS_MAX)
3173 		default_enc = has_detect ? GEANY_ENCODINGS_MAX : GEANY_ENCODING_NONE;
3174 
3175 	gtk_combo_box_set_model(GTK_COMBO_BOX(combo), GTK_TREE_MODEL(store));
3176 	if (encodings_encoding_store_get_iter(store, &iter, default_enc))
3177 		gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter);
3178 	renderer = gtk_cell_renderer_text_new();
3179 	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
3180 	gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo), renderer,
3181 			encodings_encoding_store_cell_data_func, NULL, NULL);
3182 
3183 	return combo;
3184 }
3185 
3186 
ui_encodings_combo_box_get_active_encoding(GtkComboBox * combo)3187 gint ui_encodings_combo_box_get_active_encoding(GtkComboBox *combo)
3188 {
3189 	GtkTreeIter iter;
3190 	gint enc = GEANY_ENCODING_NONE;
3191 
3192 	/* there should always be an active iter anyway, but we check just in case */
3193 	if (gtk_combo_box_get_active_iter(combo, &iter))
3194 	{
3195 		GtkTreeModel *model = gtk_combo_box_get_model(combo);
3196 		enc = encodings_encoding_store_get_encoding(GTK_TREE_STORE(model), &iter);
3197 	}
3198 
3199 	return enc;
3200 }
3201 
3202 
ui_encodings_combo_box_set_active_encoding(GtkComboBox * combo,gint enc)3203 gboolean ui_encodings_combo_box_set_active_encoding(GtkComboBox *combo, gint enc)
3204 {
3205 	GtkTreeIter iter;
3206 	GtkTreeModel *model = gtk_combo_box_get_model(combo);
3207 
3208 	if (encodings_encoding_store_get_iter(GTK_TREE_STORE(model), &iter, enc))
3209 	{
3210 		gtk_combo_box_set_active_iter(combo, &iter);
3211 		return TRUE;
3212 	}
3213 	return FALSE;
3214 }
3215