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