1 /*
2  *      geanyvc.c - Plugin to geany light IDE to work with vc
3  *
4  *      Copyright 2007-2011 Frank Lanitz <frank(at)frank(dot)uvena(dot)de>
5  *      Copyright 2007-2009 Enrico Tröger <enrico.troeger@uvena.de>
6  *      Copyright 2007 Nick Treleaven <nick.treleaven@btinternet.com>
7  *      Copyright 2007-2009 Yura Siamashka <yurand2@gmail.com>
8  *      Copyright 2020 Artur Shepilko <nomadbyte@gmail.com>
9  *
10  *      This program is free software; you can redistribute it and/or modify
11  *      it under the terms of the GNU General Public License as published by
12  *      the Free Software Foundation; either version 2 of the License, or
13  *      (at your option) any later version.
14  *
15  *      This program is distributed in the hope that it will be useful,
16  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *      GNU General Public License for more details.
19  *
20  *      You should have received a copy of the GNU General Public License
21  *      along with this program; if not, write to the Free Software
22  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23  */
24 
25 /* VC plugin */
26 /* This plugin allows to work with cvs/svn/git/bzr/fossil inside geany light IDE. */
27 
28 #include <string.h>
29 #include <glib.h>
30 #include <glib/gstdio.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <unistd.h>
33 
34 #ifdef HAVE_CONFIG_H
35 	#include "config.h"
36 #endif
37 #include <geanyplugin.h>
38 
39 #include "geanyvc.h"
40 #include "SciLexer.h"
41 
42 #ifdef USE_GTKSPELL
43 #include <gtkspell/gtkspell.h>
44 /* forward compatibility with GtkSpell3 */
45 #if GTK_CHECK_VERSION(3, 0, 0)
46 #define GtkSpell GtkSpellChecker
47 #define gtkspell_set_language gtk_spell_checker_set_language
gtkspell_new_attach(GtkTextView * view,const gchar * lang,GError ** error)48 static GtkSpell *gtkspell_new_attach(GtkTextView *view, const gchar *lang, GError **error)
49 {
50 	GtkSpellChecker *speller = gtk_spell_checker_new();
51 
52 	if (! lang || gtk_spell_checker_set_language(speller, lang, error))
53 		gtk_spell_checker_attach(speller, view);
54 	else
55 	{
56 		g_object_unref(g_object_ref_sink(speller));
57 		speller = NULL;
58 	}
59 
60 	return speller;
61 }
62 #endif
63 #endif
64 
65 GeanyData *geany_data;
66 GeanyPlugin     *geany_plugin;
67 
68 
69 PLUGIN_VERSION_CHECK(224)
70 PLUGIN_SET_TRANSLATABLE_INFO(
71 	LOCALEDIR,
72 	GETTEXT_PACKAGE,
73 	_("GeanyVC"),
74 	_("Interface to different Version Control systems."
75 	  "\nThis plugins is currently not developed actively. "
76 	  "Would you like to help by contributing to this plugin?"),
77 	VERSION,
78 	"Yura Siamashka <yurand2@gmail.com>,\nFrank Lanitz <frank@frank.uvena.de>")
79 
80 /* Some global variables */
81 static gboolean set_changed_flag;
82 static gboolean set_add_confirmation;
83 static gboolean set_maximize_commit_dialog;
84 static gboolean set_external_diff;
85 static gboolean set_editor_menu_entries;
86 static gboolean set_menubar_entry;
87 static gint commit_dialog_width = 0;
88 static gint commit_dialog_height = 0;
89 static GSList *commit_message_history = NULL;
90 
91 static gchar *config_file;
92 
93 static gboolean enable_cvs;
94 static gboolean enable_git;
95 static gboolean enable_fossil;
96 static gboolean enable_svn;
97 static gboolean enable_svk;
98 static gboolean enable_bzr;
99 static gboolean enable_hg;
100 
101 #ifdef USE_GTKSPELL
102 static gchar *lang;
103 #endif
104 
105 static GSList *VC = NULL;
106 
107 /* The addresses of these strings act as enums, their contents are not used. */
108 /* absolute path dirname of file */
109 const gchar ABS_DIRNAME[] = "*ABS_DIRNAME*";
110 /* absolute path filename of file */
111 const gchar ABS_FILENAME[] = "*ABS_FILENAME*";
112 
113 /* path to directory from base vc directory */
114 const gchar BASE_DIRNAME[] = "*BASE_DIRNAME*";
115 /* path to file from base vc directory */
116 const gchar BASE_FILENAME[] = "*BASE_FILENAME*";
117 
118 /* basename of file */
119 const gchar BASENAME[] = "*BASENAME*";
120 /* list with absolute file names*/
121 const gchar FILE_LIST[] = "*FILE_LIST*";
122 /* message */
123 const gchar MESSAGE[] = "*MESSAGE*";
124 
125 
126 /* this string is used when action require to run several commands */
127 const gchar CMD_SEPARATOR[] = "*CMD-SEPARATOR*";
128 const gchar CMD_FUNCTION[] = "*CUSTOM_FUNCTION*";
129 
130 /* commit status */
131 const gchar FILE_STATUS_MODIFIED[] = "Modified";
132 const gchar FILE_STATUS_ADDED[] = "Added";
133 const gchar FILE_STATUS_DELETED[] = "Deleted";
134 const gchar FILE_STATUS_UNKNOWN[] = "Unknown";
135 
136 static GtkWidget *editor_menu_vc = NULL;
137 static GtkWidget *editor_menu_commit = NULL;
138 static GtkWidget *menu_item_sep = NULL;
139 static GtkWidget *menu_entry = NULL;
140 
141 
142 static void registrate(void);
143 static void add_menuitems_to_editor_menu(void);
144 static void remove_menuitems_from_editor_menu(void);
145 
146 
147 /* Doing some basic keybinding stuff */
148 enum
149 {
150 	VC_DIFF_FILE,
151 	VC_DIFF_DIR,
152 	VC_DIFF_BASEDIR,
153 	VC_COMMIT,
154 	VC_STATUS,
155 	VC_UPDATE,
156 	VC_REVERT_FILE,
157 	VC_REVERT_DIR,
158 	VC_REVERT_BASEDIR,
159 	COUNT_KB
160 };
161 
162 enum
163 {
164 	COMMIT_MESSAGE_HISTORY_DISPLAY_TEXT = 0,
165 	COMMIT_MESSAGE_HISTORY_FULL_TEXT,
166 	COMMIT_MESSAGE_HISTORY_ACTIVE,
167 	COMMIT_MESSAGE_HISTORY_NUM_COLUMNS
168 };
169 #define COMMIT_MESSAGE_HISTORY_LENGTH 10
170 
171 
get_commit_files_null(G_GNUC_UNUSED const gchar * dir)172 GSList *get_commit_files_null(G_GNUC_UNUSED const gchar * dir)
173 {
174 	return NULL;
175 }
176 
177 static void
free_text_list(GSList * lst)178 free_text_list(GSList * lst)
179 {
180 	GSList *tmp;
181 	if (!lst)
182 		return;
183 	for (tmp = lst; tmp != NULL; tmp = g_slist_next(tmp))
184 	{
185 		g_free((CommitItem *) (tmp->data));
186 	}
187 	g_slist_free(lst);
188 }
189 
190 static void
free_commit_list(GSList * lst)191 free_commit_list(GSList * lst)
192 {
193 	GSList *tmp;
194 	if (!lst)
195 		return;
196 	for (tmp = lst; tmp != NULL; tmp = g_slist_next(tmp))
197 	{
198 		g_free(((CommitItem *) (tmp->data))->path);
199 		g_free((CommitItem *) (tmp->data));
200 	}
201 	g_slist_free(lst);
202 }
203 
204 gchar *
find_subdir_path(const gchar * filename,const gchar * subdir)205 find_subdir_path(const gchar * filename, const gchar * subdir)
206 {
207 	gboolean ret = FALSE;
208 	gchar *base;
209 	gchar *gitdir;
210 	gchar *base_prev = g_strdup(":");
211 
212 	if (g_file_test(filename, G_FILE_TEST_IS_DIR))
213 		base = g_strdup(filename);
214 	else
215 		base = g_path_get_dirname(filename);
216 
217 	while (strcmp(base, base_prev) != 0)
218 	{
219 		gitdir = g_build_filename(base, subdir, NULL);
220 		ret = g_file_test(gitdir, G_FILE_TEST_IS_DIR);
221 		g_free(gitdir);
222 		if (ret)
223 			break;
224 		g_free(base_prev);
225 		base_prev = base;
226 		base = g_path_get_dirname(base);
227 	}
228 
229 	g_free(base_prev);
230 	if (ret)
231 		return base;
232 	g_free(base);
233 	return NULL;
234 }
235 
236 static gboolean
find_subdir(const gchar * filename,const gchar * subdir)237 find_subdir(const gchar * filename, const gchar * subdir)
238 {
239 	gchar *basedir;
240 	basedir = find_subdir_path(filename, subdir);
241 	if (basedir)
242 	{
243 		g_free(basedir);
244 		return TRUE;
245 	}
246 	return FALSE;
247 }
248 
249 gboolean
find_dir(const gchar * filename,const char * find,gboolean recursive)250 find_dir(const gchar * filename, const char *find, gboolean recursive)
251 {
252 	gboolean ret;
253 	gchar *base;
254 	gchar *dir;
255 
256 	if (!filename)
257 		return FALSE;
258 
259 	if (recursive)
260 	{
261 		ret = find_subdir(filename, find);
262 	}
263 	else
264 	{
265 		if (g_file_test(filename, G_FILE_TEST_IS_DIR))
266 			base = g_strdup(filename);
267 		else
268 			base = g_path_get_dirname(filename);
269 		dir = g_build_filename(base, find, NULL);
270 
271 		ret = g_file_test(dir, G_FILE_TEST_IS_DIR);
272 
273 		g_free(base);
274 		g_free(dir);
275 	}
276 	return ret;
277 }
278 
279 
280 static const VC_RECORD *
find_vc(const char * filename)281 find_vc(const char *filename)
282 {
283 	GSList *tmp;
284 
285 	for (tmp = VC; tmp != NULL; tmp = g_slist_next(tmp))
286 	{
287 		if (((VC_RECORD *) tmp->data)->in_vc(filename))
288 		{
289 			return (VC_RECORD *) tmp->data;
290 		}
291 	}
292 	return NULL;
293 }
294 
295 static void *
find_cmd_env(gint cmd_type,gboolean cmd,const gchar * filename)296 find_cmd_env(gint cmd_type, gboolean cmd, const gchar * filename)
297 {
298 	const VC_RECORD *vc;
299 	vc = find_vc(filename);
300 	if (vc)
301 	{
302 		if (cmd)
303 			return vc->commands[cmd_type].command;
304 		else
305 			return vc->commands[cmd_type].env;
306 	}
307 	return NULL;
308 }
309 
310 /* Get list of commands for given command spec*/
311 static GSList *
get_cmd(const gchar ** argv,const gchar * dir,const gchar * filename,GSList * filelist,const gchar * message)312 get_cmd(const gchar ** argv, const gchar * dir, const gchar * filename, GSList * filelist,
313 	const gchar * message)
314 {
315 	gint i, j;
316 	gint len = 0;
317 	gchar **ret;
318 	gchar *abs_dir;
319 	gchar *base_filename;
320 	gchar *base_dirname;
321 	gchar *basename;
322 	GSList *head = NULL;
323 	GSList *tmp;
324 	GString *repl;
325 
326 	if (g_file_test(filename, G_FILE_TEST_IS_DIR))
327 		abs_dir = g_strdup(filename);
328 	else
329 		abs_dir = g_path_get_dirname(filename);
330 	basename = g_path_get_basename(filename);
331 	base_filename = get_relative_path(dir, filename);
332 	base_dirname = get_relative_path(dir, abs_dir);
333 
334 	while (1)
335 	{
336 		if (argv[len] == NULL)
337 			break;
338 		len++;
339 	}
340 	if (filelist)
341 		ret = g_malloc0(sizeof(gchar *) * (len * g_slist_length(filelist) + 1));
342 	else
343 		ret = g_malloc0(sizeof(gchar *) * (len + 1));
344 
345 	head = g_slist_alloc();
346 	head->data = ret;
347 
348 	for (i = 0, j = 0; i < len; i++, j++)
349 	{
350 		if (argv[i] == CMD_SEPARATOR)
351 		{
352 			if (filelist)
353 				ret = g_malloc0(sizeof(gchar *) *
354 						(len * g_slist_length(filelist) + 1));
355 			else
356 				ret = g_malloc0(sizeof(gchar *) * (len + 1));
357 			j = -1;
358 			head = g_slist_append(head, ret);
359 		}
360 		else if (argv[i] == ABS_DIRNAME)
361 		{
362 			ret[j] = utils_get_locale_from_utf8(abs_dir);
363 		}
364 		else if (argv[i] == ABS_FILENAME)
365 		{
366 			ret[j] = utils_get_locale_from_utf8(filename);
367 		}
368 		else if (argv[i] == BASE_DIRNAME)
369 		{
370 			ret[j] = utils_get_locale_from_utf8(base_dirname);
371 		}
372 		else if (argv[i] == BASE_FILENAME)
373 		{
374 			ret[j] = utils_get_locale_from_utf8(base_filename);
375 		}
376 		else if (argv[i] == BASENAME)
377 		{
378 			ret[j] = utils_get_locale_from_utf8(basename);
379 		}
380 		else if (argv[i] == FILE_LIST)
381 		{
382 			for (tmp = filelist; tmp != NULL; tmp = g_slist_next(tmp))
383 			{
384 				ret[j] = utils_get_locale_from_utf8((gchar *) tmp->data);
385 				j++;
386 			}
387 			j--;
388 		}
389 		else if (argv[i] == MESSAGE)
390 		{
391 			ret[j] = utils_get_locale_from_utf8(message);
392 		}
393 		else
394 		{
395 			repl = g_string_new(argv[i]);
396 			utils_string_replace_all(repl, P_ABS_DIRNAME, abs_dir);
397 			utils_string_replace_all(repl, P_ABS_FILENAME, filename);
398 			utils_string_replace_all(repl, P_BASENAME, basename);
399 			ret[j] = g_string_free(repl, FALSE);
400 			setptr(ret[j], utils_get_locale_from_utf8(ret[j]));
401 		}
402 	}
403 	g_free(abs_dir);
404 	g_free(base_dirname);
405 	g_free(base_filename);
406 	g_free(basename);
407 	return head;
408 }
409 
410 
411 /* name should be in UTF-8, and can have a path. */
412 static void
show_output(const gchar * std_output,const gchar * name,const gchar * force_encoding,GeanyFiletype * ftype,gint line)413 show_output(const gchar * std_output, const gchar * name,
414 		const gchar * force_encoding, GeanyFiletype * ftype,
415 		gint line)
416 {
417 	GeanyDocument *doc, *cur_doc;
418 
419 	if (std_output)
420 	{
421 		cur_doc = document_get_current();
422 		doc = document_find_by_filename(name);
423 		if (doc == NULL)
424 		{
425 			doc = document_new_file(name, ftype, std_output);
426 		}
427 		else
428 		{
429 			sci_set_text(doc->editor->sci, std_output);
430 			if (ftype)
431 				document_set_filetype(doc, ftype);
432 		}
433 
434 		document_set_text_changed(doc, set_changed_flag);
435 		document_set_encoding(doc, (force_encoding ? force_encoding : "UTF-8"));
436 
437 		navqueue_goto_line(cur_doc, doc, MAX(line + 1, 1));
438 
439 	}
440 	else
441 	{
442 		ui_set_statusbar(FALSE, _("Could not parse the output of command"));
443 	}
444 }
445 
446 /*
447  * Execute command by command spec, return std_out std_err
448  *
449  * @dir - start directory of command
450  * @argv - command spec
451  * @env - envirounment
452  * @std_out - if not NULL here will be returned standard output converted to utf8 of last command in spec
453  * @std_err - if not NULL here will be returned standard error converted to utf8 of last command in spec
454  * @filename - filename for spec, commands will be running in it's basedir . Used to replace FILENAME, BASE_FILENAME in spec
455  * @list - used to replace FILE_LIST in spec
456  * @message - used to replace MESSAGE in spec
457  *
458  * @return - exit code of last command in spec
459  */
460 gint
execute_custom_command(const gchar * dir,const gchar ** argv,const gchar ** env,gchar ** std_out,gchar ** std_err,const gchar * filename,GSList * list,const gchar * message)461 execute_custom_command(const gchar * dir, const gchar ** argv, const gchar ** env, gchar ** std_out,
462 			   gchar ** std_err, const gchar * filename, GSList * list,
463 			   const gchar * message)
464 {
465 	gint exit_code;
466 	GString *tmp;
467 	GSList *cur;
468 	GSList *largv = get_cmd(argv, dir, filename, list, message);
469 	GError *error = NULL;
470 
471 	if (std_out)
472 		*std_out = NULL;
473 	if (std_err)
474 		*std_err = NULL;
475 
476 	if (!largv)
477 	{
478 		return 0;
479 	}
480 	for (cur = largv; cur != NULL; cur = g_slist_next(cur))
481 	{
482 		argv = cur->data;
483 		if (cur != g_slist_last(largv))
484 		{
485 			utils_spawn_sync(dir, cur->data, (gchar **) env,
486 					 G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL |
487 					 G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL,
488 					 &exit_code, &error);
489 		}
490 		else
491 		{
492 			utils_spawn_sync(dir, cur->data, (gchar **) env,
493 					 G_SPAWN_SEARCH_PATH | (std_out ? 0 :
494 								G_SPAWN_STDOUT_TO_DEV_NULL) |
495 					 (std_err ? 0 : G_SPAWN_STDERR_TO_DEV_NULL), NULL, NULL,
496 					 std_out, std_err, &exit_code, &error);
497 		}
498 		if (error)
499 		{
500 			g_warning("geanyvc: s_spawn_sync error: %s", error->message);
501 			ui_set_statusbar(FALSE, _("geanyvc: s_spawn_sync error: %s"),
502 					 error->message);
503 			g_error_free(error);
504 		}
505 
506 		/* need to convert output text from the encoding of the original file into
507 		   UTF-8 because internally Geany always needs UTF-8 */
508 		if (std_out && *std_out)
509 		{
510 			tmp = g_string_new(*std_out);
511 			utils_string_replace_all(tmp, "\r\n", "\n");
512 			utils_string_replace_all(tmp, "\r", "\n");
513 			setptr(*std_out, g_string_free(tmp, FALSE));
514 
515 			if (!g_utf8_validate(*std_out, -1, NULL))
516 			{
517 				setptr(*std_out, encodings_convert_to_utf8(*std_out,
518 									   strlen(*std_out), NULL));
519 			}
520 			if (EMPTY(*std_out))
521 			{
522 				g_free(*std_out);
523 				*std_out = NULL;
524 			}
525 		}
526 		if (std_err && *std_err)
527 		{
528 			tmp = g_string_new(*std_err);
529 			utils_string_replace_all(tmp, "\r\n", "\n");
530 			utils_string_replace_all(tmp, "\r", "\n");
531 			setptr(*std_err, g_string_free(tmp, FALSE));
532 
533 			if (!g_utf8_validate(*std_err, -1, NULL))
534 			{
535 				setptr(*std_err, encodings_convert_to_utf8(*std_err,
536 									   strlen(*std_err), NULL));
537 			}
538 			if (EMPTY(*std_err))
539 			{
540 				g_free(*std_err);
541 				*std_err = NULL;
542 			}
543 		}
544 		g_strfreev(cur->data);
545 	}
546 	g_slist_free(largv);
547 	return exit_code;
548 }
549 
550 static gint
execute_command(const VC_RECORD * vc,gchar ** std_out,gchar ** std_err,const gchar * filename,gint cmd,GSList * list,const gchar * message)551 execute_command(const VC_RECORD * vc, gchar ** std_out, gchar ** std_err, const gchar * filename,
552 		gint cmd, GSList * list, const gchar * message)
553 {
554 	gchar *dir = NULL;
555 	gint ret;
556 	const gint action_command_cell = 1;
557 
558 	if (std_out)
559 		*std_out = NULL;
560 	if (std_err)
561 		*std_err = NULL;
562 
563 	if (vc->commands[cmd].function)
564 	{
565 		return vc->commands[cmd].function(std_out, std_err, filename, list, message);
566 	}
567 
568 	if (vc->commands[cmd].startdir == VC_COMMAND_STARTDIR_FILE)
569 	{
570 		if (g_file_test(filename, G_FILE_TEST_IS_DIR))
571 			dir = g_strdup(filename);
572 		else
573 			dir = g_path_get_dirname(filename);
574 	}
575 	else if (vc->commands[cmd].startdir == VC_COMMAND_STARTDIR_BASE)
576 	{
577 		dir = vc->get_base_dir(filename);
578 	}
579 	else
580 	{
581 		g_warning("geanyvc: unknown startdir type: %d", vc->commands[cmd].startdir);
582 	}
583 
584 	ret = execute_custom_command(dir, vc->commands[cmd].command, vc->commands[cmd].env, std_out,
585 					 std_err, filename, list, message);
586 
587 	ui_set_statusbar(TRUE, _("File %s: action %s executed via %s."),
588 			 filename, vc->commands[cmd].command[action_command_cell], vc->program);
589 
590 	g_free(dir);
591 	return ret;
592 }
593 
594 static gint
get_command_exit_status(gint exit_code)595 get_command_exit_status(gint exit_code)
596 {
597 	return (SPAWN_WIFEXITED(exit_code) ? SPAWN_WEXITSTATUS(exit_code) : exit_code);
598 }
599 
600 /* Callback if menu item for a single file was activated */
601 static void
vcdiff_file_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)602 vcdiff_file_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer gdata)
603 {
604 	gchar *text = NULL;
605 	gchar *new, *old;
606 	gchar *name;
607 	gchar *localename;
608 	const VC_RECORD *vc;
609 	GeanyDocument *doc;
610 
611 	doc = document_get_current();
612 	g_return_if_fail(doc != NULL && doc->file_name != NULL);
613 
614 	if (doc->changed)
615 	{
616 		document_save_file(doc, FALSE);
617 	}
618 
619 	vc = find_vc(doc->file_name);
620 	g_return_if_fail(vc);
621 
622 	execute_command(vc, &text, NULL, doc->file_name, VC_COMMAND_DIFF_FILE, NULL, NULL);
623 	if (text)
624 	{
625 		if (set_external_diff && get_external_diff_viewer())
626 		{
627 			g_free(text);
628 
629 			/*  1) rename file to file.geany.~NEW~
630 			   2) revert file
631 			   3) rename file to file.geanyvc.~BASE~
632 			   4) rename file.geany.~NEW~ to origin file
633 			   5) show diff
634 			 */
635 			localename = utils_get_locale_from_utf8(doc->file_name);
636 
637 			new = g_strconcat(doc->file_name, ".geanyvc.~NEW~", NULL);
638 			setptr(new, utils_get_locale_from_utf8(new));
639 
640 			old = g_strconcat(doc->file_name, ".geanyvc.~BASE~", NULL);
641 			setptr(old, utils_get_locale_from_utf8(old));
642 
643 			if (g_rename(localename, new) != 0)
644 			{
645 				g_warning(_
646 					  ("geanyvc: vcdiff_file_activated: Unable to rename '%s' to '%s'"),
647 					  localename, new);
648 				goto end;
649 			}
650 
651 			execute_command(vc, NULL, NULL, doc->file_name,
652 					VC_COMMAND_REVERT_FILE, NULL, NULL);
653 
654 			if (g_rename(localename, old) != 0)
655 			{
656 				g_warning(_
657 					  ("geanyvc: vcdiff_file_activated: Unable to rename '%s' to '%s'"),
658 					  localename, old);
659 				g_rename(new, localename);
660 				goto end;
661 			}
662 			g_rename(new, localename);
663 
664 			vc_external_diff(old, localename);
665 			g_unlink(old);
666 			  end:
667 			g_free(old);
668 			g_free(new);
669 			g_free(localename);
670 			return;
671 		}
672 		else
673 		{
674 			name = g_strconcat(doc->file_name, ".vc.diff", NULL);
675 			show_output(text, name, doc->encoding, NULL, 0);
676 			g_free(text);
677 			g_free(name);
678 		}
679 
680 	}
681 	else
682 	{
683 		ui_set_statusbar(FALSE, _("No changes were made."));
684 	}
685 }
686 
687 
688 
689 /* Callback if menu item for the base directory was activated */
690 static void
vcdiff_dir_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,gpointer data)691 vcdiff_dir_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, gpointer data)
692 {
693 	gchar *text = NULL;
694 	gchar *dir;
695 	gint flags = GPOINTER_TO_INT(data);
696 	const VC_RECORD *vc;
697 	GeanyDocument *doc;
698 
699 	doc = document_get_current();
700 	g_return_if_fail(doc != NULL && doc->file_name != NULL);
701 
702 	if (doc->changed)
703 	{
704 		document_save_file(doc, FALSE);
705 	}
706 
707 	vc = find_vc(doc->file_name);
708 	g_return_if_fail(vc);
709 
710 	if (flags & FLAG_BASEDIR)
711 	{
712 		dir = vc->get_base_dir(doc->file_name);
713 	}
714 	else if (flags & FLAG_DIR)
715 	{
716 		dir = g_path_get_dirname(doc->file_name);
717 	}
718 	else
719 		return;
720 	g_return_if_fail(dir);
721 
722 	execute_command(vc, &text, NULL, dir, VC_COMMAND_DIFF_DIR, NULL, NULL);
723 	if (text)
724 	{
725 		gchar *name;
726 		name = g_strconcat(dir, ".vc.diff", NULL);
727 		show_output(text, name, doc->encoding, NULL, 0);
728 		g_free(text);
729 		g_free(name);
730 	}
731 	else
732 	{
733 		ui_set_statusbar(FALSE, _("No changes were made."));
734 	}
735 	g_free(dir);
736 }
737 
738 static void
vcblame_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)739 vcblame_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer gdata)
740 {
741 	gchar *text = NULL;
742 	const VC_RECORD *vc;
743 	GeanyDocument *doc;
744 
745 	doc = document_get_current();
746 	g_return_if_fail(doc != NULL && doc->file_name != NULL);
747 
748 	vc = find_vc(doc->file_name);
749 	g_return_if_fail(vc);
750 
751 	execute_command(vc, &text, NULL, doc->file_name, VC_COMMAND_BLAME, NULL, NULL);
752 	if (text)
753 	{
754 		show_output(text, "*VC-BLAME*", NULL,
755 			doc->file_type, sci_get_current_line(doc->editor->sci));
756 		g_free(text);
757 	}
758 	else
759 	{
760 		ui_set_statusbar(FALSE, _("No history available"));
761 	}
762 }
763 
764 
765 static void
vclog_file_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)766 vclog_file_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer gdata)
767 {
768 	gchar *output = NULL;
769 	const VC_RECORD *vc;
770 	GeanyDocument *doc;
771 
772 	doc = document_get_current();
773 	g_return_if_fail(doc != NULL && doc->file_name != NULL);
774 
775 	vc = find_vc(doc->file_name);
776 	g_return_if_fail(vc);
777 
778 	execute_command(vc, &output, NULL, doc->file_name, VC_COMMAND_LOG_FILE, NULL, NULL);
779 	if (output)
780 	{
781 		show_output(output, "*VC-LOG*", NULL, NULL, 0);
782 		g_free(output);
783 	}
784 }
785 
786 static void
vclog_dir_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)787 vclog_dir_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer gdata)
788 {
789 	gchar *base_name = NULL;
790 	gchar *text = NULL;
791 	const VC_RECORD *vc;
792 	GeanyDocument *doc;
793 
794 	doc = document_get_current();
795 	g_return_if_fail(doc != NULL && doc->file_name != NULL);
796 
797 	base_name = g_path_get_dirname(doc->file_name);
798 
799 	vc = find_vc(base_name);
800 	g_return_if_fail(vc);
801 
802 	execute_command(vc, &text, NULL, base_name, VC_COMMAND_LOG_DIR, NULL, NULL);
803 	if (text)
804 	{
805 		show_output(text, "*VC-LOG*", NULL, NULL, 0);
806 		g_free(text);
807 	}
808 
809 	g_free(base_name);
810 }
811 
812 static void
vclog_basedir_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)813 vclog_basedir_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer gdata)
814 {
815 	gchar *text = NULL;
816 	const VC_RECORD *vc;
817 	GeanyDocument *doc;
818 	gchar *basedir;
819 
820 	doc = document_get_current();
821 	g_return_if_fail(doc != NULL && doc->file_name != NULL);
822 
823 	vc = find_vc(doc->file_name);
824 	g_return_if_fail(vc);
825 
826 	basedir = vc->get_base_dir(doc->file_name);
827 	g_return_if_fail(basedir);
828 
829 	execute_command(vc, &text, NULL, basedir, VC_COMMAND_LOG_DIR, NULL, NULL);
830 	if (text)
831 	{
832 		show_output(text, "*VC-LOG*", NULL, NULL, 0);
833 		g_free(text);
834 	}
835 	g_free(basedir);
836 }
837 
838 /* Show status from the current directory */
839 static void
vcstatus_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)840 vcstatus_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer gdata)
841 {
842 	gchar *base_name = NULL;
843 	gchar *text = NULL;
844 	const VC_RECORD *vc;
845 	GeanyDocument *doc;
846 
847 	doc = document_get_current();
848 	g_return_if_fail(doc != NULL && doc->file_name != NULL);
849 
850 	if (doc->changed)
851 	{
852 		document_save_file(doc, FALSE);
853 	}
854 
855 	base_name = g_path_get_dirname(doc->file_name);
856 
857 	vc = find_vc(base_name);
858 	g_return_if_fail(vc);
859 
860 	execute_command(vc, &text, NULL, base_name, VC_COMMAND_STATUS, NULL, NULL);
861 	if (text)
862 	{
863 		show_output(text, "*VC-STATUS*", NULL, NULL, 0);
864 		g_free(text);
865 	}
866 
867 	g_free(base_name);
868 }
869 
870 static void
vcshow_file_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)871 vcshow_file_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer gdata)
872 {
873 	gchar *output = NULL;
874 	const VC_RECORD *vc;
875 	GeanyDocument *doc;
876 
877 	doc = document_get_current();
878 	g_return_if_fail(doc != NULL && doc->file_name != NULL);
879 
880 	vc = find_vc(doc->file_name);
881 	g_return_if_fail(vc);
882 
883 	execute_command(vc, &output, NULL, doc->file_name, VC_COMMAND_SHOW, NULL, NULL);
884 	if (output)
885 	{
886 		gchar *name;
887 		name = g_strconcat(doc->file_name, ".vc.orig", NULL);
888 		show_output(output, name, doc->encoding, doc->file_type, 0);
889 		g_free(name);
890 		g_free(output);
891 	}
892 }
893 
894 static gboolean
command_with_question_activated(gchar ** text,gint cmd,const gchar * question,gint flags)895 command_with_question_activated(gchar ** text, gint cmd, const gchar * question, gint flags)
896 {
897 	GtkWidget *dialog;
898 	gint result;
899 	gchar *dir;
900 	const VC_RECORD *vc;
901 	GeanyDocument *doc;
902 
903 	doc = document_get_current();
904 	g_return_val_if_fail(doc != NULL && doc->file_name != NULL, FALSE);
905 
906 	dir = g_path_get_dirname(doc->file_name);
907 	vc = find_vc(dir);
908 	g_return_val_if_fail(vc, FALSE);
909 
910 	if (flags & FLAG_BASEDIR)
911 	{
912 		dir = vc->get_base_dir(dir);
913 	}
914 
915 	if (doc->changed)
916 	{
917 		document_save_file(doc, FALSE);
918 	}
919 
920 	if ((flags & FLAG_FORCE_ASK) || set_add_confirmation)
921 	{
922 		dialog = gtk_message_dialog_new(GTK_WINDOW(geany->main_widgets->window),
923 						GTK_DIALOG_DESTROY_WITH_PARENT,
924 						GTK_MESSAGE_QUESTION,
925 						GTK_BUTTONS_YES_NO, question,
926 						(flags & (FLAG_DIR | FLAG_BASEDIR) ? dir :
927 						 doc->file_name));
928 		result = gtk_dialog_run(GTK_DIALOG(dialog));
929 		gtk_widget_destroy(dialog);
930 	}
931 	else
932 	{
933 		result = GTK_RESPONSE_YES;
934 	}
935 
936 	if (result == GTK_RESPONSE_YES)
937 	{
938 		if (flags & FLAG_FILE)
939 			execute_command(vc, text, NULL, doc->file_name, cmd, NULL, NULL);
940 		if (flags & (FLAG_DIR | FLAG_BASEDIR))
941 			execute_command(vc, text, NULL, dir, cmd, NULL, NULL);
942 		if (flags & FLAG_RELOAD)
943 			document_reload_force(doc, NULL);
944 	}
945 	g_free(dir);
946 	return (result == GTK_RESPONSE_YES);
947 }
948 
949 static void
vcrevert_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)950 vcrevert_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer gdata)
951 {
952 	command_with_question_activated(NULL, VC_COMMAND_REVERT_FILE,
953 					_("Do you really want to revert: %s?"),
954 					FLAG_RELOAD | FLAG_FILE | FLAG_FORCE_ASK);
955 }
956 
957 static void
vcrevert_dir_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,gint flags)958 vcrevert_dir_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, gint flags)
959 {
960 	command_with_question_activated(NULL, VC_COMMAND_REVERT_DIR,
961 					_("Do you really want to revert: %s?"),
962 					FLAG_RELOAD | flags | FLAG_FORCE_ASK);
963 }
964 
965 static void
vcadd_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)966 vcadd_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer gdata)
967 {
968 	command_with_question_activated(NULL, VC_COMMAND_ADD,
969 					_("Do you really want to add: %s?"), FLAG_FILE);
970 }
971 
972 static void
vcremove_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)973 vcremove_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer gdata)
974 {
975 	if (command_with_question_activated(NULL, VC_COMMAND_REMOVE,
976 						_("Do you really want to remove: %s?"),
977 						FLAG_FORCE_ASK | FLAG_FILE))
978 	{
979 		document_remove_page(gtk_notebook_get_current_page
980 					 (GTK_NOTEBOOK(geany->main_widgets->notebook)));
981 	}
982 }
983 
984 static void
vcupdate_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)985 vcupdate_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer gdata)
986 {
987 	gchar *text = NULL;
988 	GeanyDocument *doc;
989 
990 	doc = document_get_current();
991 	g_return_if_fail(doc != NULL && doc->file_name != NULL);
992 
993 	if (doc->changed)
994 	{
995 		document_save_file(doc, FALSE);
996 	}
997 
998 	if (command_with_question_activated(&text, VC_COMMAND_UPDATE,
999 						_("Do you really want to update?"), FLAG_BASEDIR))
1000 	{
1001 		document_reload_force(doc, NULL);
1002 
1003 		if (!EMPTY(text))
1004 			show_output(text, "*VC-UPDATE*", NULL, NULL, 0);
1005 		g_free(text);
1006 	}
1007 }
1008 
1009 enum
1010 {
1011 	COLUMN_COMMIT,
1012 	COLUMN_STATUS,
1013 	COLUMN_PATH,
1014 	NUM_COLUMNS
1015 };
1016 
1017 static GtkTreeModel *
create_commit_model(const GSList * commit)1018 create_commit_model(const GSList * commit)
1019 {
1020 	GtkListStore *store;
1021 	GtkTreeIter iter;
1022 	const GSList *cur;
1023 
1024 	/* create list store */
1025 	store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING);
1026 
1027 	/* add data to the list store */
1028 
1029 	for (cur = commit; cur != NULL; cur = g_slist_next(cur))
1030 	{
1031 		gtk_list_store_append(store, &iter);
1032 		gtk_list_store_set(store, &iter,
1033 				   COLUMN_COMMIT, TRUE,
1034 				   COLUMN_STATUS, ((CommitItem *) (cur->data))->status,
1035 				   COLUMN_PATH, ((CommitItem *) (cur->data))->path, -1);
1036 	}
1037 
1038 	return GTK_TREE_MODEL(store);
1039 }
1040 
1041 static gboolean
get_commit_files_foreach(GtkTreeModel * model,G_GNUC_UNUSED GtkTreePath * path,GtkTreeIter * iter,gpointer data)1042 get_commit_files_foreach(GtkTreeModel * model, G_GNUC_UNUSED GtkTreePath * path, GtkTreeIter * iter,
1043 			 gpointer data)
1044 {
1045 	GSList **files = (GSList **) data;
1046 	gboolean commit;
1047 	gchar *filename;
1048 
1049 	gtk_tree_model_get(model, iter, COLUMN_COMMIT, &commit, -1);
1050 	if (!commit)
1051 		return FALSE;
1052 
1053 	gtk_tree_model_get(model, iter, COLUMN_PATH, &filename, -1);
1054 	*files = g_slist_prepend(*files, filename);
1055 	return FALSE;
1056 }
1057 
1058 static gboolean
get_commit_diff_foreach(GtkTreeModel * model,G_GNUC_UNUSED GtkTreePath * path,GtkTreeIter * iter,gpointer data)1059 get_commit_diff_foreach(GtkTreeModel * model, G_GNUC_UNUSED GtkTreePath * path, GtkTreeIter * iter,
1060 			gpointer data)
1061 {
1062 	GString *diff = data;
1063 	gboolean commit;
1064 	gchar *filename;
1065 	gchar *tmp = NULL;
1066 	gchar *status;
1067 	const VC_RECORD *vc;
1068 
1069 	gtk_tree_model_get(model, iter, COLUMN_COMMIT, &commit, -1);
1070 	if (!commit)
1071 		return FALSE;
1072 
1073 	gtk_tree_model_get(model, iter, COLUMN_STATUS, &status, -1);
1074 
1075 	if (! utils_str_equal(status, FILE_STATUS_MODIFIED))
1076 	{
1077 		g_free(status);
1078 		return FALSE;
1079 	}
1080 
1081 	gtk_tree_model_get(model, iter, COLUMN_PATH, &filename, -1);
1082 
1083 	vc = find_vc(filename);
1084 	g_return_val_if_fail(vc, FALSE);
1085 
1086 	execute_command(vc, &tmp, NULL, filename, VC_COMMAND_DIFF_FILE, NULL, NULL);
1087 	if (tmp)
1088 	{
1089 		/* We temporarily add the filename to the diff output for parsing the diff output later,
1090 		 * after we have finished parsing, we apply the tag "invisible" which hides the text. */
1091 		g_string_append_printf(diff, "VC_DIFF%s\n", filename);
1092 		g_string_append(diff, tmp);
1093 		g_free(tmp);
1094 	}
1095 	else
1096 	{
1097 		g_warning("error: geanyvc: get_commit_diff_foreach: empty diff output");
1098 	}
1099 	g_free(filename);
1100 	return FALSE;
1101 }
1102 
1103 static gchar *
get_commit_diff(GtkTreeView * treeview)1104 get_commit_diff(GtkTreeView * treeview)
1105 {
1106 	GtkTreeModel *model = gtk_tree_view_get_model(treeview);
1107 	GString *ret = g_string_new(NULL);
1108 
1109 	gtk_tree_model_foreach(model, get_commit_diff_foreach, ret);
1110 
1111 	return g_string_free(ret, FALSE);
1112 }
1113 
1114 static void
set_diff_buff(GtkWidget * textview,GtkTextBuffer * buffer,const gchar * txt)1115 set_diff_buff(GtkWidget * textview, GtkTextBuffer * buffer, const gchar * txt)
1116 {
1117 	GtkTextIter start, end;
1118 	GtkTextMark *mark;
1119 	gchar *filename;
1120 	const gchar *tagname = "";
1121 	const gchar *c, *p = txt;
1122 
1123 	if (strlen(txt) > COMMIT_DIFF_MAXLENGTH)
1124 	{
1125 		gtk_text_buffer_set_text(buffer,
1126 			_("The resulting differences cannot be displayed because "
1127 			  "the changes are too big to display here and would slow down the UI significantly."
1128 			  "\n\n"
1129 			  "To view the differences, cancel this dialog and open the differences "
1130 			  "in Geany directly by using the GeanyVC menu (Base Directory -> Diff)."), -1);
1131 		gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview), GTK_WRAP_WORD);
1132 		return;
1133 	}
1134 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview), GTK_WRAP_NONE);
1135 
1136 	gtk_text_buffer_set_text(buffer, txt, -1);
1137 
1138 	gtk_text_buffer_get_start_iter(buffer, &start);
1139 	gtk_text_buffer_get_end_iter(buffer, &end);
1140 
1141 	gtk_text_buffer_remove_all_tags(buffer, &start, &end);
1142 
1143 	while (p)
1144 	{
1145 		c = NULL;
1146 		if (*p == '-')
1147 		{
1148 			tagname = "deleted";
1149 		}
1150 		else if (*p == '+')
1151 		{
1152 			tagname = "added";
1153 		}
1154 		else if (*p == ' ')
1155 		{
1156 			tagname = "";
1157 		}
1158 		else if (strncmp(p, "VC_DIFF", 7) == 0)
1159 		{   /* Lines starting with VC_DIFF are special and were added by our code to tell about
1160 			 * filename to which the following diff lines belong. We use this file to create
1161 			 * text marks which we then later use to scroll to if the corresponding file has been
1162 			 * selected in the commit dialog's files list. */
1163 			tagname = "invisible";
1164 			c = strchr(p + 7, '\n');
1165 		}
1166 		else
1167 		{
1168 			tagname = "default";
1169 		}
1170 		gtk_text_buffer_get_iter_at_offset(buffer, &start,
1171 						   g_utf8_pointer_to_offset(txt, p));
1172 
1173 		if (c)
1174 		{   /* create the mark *after* the start iter has been updated */
1175 			filename = g_strndup(p + 7, c - p - 7);
1176 			/* delete old text marks */
1177 			mark = gtk_text_buffer_get_mark(buffer, filename);
1178 			if (mark)
1179 				gtk_text_buffer_delete_mark(buffer, mark);
1180 			/* create a new one */
1181 			gtk_text_buffer_create_mark(buffer, filename, &start, TRUE);
1182 			g_free(filename);
1183 		}
1184 
1185 		p = strchr(p, '\n');
1186 		if (p)
1187 		{
1188 			if (*tagname)
1189 			{
1190 				gtk_text_buffer_get_iter_at_offset(buffer, &end,
1191 						g_utf8_pointer_to_offset(txt, p + 1));
1192 				gtk_text_buffer_apply_tag_by_name(buffer, tagname, &start, &end);
1193 			}
1194 			p++;
1195 		}
1196 	}
1197 }
1198 
1199 static void
refresh_diff_view(GtkTreeView * treeview)1200 refresh_diff_view(GtkTreeView *treeview)
1201 {
1202 	gchar *diff;
1203 	GtkWidget *diffView = ui_lookup_widget(GTK_WIDGET(treeview), "textDiff");
1204 	diff = get_commit_diff(GTK_TREE_VIEW(treeview));
1205 	set_diff_buff(diffView, gtk_text_view_get_buffer(GTK_TEXT_VIEW(diffView)), diff);
1206 	g_free(diff);
1207 }
1208 
1209 static void
commit_toggle_commit(GtkTreeView * treeview,gchar * path_str)1210 commit_toggle_commit(GtkTreeView *treeview, gchar * path_str)
1211 {
1212 	GtkTreeModel *model = gtk_tree_view_get_model(treeview);
1213 	GtkTreeIter iter;
1214 	GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
1215 	gboolean fixed;
1216 	gchar *filename;
1217 	GtkTextView *diffView = GTK_TEXT_VIEW(ui_lookup_widget(GTK_WIDGET(treeview), "textDiff"));
1218 	GtkTextMark *mark;
1219 
1220 	/* get toggled iter */
1221 	gtk_tree_model_get_iter(model, &iter, path);
1222 	gtk_tree_model_get(model, &iter, COLUMN_COMMIT, &fixed, COLUMN_PATH, &filename, -1);
1223 
1224 	/* do something with the value */
1225 	fixed ^= 1;
1226 
1227 	/* set new value */
1228 	gtk_list_store_set(GTK_LIST_STORE(model), &iter, COLUMN_COMMIT, fixed, -1);
1229 
1230 	if (! fixed)
1231 	{
1232 		mark = gtk_text_buffer_get_mark(gtk_text_view_get_buffer(diffView), filename);
1233 		if (mark)
1234 			gtk_text_buffer_delete_mark(gtk_text_view_get_buffer(diffView), mark);
1235 	}
1236 
1237 	refresh_diff_view(treeview);
1238 
1239 	/* clean up */
1240 	gtk_tree_path_free(path);
1241 	g_free(filename);
1242 }
1243 
1244 static void
commit_toggled_cb(G_GNUC_UNUSED GtkCellRendererToggle * cell,gchar * path_str,gpointer data)1245 commit_toggled_cb(G_GNUC_UNUSED GtkCellRendererToggle * cell, gchar * path_str, gpointer data)
1246 {
1247 	commit_toggle_commit(GTK_TREE_VIEW(data), path_str);
1248 }
1249 
1250 static gboolean
toggle_all_commit_files(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1251 toggle_all_commit_files (GtkTreeModel *model, GtkTreePath *path,
1252 		GtkTreeIter *iter, gpointer data)
1253 {
1254 	(void)path;
1255 	gtk_list_store_set(GTK_LIST_STORE(model), iter, COLUMN_COMMIT, *(gint*)data, -1);
1256 	return FALSE;
1257 }
1258 
1259 static void
commit_all_toggled_cb(GtkToggleButton * check_box,gpointer treeview)1260 commit_all_toggled_cb(GtkToggleButton *check_box, gpointer treeview)
1261 {
1262 	GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
1263 	gint toggled = gtk_toggle_button_get_active(check_box);
1264 
1265 	gtk_tree_model_foreach(model, toggle_all_commit_files, &toggled);
1266 
1267 	refresh_diff_view(treeview);
1268 }
1269 
1270 static void
add_commit_columns(GtkTreeView * treeview)1271 add_commit_columns(GtkTreeView * treeview)
1272 {
1273 	GtkCellRenderer *renderer;
1274 	GtkTreeViewColumn *column;
1275 
1276 	/* column for fixed toggles */
1277 	renderer = gtk_cell_renderer_toggle_new();
1278 	g_signal_connect(renderer, "toggled", G_CALLBACK(commit_toggled_cb), treeview);
1279 
1280 	column = gtk_tree_view_column_new_with_attributes(_("Commit Y/N"),
1281 							  renderer, "active", COLUMN_COMMIT, NULL);
1282 
1283 	/* set this column to a fixed sizing (of 80 pixels) */
1284 	gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column), GTK_TREE_VIEW_COLUMN_FIXED);
1285 	gtk_tree_view_column_set_fixed_width(GTK_TREE_VIEW_COLUMN(column), 80);
1286 	gtk_tree_view_append_column(treeview, column);
1287 
1288 	/* column for status */
1289 	renderer = gtk_cell_renderer_text_new();
1290 	column = gtk_tree_view_column_new_with_attributes(_("Status"),
1291 							  renderer, "text", COLUMN_STATUS, NULL);
1292 	gtk_tree_view_column_set_sort_column_id(column, COLUMN_STATUS);
1293 	gtk_tree_view_append_column(treeview, column);
1294 
1295 	/* column for path of file to commit */
1296 	renderer = gtk_cell_renderer_text_new();
1297 	column = gtk_tree_view_column_new_with_attributes(_("Path"),
1298 							  renderer, "text", COLUMN_PATH, NULL);
1299 	gtk_tree_view_column_set_sort_column_id(column, COLUMN_PATH);
1300 	gtk_tree_view_append_column(treeview, column);
1301 }
1302 
1303 static GdkColor *
get_diff_color(G_GNUC_UNUSED GeanyDocument * doc,gint style)1304 get_diff_color(G_GNUC_UNUSED GeanyDocument * doc, gint style)
1305 {
1306 	static GdkColor c = { 0, 0, 0, 0 };
1307 	const GeanyLexerStyle *s;
1308 
1309 	s = highlighting_get_style(GEANY_FILETYPES_DIFF, style);
1310 	c.red = (s->foreground & 0xff) << 0x8;
1311 	c.green = s->foreground & 0xff00;
1312 	c.blue = (s->foreground & 0xff0000) >> 0x8;
1313 
1314 	return &c;
1315 }
1316 
1317 #define GLADE_HOOKUP_OBJECT(component,widget,name) \
1318   g_object_set_data_full (G_OBJECT (component), name, \
1319 	g_object_ref (widget), (GDestroyNotify) g_object_unref)
1320 
1321 #define GLADE_HOOKUP_OBJECT_NO_REF(component,widget,name) \
1322   g_object_set_data (G_OBJECT (component), name, widget)
1323 
commit_tree_selection_changed_cb(GtkTreeSelection * sel,GtkTextView * textview)1324 static void commit_tree_selection_changed_cb(GtkTreeSelection *sel, GtkTextView *textview)
1325 {
1326 	GtkTreeModel *model;
1327 	GtkTreeIter iter;
1328 	GtkTextMark *mark;
1329 	gchar *path;
1330 	gboolean set;
1331 
1332 	if (! gtk_tree_selection_get_selected(sel, &model, &iter))
1333 		return;
1334 
1335 	gtk_tree_model_get(model, &iter, COLUMN_COMMIT, &set, COLUMN_PATH, &path, -1);
1336 
1337 	if (set)
1338 	{
1339 		mark = gtk_text_buffer_get_mark(gtk_text_view_get_buffer(textview), path);
1340 		if (mark)
1341 			gtk_text_view_scroll_to_mark(textview, mark, 0.0, TRUE, 0.0, 0.0);
1342 	}
1343 	g_free(path);
1344 }
1345 
commit_message_update_cb(GtkWidget * widget,gpointer user_data)1346 static void commit_message_update_cb(GtkWidget *widget, gpointer user_data)
1347 {
1348 	GtkTreeIter iter;
1349 
1350 	if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter))
1351 	{
1352 		gchar *commit_message;
1353 		GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
1354 		gboolean active = FALSE;
1355 
1356 		gtk_tree_model_get(model, &iter,
1357 			COMMIT_MESSAGE_HISTORY_FULL_TEXT, &commit_message,
1358 			COMMIT_MESSAGE_HISTORY_ACTIVE, &active, -1);
1359 
1360 		if (active)
1361 		{
1362 			GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(user_data));
1363 			gtk_text_buffer_set_text(buffer, commit_message, -1);
1364 		}
1365 		g_free(commit_message);
1366 	}
1367 }
1368 
1369 static GtkWidget *
create_commit_message_history_combobox(void)1370 create_commit_message_history_combobox(void)
1371 {
1372 	GtkWidget *combobox;
1373 	GtkCellRenderer *renderer;
1374 	GtkTreeIter iter;
1375 	GtkListStore *store;
1376 	GSList *list_item = NULL;
1377 	GString *display_message_string;
1378 	gchar *display_message;
1379 
1380 	combobox = gtk_combo_box_new();
1381 	renderer = gtk_cell_renderer_text_new();
1382 	store = gtk_list_store_new(
1383 		COMMIT_MESSAGE_HISTORY_NUM_COLUMNS,
1384 		G_TYPE_STRING,
1385 		G_TYPE_STRING,
1386 		G_TYPE_BOOLEAN);
1387 
1388 	foreach_slist(list_item, commit_message_history)
1389 	{
1390 		display_message_string = g_string_new(list_item->data);
1391 		/* replace line breaks */
1392 		utils_string_replace_all(display_message_string, "\n", " ");
1393 		/* shorten commit message */
1394 		if (display_message_string->len > 80)
1395 		{
1396 			g_string_truncate(display_message_string, 80);
1397 			g_string_append(display_message_string, "...");
1398 		}
1399 		display_message = g_string_free(display_message_string, FALSE);
1400 
1401 		/* by _prepend() we reverse the list order for display on purpose:
1402 		 * most recent item should be on top */
1403 		gtk_list_store_prepend(store, &iter);
1404 		gtk_list_store_set(store, &iter,
1405 			COMMIT_MESSAGE_HISTORY_DISPLAY_TEXT, display_message,
1406 			COMMIT_MESSAGE_HISTORY_FULL_TEXT, list_item->data,
1407 			COMMIT_MESSAGE_HISTORY_ACTIVE, TRUE, -1);
1408 
1409 		g_free(display_message);
1410 	}
1411 
1412 	gtk_list_store_prepend(store, &iter);
1413 	gtk_list_store_set(store, &iter,
1414 		COMMIT_MESSAGE_HISTORY_DISPLAY_TEXT, _("Choose a previous commit message"),
1415 		COMMIT_MESSAGE_HISTORY_FULL_TEXT, NULL,
1416 		COMMIT_MESSAGE_HISTORY_ACTIVE, FALSE, -1);
1417 
1418 	gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(store));
1419 	gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0);
1420 	g_object_unref(store);
1421 
1422 	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE);
1423 	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox), renderer,
1424 		"text", COMMIT_MESSAGE_HISTORY_DISPLAY_TEXT,
1425 		"sensitive", COMMIT_MESSAGE_HISTORY_ACTIVE, NULL);
1426 
1427 	return combobox;
1428 }
1429 
commit_tree_view_key_release_cb(GtkWidget * widget,GdkEventKey * event,G_GNUC_UNUSED gpointer user_data)1430 static gboolean commit_tree_view_key_release_cb(GtkWidget *widget, GdkEventKey *event,
1431 												G_GNUC_UNUSED gpointer user_data)
1432 {
1433 	if (event->keyval == GDK_KEY_space ||
1434 		event->keyval == GDK_KEY_Return ||
1435 		event->keyval == GDK_KEY_KP_Enter)
1436 	{
1437 		GtkTreeView *treeview = GTK_TREE_VIEW(widget);
1438 		GtkTreeModel *model;
1439 		GtkTreeIter iter;
1440 		GtkTreeSelection *sel;
1441 		GtkTreePath *path;
1442 		gchar *path_str;
1443 
1444 		sel = gtk_tree_view_get_selection(treeview);
1445 		if (! gtk_tree_selection_get_selected(sel, &model, &iter))
1446 			return FALSE;
1447 
1448 		path = gtk_tree_model_get_path(model, &iter);
1449 		if (path != NULL)
1450 		{
1451 			path_str = gtk_tree_path_to_string(path);
1452 
1453 			commit_toggle_commit(treeview, path_str);
1454 			gtk_tree_path_free(path);
1455 			g_free(path_str);
1456 		}
1457 	}
1458 	return FALSE;
1459 }
1460 
commit_text_line_number_update_cb(GtkWidget * widget,GdkEvent * event,gpointer user_data)1461 static gboolean commit_text_line_number_update_cb(GtkWidget *widget, GdkEvent *event,
1462 												  gpointer user_data)
1463 {
1464 	GtkWidget *text_view = widget;
1465 	GtkLabel *line_column_label = GTK_LABEL(user_data);
1466 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));
1467 	GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
1468 	GtkTextIter iter;
1469 	gint line, column;
1470 	gchar text[64];
1471 
1472 	gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1473 	line = gtk_text_iter_get_line(&iter) + 1;
1474 	column = gtk_text_iter_get_line_offset(&iter);
1475 
1476 	g_snprintf(text, sizeof(text), _("Line: %d Column: %d"), line, column);
1477 	gtk_label_set_text(line_column_label, text);
1478 
1479 	return FALSE;
1480 }
1481 
1482 static GtkWidget *
create_commitDialog(void)1483 create_commitDialog(void)
1484 {
1485 	GtkWidget *commitDialog;
1486 	GtkWidget *dialog_vbox1;
1487 	GtkWidget *vpaned1;
1488 	GtkWidget *scrolledwindow1;
1489 	GtkWidget *treeSelect;
1490 	GtkWidget *vpaned2;
1491 	GtkWidget *scrolledwindow2;
1492 	GtkWidget *textDiff;
1493 	GtkWidget *frame1;
1494 	GtkWidget *alignment1;
1495 	GtkWidget *scrolledwindow3;
1496 	GtkWidget *textCommitMessage;
1497 	GtkWidget *label1;
1498 	GtkWidget *dialog_action_area1;
1499 	GtkWidget *btnCancel;
1500 	GtkWidget *btnCommit;
1501 	GtkWidget *select_cbox;
1502 	GtkWidget *bottom_vbox;
1503 	GtkWidget *commit_text_vbox;
1504 	GtkWidget *lineColumnLabel;
1505 	GtkWidget *commitMessageHistoryComboBox;
1506 	GtkTreeSelection *sel;
1507 
1508 	gchar *rcstyle = g_strdup_printf("style \"geanyvc-diff-font\"\n"
1509 					 "{\n"
1510 					 "    font_name=\"%s\"\n"
1511 					 "}\n"
1512 					 "widget \"*.GeanyVCCommitDialogDiff\" style \"geanyvc-diff-font\"",
1513 					 geany_data->interface_prefs->editor_font);
1514 
1515 	gtk_rc_parse_string(rcstyle);
1516 	g_free(rcstyle);
1517 
1518 	commitDialog = gtk_dialog_new();
1519 	gtk_container_set_border_width(GTK_CONTAINER(commitDialog), 5);
1520 	gtk_widget_set_events(commitDialog,
1521 				  GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
1522 				  GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
1523 	gtk_window_set_title(GTK_WINDOW(commitDialog), _("Commit"));
1524 	gtk_window_set_position(GTK_WINDOW(commitDialog), GTK_WIN_POS_CENTER_ON_PARENT);
1525 	gtk_window_set_modal(GTK_WINDOW(commitDialog), TRUE);
1526 	gtk_window_set_destroy_with_parent(GTK_WINDOW(commitDialog), TRUE);
1527 	gtk_window_set_type_hint(GTK_WINDOW(commitDialog), GDK_WINDOW_TYPE_HINT_DIALOG);
1528 
1529 	dialog_vbox1 = gtk_dialog_get_content_area(GTK_DIALOG(commitDialog));
1530 	gtk_widget_show(dialog_vbox1);
1531 
1532 	vpaned1 = gtk_vpaned_new();
1533 	gtk_widget_show(vpaned1);
1534 	gtk_box_pack_start(GTK_BOX(dialog_vbox1), vpaned1, TRUE, TRUE, 0);
1535 
1536 	scrolledwindow1 = gtk_scrolled_window_new(NULL, NULL);
1537 	gtk_widget_show(scrolledwindow1);
1538 	gtk_paned_pack1(GTK_PANED(vpaned1), scrolledwindow1, FALSE, TRUE);
1539 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow1), GTK_POLICY_AUTOMATIC,
1540 					   GTK_POLICY_AUTOMATIC);
1541 
1542 	treeSelect = gtk_tree_view_new();
1543 	gtk_widget_show(treeSelect);
1544 	gtk_container_add(GTK_CONTAINER(scrolledwindow1), treeSelect);
1545 	gtk_widget_set_events(treeSelect,
1546 				  GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
1547 				  GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
1548 
1549 	vpaned2 = gtk_vpaned_new();
1550 	gtk_widget_show(vpaned2);
1551 	gtk_paned_pack2(GTK_PANED(vpaned1), vpaned2, TRUE, TRUE);
1552 
1553 	scrolledwindow2 = gtk_scrolled_window_new(NULL, NULL);
1554 	gtk_widget_show(scrolledwindow2);
1555 	gtk_paned_pack1(GTK_PANED(vpaned2), scrolledwindow2, TRUE, TRUE);
1556 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow2), GTK_POLICY_AUTOMATIC,
1557 					   GTK_POLICY_AUTOMATIC);
1558 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwindow2), GTK_SHADOW_IN);
1559 
1560 	bottom_vbox = gtk_vbox_new(FALSE, 0);
1561 	gtk_widget_show(bottom_vbox);
1562 	gtk_paned_pack2(GTK_PANED(vpaned2), bottom_vbox, FALSE, FALSE);
1563 
1564 	select_cbox = GTK_WIDGET(gtk_check_button_new_with_mnemonic(_("_De-/select all files")));
1565 	gtk_box_pack_start(GTK_BOX(bottom_vbox), select_cbox, FALSE, FALSE, 2);
1566 	gtk_widget_show(select_cbox);
1567 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(select_cbox), TRUE);
1568 	g_signal_connect(select_cbox, "toggled", G_CALLBACK(commit_all_toggled_cb),
1569 			treeSelect);
1570 
1571 	textDiff = gtk_text_view_new();
1572 	gtk_widget_set_name(textDiff, "GeanyVCCommitDialogDiff");
1573 	gtk_widget_show(textDiff);
1574 	gtk_container_add(GTK_CONTAINER(scrolledwindow2), textDiff);
1575 	gtk_widget_set_events(textDiff,
1576 				  GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
1577 				  GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
1578 	gtk_text_view_set_editable(GTK_TEXT_VIEW(textDiff), FALSE);
1579 
1580 	frame1 = gtk_frame_new(NULL);
1581 	gtk_widget_show(frame1);
1582 	gtk_box_pack_start(GTK_BOX(bottom_vbox), frame1, TRUE, TRUE, 2);
1583 	gtk_frame_set_shadow_type(GTK_FRAME(frame1), GTK_SHADOW_NONE);
1584 
1585 	alignment1 = gtk_alignment_new(0.5, 0.5, 1, 1);
1586 	gtk_widget_show(alignment1);
1587 	gtk_container_add(GTK_CONTAINER(frame1), alignment1);
1588 	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment1), 0, 0, 12, 0);
1589 
1590 	commit_text_vbox = gtk_vbox_new(FALSE, 0);
1591 	gtk_widget_show(commit_text_vbox);
1592 	gtk_container_add(GTK_CONTAINER(alignment1), commit_text_vbox);
1593 
1594 	scrolledwindow3 = gtk_scrolled_window_new(NULL, NULL);
1595 	gtk_widget_show(scrolledwindow3);
1596 	gtk_box_pack_start(GTK_BOX(commit_text_vbox), scrolledwindow3, TRUE, TRUE, 0);
1597 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow3), GTK_POLICY_AUTOMATIC,
1598 					   GTK_POLICY_AUTOMATIC);
1599 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwindow3), GTK_SHADOW_IN);
1600 
1601 	textCommitMessage = gtk_text_view_new();
1602 	gtk_widget_show(textCommitMessage);
1603 	gtk_container_add(GTK_CONTAINER(scrolledwindow3), textCommitMessage);
1604 	gtk_widget_set_events(textCommitMessage,
1605 				  GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
1606 				  GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
1607 
1608 	label1 = gtk_label_new(_("<b>Commit message:</b>"));
1609 	gtk_widget_show(label1);
1610 	gtk_frame_set_label_widget(GTK_FRAME(frame1), label1);
1611 	gtk_label_set_use_markup(GTK_LABEL(label1), TRUE);
1612 
1613 	/* line/column status label */
1614 	lineColumnLabel = gtk_label_new("");
1615 	gtk_misc_set_alignment(GTK_MISC(lineColumnLabel), 0, 0.5);
1616 	gtk_box_pack_end(GTK_BOX(commit_text_vbox), lineColumnLabel, FALSE, TRUE, 0);
1617 	gtk_widget_show(lineColumnLabel);
1618 
1619 	/* last commit message dropdown */
1620 	commitMessageHistoryComboBox = create_commit_message_history_combobox();
1621 	gtk_box_pack_end(GTK_BOX(commit_text_vbox), commitMessageHistoryComboBox, FALSE, TRUE, 0);
1622 	gtk_widget_show(commitMessageHistoryComboBox);
1623 
1624 	dialog_action_area1 = gtk_dialog_get_action_area(GTK_DIALOG(commitDialog));
1625 	gtk_widget_show(dialog_action_area1);
1626 	gtk_button_box_set_layout(GTK_BUTTON_BOX(dialog_action_area1), GTK_BUTTONBOX_END);
1627 
1628 	btnCancel = gtk_button_new_from_stock("gtk-cancel");
1629 	gtk_widget_show(btnCancel);
1630 	gtk_dialog_add_action_widget(GTK_DIALOG(commitDialog), btnCancel, GTK_RESPONSE_CANCEL);
1631 
1632 	btnCommit = gtk_button_new_with_mnemonic(_("C_ommit"));
1633 	gtk_widget_show(btnCommit);
1634 	gtk_dialog_add_action_widget(GTK_DIALOG(commitDialog), btnCommit, GTK_RESPONSE_APPLY);
1635 
1636 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeSelect));
1637 	gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE);
1638 	g_signal_connect(sel, "changed", G_CALLBACK(commit_tree_selection_changed_cb), textDiff);
1639 
1640 	g_signal_connect(treeSelect, "key-release-event",
1641 		G_CALLBACK(commit_tree_view_key_release_cb), NULL);
1642 	g_signal_connect(textCommitMessage, "key-release-event",
1643 		G_CALLBACK(commit_text_line_number_update_cb), lineColumnLabel);
1644 	g_signal_connect(textCommitMessage, "button-release-event",
1645 		G_CALLBACK(commit_text_line_number_update_cb), lineColumnLabel);
1646 	g_signal_connect(commitMessageHistoryComboBox, "changed",
1647 		G_CALLBACK(commit_message_update_cb), textCommitMessage);
1648 	/* initial setup */
1649 	commit_text_line_number_update_cb(textCommitMessage, NULL, lineColumnLabel);
1650 
1651 	/* Store pointers to all widgets, for use by lookup_widget(). */
1652 	GLADE_HOOKUP_OBJECT_NO_REF(commitDialog, commitDialog, "commitDialog");
1653 	GLADE_HOOKUP_OBJECT_NO_REF(commitDialog, dialog_vbox1, "dialog_vbox1");
1654 	GLADE_HOOKUP_OBJECT(commitDialog, vpaned1, "vpaned1");
1655 	GLADE_HOOKUP_OBJECT(commitDialog, scrolledwindow1, "scrolledwindow1");
1656 	GLADE_HOOKUP_OBJECT(commitDialog, treeSelect, "treeSelect");
1657 	GLADE_HOOKUP_OBJECT(commitDialog, vpaned2, "vpaned2");
1658 	GLADE_HOOKUP_OBJECT(commitDialog, scrolledwindow2, "scrolledwindow2");
1659 	GLADE_HOOKUP_OBJECT(commitDialog, textDiff, "textDiff");
1660 	GLADE_HOOKUP_OBJECT(commitDialog, frame1, "frame1");
1661 	GLADE_HOOKUP_OBJECT(commitDialog, alignment1, "alignment1");
1662 	GLADE_HOOKUP_OBJECT(commitDialog, scrolledwindow3, "scrolledwindow3");
1663 	GLADE_HOOKUP_OBJECT(commitDialog, textCommitMessage, "textCommitMessage");
1664 	GLADE_HOOKUP_OBJECT(commitDialog, label1, "label1");
1665 	GLADE_HOOKUP_OBJECT_NO_REF(commitDialog, dialog_action_area1, "dialog_action_area1");
1666 	GLADE_HOOKUP_OBJECT(commitDialog, btnCancel, "btnCancel");
1667 	GLADE_HOOKUP_OBJECT(commitDialog, btnCommit, "btnCommit");
1668 	GLADE_HOOKUP_OBJECT(commitDialog, select_cbox, "select_cbox");
1669 
1670 	return commitDialog;
1671 }
1672 
1673 static void
add_commit_message_to_history(const gchar * commit_message)1674 add_commit_message_to_history(const gchar *commit_message)
1675 {
1676 	GSList *list_item = NULL;
1677 	gchar *message = g_strdup(commit_message);
1678 
1679 	/* add commit message to the history */
1680 	commit_message_history = g_slist_append(commit_message_history, (gpointer)message);
1681 
1682 	/* House keeping: keep only a maximum of COMMIT_MESSAGE_HISTORY_LENGTH items and delete the rest
1683 	 * We remove the first element from the list as often as the list is too long.
1684 	 * While the loop is not the most efficient way, it should be ok since the list never has
1685 	 * much more than 10 items */
1686 	while (g_slist_length(commit_message_history) > COMMIT_MESSAGE_HISTORY_LENGTH)
1687 	{
1688 		list_item = commit_message_history; /* remember the element to delete */
1689 		commit_message_history = list_item->next; /* start the list with the next item */
1690 		/* delete the current item and its data */
1691 		list_item->next = NULL;
1692 		g_slist_free_full(list_item, g_free);
1693 	}
1694 }
1695 
1696 static void
vccommit_activated(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)1697 vccommit_activated(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer gdata)
1698 {
1699 	GeanyDocument *doc;
1700 	gint result;
1701 	const VC_RECORD *vc;
1702 	GSList *lst;
1703 	GtkTreeModel *model;
1704 	GtkWidget *commit = create_commitDialog();
1705 	GtkWidget *treeview = ui_lookup_widget(commit, "treeSelect");
1706 	GtkWidget *diffView = ui_lookup_widget(commit, "textDiff");
1707 	GtkWidget *messageView = ui_lookup_widget(commit, "textCommitMessage");
1708 	GtkWidget *vpaned1 = ui_lookup_widget(commit, "vpaned1");
1709 	GtkWidget *vpaned2 = ui_lookup_widget(commit, "vpaned2");
1710 
1711 	GtkTextBuffer *mbuf;
1712 	GtkTextBuffer *diffbuf;
1713 
1714 	GtkTextIter begin;
1715 	GtkTextIter end;
1716 	GSList *selected_files = NULL;
1717 
1718 	gchar *dir;
1719 	gchar *message;
1720 	gchar *diff;
1721 
1722 	gint height;
1723 
1724 #ifdef USE_GTKSPELL
1725 	GtkSpell *speller = NULL;
1726 	GError *spellcheck_error = NULL;
1727 #endif
1728 
1729 	doc = document_get_current();
1730 	g_return_if_fail(doc);
1731 	g_return_if_fail(doc->file_name);
1732 	vc = find_vc(doc->file_name);
1733 	g_return_if_fail(vc);
1734 	dir = vc->get_base_dir(doc->file_name);
1735 
1736 	lst = vc->get_commit_files(dir);
1737 	if (!lst)
1738 	{
1739 		g_free(dir);
1740 		ui_set_statusbar(FALSE, _("Nothing to commit."));
1741 		return;
1742 	}
1743 
1744 	model = create_commit_model(lst);
1745 	gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), model);
1746 	g_object_unref(model);
1747 
1748 	/* add columns to the tree view */
1749 	add_commit_columns(GTK_TREE_VIEW(treeview));
1750 
1751 	diff = get_commit_diff(GTK_TREE_VIEW(treeview));
1752 	diffbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(diffView));
1753 
1754 	gtk_text_buffer_create_tag(diffbuf, "deleted", "foreground-gdk",
1755 				   get_diff_color(doc, SCE_DIFF_DELETED), NULL);
1756 
1757 	gtk_text_buffer_create_tag(diffbuf, "added", "foreground-gdk",
1758 				   get_diff_color(doc, SCE_DIFF_ADDED), NULL);
1759 
1760 	gtk_text_buffer_create_tag(diffbuf, "default", "foreground-gdk",
1761 				   get_diff_color(doc, SCE_DIFF_POSITION), NULL);
1762 
1763 	gtk_text_buffer_create_tag(diffbuf, "invisible", "invisible",
1764 				   TRUE, NULL);
1765 
1766 	set_diff_buff(diffView, diffbuf, diff);
1767 
1768 	if (set_maximize_commit_dialog)
1769 	{
1770 		gtk_window_maximize(GTK_WINDOW(commit));
1771 	}
1772 	else
1773 	{
1774 		gtk_widget_set_size_request(commit, 700, 500);
1775 		gtk_window_set_default_size(GTK_WINDOW(commit),
1776 			commit_dialog_width, commit_dialog_height);
1777 	}
1778 
1779 	gtk_widget_show_now(commit);
1780 	gtk_window_get_size(GTK_WINDOW(commit), NULL, &height);
1781 	gtk_paned_set_position(GTK_PANED(vpaned1), height * 25 / 100);
1782 	gtk_paned_set_position(GTK_PANED(vpaned2), height * 50 / 100);
1783 
1784 #ifdef USE_GTKSPELL
1785 	speller = gtkspell_new_attach(GTK_TEXT_VIEW(messageView), EMPTY(lang) ? NULL : lang, &spellcheck_error);
1786 	if (speller == NULL && spellcheck_error != NULL)
1787 	{
1788 		ui_set_statusbar(TRUE, _("Error initializing GeanyVC spell checking: %s. Check your configuration."),
1789 				 spellcheck_error->message);
1790 		g_error_free(spellcheck_error);
1791 		spellcheck_error = NULL;
1792 	}
1793 #endif
1794 
1795 	/* put the input focus to the commit message text view */
1796 	gtk_widget_grab_focus(messageView);
1797 
1798 	result = gtk_dialog_run(GTK_DIALOG(commit));
1799 	if (result == GTK_RESPONSE_APPLY)
1800 	{
1801 		mbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messageView));
1802 		gtk_text_buffer_get_start_iter(mbuf, &begin);
1803 		gtk_text_buffer_get_end_iter(mbuf, &end);
1804 		message = gtk_text_buffer_get_text(mbuf, &begin, &end, FALSE);
1805 		add_commit_message_to_history(message);
1806 		gtk_tree_model_foreach(model, get_commit_files_foreach, &selected_files);
1807 		if (!EMPTY(message) && selected_files)
1808 		{
1809 			gchar *err_output = NULL;
1810 			gint exit_code;
1811 
1812 			exit_code = execute_command(vc, NULL, &err_output, dir, VC_COMMAND_COMMIT, selected_files,
1813 					message);
1814 
1815 			if (err_output)
1816 			{
1817 				gint status = get_command_exit_status(exit_code);
1818 
1819 				/* - log the commit error (may be a very long message) into the Message Window
1820 				   - overwrite the status bar with a more concise message
1821 				*/
1822 				g_warning("geanyvc: vccommit_activated: Commit failed (status:%d).", status);
1823 				ui_set_statusbar(TRUE, _("Commit failed (status: %d, error: %s)."), status, err_output);
1824 				ui_set_statusbar(FALSE, _("Commit failed; see status in the Message window."));
1825 				g_free(err_output);
1826 			}
1827 			else
1828 			{
1829 				ui_set_statusbar(FALSE, _("Changes committed."));
1830 			}
1831 			free_text_list(selected_files);
1832 		}
1833 		g_free(message);
1834 	}
1835 	/* remember commit dialog widget size */
1836 	gtk_window_get_size(GTK_WINDOW(commit),
1837 		&commit_dialog_width, &commit_dialog_height);
1838 
1839 	gtk_widget_destroy(commit);
1840 	free_commit_list(lst);
1841 	g_free(dir);
1842 	g_free(diff);
1843 }
1844 
1845 typedef struct _VCFileMenu
1846 {
1847 	GtkWidget *menu;
1848 	struct
1849 	{
1850 		GtkWidget *diff;
1851 		GtkWidget *blame;
1852 		GtkWidget *log;
1853 		GtkWidget *revert;
1854 		GtkWidget *add;
1855 		GtkWidget *remove;
1856 		GtkWidget *show;
1857 	} item;
1858 } VCFileMenu;
1859 
1860 static VCFileMenu *menu_vc_file_menu = NULL;
1861 static VCFileMenu *editor_menu_vc_file_menu = NULL;
1862 
1863 static GtkWidget *menu_vc_diff_dir = NULL;
1864 static GtkWidget *menu_vc_diff_basedir = NULL;
1865 static GtkWidget *menu_vc_log_dir = NULL;
1866 static GtkWidget *menu_vc_log_basedir = NULL;
1867 static GtkWidget *menu_vc_status = NULL;
1868 static GtkWidget *menu_vc_revert_dir = NULL;
1869 static GtkWidget *menu_vc_revert_basedir = NULL;
1870 static GtkWidget *menu_vc_update = NULL;
1871 static GtkWidget *menu_vc_commit = NULL;
1872 
1873 static VCFileMenu ***
get_vc_file_menu_arrv(void)1874 get_vc_file_menu_arrv(void)
1875 {
1876 	static VCFileMenu **arrv[] = {NULL, NULL, NULL};
1877 
1878 	arrv[0] = &menu_vc_file_menu;
1879 	if (set_editor_menu_entries == TRUE)
1880 		arrv[1] = &editor_menu_vc_file_menu;
1881 
1882 	return arrv;
1883 }
1884 
1885 static void
update_menu_items(void)1886 update_menu_items(void)
1887 {
1888 	GeanyDocument *doc;
1889 
1890 	VCFileMenu ***file_menu_arrv = get_vc_file_menu_arrv();
1891 	gboolean have_file;
1892 	gboolean d_have_vc = FALSE;
1893 	gboolean f_have_vc = FALSE;
1894 
1895 	gchar *dir;
1896 
1897 	doc = document_get_current();
1898 	have_file = doc && doc->file_name && g_path_is_absolute(doc->file_name);
1899 
1900 	if (have_file)
1901 	{
1902 		dir = g_path_get_dirname(doc->file_name);
1903 		if (find_cmd_env(VC_COMMAND_DIFF_FILE, TRUE, dir))
1904 			d_have_vc = TRUE;
1905 
1906 		if (find_cmd_env(VC_COMMAND_DIFF_FILE, TRUE, doc->file_name))
1907 			f_have_vc = TRUE;
1908 		g_free(dir);
1909 	}
1910 
1911 	/* update items for each of the file menu sets */
1912 	for ( ; file_menu_arrv[0] != NULL; ++file_menu_arrv)
1913 	{
1914 		VCFileMenu *fm = *(file_menu_arrv[0]);
1915 		if (fm == NULL) continue;
1916 
1917 		GtkWidget *menu_vc_diff_file = fm->item.diff;
1918 		GtkWidget *menu_vc_blame = fm->item.blame;
1919 		GtkWidget *menu_vc_log_file = fm->item.log;
1920 		GtkWidget *menu_vc_revert_file = fm->item.revert;
1921 		GtkWidget *menu_vc_add_file = fm->item.add;
1922 		GtkWidget *menu_vc_remove_file = fm->item.remove;
1923 		GtkWidget *menu_vc_show_file = fm->item.show;
1924 
1925 		gtk_widget_set_sensitive(menu_vc_diff_file, f_have_vc);
1926 		gtk_widget_set_sensitive(menu_vc_blame, f_have_vc);
1927 		gtk_widget_set_sensitive(menu_vc_log_file, f_have_vc);
1928 		gtk_widget_set_sensitive(menu_vc_revert_file, f_have_vc);
1929 		gtk_widget_set_sensitive(menu_vc_remove_file, f_have_vc);
1930 		gtk_widget_set_sensitive(menu_vc_add_file, d_have_vc && !f_have_vc);
1931 		gtk_widget_set_sensitive(menu_vc_show_file, f_have_vc);
1932 	}
1933 
1934 	gtk_widget_set_sensitive(menu_vc_diff_dir, d_have_vc);
1935 	gtk_widget_set_sensitive(menu_vc_diff_basedir, d_have_vc);
1936 
1937 	gtk_widget_set_sensitive(menu_vc_log_dir, d_have_vc);
1938 	gtk_widget_set_sensitive(menu_vc_log_basedir, d_have_vc);
1939 
1940 	gtk_widget_set_sensitive(menu_vc_status, d_have_vc);
1941 
1942 	gtk_widget_set_sensitive(menu_vc_revert_dir, f_have_vc);
1943 	gtk_widget_set_sensitive(menu_vc_revert_basedir, f_have_vc);
1944 
1945 	gtk_widget_set_sensitive(menu_vc_update, d_have_vc);
1946 	gtk_widget_set_sensitive(menu_vc_commit, d_have_vc);
1947 }
1948 
1949 
1950 static void
kbdiff_file(G_GNUC_UNUSED guint key_id)1951 kbdiff_file(G_GNUC_UNUSED guint key_id)
1952 {
1953 	vcdiff_file_activated(NULL, NULL);
1954 }
1955 
1956 static void
kbdiff_dir(G_GNUC_UNUSED guint key_id)1957 kbdiff_dir(G_GNUC_UNUSED guint key_id)
1958 {
1959 	vcdiff_dir_activated(NULL, GINT_TO_POINTER(FLAG_DIR));
1960 }
1961 
1962 static void
kbdiff_basedir(G_GNUC_UNUSED guint key_id)1963 kbdiff_basedir(G_GNUC_UNUSED guint key_id)
1964 {
1965 	vcdiff_dir_activated(NULL, GINT_TO_POINTER(FLAG_BASEDIR));
1966 }
1967 
1968 static void
kbstatus(G_GNUC_UNUSED guint key_id)1969 kbstatus(G_GNUC_UNUSED guint key_id)
1970 {
1971 	vcstatus_activated(NULL, NULL);
1972 }
1973 
1974 static void
kbcommit(G_GNUC_UNUSED guint key_id)1975 kbcommit(G_GNUC_UNUSED guint key_id)
1976 {
1977 	vccommit_activated(NULL, NULL);
1978 }
1979 
1980 static void
kbrevert_file(G_GNUC_UNUSED guint key_id)1981 kbrevert_file(G_GNUC_UNUSED guint key_id)
1982 {
1983 	vcrevert_activated(NULL, NULL);
1984 }
1985 
1986 static void
kbrevert_dir(G_GNUC_UNUSED guint key_id)1987 kbrevert_dir(G_GNUC_UNUSED guint key_id)
1988 {
1989 	vcrevert_dir_activated(NULL, FLAG_DIR);
1990 }
1991 
1992 static void
kbrevert_basedir(G_GNUC_UNUSED guint key_id)1993 kbrevert_basedir(G_GNUC_UNUSED guint key_id)
1994 {
1995 	vcrevert_dir_activated(NULL, FLAG_BASEDIR);
1996 }
1997 
1998 static void
kbupdate(G_GNUC_UNUSED guint key_id)1999 kbupdate(G_GNUC_UNUSED guint key_id)
2000 {
2001 	vcupdate_activated(NULL, NULL);
2002 }
2003 
2004 
2005 static struct
2006 {
2007 	GtkWidget *cb_changed_flag;
2008 	GtkWidget *cb_confirm_add;
2009 	GtkWidget *cb_max_commit;
2010 	GtkWidget *cb_external_diff;
2011 	GtkWidget *cb_editor_menu_entries;
2012 	GtkWidget *cb_attach_to_menubar;
2013 	GtkWidget *cb_cvs;
2014 	GtkWidget *cb_git;
2015 	GtkWidget *cb_fossil;
2016 	GtkWidget *cb_svn;
2017 	GtkWidget *cb_svk;
2018 	GtkWidget *cb_bzr;
2019 	GtkWidget *cb_hg;
2020 #ifdef USE_GTKSPELL
2021 	GtkWidget *spellcheck_lang_textbox;
2022 #endif
2023 }
2024 widgets;
2025 
2026 static void
save_config(void)2027 save_config(void)
2028 {
2029 	GSList *list_item = NULL;
2030 	gint list_item_count = 0;
2031 	GKeyFile *config = g_key_file_new();
2032 	gchar *config_dir = g_path_get_dirname(config_file);
2033 
2034 	g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL);
2035 
2036 	g_key_file_set_boolean(config, "VC", "set_changed_flag", set_changed_flag);
2037 	g_key_file_set_boolean(config, "VC", "set_add_confirmation", set_add_confirmation);
2038 	g_key_file_set_boolean(config, "VC", "set_external_diff", set_external_diff);
2039 	g_key_file_set_boolean(config, "VC", "set_maximize_commit_dialog",
2040 				   set_maximize_commit_dialog);
2041 	g_key_file_set_boolean(config, "VC", "set_editor_menu_entries", set_editor_menu_entries);
2042 	g_key_file_set_boolean(config, "VC", "attach_to_menubar", set_menubar_entry);
2043 
2044 	g_key_file_set_boolean(config, "VC", "enable_cvs", enable_cvs);
2045 	g_key_file_set_boolean(config, "VC", "enable_git", enable_git);
2046 	g_key_file_set_boolean(config, "VC", "enable_fossil", enable_fossil);
2047 	g_key_file_set_boolean(config, "VC", "enable_svn", enable_svn);
2048 	g_key_file_set_boolean(config, "VC", "enable_svk", enable_svk);
2049 	g_key_file_set_boolean(config, "VC", "enable_bzr", enable_bzr);
2050 	g_key_file_set_boolean(config, "VC", "enable_hg", enable_hg);
2051 
2052 #ifdef USE_GTKSPELL
2053 	g_key_file_set_string(config, "VC", "spellchecking_language", lang);
2054 #endif
2055 
2056 	if (commit_dialog_width > 0 && commit_dialog_height > 0)
2057 	{
2058 		g_key_file_set_integer(config, "CommitDialog",
2059 			"commit_dialog_width", commit_dialog_width);
2060 		g_key_file_set_integer(config, "CommitDialog",
2061 			"commit_dialog_height", commit_dialog_height);
2062 	}
2063 
2064 	g_key_file_remove_group(config, "CommitMessageHistory", NULL); /* cleanup */
2065 	foreach_slist(list_item, commit_message_history)
2066 	{
2067 		gchar *key = g_strdup_printf("message_%d", list_item_count);
2068 		g_key_file_set_string(config, "CommitMessageHistory", key, list_item->data);
2069 		g_free(key);
2070 
2071 		list_item_count++;
2072 	}
2073 
2074 	if (!g_file_test(config_dir, G_FILE_TEST_IS_DIR)
2075 		&& utils_mkdir(config_dir, TRUE) != 0)
2076 	{
2077 		dialogs_show_msgbox(GTK_MESSAGE_ERROR,
2078 					_
2079 					("Plugin configuration directory could not be created."));
2080 	}
2081 	else
2082 	{
2083 		/* write config to file */
2084 		gchar *data = g_key_file_to_data(config, NULL, NULL);
2085 		utils_write_file(config_file, data);
2086 		g_free(data);
2087 	}
2088 
2089 	g_free(config_dir);
2090 	g_key_file_free(config);
2091 }
2092 
2093 static void
on_configure_response(G_GNUC_UNUSED GtkDialog * dialog,gint response,G_GNUC_UNUSED gpointer user_data)2094 on_configure_response(G_GNUC_UNUSED GtkDialog * dialog, gint response,
2095 			  G_GNUC_UNUSED gpointer user_data)
2096 {
2097 	if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY)
2098 	{
2099 		set_changed_flag =
2100 			gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.cb_changed_flag));
2101 		set_add_confirmation =
2102 			gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.cb_confirm_add));
2103 		set_maximize_commit_dialog =
2104 			gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.cb_max_commit));
2105 
2106 		set_external_diff =
2107 			gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.cb_external_diff));
2108 
2109 		set_editor_menu_entries =
2110 			gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.cb_editor_menu_entries));
2111 		set_menubar_entry =
2112 			gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.cb_attach_to_menubar));
2113 
2114 		enable_cvs = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.cb_cvs));
2115 		enable_git = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.cb_git));
2116 		enable_fossil = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.cb_fossil));
2117 		enable_svn = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.cb_svn));
2118 		enable_svk = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.cb_svk));
2119 		enable_bzr = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.cb_bzr));
2120 		enable_hg = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.cb_hg));
2121 
2122 #ifdef USE_GTKSPELL
2123 		g_free(lang);
2124 		lang = g_strdup(gtk_entry_get_text(GTK_ENTRY(widgets.spellcheck_lang_textbox)));
2125 #endif
2126 
2127 		save_config();
2128 
2129 		if (set_editor_menu_entries == FALSE)
2130 			remove_menuitems_from_editor_menu();
2131 		else
2132 			add_menuitems_to_editor_menu();
2133 
2134 		registrate();
2135 	}
2136 }
2137 
2138 GtkWidget *
plugin_configure(GtkDialog * dialog)2139 plugin_configure(GtkDialog * dialog)
2140 {
2141 	GtkWidget *vbox;
2142 
2143 #ifdef USE_GTKSPELL
2144 	GtkWidget *label_spellcheck_lang;
2145 #endif
2146 
2147 	vbox = gtk_vbox_new(FALSE, 6);
2148 
2149 	widgets.cb_changed_flag =
2150 		gtk_check_button_new_with_label(_
2151 						("Set Changed-flag for document tabs created by the plugin"));
2152 	gtk_widget_set_tooltip_text(widgets.cb_changed_flag,
2153 				 _
2154 				 ("If this option is activated, every new by the VC-plugin created document tab "
2155 				  "will be marked as changed. Even this option is useful in some cases, it could cause "
2156 				  "a big number of annoying \"Do you want to save\"-dialogs."));
2157 	gtk_button_set_focus_on_click(GTK_BUTTON(widgets.cb_changed_flag), FALSE);
2158 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.cb_changed_flag), set_changed_flag);
2159 	gtk_box_pack_start(GTK_BOX(vbox), widgets.cb_changed_flag, FALSE, FALSE, 2);
2160 
2161 	widgets.cb_confirm_add =
2162 		gtk_check_button_new_with_label(_("Confirm adding new files to a VCS"));
2163 	gtk_widget_set_tooltip_text(widgets.cb_confirm_add,
2164 				 _
2165 				 ("Shows a confirmation dialog on adding a new (created) file to VCS."));
2166 	gtk_button_set_focus_on_click(GTK_BUTTON(widgets.cb_confirm_add), FALSE);
2167 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.cb_confirm_add),
2168 					 set_add_confirmation);
2169 	gtk_box_pack_start(GTK_BOX(vbox), widgets.cb_confirm_add, TRUE, FALSE, 2);
2170 
2171 	widgets.cb_max_commit = gtk_check_button_new_with_label(_("Maximize commit dialog"));
2172 	gtk_widget_set_tooltip_text(widgets.cb_max_commit, _("Show commit dialog maximize."));
2173 	gtk_button_set_focus_on_click(GTK_BUTTON(widgets.cb_max_commit), FALSE);
2174 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.cb_max_commit),
2175 					 set_maximize_commit_dialog);
2176 	gtk_box_pack_start(GTK_BOX(vbox), widgets.cb_max_commit, TRUE, FALSE, 2);
2177 
2178 	widgets.cb_external_diff = gtk_check_button_new_with_label(_("Use external diff viewer"));
2179 	gtk_widget_set_tooltip_text(widgets.cb_external_diff,
2180 				 _("Use external diff viewer for file diff."));
2181 	gtk_button_set_focus_on_click(GTK_BUTTON(widgets.cb_external_diff), FALSE);
2182 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.cb_external_diff),
2183 					 set_external_diff);
2184 	gtk_box_pack_start(GTK_BOX(vbox), widgets.cb_external_diff, TRUE, FALSE, 2);
2185 
2186 	widgets.cb_editor_menu_entries = gtk_check_button_new_with_label(_("Show VC entries at editor menu"));
2187 	gtk_widget_set_tooltip_text(widgets.cb_editor_menu_entries,
2188 				 _("Show entries for VC functions inside editor menu"));
2189 	gtk_button_set_focus_on_click(GTK_BUTTON(widgets.cb_editor_menu_entries), FALSE);
2190 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.cb_editor_menu_entries), set_editor_menu_entries);
2191 	gtk_box_pack_start(GTK_BOX(vbox), widgets.cb_editor_menu_entries, TRUE, FALSE, 2);
2192 
2193 	widgets.cb_attach_to_menubar = gtk_check_button_new_with_label(_("Attach menu to menubar"));
2194 	gtk_widget_set_tooltip_text(widgets.cb_attach_to_menubar,
2195 				 _("Whether menu for this plugin are getting placed either "
2196 				   "inside tools menu or directly inside Geany's menubar. "
2197 				   "Will take in account after next start of GeanyVC"));
2198 	gtk_button_set_focus_on_click(GTK_BUTTON(widgets.cb_attach_to_menubar), FALSE);
2199 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.cb_attach_to_menubar),
2200 		set_menubar_entry);
2201 	gtk_box_pack_start(GTK_BOX(vbox), widgets.cb_attach_to_menubar, TRUE, FALSE, 2);
2202 
2203 	widgets.cb_cvs = gtk_check_button_new_with_label(_("Enable CVS"));
2204 	gtk_button_set_focus_on_click(GTK_BUTTON(widgets.cb_cvs), FALSE);
2205 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.cb_cvs), enable_cvs);
2206 	gtk_box_pack_start(GTK_BOX(vbox), widgets.cb_cvs, TRUE, FALSE, 2);
2207 
2208 	widgets.cb_git = gtk_check_button_new_with_label(_("Enable GIT"));
2209 	gtk_button_set_focus_on_click(GTK_BUTTON(widgets.cb_git), FALSE);
2210 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.cb_git), enable_git);
2211 	gtk_box_pack_start(GTK_BOX(vbox), widgets.cb_git, TRUE, FALSE, 2);
2212 
2213 	widgets.cb_fossil = gtk_check_button_new_with_label(_("Enable Fossil"));
2214 	gtk_button_set_focus_on_click(GTK_BUTTON(widgets.cb_fossil), FALSE);
2215 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.cb_fossil), enable_fossil);
2216 	gtk_box_pack_start(GTK_BOX(vbox), widgets.cb_fossil, TRUE, FALSE, 2);
2217 
2218 	widgets.cb_svn = gtk_check_button_new_with_label(_("Enable SVN"));
2219 	gtk_button_set_focus_on_click(GTK_BUTTON(widgets.cb_svn), FALSE);
2220 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.cb_svn), enable_svn);
2221 	gtk_box_pack_start(GTK_BOX(vbox), widgets.cb_svn, TRUE, FALSE, 2);
2222 
2223 	widgets.cb_svk = gtk_check_button_new_with_label(_("Enable SVK"));
2224 	gtk_button_set_focus_on_click(GTK_BUTTON(widgets.cb_svk), FALSE);
2225 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.cb_svk), enable_svk);
2226 	gtk_box_pack_start(GTK_BOX(vbox), widgets.cb_svk, TRUE, FALSE, 2);
2227 
2228 	widgets.cb_bzr = gtk_check_button_new_with_label(_("Enable Bazaar"));
2229 	gtk_button_set_focus_on_click(GTK_BUTTON(widgets.cb_bzr), FALSE);
2230 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.cb_bzr), enable_bzr);
2231 	gtk_box_pack_start(GTK_BOX(vbox), widgets.cb_bzr, TRUE, FALSE, 2);
2232 
2233 	widgets.cb_hg = gtk_check_button_new_with_label(_("Enable Mercurial"));
2234 	gtk_button_set_focus_on_click(GTK_BUTTON(widgets.cb_hg), FALSE);
2235 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.cb_hg), enable_hg);
2236 	gtk_box_pack_start(GTK_BOX(vbox), widgets.cb_hg, TRUE, FALSE, 2);
2237 
2238 #ifdef USE_GTKSPELL
2239 	label_spellcheck_lang = gtk_label_new(_("Spellcheck language"));
2240 	widgets.spellcheck_lang_textbox = gtk_entry_new();
2241 	gtk_widget_show(widgets.spellcheck_lang_textbox);
2242 	if (lang != NULL)
2243 		gtk_entry_set_text(GTK_ENTRY(widgets.spellcheck_lang_textbox), lang);
2244 
2245 	gtk_misc_set_alignment(GTK_MISC(label_spellcheck_lang), 0, 0.5);
2246 	gtk_container_add(GTK_CONTAINER(vbox), label_spellcheck_lang);
2247 	gtk_container_add(GTK_CONTAINER(vbox), widgets.spellcheck_lang_textbox);
2248 #endif
2249 	gtk_widget_show_all(vbox);
2250 
2251 	g_signal_connect(dialog, "response", G_CALLBACK(on_configure_response), NULL);
2252 	return vbox;
2253 }
2254 
2255 static void
load_config(void)2256 load_config(void)
2257 {
2258 #ifdef USE_GTKSPELL
2259 	GError *error = NULL;
2260 #endif
2261 
2262 	gchar **commit_message_history_keys, **ptr = NULL;
2263 	gchar *commit_message;
2264 	GKeyFile *config = g_key_file_new();
2265 
2266 	g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL);
2267 
2268 	set_changed_flag = utils_get_setting_boolean(config, "VC",
2269 		"set_changed_flag", FALSE);
2270 	set_add_confirmation = utils_get_setting_boolean(config, "VC",
2271 		"set_add_confirmation", TRUE);
2272 	set_maximize_commit_dialog = utils_get_setting_boolean(config, "VC",
2273 		"set_maximize_commit_dialog", FALSE);
2274 	set_external_diff = utils_get_setting_boolean(config, "VC",
2275 		"set_external_diff", TRUE);
2276 	set_editor_menu_entries = utils_get_setting_boolean(config, "VC",
2277 		"set_editor_menu_entries", FALSE);
2278 	enable_cvs = utils_get_setting_boolean(config, "VC", "enable_cvs",
2279 		TRUE);
2280 	enable_git = utils_get_setting_boolean(config, "VC", "enable_git",
2281 		TRUE);
2282 	enable_fossil = utils_get_setting_boolean(config, "VC", "enable_fossil",
2283 		TRUE);
2284 	enable_svn = utils_get_setting_boolean(config, "VC", "enable_svn",
2285 		TRUE);
2286 	enable_svk = utils_get_setting_boolean(config, "VC", "enable_svk",
2287 		TRUE);
2288 	enable_bzr = utils_get_setting_boolean(config, "VC", "enable_bzr",
2289 		TRUE);
2290 	enable_hg = utils_get_setting_boolean(config, "VC", "enable_hg",
2291 		TRUE);
2292 	set_menubar_entry = utils_get_setting_boolean(config, "VC", "attach_to_menubar",
2293 		FALSE);
2294 
2295 #ifdef USE_GTKSPELL
2296 	lang = g_key_file_get_string(config, "VC", "spellchecking_language", &error);
2297 	if (error != NULL)
2298 	{
2299 		/* Set default value. Using system standard language. */
2300 		lang = NULL;
2301 		g_error_free(error);
2302 		error = NULL;
2303 	}
2304 #endif
2305 
2306 	commit_dialog_width = utils_get_setting_integer(config, "CommitDialog",
2307 		"commit_dialog_width", 700);
2308 	commit_dialog_height = utils_get_setting_integer(config, "CommitDialog",
2309 		"commit_dialog_height", 500);
2310 
2311 	commit_message_history_keys = g_key_file_get_keys(config,"CommitMessageHistory", NULL, NULL);
2312 	if (commit_message_history_keys != NULL)
2313 	{
2314 		foreach_strv(ptr, commit_message_history_keys)
2315 		{
2316 			commit_message = g_key_file_get_string(config, "CommitMessageHistory", *ptr, NULL);
2317 			/* do not free the newly allocated commit_message string as we need it later in the list,
2318 			 * will be finally freed when cleaning up the commit_message_history list */
2319 			commit_message_history = g_slist_append(commit_message_history, commit_message);
2320 		}
2321 		g_strfreev(commit_message_history_keys);
2322 	}
2323 	g_key_file_free(config);
2324 }
2325 
2326 static void
registrate(void)2327 registrate(void)
2328 {
2329 	gchar *path;
2330 	if (VC)
2331 	{
2332 		g_slist_free(VC);
2333 		VC = NULL;
2334 	}
2335 	REGISTER_VC(FOSSIL, enable_fossil);
2336 	REGISTER_VC(GIT, enable_git);
2337 	REGISTER_VC(SVN, enable_svn);
2338 	REGISTER_VC(CVS, enable_cvs);
2339 	REGISTER_VC(SVK, enable_svk);
2340 	REGISTER_VC(BZR, enable_bzr);
2341 	REGISTER_VC(HG, enable_hg);
2342 }
2343 
2344 static void
do_current_file_menu(GtkWidget ** parent_menu,const gchar * label,VCFileMenu ** file_menu)2345 do_current_file_menu(GtkWidget ** parent_menu, const gchar * label, VCFileMenu ** file_menu)
2346 {
2347 	/* Menu which will hold the items in the current file menu.
2348 	 * This menu has the same structure in case of Main menu and Editor's context menu.
2349 	 * We construct the menu, attach it to the parent menu, and save it so that
2350 	 * the updates could be applied in both Main menu and Editor context scopes.
2351 	 */
2352 	GtkWidget *cur_file_menu = NULL;
2353 	GtkWidget *menu_vc_diff_file = NULL;
2354 	GtkWidget *menu_vc_blame = NULL;
2355 	GtkWidget *menu_vc_log_file = NULL;
2356 	GtkWidget *menu_vc_revert_file = NULL;
2357 	GtkWidget *menu_vc_add_file = NULL;
2358 	GtkWidget *menu_vc_remove_file = NULL;
2359 	GtkWidget *menu_vc_show_file = NULL;
2360 
2361 	/* create the parent menu */
2362 	*parent_menu = gtk_image_menu_item_new_with_mnemonic(label);
2363 	g_signal_connect(* parent_menu, "activate", G_CALLBACK(update_menu_items), NULL);
2364 
2365 	cur_file_menu = gtk_menu_new();
2366 
2367 	/* Diff of current file */
2368 	menu_vc_diff_file = gtk_menu_item_new_with_mnemonic(_("_Diff"));
2369 	gtk_container_add(GTK_CONTAINER(cur_file_menu), menu_vc_diff_file);
2370 	gtk_widget_set_tooltip_text(menu_vc_diff_file,
2371 				 _("Make a diff from the current active file"));
2372 
2373 	g_signal_connect(menu_vc_diff_file, "activate", G_CALLBACK(vcdiff_file_activated), NULL);
2374 
2375 	/* Revert current file */
2376 	menu_vc_revert_file = gtk_menu_item_new_with_mnemonic(_("_Revert"));
2377 	gtk_container_add(GTK_CONTAINER(cur_file_menu), menu_vc_revert_file);
2378 	gtk_widget_set_tooltip_text(menu_vc_revert_file,
2379 				 _("Restore pristine working copy file (undo local edits)."));
2380 
2381 	g_signal_connect(menu_vc_revert_file, "activate", G_CALLBACK(vcrevert_activated), NULL);
2382 
2383 
2384 	gtk_container_add(GTK_CONTAINER(cur_file_menu), gtk_separator_menu_item_new());
2385 
2386 
2387 	/* Blame for current file */
2388 	menu_vc_blame = gtk_menu_item_new_with_mnemonic(_("_Blame"));
2389 	gtk_container_add(GTK_CONTAINER(cur_file_menu), menu_vc_blame);
2390 	gtk_widget_set_tooltip_text(menu_vc_blame,
2391 				 _("Shows the changes made at one file per revision and author."));
2392 
2393 	g_signal_connect(menu_vc_blame, "activate", G_CALLBACK(vcblame_activated), NULL);
2394 
2395 	gtk_container_add(GTK_CONTAINER(cur_file_menu), gtk_separator_menu_item_new());
2396 
2397 	/* History/log of current file */
2398 	menu_vc_log_file = gtk_menu_item_new_with_mnemonic(_("_History (log)"));
2399 	gtk_container_add(GTK_CONTAINER(cur_file_menu), menu_vc_log_file);
2400 	gtk_widget_set_tooltip_text(menu_vc_log_file,
2401 				 _("Shows the log of the current file"));
2402 
2403 	g_signal_connect(menu_vc_log_file, "activate", G_CALLBACK(vclog_file_activated), NULL);
2404 
2405 	/* base version of the current file */
2406 	menu_vc_show_file = gtk_menu_item_new_with_mnemonic(_("_Original"));
2407 	gtk_container_add(GTK_CONTAINER(cur_file_menu), menu_vc_show_file);
2408 	gtk_widget_set_tooltip_text(menu_vc_show_file,
2409 				 _("Shows the original of the current file"));
2410 
2411 	g_signal_connect(menu_vc_show_file, "activate", G_CALLBACK(vcshow_file_activated), NULL);
2412 
2413 
2414 	gtk_container_add(GTK_CONTAINER(cur_file_menu), gtk_separator_menu_item_new());
2415 
2416 	/* add current file */
2417 	menu_vc_add_file = gtk_menu_item_new_with_mnemonic(_("_Add to Version Control"));
2418 	gtk_container_add(GTK_CONTAINER(cur_file_menu), menu_vc_add_file);
2419 	gtk_widget_set_tooltip_text(menu_vc_add_file, _("Add file to repository."));
2420 
2421 	g_signal_connect(menu_vc_add_file, "activate",
2422 			 G_CALLBACK(vcadd_activated), NULL);
2423 
2424 	/* remove current file */
2425 	menu_vc_remove_file = gtk_menu_item_new_with_mnemonic(_("_Remove from Version Control"));
2426 	gtk_container_add(GTK_CONTAINER(cur_file_menu), menu_vc_remove_file);
2427 	gtk_widget_set_tooltip_text(menu_vc_remove_file, _("Remove file from repository."));
2428 
2429 	g_signal_connect(menu_vc_remove_file, "activate", G_CALLBACK(vcremove_activated), NULL);
2430 
2431 	/* connect to parent menu */
2432 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(*parent_menu), cur_file_menu);
2433 
2434 	/* save the created file menu */
2435 	if (*file_menu == NULL)
2436 		*file_menu = g_new0(VCFileMenu, 1);
2437 
2438 	if (*file_menu)
2439 	{
2440 		VCFileMenu *fm = *file_menu;
2441 
2442 		fm->menu = cur_file_menu;
2443 		fm->item.diff = menu_vc_diff_file;
2444 		fm->item.blame = menu_vc_blame;
2445 		fm->item.log = menu_vc_log_file;
2446 		fm->item.revert = menu_vc_revert_file;
2447 		fm->item.add = menu_vc_add_file;
2448 		fm->item.remove = menu_vc_remove_file;
2449 		fm->item.show = menu_vc_show_file;
2450 	}
2451 }
2452 
2453 static void
do_current_dir_menu(GtkWidget ** parent_menu)2454 do_current_dir_menu(GtkWidget ** parent_menu)
2455 {
2456 	GtkWidget *cur_dir_menu = NULL;
2457 	/* Menu which will hold the items in the current file menu */
2458 	cur_dir_menu = gtk_menu_new();
2459 
2460 	*parent_menu = gtk_image_menu_item_new_with_mnemonic(_("_Directory"));
2461 	g_signal_connect(* parent_menu, "activate", G_CALLBACK(update_menu_items), NULL);
2462 	/* Diff of the current dir */
2463 	menu_vc_diff_dir = gtk_menu_item_new_with_mnemonic(_("_Diff"));
2464 	gtk_container_add(GTK_CONTAINER(cur_dir_menu), menu_vc_diff_dir);
2465 	gtk_widget_set_tooltip_text(menu_vc_diff_dir,
2466 				 _("Make a diff from the directory of the current active file"));
2467 
2468 	g_signal_connect(menu_vc_diff_dir, "activate",
2469 			 G_CALLBACK(vcdiff_dir_activated), GINT_TO_POINTER(FLAG_DIR));
2470 
2471 	/* Revert current dir */
2472 	menu_vc_revert_dir = gtk_menu_item_new_with_mnemonic(_("_Revert"));
2473 	gtk_container_add(GTK_CONTAINER(cur_dir_menu), menu_vc_revert_dir);
2474 	gtk_widget_set_tooltip_text(menu_vc_revert_dir,
2475 				 _("Restore original files in the current folder (undo local edits)."));
2476 
2477 	g_signal_connect(menu_vc_revert_dir, "activate",
2478 			 G_CALLBACK(vcrevert_dir_activated), GINT_TO_POINTER(FLAG_DIR));
2479 
2480 	gtk_container_add(GTK_CONTAINER(cur_dir_menu), gtk_separator_menu_item_new());
2481 	/* History/log of the current dir */
2482 	menu_vc_log_dir = gtk_menu_item_new_with_mnemonic(_("_History (log)"));
2483 	gtk_container_add(GTK_CONTAINER(cur_dir_menu), menu_vc_log_dir);
2484 	gtk_widget_set_tooltip_text(menu_vc_log_dir,
2485 				 _("Shows the log of the current directory"));
2486 
2487 
2488 	/* connect to parent menu */
2489 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(*parent_menu), cur_dir_menu);
2490 }
2491 
2492 static void
do_basedir_menu(GtkWidget ** parent_menu)2493 do_basedir_menu(GtkWidget ** parent_menu)
2494 {
2495 	GtkWidget *basedir_menu = NULL;
2496 	/* Menu which will hold the items in the current file menu */
2497 	basedir_menu = gtk_menu_new();
2498 
2499 	*parent_menu = gtk_image_menu_item_new_with_mnemonic(_("_Base Directory"));
2500 	g_signal_connect(* parent_menu, "activate", G_CALLBACK(update_menu_items), NULL);
2501 
2502 	/* Complete diff of base directory */
2503 	menu_vc_diff_basedir = gtk_menu_item_new_with_mnemonic(_("_Diff"));
2504 	gtk_container_add(GTK_CONTAINER(basedir_menu), menu_vc_diff_basedir);
2505 	gtk_widget_set_tooltip_text(menu_vc_diff_basedir, _("Make a diff from the top VC directory"));
2506 
2507 	g_signal_connect(menu_vc_diff_basedir, "activate",
2508 			 G_CALLBACK(vcdiff_dir_activated), GINT_TO_POINTER(FLAG_BASEDIR));
2509 
2510 	/* Revert everything */
2511 	menu_vc_revert_basedir = gtk_menu_item_new_with_mnemonic(_("_Revert"));
2512 	gtk_container_add(GTK_CONTAINER(basedir_menu), menu_vc_revert_basedir);
2513 	gtk_widget_set_tooltip_text(menu_vc_revert_basedir, _("Revert any local edits."));
2514 
2515 	g_signal_connect(menu_vc_revert_basedir, "activate",
2516 			 G_CALLBACK(vcrevert_dir_activated), GINT_TO_POINTER(FLAG_BASEDIR));
2517 
2518 	gtk_container_add(GTK_CONTAINER(basedir_menu), gtk_separator_menu_item_new());
2519 	g_signal_connect(menu_vc_log_dir, "activate",
2520 			 G_CALLBACK(vclog_dir_activated), NULL);
2521 
2522 	/* Complete History/Log of base directory */
2523 	menu_vc_log_basedir = gtk_menu_item_new_with_mnemonic(_("_History (log)"));
2524 	gtk_container_add(GTK_CONTAINER(basedir_menu), menu_vc_log_basedir);
2525 	gtk_widget_set_tooltip_text(menu_vc_log_basedir,
2526 				 _("Shows the log of the top VC directory"));
2527 
2528 	g_signal_connect(menu_vc_log_basedir, "activate",
2529 			 G_CALLBACK(vclog_basedir_activated), NULL);
2530 
2531 	/* connect to parent menu */
2532 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(*parent_menu), basedir_menu);
2533 }
2534 
2535 static void
add_menuitems_to_editor_menu(void)2536 add_menuitems_to_editor_menu(void)
2537 {
2538 	/* Add file menu also to editor menu (at mouse cursor) */
2539 	if (set_editor_menu_entries == TRUE && editor_menu_vc == NULL)
2540 	{
2541 		menu_item_sep = gtk_separator_menu_item_new();
2542 		gtk_container_add(GTK_CONTAINER(geany->main_widgets->editor_menu), menu_item_sep);
2543 		do_current_file_menu(&editor_menu_vc, _("_VC file Actions"), &editor_menu_vc_file_menu);
2544 		gtk_container_add(GTK_CONTAINER(geany->main_widgets->editor_menu), editor_menu_vc);
2545 		gtk_widget_show_all(editor_menu_vc);
2546 		gtk_widget_show_all(menu_item_sep);
2547 	}
2548 
2549 	/* Add commit item to editor menu */
2550 	if (set_editor_menu_entries == TRUE && editor_menu_commit == NULL)
2551 	{
2552 		editor_menu_commit = gtk_menu_item_new_with_mnemonic(_("VC _Commit..."));
2553 		gtk_container_add(GTK_CONTAINER(geany->main_widgets->editor_menu), editor_menu_commit);
2554 		g_signal_connect(editor_menu_commit, "activate",
2555 			G_CALLBACK(vccommit_activated), NULL);
2556 		gtk_widget_show_all(editor_menu_commit);
2557 	}
2558 }
2559 
2560 static void
remove_menuitems_from_editor_menu(void)2561 remove_menuitems_from_editor_menu(void)
2562 {
2563 	if (editor_menu_vc != NULL)
2564 	{
2565 		gtk_widget_destroy(editor_menu_vc);
2566 		editor_menu_vc = NULL;
2567 	}
2568 	if (editor_menu_vc_file_menu)
2569 	{
2570 		g_free(editor_menu_vc_file_menu);
2571 		editor_menu_vc_file_menu = NULL;
2572 	}
2573 	if (editor_menu_commit != NULL)
2574 	{
2575 		gtk_widget_destroy(editor_menu_commit);
2576 		editor_menu_commit = NULL;
2577 	}
2578 	if (menu_item_sep != NULL)
2579 	{
2580 		gtk_widget_destroy(menu_item_sep);
2581 		menu_item_sep = NULL;
2582 	}
2583 }
2584 
2585 static void
init_keybindings(void)2586 init_keybindings(void)
2587 {
2588 	GtkWidget *menu_vc_diff_file = menu_vc_file_menu->item.diff;
2589 	GtkWidget *menu_vc_revert_file = menu_vc_file_menu->item.revert;
2590 
2591 	/* init keybindings */
2592 	GeanyKeyGroup *plugin_key_group =
2593 				plugin_set_key_group(geany_plugin, "geanyvc", COUNT_KB, NULL);
2594 
2595 	keybindings_set_item(plugin_key_group, VC_DIFF_FILE, kbdiff_file, 0, 0,
2596 				 "vc_show_diff_of_file", _("Show diff of file"), menu_vc_diff_file);
2597 	keybindings_set_item(plugin_key_group, VC_DIFF_DIR, kbdiff_dir, 0, 0,
2598 				 "vc_show_diff_of_dir", _("Show diff of directory"), menu_vc_diff_dir);
2599 	keybindings_set_item(plugin_key_group, VC_DIFF_BASEDIR, kbdiff_basedir, 0, 0,
2600 				 "vc_show_diff_of_basedir", _("Show diff of basedir"),
2601 				 menu_vc_diff_basedir);
2602 	keybindings_set_item(plugin_key_group, VC_COMMIT, kbcommit, 0, 0, "vc_commit",
2603 				 _("Commit changes"), menu_vc_commit);
2604 	keybindings_set_item(plugin_key_group, VC_STATUS, kbstatus, 0, 0, "vc_status",
2605 				 _("Show status"), menu_vc_status);
2606 	keybindings_set_item(plugin_key_group, VC_REVERT_FILE, kbrevert_file, 0, 0,
2607 				 "vc_revert_file", _("Revert single file"), menu_vc_revert_file);
2608 	keybindings_set_item(plugin_key_group, VC_REVERT_DIR, kbrevert_dir, 0, 0, "vc_revert_dir",
2609 				 _("Revert directory"), menu_vc_revert_dir);
2610 	keybindings_set_item(plugin_key_group, VC_REVERT_BASEDIR, kbrevert_basedir, 0, 0,
2611 				 "vc_revert_basedir", _("Revert base directory"), menu_vc_revert_basedir);
2612 	keybindings_set_item(plugin_key_group, VC_UPDATE, kbupdate, 0, 0, "vc_update",
2613 				 _("Update file"), menu_vc_update);
2614 }
2615 
2616 /* Called by Geany to initialize the plugin */
2617 void
plugin_init(G_GNUC_UNUSED GeanyData * data)2618 plugin_init(G_GNUC_UNUSED GeanyData * data)
2619 {
2620 	GtkWidget *menu_vc = NULL;
2621 	GtkWidget *menu_vc_menu = NULL;
2622 	GtkWidget *menu_vc_file = NULL;
2623 	GtkWidget *menu_vc_dir = NULL;
2624 	GtkWidget *menu_vc_basedir = NULL;
2625 
2626 	config_file =
2627 		g_strconcat(geany->app->configdir, G_DIR_SEPARATOR_S, "plugins", G_DIR_SEPARATOR_S,
2628 				"VC", G_DIR_SEPARATOR_S, "VC.conf", NULL);
2629 
2630 	load_config();
2631 	registrate();
2632 
2633 	external_diff_viewer_init();
2634 
2635 	if (set_menubar_entry == TRUE)
2636 	{
2637 		GtkMenuShell *menubar;
2638 		GList *menubar_children;
2639 
2640 		menubar = GTK_MENU_SHELL(
2641 				ui_lookup_widget(geany->main_widgets->window, "menubar1"));
2642 
2643 		menu_vc = gtk_menu_item_new_with_mnemonic(_("_VC"));
2644 		menubar_children = gtk_container_get_children(GTK_CONTAINER(menubar));
2645 		gtk_menu_shell_insert(menubar, menu_vc, g_list_length(menubar_children) - 1);
2646 		g_list_free(menubar_children);
2647 	}
2648 	else
2649 	{
2650 		menu_vc = gtk_image_menu_item_new_with_mnemonic(_("_Version Control"));
2651 		gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu), menu_vc);
2652 	}
2653 
2654 	g_signal_connect(menu_vc, "activate", G_CALLBACK(update_menu_items), NULL);
2655 
2656 	menu_vc_menu = gtk_menu_new();
2657 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_vc), menu_vc_menu);
2658 
2659 	/* Create the current file Submenu */
2660 	do_current_file_menu(&menu_vc_file, _("_File"), &menu_vc_file_menu);
2661 	gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_file);
2662 
2663 	/* Create the current directory Submenu */
2664 	do_current_dir_menu(&menu_vc_dir);
2665 	gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_dir);
2666 	/* Create the current base directory Submenu */
2667 	do_basedir_menu(&menu_vc_basedir);
2668 	gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_basedir);
2669 	gtk_container_add(GTK_CONTAINER(menu_vc_menu), gtk_separator_menu_item_new());
2670 
2671 	/* Status of basedir */
2672 	menu_vc_status = gtk_menu_item_new_with_mnemonic(_("_Status"));
2673 	gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_status);
2674 	gtk_widget_set_tooltip_text(menu_vc_status, _("Show status."));
2675 
2676 	g_signal_connect(menu_vc_status, "activate", G_CALLBACK(vcstatus_activated), NULL);
2677 
2678 	/* complete update */
2679 	menu_vc_update = gtk_menu_item_new_with_mnemonic(_("_Update"));
2680 	gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_update);
2681 	gtk_widget_set_tooltip_text(menu_vc_update, _("Update from remote repository."));
2682 
2683 	g_signal_connect(menu_vc_update, "activate", G_CALLBACK(vcupdate_activated), NULL);
2684 
2685 	/* Commit all changes */
2686 	menu_vc_commit = gtk_menu_item_new_with_mnemonic(_("_Commit..."));
2687 	gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_commit);
2688 	gtk_widget_set_tooltip_text(menu_vc_commit, _("Commit changes."));
2689 
2690 	g_signal_connect(menu_vc_commit, "activate", G_CALLBACK(vccommit_activated), NULL);
2691 
2692 	gtk_widget_show_all(menu_vc);
2693 
2694 	/* initialize keybindings */
2695 	init_keybindings();
2696 
2697 	/* init entries inside editor menu */
2698 	add_menuitems_to_editor_menu();
2699 
2700 	ui_add_document_sensitive(menu_vc);
2701 	menu_entry = menu_vc;
2702 }
2703 
2704 
2705 /* Called by Geany before unloading the plugin. */
2706 void
plugin_cleanup(void)2707 plugin_cleanup(void)
2708 {
2709 	save_config();
2710 	external_diff_viewer_deinit();
2711 	remove_menuitems_from_editor_menu();
2712 	gtk_widget_destroy(menu_entry);
2713 	if (menu_vc_file_menu)
2714 	{
2715 		g_free(menu_vc_file_menu);
2716 		menu_vc_file_menu = NULL;
2717 	}
2718 	g_slist_free(VC);
2719 	VC = NULL;
2720 	g_slist_free_full(commit_message_history, g_free);
2721 	g_free(config_file);
2722 }
2723