1 /*
2 * This code is supplied as is, and is used at your own risk.
3 * The GNU GPL version 2 rules apply to this code (see http://fsf.org/>
4 * You can alter it, and pass it on as you want.
5 * If you alter it, or pass it on, the only restriction is that this disclamour and licence be
6 * left intact
7 *
8 * william.fraser@virgin.net
9 * 2010-11-01
10 */
11
12
13 #include "config.h"
14 #include <geanyplugin.h>
15 #include "utils.h"
16 #include "Scintilla.h"
17 #include <stdlib.h>
18 #include <sys/stat.h>
19 #include <string.h>
20 #include <gdk/gdkkeysyms.h>
21 #include <gtk/gtk.h>
22 #include <glib/gstdio.h>
23 #include <gp_gtkcompat.h>
24
25 static const gint base64_char_to_int[]=
26 {
27 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
28 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
29 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
30 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255,
31 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
32 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
33 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
34 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255
35 };
36
37 static const gchar base64_int_to_char[]=
38 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
39
40 /* define structures used in this plugin */
41 typedef struct FileData
42 {
43 gchar *pcFileName; /* holds filename */
44 gint iBookmark[10]; /* holds bookmark lines or -1 for not set */
45 gint iBookmarkMarkerUsed[10]; /*holds which marker (2-24) is used for this bookmark */
46 gint iBookmarkLinePos[10]; /* holds position of cursor in line */
47 gchar *pcFolding; /* holds which folds are open and which not */
48 gint LastChangedTime; /* time file was last changed by this editor */
49 gchar *pcBookmarks; /* holds non-numbered bookmarks */
50 struct FileData * NextNode;
51 } FileData;
52
53
54 GeanyPlugin *geany_plugin;
55 GeanyData *geany_data;
56
57 PLUGIN_VERSION_CHECK(224)
58
59 PLUGIN_SET_TRANSLATABLE_INFO(LOCALEDIR, GETTEXT_PACKAGE,
60 "Numbered Bookmarks",
61 _("Numbered Bookmarks for Geany"),
62 "1.0",
63 "William Fraser <william.fraser@virgin.net>")
64
65 /* Plugin user alterable settings */
66 static gboolean bCenterWhenGotoBookmark=TRUE;
67 static gboolean bRememberFolds=TRUE;
68 static gint PositionInLine=0;
69 static gint WhereToSaveFileDetails=0;
70 static gchar *FileDetailsSuffix; /* initialised when settings loaded */
71 static gboolean bRememberBookmarks=TRUE;
72
73 /* internal variables */
74 static gint iShiftNumbers[]={41,33,34,163,36,37,94,38,42,40};
75 static FileData *fdKnownFilesSettings=NULL;
76 static gulong key_release_signal_id;
77
78 /* default config file */
79 const gchar default_config[] =
80 "[Settings]\n"
81 "Center_When_Goto_Bookmark = true\n"
82 "Remember_Folds = true\n"
83 "Position_In_Line = 0\n"
84 "Remember_Bookmarks = true\n"
85 "[FileData]";
86
87 /* Definitions for bookmark images */
88 static const gchar * aszMarkerImage0[] =
89 {
90 "17 14 3 1", /* width height colours characters-per-pixel */
91 ". c None",
92 "B c #000000",
93 "* c #FFFF00",
94 "...BBBBBBBBBB....",
95 "..B**********B...",
96 ".B****BBBB****B..",
97 "B****BB**BB****B.",
98 "B****B****B****B.",
99 "B****B****B****B.",
100 "B****B*BB*B****B.",
101 "B****B****B****B.",
102 "B****B****B****B.",
103 "B****B****B****B.",
104 "B****BB**BB****B.",
105 ".B****BBBB****B..",
106 "..B**********B...",
107 "...BBBBBBBBBB...."
108 };
109 static const gchar * aszMarkerImage1[] =
110 {
111 "17 14 3 1", /* width height colours characters-per-pixel */
112 ". c None",
113 "B c #000000",
114 "* c #FFFF00",
115 "...BBBBBBBBBB....",
116 "..B**********B...",
117 ".B*****BB*****B..",
118 "B*****BBB******B.",
119 "B*******B******B.",
120 "B*******B******B.",
121 "B*******B******B.",
122 "B*******B******B.",
123 "B*******B******B.",
124 "B*******B******B.",
125 "B*******B******B.",
126 ".B****BBBBB***B..",
127 "..B**********B...",
128 "...BBBBBBBBBB...."
129 };
130 static const gchar * aszMarkerImage2[] =
131 {
132 "17 14 3 1", /* width height colours characters-per-pixel */
133 ". c None",
134 "B c #000000",
135 "* c #FFFF00",
136 "...BBBBBBBBBB....",
137 "..B**********B...",
138 ".B****BBBB****B..",
139 "B****BB**BB****B.",
140 "B*********B****B.",
141 "B*********B****B.",
142 "B********BB****B.",
143 "B*******BB*****B.",
144 "B******BB******B.",
145 "B*****BB*******B.",
146 "B****BB********B.",
147 ".B***BBBBBB***B..",
148 "..B**********B...",
149 "...BBBBBBBBBB...."
150 };
151 static const gchar * aszMarkerImage3[] =
152 {
153 "17 14 3 1", /* width height colours characters-per-pixel */
154 ". c None",
155 "B c #000000",
156 "* c #FFFF00",
157 "...BBBBBBBBBB....",
158 "..B**********B...",
159 ".B****BBBB****B..",
160 "B****BB**BB****B.",
161 "B*********B****B.",
162 "B********BB****B.",
163 "B*****BBBB*****B.",
164 "B********BB****B.",
165 "B*********B****B.",
166 "B*********B****B.",
167 "B****BB**BB****B.",
168 ".B****BBBB****B..",
169 "..B**********B...",
170 "...BBBBBBBBBB...."
171 };
172 static const gchar * aszMarkerImage4[] =
173 {
174 "17 14 3 1", /* width height colours characters-per-pixel */
175 ". c None",
176 "B c #000000",
177 "* c #FFFF00",
178 "...BBBBBBBBBB....",
179 "..B**********B...",
180 ".B******BB****B..",
181 "B*******BB*****B.",
182 "B******B*B*****B.",
183 "B******B*B*****B.",
184 "B*****B**B*****B.",
185 "B*****B**B*****B.",
186 "B****BBBBBB****B.",
187 "B********B*****B.",
188 "B********B*****B.",
189 ".B*******B****B..",
190 "..B**********B...",
191 "...BBBBBBBBBB...."
192 };
193 static const gchar * aszMarkerImage5[] =
194 {
195 "17 14 3 1", /* width height colours characters-per-pixel */
196 ". c None",
197 "B c #000000",
198 "* c #FFFF00",
199 "...BBBBBBBBBB....",
200 "..B**********B...",
201 ".B***BBBBBB***B..",
202 "B****B*********B.",
203 "B****B*********B.",
204 "B****B*********B.",
205 "B****BBBBB*****B.",
206 "B********BB****B.",
207 "B*********B****B.",
208 "B*********B****B.",
209 "B****BB**BB****B.",
210 ".B****BBBB****B..",
211 "..B**********B...",
212 "...BBBBBBBBBB...."
213 };
214 static const gchar * aszMarkerImage6[] =
215 {
216 "17 14 3 1", /* width height colours characters-per-pixel */
217 ". c None",
218 "B c #000000",
219 "* c #FFFF00",
220 "...BBBBBBBBBB....",
221 "..B**********B...",
222 ".B*****BBBB***B..",
223 "B*****BB*******B.",
224 "B****BB********B.",
225 "B****B*********B.",
226 "B****BBBBB*****B.",
227 "B****BB**BB****B.",
228 "B****B****B****B.",
229 "B****B****B****B.",
230 "B****BB**BB****B.",
231 ".B****BBBB****B..",
232 "..B**********B...",
233 "...BBBBBBBBBB...."
234 };
235 static const gchar * aszMarkerImage7[] =
236 {
237 "17 14 3 1", /* width height colours characters-per-pixel */
238 ". c None",
239 "B c #000000",
240 "* c #FFFF00",
241 "...BBBBBBBBBB....",
242 "..B**********B...",
243 ".B***BBBBBB***B..",
244 "B*********B****B.",
245 "B********B*****B.",
246 "B*******B******B.",
247 "B*******B******B.",
248 "B******B*******B.",
249 "B*****B********B.",
250 "B*****B********B.",
251 "B****B*********B.",
252 ".B***B********B..",
253 "..B**********B...",
254 "...BBBBBBBBBB...."
255 };
256 static const gchar * aszMarkerImage8[] =
257 {
258 "17 14 3 1", /* width height colours characters-per-pixel */
259 ". c None",
260 "B c #000000",
261 "* c #FFFF00",
262 "...BBBBBBBBBB....",
263 "..B**********B...",
264 ".B****BBBB****B..",
265 "B****BB**BB****B.",
266 "B****B****B****B.",
267 "B****BB**BB****B.",
268 "B*****BBBB*****B.",
269 "B****BB**BB****B.",
270 "B****B****B****B.",
271 "B****B****B****B.",
272 "B****BB**BB****B.",
273 ".B****BBBB****B..",
274 "..B**********B...",
275 "...BBBBBBBBBB...."
276 };
277 static const gchar * aszMarkerImage9[] =
278 {
279 "17 14 3 1", /* width height colours characters-per-pixel */
280 ". c None",
281 "B c #000000",
282 "* c #FFFF00",
283 "...BBBBBBBBBB....",
284 "..B**********B...",
285 ".B****BBBB****B..",
286 "B****BB**BB****B.",
287 "B****B****B****B.",
288 "B****BB**BB****B.",
289 "B*****BBBBB****B.",
290 "B*********B****B.",
291 "B*********B****B.",
292 "B********BB****B.",
293 "B*******BB*****B.",
294 ".B***BBBB*****B..",
295 "..B**********B...",
296 "...BBBBBBBBBB...."
297 };
298
299
300 static const gchar ** aszMarkerImages[]=
301 {
302 aszMarkerImage0,aszMarkerImage1,aszMarkerImage2,aszMarkerImage3,aszMarkerImage4,
303 aszMarkerImage5,aszMarkerImage6,aszMarkerImage7,aszMarkerImage8,aszMarkerImage9
304 };
305
306
307 /* return a FileData structure for a file
308 * if not come across this file before then create one, otherwise return existing structure with
309 * data in it
310 * returns NULL on error
311 */
GetFileData(gchar * pcFileName)312 static FileData * GetFileData(gchar *pcFileName)
313 {
314 FileData *fdTemp=fdKnownFilesSettings;
315 gint i;
316
317 /* First handle if main pointer doesn't point to anything */
318 if(fdTemp==NULL)
319 {
320 if((fdKnownFilesSettings=(FileData*)(g_malloc(sizeof *fdTemp)))!=NULL)
321 {
322 fdKnownFilesSettings->pcFileName=g_strdup(pcFileName);
323 for(i=0;i<10;i++)
324 fdKnownFilesSettings->iBookmark[i]=-1;
325
326 /* don't need to initiate iBookmarkLinePos */
327 fdKnownFilesSettings->pcFolding=NULL;
328 fdKnownFilesSettings->LastChangedTime=-1;
329 fdKnownFilesSettings->pcBookmarks=NULL;
330 fdKnownFilesSettings->NextNode=NULL;
331 }
332 return fdKnownFilesSettings;
333 }
334
335 /* move through chain to the correct entry or the end of a chain */
336 while(TRUE)
337 {
338 /* if have found relavent FileData, then exit */
339 if(utils_str_equal(pcFileName,fdTemp->pcFileName)==TRUE)
340 return fdTemp;
341
342 /* if end of chain, then add new entry, and return it. */
343 if(fdTemp->NextNode==NULL)
344 {
345 if((fdTemp->NextNode=(FileData*)(g_malloc(sizeof *fdTemp)))!=NULL)
346 {
347 fdTemp->NextNode->pcFileName=g_strdup(pcFileName);
348 for(i=0;i<10;i++)
349 fdTemp->NextNode->iBookmark[i]=-1;
350
351 /* don't need to initiate iBookmarkLinePos */
352 fdTemp->NextNode->pcFolding=NULL;
353 fdTemp->NextNode->LastChangedTime=-1;
354 fdTemp->NextNode->pcBookmarks=NULL;
355 fdTemp->NextNode->NextNode=NULL;
356 }
357 return fdTemp->NextNode;
358
359 }
360 fdTemp=fdTemp->NextNode;
361
362 }
363 }
364
365
366 /* save individual file details. return TRUE if saved, FALSE if doesn't need to be saved */
SaveIndividualSetting(GKeyFile * gkf,FileData * fd,gint iNumber,gchar * Filename)367 static gboolean SaveIndividualSetting(GKeyFile *gkf,FileData *fd,gint iNumber,gchar *Filename)
368 {
369 gchar *cKey;
370 gchar szMarkers[1000];
371 gchar *pszMarkers;
372 gint i;
373
374 /* first check if any bookmarks or folds for this file */
375 /* if not can skip it */
376 for(i=0;i<10;i++)
377 if(fd->iBookmark[i]!=-1) break;
378 /* i==10 if no markers set */
379
380 /* skip if no folding data or markers */
381 if(i==10 && (bRememberFolds==FALSE || fd->pcFolding==NULL) &&
382 (bRememberBookmarks==FALSE || fd->pcBookmarks==NULL))
383 return FALSE;
384
385 /* now save file data */
386 if(iNumber==-1)
387 cKey=g_strdup("A");
388 else
389 cKey=g_strdup_printf("A%d",iNumber);
390
391 /* save filename */
392 if(Filename!=NULL)
393 g_key_file_set_string(gkf,"FileData",cKey,Filename);
394
395 /* save folding data */
396 cKey[0]='B';
397 if(fd->pcFolding!=NULL && bRememberFolds==TRUE)
398 g_key_file_set_string(gkf,"FileData",cKey,fd->pcFolding);
399
400 /* save last saved time */
401 cKey[0]='C';
402 if(fd->LastChangedTime!=-1)
403 g_key_file_set_integer(gkf,"FileData",cKey,fd->LastChangedTime);
404
405 /* save bookmarks */
406 cKey[0]='D';
407 pszMarkers=szMarkers;
408 pszMarkers[0]=0;
409 for(i=0;i<10;i++)
410 {
411 if(fd->iBookmark[i]!=-1)
412 {
413 sprintf(pszMarkers,"%d",fd->iBookmark[i]);
414 while(pszMarkers[0]!=0)
415 pszMarkers++;
416 }
417
418 pszMarkers[0]=',';
419 pszMarkers[1]=0;
420 pszMarkers++;
421 }
422
423 /* don't need a ',' after last position (have '\0' instead) */
424 pszMarkers--;
425 pszMarkers[0]=0;
426 /* only save markers if have any set. Will contain 9 commas only if none set */
427 if(szMarkers[9]!=0)
428 g_key_file_set_string(gkf,"FileData",cKey,szMarkers);
429
430 /* save positions in bookmarked lines */
431 cKey[0]='E';
432 pszMarkers=szMarkers;
433 pszMarkers[0]=0;
434 for(i=0;i<10;i++)
435 {
436 if(fd->iBookmark[i]!=-1)
437 {
438 sprintf(pszMarkers,"%d",fd->iBookmarkLinePos[i]);
439 while(pszMarkers[0]!=0)
440 pszMarkers++;
441 }
442
443 pszMarkers[0]=',';
444 pszMarkers[1]=0;
445 pszMarkers++;
446 }
447
448 /* don't need a ',' after last position (have '\0' instead) */
449 pszMarkers--;
450 pszMarkers[0]=0;
451 /* only save positions of markers if set. Will contain 9 commas only if none set */
452 if(szMarkers[9]!=0)
453 g_key_file_set_string(gkf,"FileData",cKey,szMarkers);
454
455 /* save non-numbered bookmarks */
456 cKey[0]='F';
457 if(fd->pcBookmarks!=NULL && bRememberBookmarks==TRUE)
458 g_key_file_set_string(gkf,"FileData",cKey,fd->pcBookmarks);
459
460 g_free(cKey);
461
462 return TRUE;
463 }
464
465
466 /* save settings (preferences, file data such as fold states, marker positions) */
SaveSettings(gchar * filename)467 static void SaveSettings(gchar *filename)
468 {
469 GKeyFile *config=NULL;
470 gchar *config_file=NULL,*config_dir=NULL;
471 gchar *data;
472 FileData* fdTemp=fdKnownFilesSettings;
473 gint i=0;
474
475 /* create new config from default settings */
476 config=g_key_file_new();
477
478 /* now set settings */
479 g_key_file_set_boolean(config,"Settings","Center_When_Goto_Bookmark",bCenterWhenGotoBookmark);
480 g_key_file_set_boolean(config,"Settings","Remember_Folds",bRememberFolds);
481 g_key_file_set_integer(config,"Settings","Position_In_Line",PositionInLine);
482 g_key_file_set_integer(config,"Settings","Where_To_Save_File_Details",WhereToSaveFileDetails);
483 g_key_file_set_boolean(config,"Settings","Remember_Bookmarks",bRememberBookmarks);
484 if(FileDetailsSuffix!=NULL)
485 g_key_file_set_string(config,"Settings","File_Details_Suffix",FileDetailsSuffix);
486
487 /* now save file data */
488 while(fdTemp!=NULL)
489 {
490 /* if this entry has data needing saveing then save it and increment the counter */
491 if(SaveIndividualSetting(config,fdTemp,i,fdTemp->pcFileName))
492 i++;
493
494 fdTemp=fdTemp->NextNode;
495 }
496
497 /* turn config into data */
498 data=g_key_file_to_data(config,NULL,NULL);
499
500 /* calculate setting directory name */
501 config_dir=g_build_filename(geany->app->configdir,"plugins","Geany_Numbered_Bookmarks",NULL);
502 /* ensure directory exists */
503 g_mkdir_with_parents(config_dir,0755);
504
505 /* make config_file hold name of settings file */
506 config_file=g_build_filename(config_dir,"settings.conf",NULL);
507
508 /* write data */
509 utils_write_file(config_file,data);
510
511 /* free memory */
512 g_free(config_dir);
513 g_free(config_file);
514 g_key_file_free(config);
515 g_free(data);
516
517 /* now consider if not purely saving file settings to main settings file */
518 /* return if not saving data with file */
519 if(filename==NULL || WhereToSaveFileDetails==0)
520 return;
521
522 /* setup keyfile to hold values */
523 config=g_key_file_new();
524
525 /* get pointer to data we're saving */
526 fdTemp=GetFileData(filename);
527
528 /* calculate settings filename */
529 config_file=g_strdup_printf("%s%s",filename,FileDetailsSuffix);
530
531 /* if nothing to save then delete any old data */
532 if(SaveIndividualSetting(config,fdTemp,-1,NULL)==FALSE)
533 g_remove(config_file);
534 /* otherwise save the data */
535 else
536 {
537 /* turn config into data */
538 data=g_key_file_to_data(config,NULL,NULL);
539 /* write data */
540 utils_write_file(config_file,data);
541
542 g_free(data);
543 }
544
545 /* free memory */
546 g_free(config_file);
547 g_key_file_free(config);
548 }
549
550
551 /* load individual file details. return TRUE if data there, FALSE if there isn't */
LoadIndividualSetting(GKeyFile * gkf,gint iNumber,gchar * Filename)552 static gboolean LoadIndividualSetting(GKeyFile *gkf,gint iNumber,gchar *Filename)
553 {
554 gchar *pcKey=NULL;
555 gchar *pcTemp;
556 gchar *pcTemp2;
557 gint l;
558 FileData *fd=NULL;
559
560 /* if loading from local file then no fiilename in file and no number in key*/
561 if(iNumber==-1)
562 {
563 /* get structure to hold filedetails */
564 fd=GetFileData(Filename);
565
566 /* create key */
567 pcKey=g_strdup("A");
568 }
569 /* if loading from central file then need to extract filename from A key */
570 else
571 {
572 pcKey=g_strdup_printf("A%d",iNumber);
573
574 /* get filename */
575 pcTemp=(gchar*)(utils_get_setting_string(gkf,"FileData",pcKey,NULL));
576 /* if null then have reached end of files */
577 if(pcTemp==NULL)
578 {
579 g_free(pcKey);
580 return FALSE;
581 }
582
583 fd=GetFileData(pcTemp);
584 g_free(pcTemp);
585 }
586
587 /* get folding data */
588 pcKey[0]='B';
589 if(bRememberFolds==TRUE)
590 fd->pcFolding=(gchar*)(utils_get_setting_string(gkf,"FileData",pcKey,NULL));
591 else
592 fd->pcFolding=NULL;
593
594 /* load last saved time */
595 pcKey[0]='C';
596 fd->LastChangedTime=utils_get_setting_integer(gkf,"FileData",pcKey,-1);
597 /* get bookmarks */
598 pcKey[0]='D';
599 pcTemp=(gchar*)(utils_get_setting_string(gkf,"FileData",pcKey,NULL));
600 /* pcTemp contains comma seperated numbers (or blank for -1) */
601 pcTemp2=pcTemp;
602 if(pcTemp!=NULL) for(l=0;l<10;l++)
603 {
604 /* Bookmark entries are initialized to -1, so only need to parse non-empty slots */
605 if(pcTemp2[0]!=',' && pcTemp2[0]!=0)
606 {
607 fd->iBookmark[l]=strtoll(pcTemp2,NULL,10);
608 while(pcTemp2[0]!=0 && pcTemp2[0]!=',')
609 pcTemp2++;
610 }
611
612 pcTemp2++;
613 }
614 g_free(pcTemp);
615
616 /* get position in bookmarked lines */
617 pcKey[0]='E';
618 pcTemp=(gchar*)(utils_get_setting_string(gkf,"FileData",pcKey,NULL));
619 /* pcTemp contains comma seperated numbers (or blank for -1) */
620 pcTemp2=pcTemp;
621 if(pcTemp!=NULL) for(l=0;l<10;l++)
622 {
623 /* Bookmark entries are initialized to -1, so only need to parse non-empty slots */
624 if(pcTemp2[0]!=',' && pcTemp2[0]!=0)
625 {
626 fd->iBookmarkLinePos[l]=strtoll(pcTemp2,NULL,10);
627 while(pcTemp2[0]!=0 && pcTemp2[0]!=',')
628 pcTemp2++;
629 }
630
631 pcTemp2++;
632 }
633
634 /* get non-numbered bookmarks */
635 pcKey[0]='F';
636 if(bRememberBookmarks==TRUE)
637 fd->pcBookmarks=(gchar*)(utils_get_setting_string(gkf,"FileData",pcKey,NULL));
638 else
639 fd->pcBookmarks=NULL;
640
641 /* free used memory */
642 g_free(pcTemp);
643 g_free(pcKey);
644
645 return TRUE;
646 }
647
648
649 /* load settings (preferences, file data, and macro data) */
LoadSettings(void)650 static void LoadSettings(void)
651 {
652 gint i;
653 gchar *config_file=NULL;
654 gchar *config_dir=NULL;
655 GKeyFile *config=NULL;
656
657 /* Make config_dir hold directory name of settings file */
658 config_dir=g_build_filename(geany->app->configdir,"plugins","Geany_Numbered_Bookmarks",NULL);
659 /* ensure directory exists */
660 g_mkdir_with_parents(config_dir,0755);
661
662 /* make config_file hold name of settings file */
663 config_file=g_build_filename(config_dir,"settings.conf",NULL);
664
665 /* either load settings file, or create one from default */
666 config=g_key_file_new();
667 if(!g_key_file_load_from_file(config,config_file, G_KEY_FILE_KEEP_COMMENTS,NULL))
668 g_key_file_load_from_data(config,default_config,sizeof(default_config),
669 G_KEY_FILE_KEEP_COMMENTS,NULL);
670
671 /* extract settings */
672 bCenterWhenGotoBookmark=utils_get_setting_boolean(config,"Settings",
673 "Center_When_Goto_Bookmark",FALSE);
674 bRememberFolds=utils_get_setting_boolean(config,"Settings","Remember_Folds",FALSE);
675 PositionInLine=utils_get_setting_integer(config,"Settings","Position_In_Line",0);
676 WhereToSaveFileDetails=utils_get_setting_integer(config,"Settings",
677 "Where_To_Save_File_Details",0);
678 bRememberBookmarks=utils_get_setting_boolean(config,"Settings","Remember_Bookmarks",FALSE);
679 FileDetailsSuffix=utils_get_setting_string(config,"Settings","File_Details_Suffix",
680 ".gnbs.conf");
681
682 /* extract data about files */
683 i=0;
684 while(LoadIndividualSetting(config,i,NULL))
685 i++;
686
687 /* free memory */
688 g_free(config_dir);
689 g_free(config_file);
690 g_key_file_free(config);
691 }
692
693
694 /* try to load localy saved file details */
LoadLocalFileDetails(gchar * filename)695 static void LoadLocalFileDetails(gchar *filename)
696 {
697 gchar *config_file=NULL;
698 GKeyFile *config=NULL;
699
700 /* calculate settings filename */
701 config_file=g_strdup_printf("%s%s",filename,FileDetailsSuffix);
702
703 /* create keyfile to hold data */
704 config=g_key_file_new();
705
706 /* if can load settings file then extract the info */
707 if(g_key_file_load_from_file(config,config_file,G_KEY_FILE_KEEP_COMMENTS,NULL))
708 {
709 /* load file details */
710 LoadIndividualSetting(config,-1,filename);
711 }
712
713 /* free memory */
714 g_free(config_file);
715 g_key_file_free(config);
716 }
717
718
719 /* Get markers for editor. If not set then initiate */
GetMarkersUsed(ScintillaObject * sci)720 static guint32 * GetMarkersUsed(ScintillaObject* sci)
721 {
722 guint32 *markers;
723
724 /*fetch pointer to markers */
725 markers=(guint32*)(g_object_get_data(G_OBJECT(sci),"Geany_Numbered_Bookmarks_Used"));
726
727 /* if initialised then return these */
728 if(markers!=NULL)
729 return markers;
730
731 /* initialise markers as none initialised */
732 markers=g_malloc(sizeof(guint32));
733
734 /* if failed to allocate space return NULL */
735 if(markers==NULL)
736 return NULL;
737
738 /*initiate markers */
739 (*markers)=0;
740
741 /* save record of which markers are being used */
742 g_object_set_data(G_OBJECT(sci),"Geany_Numbered_Bookmarks_Used",(gpointer)markers);
743
744 return markers;
745 }
746
747
748 /* get next free marker number */
NextFreeMarker(GeanyDocument * doc)749 static gint NextFreeMarker(GeanyDocument* doc)
750 {
751 gint i,l,m,k;
752 guint32 *markers;
753 FileData *fd;
754 ScintillaObject *sci=doc->editor->sci;
755
756 markers=GetMarkersUsed(sci);
757
758 /* fail if can't allocate space for markers */
759 if(markers==NULL)
760 return -1;
761
762 /* markers 0 and 1 reserved for bookmarks & errors, 25 onwards for folds */
763 /* find first free marker after last defined marker. Will ensure that new marker */
764 /* is displayed over any previously set markers */
765 for(i=24,m=-1;i>1;i--)
766 {
767 l=scintilla_send_message(sci,SCI_MARKERSYMBOLDEFINED,i,0);
768
769 if(l==SC_MARK_CIRCLE || l==SC_MARK_AVAILABLE)
770 {
771 /* if reached start of user-defined markers then return this */
772 if(i==2)
773 return 2;
774
775 /* found empty marker so make note of it */
776 m=i;
777 /* keep looking for 1st unused marker after last used marker */
778 continue;
779 }
780
781 /* found marker */
782
783 /* if not a numbered bookmark then ignore it */
784 if(((*markers)&(1<<i))==0)
785 continue;
786
787 /* if have found an empty marker higher then return this */
788 if(m!=-1)
789 return m;
790
791 /* no empty markers above last used */
792
793 /* first see if there are any unused markers */
794 while(i>1)
795 {
796 l=scintilla_send_message(sci,SCI_MARKERSYMBOLDEFINED,i,0);
797 if(l==SC_MARK_CIRCLE || l==SC_MARK_AVAILABLE)
798 break;
799
800 i--;
801 }
802
803 /* no empty markers available so return -1 */
804 if(i==1)
805 return -1;
806
807 /* there are empty markers available. Break out of for loop & make them available */
808 break;
809 }
810
811 /* compact used numbered markers */
812 /* move through markers moving numbered bookmarks ones from i to m */
813 for(m=2,i=2;i<25;i++)
814 {
815 /* don't move marker unless it's a numbered bookmark marker */
816 if(((*markers)&(1<<i))==0)
817 continue;
818
819 /* find unused marker */
820 l=scintilla_send_message(sci,SCI_MARKERSYMBOLDEFINED,m,0);
821 while((l!=SC_MARK_CIRCLE && l!=SC_MARK_AVAILABLE) && m<i)
822 {
823 m++;
824 l=scintilla_send_message(sci,SCI_MARKERSYMBOLDEFINED,m,0);
825 }
826
827 /* if can't move marker forward then don't */
828 if(m==i)
829 continue;
830
831 /* move marker from i to m */
832 /* first make note of line number of marker */
833 l=scintilla_send_message(sci,SCI_MARKERNEXT,0,1<<i);
834 /* remove old marker */
835 scintilla_send_message(sci,SCI_MARKERDELETEALL,i,0);
836 scintilla_send_message(sci,SCI_MARKERDEFINE,i,SC_MARK_AVAILABLE);
837
838 /* find bookmark number, put in k */
839 fd=GetFileData(doc->file_name);
840 for(k=0;k<10;k++)
841 if(fd->iBookmarkMarkerUsed[k]==i)
842 break;
843
844 /* insert new marker */
845 scintilla_send_message(sci,SCI_MARKERDEFINEPIXMAP,m,(sptr_t)(aszMarkerImages[k]));
846 scintilla_send_message(sci,SCI_MARKERADD,l,m);
847
848 /* update markers record */
849 (*markers)-=1<<i;
850 (*markers)|=1<<m;
851
852 /* update record of which bookmark uses which marker */
853 fd->iBookmarkMarkerUsed[k]=m;
854 }
855
856 /* save record of which markers are being used */
857 g_object_set_data(G_OBJECT(sci),"Geany_Numbered_Bookmarks_Used",(gpointer)markers);
858
859
860 /* m should point to last used marker. Next free marker should lie after this */
861 /* find free marker & return it */
862 for(;m<25;m++)
863 {
864 l=scintilla_send_message(sci,SCI_MARKERSYMBOLDEFINED,m,0);
865 if(l==SC_MARK_CIRCLE || l==SC_MARK_AVAILABLE)
866 return m;
867 }
868
869 /* no empty markers available so return -1 */
870 /* in theory shouldn't get here but leave just in case my logic is flawed */
871 return -1;
872 }
873
874
SetMarker(GeanyDocument * doc,gint bookmarkNumber,gint markerNumber,gint line)875 static void SetMarker(GeanyDocument* doc,gint bookmarkNumber,gint markerNumber,gint line)
876 {
877 guint32 *markers;
878 FileData *fd;
879 ScintillaObject *sci=doc->editor->sci;
880
881 /* insert new marker */
882 scintilla_send_message(sci,SCI_MARKERDEFINEPIXMAP,markerNumber,
883 (sptr_t)(aszMarkerImages[bookmarkNumber]));
884 scintilla_send_message(sci,SCI_MARKERADD,line,markerNumber);
885
886 /* update record of which bookmark uses which marker */
887 fd=GetFileData(doc->file_name);
888 fd->iBookmarkMarkerUsed[bookmarkNumber]=markerNumber;
889
890 /* update record of which markers are being used */
891 markers=GetMarkersUsed(sci);
892 (*markers)|=1<<markerNumber;
893 g_object_set_data(G_OBJECT(sci),"Geany_Numbered_Bookmarks_Used",(gpointer)markers);
894 }
895
896
DeleteMarker(GeanyDocument * doc,gint bookmarkNumber,gint markerNumber)897 static void DeleteMarker(GeanyDocument* doc,gint bookmarkNumber,gint markerNumber)
898 {
899 guint32 *markers;
900 ScintillaObject *sci=doc->editor->sci;
901
902 /* remove marker */
903 scintilla_send_message(sci,SCI_MARKERDELETEALL,markerNumber,0);
904 scintilla_send_message(sci,SCI_MARKERDEFINE,markerNumber,SC_MARK_AVAILABLE);
905
906 /* update record of which markers are being used */
907 markers=GetMarkersUsed(sci);
908 (*markers)-=1<<markerNumber;
909 g_object_set_data(G_OBJECT(sci),"Geany_Numbered_Bookmarks_Used",(gpointer)markers);
910 }
911
912
ApplyBookmarks(GeanyDocument * doc,FileData * fd)913 static void ApplyBookmarks(GeanyDocument* doc,FileData *fd)
914 {
915 gint i,iLineCount,m;
916 GtkWidget *dialog;
917 ScintillaObject* sci=doc->editor->sci;
918
919 iLineCount=scintilla_send_message(sci,SCI_GETLINECOUNT,0,0);
920
921 for(i=0;i<10;i++)
922 if(fd->iBookmark[i]!=-1 && fd->iBookmark[i]<iLineCount)
923 {
924 m=NextFreeMarker(doc);
925 /* if run out of markers report this */
926 if(m==-1)
927 {
928 dialog=gtk_message_dialog_new(GTK_WINDOW(geany->main_widgets->window),
929 GTK_DIALOG_DESTROY_WITH_PARENT,
930 GTK_MESSAGE_ERROR,GTK_BUTTONS_NONE,
931 _("Unable to apply all markers to '%s' as all being used."),
932 doc->file_name);
933 gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Okay"),GTK_RESPONSE_OK);
934 gtk_dialog_run(GTK_DIALOG(dialog));
935 gtk_widget_destroy(dialog);
936 return;
937 }
938
939 /* otherwise ok to set marker */
940 SetMarker(doc,i,m,fd->iBookmark[i]);
941 }
942 }
943
944
945 /* handler for when a document has been opened
946 * this checks to see if a document has been altered since it was last saved in geany (as plugin
947 * data may then be out of date for file)
948 * It then applies file settings
949 */
on_document_open(GObject * obj,GeanyDocument * doc,gpointer user_data)950 static void on_document_open(GObject *obj, GeanyDocument *doc, gpointer user_data)
951 {
952 FileData *fd;
953 gint i,l=GTK_RESPONSE_ACCEPT,iLineCount;
954 ScintillaObject* sci=doc->editor->sci;
955 struct stat sBuf;
956 GtkWidget *dialog;
957 gchar *cFoldData=NULL;
958 gchar *pcTemp;
959 /* keep compiler happy & initialise iBits: will logically be initiated anyway */
960 gint iBits=0,iFlags,iBitCounter;
961
962 /* if saving details in file alongside file we're editing then load it up */
963 if(WhereToSaveFileDetails==1)
964 LoadLocalFileDetails(doc->file_name);
965
966 /* check to see if file has changed since geany last saved it */
967 fd=GetFileData(doc->file_name);
968 if(stat(doc->file_name,&sBuf)==0 && fd!=NULL && fd->LastChangedTime!=-1 &&
969 fd->LastChangedTime!=sBuf.st_mtime)
970 {
971 /* notify user that file has been changed */
972 dialog=gtk_message_dialog_new(GTK_WINDOW(geany->main_widgets->window),
973 GTK_DIALOG_DESTROY_WITH_PARENT,GTK_MESSAGE_ERROR,
974 GTK_BUTTONS_NONE,
975 _("'%s' has been edited since it was last saved by geany. Marker positions may be unreliable and w\
976 ill not be loaded.\nPress Ignore to try an load markers anyway."),doc->file_name);
977 gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Okay"),GTK_RESPONSE_OK);
978 gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Ignore"),GTK_RESPONSE_REJECT);
979 l=gtk_dialog_run(GTK_DIALOG(dialog));
980 gtk_widget_destroy(dialog);
981 }
982
983 switch(l)
984 {
985 /* file not changed since Geany last saved it so saved settings should be fine */
986 case GTK_RESPONSE_ACCEPT:
987 /* now set markers */
988 ApplyBookmarks(doc,fd);
989
990 /* get fold settings if present and want to use them */
991 if(fd->pcFolding!=NULL && bRememberFolds==TRUE)
992 {
993 cFoldData=fd->pcFolding;
994
995 /* first ensure fold positions exist */
996 scintilla_send_message(sci,SCI_COLOURISE,0,-1);
997
998 iLineCount=scintilla_send_message(sci,SCI_GETLINECOUNT,0,0);
999
1000 /* go through lines setting fold status */
1001 for(i=0,iBitCounter=6;i<iLineCount;i++)
1002 {
1003 iFlags=scintilla_send_message(sci,SCI_GETFOLDLEVEL,i,0);
1004 /* ignore non-folding lines */
1005 if((iFlags & SC_FOLDLEVELHEADERFLAG)==0)
1006 continue;
1007
1008 /* get next 6 fold states if needed */
1009 if(iBitCounter==6)
1010 {
1011 iBitCounter=0;
1012 iBits=base64_char_to_int[(gint)(*cFoldData)];
1013 cFoldData++;
1014 }
1015
1016 /* set fold if needed */
1017 if(((iBits>>iBitCounter)&1)==0)
1018 scintilla_send_message(sci,SCI_TOGGLEFOLD,i,0);
1019
1020 /* increment counter */
1021 iBitCounter++;
1022 }
1023 }
1024
1025 /* get non-numbered bookmark settings if present and want to use them */
1026 if(fd->pcBookmarks!=NULL && bRememberBookmarks==TRUE)
1027 {
1028 pcTemp=fd->pcBookmarks;
1029 while(pcTemp[0]!=0)
1030 {
1031 /* get next linenumber */
1032 i=strtoll(pcTemp,NULL,16);
1033 /* set bookmark */
1034 scintilla_send_message(sci,SCI_MARKERADD,i,1);
1035 /* move to next linenumber */
1036 while(pcTemp[0]!=0 && pcTemp[0]!=',')
1037 pcTemp++;
1038
1039 if(pcTemp[0]==',')
1040 pcTemp++;
1041
1042 }
1043
1044 }
1045
1046 break;
1047 /* file has changed since Geany last saved but, try to load bookmarks anyway */
1048 case GTK_RESPONSE_REJECT:
1049 ApplyBookmarks(doc,fd);
1050 break;
1051 default: /* default - don't try to set markers */
1052 break;
1053 }
1054 }
1055
1056
1057 /* handler for when a document has been saved
1058 * This saves off fold state, and marker positions for the file
1059 */
on_document_save(GObject * obj,GeanyDocument * doc,gpointer user_data)1060 static void on_document_save(GObject *obj, GeanyDocument *doc, gpointer user_data)
1061 {
1062 FileData *fd;
1063 gint i,iLineCount,iFlags,iBitCounter=0;
1064 ScintillaObject* sci=doc->editor->sci;
1065 struct stat sBuf;
1066 GByteArray *gbaFoldData=NULL;
1067 guint8 guiFold=0;
1068 gboolean bHasClosedFold=FALSE,bHasBookmark=FALSE;
1069 gchar szLine[20];
1070
1071 /* update markerpos */
1072 fd=GetFileData(doc->file_name);
1073 for(i=0;i<10;i++)
1074 fd->iBookmark[i]=scintilla_send_message(sci,SCI_MARKERNEXT,0,
1075 1<<(fd->iBookmarkMarkerUsed[i]));
1076
1077 /* save fold state */
1078 if(bRememberFolds==TRUE)
1079 {
1080 gbaFoldData=g_byte_array_sized_new(1000);
1081
1082 iLineCount=scintilla_send_message(sci,SCI_GETLINECOUNT,0,0);
1083 /* go through each line */
1084 for(i=0;i<iLineCount;i++)
1085 {
1086 iFlags=scintilla_send_message(sci,SCI_GETFOLDLEVEL,i,0);
1087 /* ignore line if not a folding point */
1088 if((iFlags & SC_FOLDLEVELHEADERFLAG)==0)
1089 continue;
1090
1091 iFlags=scintilla_send_message(sci,SCI_GETFOLDEXPANDED,i,0);
1092 /* make note of if any folds closed or not */
1093 bHasClosedFold|=((iFlags&1)==0);
1094 /* remember if folded or not */
1095 guiFold|=(iFlags&1)<<iBitCounter;
1096 iBitCounter++;
1097 if(iBitCounter<6)
1098 continue;
1099
1100 /* if have 6 bits then store these */
1101 iBitCounter=0;
1102 guiFold=(guint8)base64_int_to_char[guiFold];
1103 g_byte_array_append(gbaFoldData,&guiFold,1);
1104 guiFold=0;
1105 }
1106
1107 /* flush buffer */
1108 if(iBitCounter!=0)
1109 {
1110 guiFold=(guint8)base64_int_to_char[guiFold];
1111 g_byte_array_append(gbaFoldData,&guiFold,1);
1112 }
1113
1114 /* transfer data to text string if have closed fold. Default will leave them open*/
1115 fd->pcFolding=(!bHasClosedFold)?NULL:g_strndup((gchar*)(gbaFoldData->data),
1116 gbaFoldData->len);
1117
1118 /* free byte array space */
1119 g_byte_array_free(gbaFoldData,TRUE);
1120 }
1121 else
1122 fd->pcFolding=NULL;
1123
1124 /* now save off bookmarks */
1125 if(bRememberBookmarks==TRUE)
1126 {
1127 gbaFoldData=g_byte_array_sized_new(1000);
1128
1129 i=0;
1130 while((i=scintilla_send_message(sci,SCI_MARKERNEXT,i+1,2))!=-1)
1131 {
1132 g_sprintf(szLine,"%s%X",bHasBookmark?",":"",i);
1133 g_byte_array_append(gbaFoldData,(guint8*)szLine,strlen(szLine));
1134
1135 /* will be data in byte array to save */
1136 bHasBookmark=TRUE;
1137 }
1138
1139 /* transfer data to text string */
1140 fd->pcBookmarks=(!bHasBookmark)?NULL:g_strndup((gchar*)(gbaFoldData->data),
1141 gbaFoldData->len);
1142
1143 /* free byte array space */
1144 g_byte_array_free(gbaFoldData,TRUE);
1145 }
1146 else
1147 fd->pcBookmarks=NULL;
1148
1149
1150
1151 /* make note of time last saved */
1152 if(stat(doc->file_name,&sBuf)==0)
1153 fd->LastChangedTime=sBuf.st_mtime;
1154
1155 /* save settings */
1156 SaveSettings(doc->file_name);
1157 }
1158
1159
1160 PluginCallback plugin_callbacks[] =
1161 {
1162 { "document-open", (GCallback) &on_document_open, FALSE, NULL },
1163 { "document-save", (GCallback) &on_document_save, FALSE, NULL },
1164 { NULL, NULL, FALSE, NULL }
1165 };
1166
1167
1168 /* returns current line number */
GetLine(ScintillaObject * sci)1169 static gint GetLine(ScintillaObject* sci)
1170 {
1171 return scintilla_send_message(sci,SCI_LINEFROMPOSITION,
1172 scintilla_send_message(sci,SCI_GETCURRENTPOS,10,0),0);
1173 }
1174
1175
1176 /* handle button presses in the preferences dialog box */
on_configure_response(GtkDialog * dialog,gint response,gpointer user_data)1177 static void on_configure_response(GtkDialog *dialog, gint response, gpointer user_data)
1178 {
1179 gboolean bSettingsHaveChanged;
1180 GtkCheckButton *cb1,*cb2,*cb3;
1181 GtkComboBox *gtkcb1,*gtkcb2;
1182
1183 if(response!=GTK_RESPONSE_OK && response!=GTK_RESPONSE_APPLY)
1184 return;
1185
1186 /* retreive pointers to widgets */
1187 cb1=(GtkCheckButton*)(g_object_get_data(G_OBJECT(dialog),"Geany_Numbered_Bookmarks_cb1"));
1188 cb2=(GtkCheckButton*)(g_object_get_data(G_OBJECT(dialog),"Geany_Numbered_Bookmarks_cb2"));
1189 gtkcb1=(GtkComboBox*)(g_object_get_data(G_OBJECT(dialog),"Geany_Numbered_Bookmarks_cb3"));
1190 gtkcb2=(GtkComboBox*)(g_object_get_data(G_OBJECT(dialog),"Geany_Numbered_Bookmarks_cb4"));
1191 cb3=(GtkCheckButton*)(g_object_get_data(G_OBJECT(dialog),"Geany_Numbered_Bookmarks_cb5"));
1192
1193 /* first see if settings are going to change */
1194 bSettingsHaveChanged=(bRememberFolds!=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb1)));
1195 bSettingsHaveChanged|=(bCenterWhenGotoBookmark!=gtk_toggle_button_get_active(
1196 GTK_TOGGLE_BUTTON(cb2)));
1197 bSettingsHaveChanged|=(gtk_combo_box_get_active(gtkcb1)!=PositionInLine);
1198 bSettingsHaveChanged|=(gtk_combo_box_get_active(gtkcb2)!=WhereToSaveFileDetails);
1199 bSettingsHaveChanged|=(bRememberBookmarks!=gtk_toggle_button_get_active(
1200 GTK_TOGGLE_BUTTON(cb3)));
1201
1202 /* set new settings settings */
1203 bRememberFolds=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb1));
1204 bCenterWhenGotoBookmark=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb2));
1205 PositionInLine=gtk_combo_box_get_active(gtkcb1);
1206 WhereToSaveFileDetails=gtk_combo_box_get_active(gtkcb2);
1207 bRememberBookmarks=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb3));
1208
1209 /* now save new settings if they have changed */
1210 if(bSettingsHaveChanged)
1211 SaveSettings(NULL);
1212 }
1213
1214
1215 /* return a widget containing settings for plugin that can be changed */
plugin_configure(GtkDialog * dialog)1216 GtkWidget *plugin_configure(GtkDialog *dialog)
1217 {
1218 GtkWidget *vbox;
1219 GtkWidget *gtkw;
1220
1221 vbox=gtk_vbox_new(FALSE, 6);
1222
1223 gtkw=gtk_check_button_new_with_label(_("remember fold state"));
1224 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkw),bRememberFolds);
1225 gtk_box_pack_start(GTK_BOX(vbox),gtkw,FALSE,FALSE,2);
1226 /* save pointer to check_button */
1227 g_object_set_data(G_OBJECT(dialog),"Geany_Numbered_Bookmarks_cb1",gtkw);
1228
1229 gtkw=gtk_check_button_new_with_label(_("Center view when goto bookmark"));
1230 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkw),bCenterWhenGotoBookmark);
1231 gtk_box_pack_start(GTK_BOX(vbox),gtkw,FALSE,FALSE,2);
1232 /* save pointer to check_button */
1233 g_object_set_data(G_OBJECT(dialog),"Geany_Numbered_Bookmarks_cb2",gtkw);
1234
1235 gtkw=gtk_combo_box_text_new();
1236 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gtkw),_("Move to start of line"));
1237 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gtkw),_("Move to remembered position in line"));
1238 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gtkw),_("Move to position in current line"));
1239 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gtkw),_("Move to End of line"));
1240 gtk_combo_box_set_active(GTK_COMBO_BOX(gtkw),PositionInLine);
1241 gtk_box_pack_start(GTK_BOX(vbox),gtkw,FALSE,FALSE,2);
1242 g_object_set_data(G_OBJECT(dialog),"Geany_Numbered_Bookmarks_cb3",gtkw);
1243
1244 gtkw=gtk_combo_box_text_new();
1245 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gtkw),_("Save file settings with program settings"));
1246 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gtkw),_("Save file settings to filename with suffix"));
1247 gtk_combo_box_set_active(GTK_COMBO_BOX(gtkw),WhereToSaveFileDetails);
1248 gtk_box_pack_start(GTK_BOX(vbox),gtkw,FALSE,FALSE,2);
1249 g_object_set_data(G_OBJECT(dialog),"Geany_Numbered_Bookmarks_cb4",gtkw);
1250
1251 gtkw=gtk_check_button_new_with_label(_("remember normal Bookmarks"));
1252 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkw),bRememberBookmarks);
1253 gtk_box_pack_start(GTK_BOX(vbox),gtkw,FALSE,FALSE,2);
1254 /* save pointer to check_button */
1255 g_object_set_data(G_OBJECT(dialog),"Geany_Numbered_Bookmarks_cb5",gtkw);
1256
1257 gtk_widget_show_all(vbox);
1258
1259 g_signal_connect(dialog,"response",G_CALLBACK(on_configure_response),NULL);
1260
1261 return vbox;
1262 }
1263
1264
1265 /* display help box */
plugin_help(void)1266 void plugin_help(void)
1267 {
1268 GtkWidget *dialog,*label,*scroll;
1269
1270 /* create dialog box */
1271 dialog=gtk_dialog_new_with_buttons(_("Numbered Bookmarks help"),
1272 GTK_WINDOW(geany->main_widgets->window),
1273 GTK_DIALOG_DESTROY_WITH_PARENT,
1274 GTK_STOCK_OK,GTK_RESPONSE_ACCEPT,NULL);
1275
1276 /* create label */
1277 label=gtk_label_new(
1278 _("This Plugin implements Numbered Bookmarks in Geany, as well as remembering the state of folds, \
1279 and positions of standard non-numbered bookmarks when a file is saved.\n\n"
1280 "It allows you to use up to 10 numbered bookmarks. To set a numbered bookmark press Ctrl+Shift+a n\
1281 umber from 0 to 9. You will see a marker appear next to the line number. If you press Ctrl+Shift+a \
1282 number on a line that already has that bookmark number then it removes the bookmark, otherwise it \
1283 will move the bookmark there if it was set on a different line, or create it if it had not already\
1284 been set. Only the most recently set bookmark on a line will be shown, but you can have more than\
1285 one bookmark per line. To move to a previously set bookmark, press Ctrl+a number from 0 to 9."));
1286 gtk_label_set_line_wrap(GTK_LABEL(label),TRUE);
1287 gtk_widget_show(label);
1288
1289 /* create scrolled window to display label */
1290 scroll=gtk_scrolled_window_new(NULL,NULL);
1291 gtk_scrolled_window_set_policy((GtkScrolledWindow*)scroll,GTK_POLICY_NEVER,
1292 GTK_POLICY_AUTOMATIC);
1293 #if GTK_CHECK_VERSION(3, 8, 0)
1294 gtk_widget_set_size_request(label, 600, -1);
1295 gtk_widget_set_halign(label, GTK_ALIGN_CENTER);
1296 gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
1297 gtk_widget_set_vexpand(label, TRUE);
1298 gtk_container_add(GTK_CONTAINER(scroll),label);
1299 gtk_scrolled_window_set_shadow_type((GtkScrolledWindow*)scroll, GTK_SHADOW_IN);
1300 #else
1301 gtk_scrolled_window_add_with_viewport((GtkScrolledWindow*)scroll,label);
1302 #endif
1303
1304 gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),scroll);
1305 gtk_widget_show_all(dialog);
1306
1307 /* set dialog size (leave width default) */
1308 gtk_widget_set_size_request(dialog,-1,300);
1309
1310 /* display the dialog */
1311 gtk_dialog_run(GTK_DIALOG(dialog));
1312 gtk_widget_destroy(dialog);
1313 }
1314
1315
1316 /* goto numbered bookmark */
GotoBookMark(GeanyDocument * doc,gint iBookMark)1317 static void GotoBookMark(GeanyDocument* doc, gint iBookMark)
1318 {
1319 gint iLine,iLinesVisible,iLineCount,iPosition,iEndOfLine;
1320 ScintillaObject* sci=doc->editor->sci;
1321 FileData *fd;
1322
1323 fd=GetFileData(doc->file_name);
1324
1325 iLine=scintilla_send_message(sci,SCI_MARKERNEXT,0,1<<(fd->iBookmarkMarkerUsed[iBookMark]));
1326
1327 /* ignore if no marker placed for requested bookmark */
1328 if(iLine==-1)
1329 return;
1330
1331 /* calculate position we're moving to */
1332 /* first get position of start of line we're after */
1333 iPosition=scintilla_send_message(sci,SCI_POSITIONFROMLINE,iLine,0);
1334 /* adjust for where in line we want to be */
1335 iEndOfLine=scintilla_send_message(sci,SCI_GETLINEENDPOSITION,iLine,0);
1336 switch(PositionInLine)
1337 {
1338 /* start of line */
1339 case 0:
1340 break;
1341 /* remembered line position */
1342 case 1:
1343 iPosition+=fd->iBookmarkLinePos[iBookMark];
1344 if(iPosition>iEndOfLine)
1345 iPosition=iEndOfLine;
1346
1347 break;
1348 /* try to go to position in current line */
1349 case 2:
1350 iPosition+=scintilla_send_message(sci,SCI_GETCURRENTPOS,0,0)-
1351 scintilla_send_message(sci,SCI_POSITIONFROMLINE,GetLine(sci),0);
1352 if(iPosition>iEndOfLine)
1353 iPosition=iEndOfLine;
1354
1355 break;
1356 /* goto end of line */
1357 case 3:
1358 iPosition=iEndOfLine;
1359 break;
1360 }
1361
1362 /* move to bookmark */
1363 scintilla_send_message(sci,SCI_GOTOPOS,iPosition,0);
1364
1365 /* finnished unless centering on line */
1366 if(bCenterWhenGotoBookmark==FALSE)
1367 return;
1368
1369 /* try and center bookmark on screen */
1370 iLinesVisible=scintilla_send_message(sci,SCI_LINESONSCREEN,0,0);
1371 iLineCount=scintilla_send_message(sci,SCI_GETLINECOUNT,0,0);
1372 iLine-=iLinesVisible/2;
1373 /* make sure view is not beyond start or end of document */
1374 if(iLine+iLinesVisible>iLineCount)
1375 iLine=iLineCount-iLinesVisible;
1376
1377 if(iLine<0)
1378 iLine=0;
1379
1380 scintilla_send_message(sci,SCI_SETFIRSTVISIBLELINE,iLine,0);
1381 }
1382
1383
1384 /* set (or remove) numbered bookmark */
SetBookMark(GeanyDocument * doc,gint iBookMark)1385 static void SetBookMark(GeanyDocument *doc, gint iBookMark)
1386 {
1387 gint iNewLine,iOldLine,iPosInLine,m;
1388 ScintillaObject* sci=doc->editor->sci;
1389 FileData *fd=GetFileData(doc->file_name);
1390 GtkWidget *dialog;
1391
1392 /* see if already such a bookmark present */
1393 iOldLine=scintilla_send_message(sci,SCI_MARKERNEXT,0,1<<(fd->iBookmarkMarkerUsed[iBookMark]));
1394 iNewLine=GetLine(sci);
1395 iPosInLine=scintilla_send_message(sci,SCI_GETCURRENTPOS,0,0)-
1396 scintilla_send_message(sci,SCI_POSITIONFROMLINE,iNewLine,0);
1397
1398 /* if no marker then simply add one to current line */
1399 if(iOldLine==-1)
1400 {
1401 m=NextFreeMarker(doc);
1402 /* if run out of markers report this */
1403 if(m==-1)
1404 {
1405 dialog=gtk_message_dialog_new(GTK_WINDOW(geany->main_widgets->window),
1406 GTK_DIALOG_DESTROY_WITH_PARENT,
1407 GTK_MESSAGE_ERROR,GTK_BUTTONS_NONE,
1408 _("Unable to apply markers as all being used."));
1409 gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Okay"),GTK_RESPONSE_OK);
1410 gtk_dialog_run(GTK_DIALOG(dialog));
1411 gtk_widget_destroy(dialog);
1412 return;
1413 }
1414 /* otherwise ok to set marker */
1415 SetMarker(doc,iBookMark,m,iNewLine);
1416 fd->iBookmarkLinePos[iBookMark]=iPosInLine;
1417 }
1418 /* else if have to remove marker from current line */
1419 else if(iOldLine==iNewLine)
1420 {
1421 DeleteMarker(doc,iBookMark,fd->iBookmarkMarkerUsed[iBookMark]);
1422 }
1423 /* else have to move it to current line */
1424 else
1425 {
1426 /* go through the process of removing and adding the marker so the marker will */
1427 /* have the highest value and be on top */
1428
1429 /* remove old marker */
1430 DeleteMarker(doc,iBookMark,fd->iBookmarkMarkerUsed[iBookMark]);
1431 /* add new marker if moving marker */
1432 m=NextFreeMarker(doc);
1433 /* don't bother checking for failure to find marker as have just released one so */
1434 /* there should be one free */
1435 SetMarker(doc,iBookMark,m,iNewLine);
1436 fd->iBookmarkLinePos[iBookMark]=iPosInLine;
1437 }
1438 }
1439
1440
1441 /* handle key press
1442 * used to see if macro is being triggered and to control numbered bookmarks
1443 */
Key_Released_CallBack(GtkWidget * widget,GdkEventKey * ev,gpointer data)1444 static gboolean Key_Released_CallBack(GtkWidget *widget, GdkEventKey *ev, gpointer data)
1445 {
1446 GeanyDocument *doc;
1447 gint i;
1448
1449 doc=document_get_current();
1450 if(doc==NULL)
1451 return FALSE;
1452
1453 if(ev->type!=GDK_KEY_RELEASE)
1454 return FALSE;
1455
1456 /* control and number pressed */
1457 if(ev->state==4)
1458 {
1459 i=((gint)(ev->keyval))-'0';
1460 if(i<0 || i>9)
1461 return FALSE;
1462
1463 GotoBookMark(doc, i);
1464 return TRUE;
1465 }
1466 /* control+shift+number */
1467 if(ev->state==5) {
1468 /* could use hardware keycode instead of keyvals but if unable to get keyode then don't
1469 * have logical default to fall back on
1470 */
1471 for(i=0;i<10;i++) if((gint)(ev->keyval)==iShiftNumbers[i])
1472 {
1473 SetBookMark(doc, i);
1474 return TRUE;
1475 }
1476
1477 }
1478
1479 return FALSE;
1480 }
1481
1482
1483 /* set up this plugin */
plugin_init(GeanyData * data)1484 void plugin_init(GeanyData *data)
1485 {
1486 gint i,k,iResults=0;
1487 GdkKeymapKey *gdkkmkResults;
1488 #if GTK_CHECK_VERSION(3, 22, 0)
1489 GdkKeymap *gdkKeyMap=gdk_keymap_get_for_display(gdk_display_get_default());
1490 #else
1491 GdkKeymap *gdkKeyMap=gdk_keymap_get_default();
1492 #endif
1493
1494 /* Load settings */
1495 LoadSettings();
1496
1497 /* Calculate what shift '0' to '9 will be (£ is above 3 on uk keyboard, but it's # or ~ on us
1498 * keyboard.)
1499 * there must be an easier way than this of working this out, but I've not figured it out.
1500 */
1501
1502 /* go through '0' to '9', work out hardware keycode, then find out what shift+this keycode
1503 * results in
1504 */
1505 for(i=0;i<10;i++)
1506 {
1507 /* Get keymapkey data for number key */
1508 k=gdk_keymap_get_entries_for_keyval(gdkKeyMap,'0'+i,&gdkkmkResults,&iResults);
1509 /* error retrieving hardware keycode, so leave as standard uk character for shift + number */
1510 if(k==0)
1511 continue;
1512
1513 /* unsure, just in case it does return 0 results but reserve memory */
1514 if(iResults==0)
1515 {
1516 g_free(gdkkmkResults);
1517 continue;
1518 }
1519
1520 /* now use k to indicate GdkKeymapKey we're after */
1521 k=0; /* default if only one hit found */
1522 if(iResults>1)
1523 /* cycle through results if more than one matches */
1524 for(k=0;k<iResults;k++)
1525 /* have found number without using shift, ctrl, Alt etc, so shold be it. */
1526 if(gdkkmkResults[k].level==0)
1527 break;
1528
1529 /* error figuring out which keycode to use so default to standard uk */
1530 if(k==iResults)
1531 {
1532 g_free(gdkkmkResults);
1533 continue;
1534 }
1535
1536 /* set shift pressed */
1537 gdkkmkResults[k].level=1;
1538 /* now get keycode for shift + number */
1539 iResults=gdk_keymap_lookup_key(gdkKeyMap,&(gdkkmkResults[k]));
1540 /* if valid keycode, enter into list of shift + numbers */
1541 if(iResults!=0)
1542 iShiftNumbers[i]=iResults;
1543
1544 /* free resources */
1545 g_free(gdkkmkResults);
1546 }
1547
1548 /* set key press monitor handle */
1549 key_release_signal_id=g_signal_connect(geany->main_widgets->window,"key-release-event",
1550 G_CALLBACK(Key_Released_CallBack),NULL);
1551 }
1552
1553
1554 /* clean up on exiting this plugin */
plugin_cleanup(void)1555 void plugin_cleanup(void)
1556 {
1557 gint k;
1558 guint i;
1559 ScintillaObject* sci;
1560 FileData *fdTemp=fdKnownFilesSettings;
1561 FileData *fdTemp2;
1562 guint32 *markers;
1563
1564 /* uncouple keypress monitor */
1565 g_signal_handler_disconnect(geany->main_widgets->window,key_release_signal_id);
1566
1567 /* go through all documents removing markers (?needed) */
1568 for(i=0;i<GEANY(documents_array)->len;i++)
1569 if(documents[i]->is_valid) {
1570 sci=documents[i]->editor->sci;
1571 markers=g_object_steal_data(G_OBJECT(sci), "Geany_Numbered_Bookmarks_Used");
1572 if(!markers)
1573 continue;
1574 for(k=2;k<25;k++)
1575 if(((*markers)&(1<<k))!=0)
1576 scintilla_send_message(sci,SCI_MARKERDELETEALL,k,0);
1577
1578 g_free(markers);
1579 }
1580
1581 /* Clear memory used to hold file details */
1582 while(fdTemp!=NULL)
1583 {
1584 /* free filename */
1585 g_free(fdTemp->pcFileName);
1586 /* free folding & bookmark information if present */
1587 g_free(fdTemp->pcFolding);
1588 g_free(fdTemp->pcBookmarks);
1589
1590 fdTemp2=fdTemp->NextNode;
1591 /* free memory block */
1592 g_free(fdTemp);
1593 fdTemp=fdTemp2;
1594 }
1595 fdKnownFilesSettings = NULL;
1596
1597 /* free memory used for settings */
1598 g_free(FileDetailsSuffix);
1599 }
1600