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