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  * Features of Macro implementation adapted from gPHPedit (currently maintained by Anoop John)
9  *
10  * william.fraser@virgin.net
11  * 2010-11-01
12 */
13 
14 
15 #ifdef HAVE_CONFIG_H
16 	#include "config.h"
17 #endif
18 #include <geanyplugin.h>
19 
20 #include "utils.h"
21 #include "Scintilla.h"
22 #include <stdlib.h>
23 #include <sys/stat.h>
24 #include <string.h>
25 #include <gdk/gdkkeysyms.h>
26 #include <gtk/gtk.h>
27 
28 /* structure to hold details of Macro event */
29 typedef struct
30 {
31 	gint message;
32 	gulong wparam;
33 	sptr_t lparam;
34 } MacroEvent;
35 
36 /* structure to hold details of a macro */
37 typedef struct
38 {
39 	gchar *name;
40 	/* trigger codes */
41 	guint keyval;
42 	guint state;
43 	GSList *MacroEvents;
44 } Macro;
45 
46 /* structure to hold details of Macro for macro editor */
47 typedef struct
48 {
49 	gint message;
50 	const gchar *description;
51 } MacroDetailEntry;
52 
53 /* list of editor messages this plugin can handle & a description */
54 const MacroDetailEntry MacroDetails[]={
55 {SCI_CUT,N_("Cut to Clipboard")},
56 {SCI_COPY,N_("Copy to Clipboard")},
57 {SCI_PASTE,N_("Paste from Clipboard")},
58 {SCI_LINECUT,N_("Cut current line to Clipboard")},
59 {SCI_LINECOPY,N_("Copy current line to Clipboard")},
60 
61 {SCI_DELETEBACK,N_("Delete character to the left")},
62 {SCI_CLEAR,N_("Delete character to the right")},
63 {SCI_DELETEBACKNOTLINE,N_("Delete character to the left (but not newline)")},
64 {SCI_DELWORDLEFT,N_("Delete up to start of word to the Left")},
65 {SCI_DELWORDRIGHT,N_("Delete up to start of word to the Right")},
66 {SCI_DELWORDRIGHTEND,N_("Delete up to end of word to the Right")},
67 {SCI_DELLINELEFT,N_("Delete to beginning of line")},
68 {SCI_DELLINERIGHT,N_("Delete to end of line")},
69 {SCI_LINEDELETE,N_("Delete current line")},
70 {SCI_BACKTAB,N_("Backwards Tab (deletes tab if nothing after it)")},
71 
72 {SCI_LINESCROLLDOWN,N_("Scroll Display down a line")},
73 {SCI_LINESCROLLUP,N_("Scroll Display up a line")},
74 {SCI_ZOOMIN,N_("Zoom view in")},
75 {SCI_ZOOMOUT,N_("Zoom view out")},
76 
77 {SCI_LINEDOWN,N_("Move Cursor Down")},
78 {SCI_LINEUP,N_("Move Cursor Up")},
79 {SCI_CHARLEFT,N_("Move Cursor Left")},
80 {SCI_CHARRIGHT,N_("Move Cursor Right")},
81 {SCI_WORDLEFT,N_("Move Cursor to start of Word to the Left")},
82 {SCI_WORDRIGHT,N_("Move Cursor to start of Word to the Right")},
83 {SCI_WORDPARTLEFT,N_("Move Cursor to start of Part of Word to the Left")},
84 {SCI_WORDPARTRIGHT,N_("Move Cursor to start of Part of Word to the Right")},
85 {SCI_HOME,N_("Move Cursor to start of line")},
86 {SCI_LINEEND,N_("Move Cursor to end of line")},
87 {SCI_DOCUMENTSTART,N_("Move Cursor to 1st line of Document")},
88 {SCI_DOCUMENTEND,N_("Move Cursor to last line of document")},
89 {SCI_PAGEUP,N_("Move Cursor up one Page")},
90 {SCI_PAGEDOWN,N_("Move Cursor down one Page")},
91 {SCI_HOMEDISPLAY,N_("Move Cursor to first visible character")},
92 {SCI_LINEENDDISPLAY,N_("Move Cursor to last visible character")},
93 {SCI_VCHOME,N_("Move Cursor to 1st non-whitespace character of line, or 1st character of line if\
94  already at 1st non-whitespace character")},
95 {SCI_PARADOWN,N_("Move Cursor to beginning of next paragraph")},
96 {SCI_PARAUP,N_("Move Cursor up to beginning of current/previous paragraph")},
97 {SCI_WORDLEFTEND,N_("Move Cursor to end of Word to the Left")},
98 {SCI_WORDRIGHTEND,N_("Move Cursor to end of Word to the Right")},
99 
100 {SCI_LINEDOWNEXTEND,N_("Extend Selection down a line")},
101 {SCI_LINEUPEXTEND,N_("Extend Selection up a line")},
102 {SCI_CHARLEFTEXTEND,N_("Extend Selection Left a line")},
103 {SCI_CHARRIGHTEXTEND,N_("Extend Selection Right a line")},
104 {SCI_WORDLEFTEXTEND,N_("Extend Selection to start of Word to the Left")},
105 {SCI_WORDRIGHTEXTEND,N_("Extend Selection to start of Word to the Right")},
106 {SCI_WORDPARTLEFTEXTEND,N_("Extend Selection to start of Part of Word to the Left")},
107 {SCI_WORDPARTRIGHTEXTEND,N_("Extend Selection to start of Part of Word to the Right")},
108 {SCI_HOMEEXTEND,N_("Extend Selection to start of line")},
109 {SCI_LINEENDEXTEND,N_("Extend Selection to end of line")},
110 {SCI_DOCUMENTSTARTEXTEND,N_("Extend Selection to start of document")},
111 {SCI_DOCUMENTENDEXTEND,N_("Extend Selection to end of document")},
112 {SCI_PAGEUPEXTEND,N_("Extend Selection up one Page")},
113 {SCI_PAGEDOWNEXTEND,N_("Extend Selection down one Page")},
114 {SCI_HOMEDISPLAYEXTEND,N_("Extend Selection to fist visible character")},
115 {SCI_LINEENDDISPLAYEXTEND,N_("Extend Selection to last visible character")},
116 {SCI_VCHOMEEXTEND,N_("Extend Selection to 1st non-whitespace character of line, or 1st character of\
117  line if already at 1st non-whitespace character")},
118 {SCI_PARADOWNEXTEND,N_("Extend Selection to beginning of next paragraph")},
119 {SCI_PARAUPEXTEND,N_("Extend Selection up to beginning of current/previous paragraph")},
120 {SCI_WORDLEFTENDEXTEND,N_("Extend Selection to end of Word to the Left")},
121 {SCI_WORDRIGHTENDEXTEND,N_("Extend Selection to end of Word to the Right")},
122 
123 {SCI_LINEDOWNRECTEXTEND,N_("Extend Rectangular Selection down a line")},
124 {SCI_LINEUPRECTEXTEND,N_("Extend Rectangular Selection up a line")},
125 {SCI_CHARLEFTRECTEXTEND,N_("Extend Rectangular Selection Left a line")},
126 {SCI_CHARRIGHTRECTEXTEND,N_("Extend Rectangular Selection Right a line")},
127 {SCI_HOMERECTEXTEND,N_("Extend Rectangular Selection to start of line")},
128 {SCI_LINEENDRECTEXTEND,N_("Extend Rectangular Selection to end of line")},
129 {SCI_PAGEUPRECTEXTEND,N_("Extend Rectangular Selection up one Page")},
130 {SCI_PAGEDOWNRECTEXTEND,N_("Extend Rectangular Selection down one Page")},
131 {SCI_VCHOMERECTEXTEND,N_("Extend Rectangular Selection to 1st non-whitespace character of line, or\
132  1st character of line if already at 1st non-whitespace character")},
133 
134 {SCI_CANCEL,N_("Cancel Selection")},
135 
136 {SCI_EDITTOGGLEOVERTYPE,N_("Toggle Insert/Overwrite mode")},
137 {SCI_TAB,N_("Tab")},
138 {SCI_NEWLINE,N_("Newline")},
139 
140 {SCI_REPLACESEL,N_("Insert/replace with \"\"")},
141 
142 {SCI_LINETRANSPOSE,N_("Swap current line with one above")},
143 {SCI_LOWERCASE,N_("Change selected text to lowercase")},
144 {SCI_UPPERCASE,N_("Change selected text to uppercase")},
145 
146 {SCI_LINEDUPLICATE,N_("Insert duplicate of current line below")},
147 {SCI_SELECTIONDUPLICATE,N_("Insert duplicate of selected text after selection. If nothing selected,\
148  duplicate line")},
149 
150 {SCI_SEARCHNEXT,N_("Search for next \"\"")},
151 {SCI_SEARCHPREV,N_("Search for previous \"\"")},
152 {SCI_SEARCHANCHOR,N_("Set start of search to beginning of selection")},
153 
154 /* editor commands that don't seem to work well in editing
155  * {SCI_FORMFEED,N_("FormFeed")},
156  *
157  * other commands ommited as they don't appear to do anything different to existing commands
158 */
159 {0,NULL}
160 };
161 
162 /* define IDs for dialog buttons */
163 enum GEANY_MACRO_BUTTON {
164 	GEANY_MACRO_BUTTON_CANCEL,
165 	GEANY_MACRO_BUTTON_DELETE,
166 	GEANY_MACRO_BUTTON_EDIT,
167 	GEANY_MACRO_BUTTON_RERECORD,
168 	GEANY_MACRO_BUTTON_UP,
169 	GEANY_MACRO_BUTTON_DOWN,
170 	GEANY_MACRO_BUTTON_ABOVE,
171 	GEANY_MACRO_BUTTON_BELOW,
172 	GEANY_MACRO_BUTTON_APPLY
173 };
174 
175 GeanyPlugin     *geany_plugin;
176 GeanyData       *geany_data;
177 
178 PLUGIN_VERSION_CHECK(224)
179 
180 PLUGIN_SET_TRANSLATABLE_INFO(LOCALEDIR, GETTEXT_PACKAGE,
181                              _("Macros"),_("Macros for Geany"),
182                              "1.1","William Fraser <william.fraser@virgin.net>")
183 
184 /* Plugin user alterable settings */
185 static gboolean bSaveMacros=TRUE;
186 static gboolean bQueryOverwriteMacros=TRUE;
187 
188 /* internal variables */
189 static gint iShiftNumbers[]={41,33,34,163,36,37,94,38,42,40};
190 static gulong key_release_signal_id;
191 static GtkWidget *Record_Macro_menu_item=NULL;
192 static GtkWidget *Stop_Record_Macro_menu_item=NULL;
193 static GtkWidget *Edit_Macro_menu_item=NULL;
194 static Macro *RecordingMacro=NULL;
195 static GSList *mList=NULL;
196 static gboolean bMacrosHaveChanged=FALSE;
197 
198 /* default config file */
199 const gchar default_config[] =
200 	"[Settings]\n"
201 	"Save_Macros = true\n"
202 	"Question_Macro_Overwrite = true\n"
203 	"[Macros]";
204 
205 /* clear macro events list and free up any memory they are using */
ClearMacroList(GSList * gsl)206 static GSList * ClearMacroList(GSList *gsl)
207 {
208 	MacroEvent *me;
209 	GSList * gslTemp=gsl;
210 
211 	/* free data held in GSLIST structure */
212 	while(gslTemp!=NULL)
213 	{
214 		me=gslTemp->data;
215 		/* check to see if it's a message that has string attached, and free it if so
216 		 * lparam might be NULL for SCI_SEARCHNEXT or SCI_SEARCHPREV but g_free is ok
217 		 * with this
218 		*/
219 		if(me->message==SCI_REPLACESEL ||
220 		   me->message==SCI_SEARCHNEXT ||
221 		   me->message==SCI_SEARCHPREV)
222 			g_free((void*)(me->lparam));
223 
224 		g_free((void*)(gslTemp->data));
225 		gslTemp=g_slist_next(gslTemp);
226 	}
227 
228 	/* free SLIST structure */
229 	g_slist_free(gsl);
230 
231 	return NULL;
232 }
233 
234 
235 /* create new Macro */
CreateMacro(void)236 static Macro * CreateMacro(void)
237 {
238 	Macro *m;
239 
240 	if((m=(Macro*)(g_malloc(sizeof *m)))!=NULL)
241 	{
242 		m->name=NULL;
243 		m->MacroEvents=NULL;
244 		return m;
245 	}
246 	return NULL;
247 }
248 
249 
250 /* delete macro */
FreeMacro(Macro * m)251 static Macro * FreeMacro(Macro *m)
252 {
253 	if(m==NULL)
254 		return NULL;
255 
256 	g_free(m->name);
257 	ClearMacroList(m->MacroEvents);
258 	g_free(m);
259 
260 	return NULL;
261 }
262 
263 
264 /* add a macro to the list of defined macros */
AddMacroToList(Macro * m)265 static void AddMacroToList(Macro *m)
266 {
267 	mList=g_slist_append(mList,m);
268 }
269 
270 
271 /* remove macro from list of defined macros */
RemoveMacroFromList(Macro * m)272 static void RemoveMacroFromList(Macro *m)
273 {
274 	mList=g_slist_remove(mList,m);
275 }
276 
277 
278 /* returns a macro in the defined list by name, or NULL if no macro exists with the specified name
279 */
FindMacroByName(gchar * name)280 static Macro * FindMacroByName(gchar *name)
281 {
282 	GSList *gsl=mList;
283 
284 	if(name==NULL)
285 		return NULL;
286 
287 	while(gsl!=NULL)
288 	{
289 		if(strcmp(name,((Macro*)(gsl->data))->name)==0)
290 			return gsl->data;
291 
292 		gsl=g_slist_next(gsl);
293 	}
294 
295 	return NULL;
296 }
297 
298 
299 /* returns a macro in the defined list by key press combination, or NULL if no macro exists
300  * with the specified key combination */
FindMacroByKey(guint keyval,guint state)301 static Macro * FindMacroByKey(guint keyval,guint state)
302 {
303 	GSList *gsl=mList;
304 
305 	while(gsl!=NULL)
306 	{
307 		if(((Macro*)(gsl->data))->keyval==keyval && ((Macro*)(gsl->data))->state==state)
308 			return gsl->data;
309 
310 		gsl=g_slist_next(gsl);
311 	}
312 
313 	return NULL;
314 }
315 
316 
317 /* completely wipe all saved macros and ascosiated memory */
ClearAllMacros(void)318 static void ClearAllMacros(void)
319 {
320 	GSList *gsl=mList;
321 
322 	while(gsl!=NULL)
323 	{
324 		FreeMacro((Macro*)(gsl->data));
325 		gsl=g_slist_next(gsl);
326 	}
327 
328 	g_slist_free(mList);
329 	mList=NULL;
330 }
331 
332 
333 /* Repeat a macro to the editor */
ReplayMacro(Macro * m)334 static void ReplayMacro(Macro *m)
335 {
336 	MacroEvent *me;
337 	GSList *gsl=m->MacroEvents;
338 	ScintillaObject* sci=document_get_current()->editor->sci;
339 	gchar *clipboardcontents;
340 	gboolean bFoundAnchor=FALSE;
341 
342 	scintilla_send_message(sci,SCI_BEGINUNDOACTION,0,0);
343 
344 	while(gsl!=NULL)
345 	{
346 		me=gsl->data;
347 
348 		/* make not if anchor has been found */
349 		if(me->message==SCI_SEARCHANCHOR)
350 			bFoundAnchor=TRUE;
351 
352 		/* possibility that user edited macros might not have anchor before search */
353 		if((me->message==SCI_SEARCHNEXT || me->message==SCI_SEARCHPREV) &&
354 		   bFoundAnchor==FALSE)
355 		{
356 			scintilla_send_message(sci,SCI_SEARCHANCHOR,0,0);
357 			bFoundAnchor=TRUE;
358 		}
359 
360 		/* search might use clipboard to look for: check & hanndle */
361 		if((me->message==SCI_SEARCHNEXT || me->message==SCI_SEARCHPREV) &&
362 		   ((gchar*)me->lparam)==NULL)
363 		{
364 			clipboardcontents=gtk_clipboard_wait_for_text(gtk_clipboard_get(
365 			                  GDK_SELECTION_CLIPBOARD));
366 			/* ensure there is something in the clipboard */
367 			if(clipboardcontents==NULL)
368 			{
369 				dialogs_show_msgbox(GTK_MESSAGE_INFO,_("No text in clipboard!"));
370 				break;
371 			}
372 
373 			scintilla_send_message(sci,me->message,me->wparam,(sptr_t)clipboardcontents);
374 		}
375 		else
376 			scintilla_send_message(sci,me->message,me->wparam,me->lparam);
377 
378 		/* move to next macro event */
379 		gsl=g_slist_next(gsl);
380 	}
381 
382 	scintilla_send_message(sci,SCI_ENDUNDOACTION,0,0);
383 }
384 
385 
386 /* convert string so that it can be saved as text in a comma separated text entry in an ini file
387  * resultant string needs to be freed after use
388 */
MakeStringSaveable(gchar * s)389 static gchar * MakeStringSaveable(gchar *s)
390 {
391 	gchar *cTemp;
392 	gchar **pszBits;
393 
394 	/* use GLib routine to do most of the work
395 	 * (sort out all special characters, and non-ASCII characters)
396 	*/
397 	cTemp=g_strescape(s,"");
398 	/* now make sure that all instances of ',' are replaced by octal version so that can use ',' as
399 	 * a separator
400 	 * first break the string up at the commas
401 	*/
402 	pszBits=g_strsplit(cTemp,",",0);
403 	/* can now free up cTemp */
404 	g_free(cTemp);
405 	/* now combine bits together with octal verion of comma in-between */
406 	cTemp=g_strjoinv("\\054",pszBits);
407 	/* free up memory used by bits */
408 	g_strfreev(pszBits);
409 
410 	return cTemp;
411 }
412 
413 
414 /* create a macro event from an array of stings. This command may move past more than one array
415  * entry if the macro event details require it
416 */
GetMacroEventFromString(gchar ** s,gint * k)417 static MacroEvent * GetMacroEventFromString(gchar **s,gint *k)
418 {
419 	MacroEvent *me;
420 
421 	me=g_new0(MacroEvent,1);
422 	/* get event number */
423 	me->message=strtoll(s[(*k)++],NULL,10);
424 
425 	/* default to 0 */
426 	me->wparam=0;
427 
428 	/* now handle lparam if required */
429 	switch(me->message)
430 	{
431 		case SCI_SEARCHNEXT:
432 		case SCI_SEARCHPREV:
433 			/* get text */
434 			me->lparam=(sptr_t)(g_strcompress(s[(*k)++]));
435 			/* if text is empty string replace with NULL to signify use clipboard */
436 			if((*((gchar*)(me->lparam)))==0)
437 			{
438 				g_free((gchar*)me->lparam);
439 				me->lparam=(sptr_t)NULL;
440 			}
441 
442 			/* get search flags */
443 			me->wparam=strtoll(s[(*k)++],NULL,10);
444 			break;
445 		case SCI_REPLACESEL:
446 			/* get text */
447 			me->lparam=(sptr_t)(g_strcompress(s[(*k)++]));
448 			break;
449 		/* default handler for messages without extra data */
450 		default:
451 			me->lparam=0;
452 			break;
453 	}
454 
455 	/* return MacroEvent object */
456 	return me;
457 }
458 
459 
460 /* convert a Macro event to a string that can be savable in a comma separated ini file entry
461  * resultant string needs to be freed afterwards
462 */
MacroEventToString(MacroEvent * me)463 static gchar *MacroEventToString(MacroEvent *me)
464 {
465 	gchar *szMacroNumber;
466 	gchar *szNumberAndData;
467 	gchar *pTemp;
468 
469 	/* save off macro code */
470 	szMacroNumber=g_strdup_printf("%i",me->message);
471 
472 	/* now handle lparam if required */
473 	switch(me->message)
474 	{
475 		case SCI_SEARCHNEXT:
476 		case SCI_SEARCHPREV:
477 			/* check if string is NULL */
478 			if(((gchar*)(me->lparam))==NULL)
479 			{
480 				/* now merge code and data */
481 				szNumberAndData=g_strdup_printf("%s,,%lu",szMacroNumber,me->wparam);
482 				/* free memory */
483 				g_free(szMacroNumber);
484 				return szNumberAndData;
485 			}
486 
487 			/* first get string reprisentation of data */
488 			pTemp=MakeStringSaveable((gchar*)(me->lparam));
489 			/* now merge code and data */
490 			szNumberAndData=g_strdup_printf("%s,%s,%lu",szMacroNumber,pTemp,me->wparam);
491 			/* free memory */
492 			g_free(szMacroNumber);
493 			g_free(pTemp);
494 			return szNumberAndData;
495 
496 		case SCI_REPLACESEL:
497 			/* first get string reprisentation of data */
498 			pTemp=MakeStringSaveable((gchar*)(me->lparam));
499 			/* now merge code and data */
500 			szNumberAndData=g_strdup_printf("%s,%s",szMacroNumber,pTemp);
501 			/* free memory */
502 			g_free(szMacroNumber);
503 			g_free(pTemp);
504 			return szNumberAndData;
505 
506 		/* default handler for messages without extra data */
507 		default:
508 			return szMacroNumber;
509 	}
510 }
511 
512 
513 /* Is there a document open in the editor */
DocumentPresent(void)514 static gboolean DocumentPresent(void)
515 {
516   return (document_get_current()!=NULL);
517 }
518 
519 
520 /* check editor notifications and remember editor events */
Notification_Handler(GObject * obj,GeanyEditor * ed,SCNotification * nt,gpointer ud)521 static gboolean Notification_Handler(GObject *obj,GeanyEditor *ed,SCNotification *nt,gpointer ud)
522 {
523 	MacroEvent *me;
524 	gint i;
525 
526 	/* ignore non macro recording messages */
527 	if(nt->nmhdr.code!=SCN_MACRORECORD)
528 		return FALSE;
529 
530 	/* probably overkill as should not recieve SCN_MACRORECORD messages unless recording
531 	 * macros
532 	*/
533 	if(RecordingMacro==NULL)
534 		return FALSE;
535 
536 	/* check to see if it's a code we're happy to deal with */
537 	for(i=0;;i++)
538 	{
539 		/* break if code we can deal with */
540 		if(nt->message==MacroDetails[i].message)
541 			break;
542 
543 		/* got to end of codes without match: unrecognised */
544 		if(MacroDetails[i].description==NULL)
545 		{
546 			dialogs_show_msgbox(GTK_MESSAGE_INFO,_("Unrecognised message\n%i %i %i"),nt->message,
547 			                    (gint)(nt->wParam),(gint)(nt->lParam));
548 			return FALSE;
549 		}
550 
551 	}
552 	me=g_new0(MacroEvent,1);
553 	me->message=nt->message;
554 	me->wparam=nt->wParam;
555 	/* Special handling for text in lparam */
556 	me->lparam=(me->message==SCI_SEARCHNEXT ||
557 	            me->message==SCI_SEARCHPREV ||
558 	            me->message==SCI_REPLACESEL)
559 		?((sptr_t) g_strdup((gchar *)(nt->lParam))) : nt->lParam;
560 
561 	/* more efficient to create reverse list and reverse it at the end */
562 	RecordingMacro->MacroEvents=g_slist_prepend(RecordingMacro->MacroEvents,me);
563 
564 	return FALSE;
565 }
566 
567 
568 /* convert GTK <Alt><Control><Shift>lowercase char to standard Alt+Ctrl+Shift+uppercase char
569  * format resultant string needs to be freed
570 */
GetPretyKeyName(guint keyval,guint state)571 static gchar *GetPretyKeyName(guint keyval,guint state)
572 {
573 	gboolean bAlt,bCtrl,bShift;
574 	const gchar *cTemp;
575 	gchar *cName;
576 	gchar *cPretyName;
577 
578 	/* get basic keypress description */
579 	cName=gtk_accelerator_name(keyval,state);
580 
581 	/* cName now holds the name but in the <Control><Shift>h format.
582 	* Pretify to Ctrl+Shift+H like in menus
583 	*/
584 	bAlt=((gchar*)g_strrstr(cName,"<Alt>")!=NULL);
585 	bCtrl=((gchar*)g_strrstr(cName,"<Control>")!=NULL);
586 	bShift=((gchar*)g_strrstr(cName,"<Shift>")!=NULL);
587 
588 	/* find out where the modifyer key description ends. */
589 	cTemp=g_strrstr(cName,">");
590 	/* if there are no modifyers then point to start of string */
591 	if(cTemp==NULL)
592 		cTemp=cName;
593 	else
594 		cTemp++;
595 
596 	/* put string back together but pretier */
597 	cPretyName=g_strdup_printf("%s%s%s%c%s",(bShift?"Shift+":""),(bCtrl?"Ctrl+":""),
598 							(bAlt?"Alt+":""),g_ascii_toupper(cTemp[0]),
599 							&(g_ascii_strdown(cTemp,-1)[1]));
600 
601 	/* tidy up */
602 	g_free(cName);
603 
604 	/* return pretified name */
605 	return cPretyName;
606 }
607 
608 
609 /* save settings (preferences, and macro data) */
SaveSettings(void)610 static void SaveSettings(void)
611 {
612 	GKeyFile *config = NULL;
613 	gchar *config_file = NULL;
614 	gchar *data;
615 	gchar *cKey;
616 	gchar *pcTemp;
617 	gint i,k;
618 	GSList *gsl=mList;
619 	GSList *gslTemp;
620 	gchar **pszMacroStrings;
621 	Macro *m;
622 
623 	/* create new config from default settings */
624 	config=g_key_file_new();
625 
626 	/* now set settings */
627 	g_key_file_set_boolean(config,"Settings","Save_Macros",bSaveMacros);
628 	g_key_file_set_boolean(config,"Settings","Question_Macro_Overwrite",bQueryOverwriteMacros);
629 
630 	/* now save macros */
631 	if(bSaveMacros==TRUE)
632 	{
633 		i=0;
634 
635 		/* iterate through macros and save them */
636 		while(gsl!=NULL)
637 		{
638 			m=(Macro*)(gsl->data);
639 			cKey=g_strdup_printf("A%d",i);
640 
641 			/* save macro name */
642 			pcTemp=MakeStringSaveable(m->name);
643 			g_key_file_set_string(config,"Macros",cKey,pcTemp);
644 			g_free(pcTemp);
645 			/* save trigger data */
646 			cKey[0]='B';
647 			g_key_file_set_integer(config,"Macros",cKey,m->keyval);
648 			cKey[0]='C';
649 			g_key_file_set_integer(config,"Macros",cKey,m->state);
650 			/* convert macros to saveable format
651 			* first generate list of all macrodetails
652 			*/
653 			pszMacroStrings=(gchar **)
654 				(g_malloc(sizeof(gchar *)*(g_slist_length(m->MacroEvents)+1)));
655 			gslTemp=m->MacroEvents;
656 			k=0;
657 			while(gslTemp!=NULL)
658 			{
659 				pszMacroStrings[k++]=MacroEventToString((MacroEvent*)(gslTemp->data));
660 				gslTemp=g_slist_next(gslTemp);
661 			}
662 
663 			/* null terminate array for g_strfreev to work */
664 			pszMacroStrings[k]=NULL;
665 			/* now transfer to single string */
666 			pcTemp=g_strjoinv(",",pszMacroStrings);
667 			/* save data */
668 			cKey[0]='D';
669 			g_key_file_set_string(config,"Macros",cKey,pcTemp);
670 			/* free up memory */
671 			g_free(pcTemp);
672 			g_strfreev(pszMacroStrings);
673 			g_free(cKey);
674 
675 			/* move to next macro */
676 			i++;
677 			gsl=g_slist_next(gsl);
678 		}
679 	}
680 
681 	/* turn config into data */
682 	data=g_key_file_to_data(config,NULL,NULL);
683 
684 	/* calculate setting directory name */
685 	config_file=g_build_filename(geany->app->configdir,"plugins","Geany_Macros",NULL);
686 	/* ensure directory exists */
687 	g_mkdir_with_parents(config_file,0755);
688 
689 	/* make config_file hold name of settings file */
690 	setptr(config_file,g_build_filename(config_file,"settings.conf",NULL));
691 
692 	/* write data */
693 	utils_write_file(config_file, data);
694 
695 	/* free memory */
696 	g_free(config_file);
697 	g_key_file_free(config);
698 	g_free(data);
699 
700 	/* Macros have now been saved */
701 	bMacrosHaveChanged=FALSE;
702 }
703 
704 
705 /* load settings (preferences, file data, and macro data) */
LoadSettings(void)706 static void LoadSettings(void)
707 {
708 	gchar *pcTemp;
709 	gchar *pcKey;
710 	gint i,k;
711 	gchar *config_file=NULL;
712 	GKeyFile *config=NULL;
713 	Macro *m;
714 	gchar **pcMacroCommands;
715 
716 	/* Make config_file hold directory name of settings file */
717 	config_file=g_build_filename(geany->app->configdir,"plugins","Geany_Macros",NULL);
718 	/* ensure directory exists */
719 	g_mkdir_with_parents(config_file,0755);
720 
721 	/* make config_file hold name of settings file */
722 	setptr(config_file,g_build_filename(config_file,"settings.conf",NULL));
723 
724 	/* either load settings file, or create one from default */
725 	config=g_key_file_new();
726 	if(!g_key_file_load_from_file(config,config_file, G_KEY_FILE_KEEP_COMMENTS,NULL))
727 		g_key_file_load_from_data(config,default_config,sizeof(default_config),
728 								G_KEY_FILE_KEEP_COMMENTS,NULL);
729 
730 	/* extract settings */
731 	bQueryOverwriteMacros=utils_get_setting_boolean(config,"Settings",
732 	                                                "Question_Macro_Overwrite",FALSE);
733 	bSaveMacros=utils_get_setting_boolean(config,"Settings","Save_Macros",FALSE);
734 
735 	/* extract macros */
736 	i=0;
737 	while(TRUE)
738 	{
739 		pcKey=g_strdup_printf("A%d",i);
740 		i++;
741 		/* get macro name */
742 		pcTemp=(gchar*)(utils_get_setting_string(config,"Macros",pcKey,NULL));
743 		/* if null then have reached end of macros */
744 		if(pcTemp==NULL)
745 		{
746 			g_free(pcKey);
747 			break;
748 		}
749 
750 		m=CreateMacro();
751 		m->name=pcTemp;
752 		/* load triggers */
753 		pcKey[0]='B';
754 		m->keyval=utils_get_setting_integer(config,"Macros",pcKey,0);
755 		pcKey[0]='C';
756 		m->state=utils_get_setting_integer(config,"Macros",pcKey,0);
757 		/* Load macro list */
758 		pcKey[0]='D';
759 		pcTemp=(gchar*)(utils_get_setting_string(config,"Macros",pcKey,NULL));
760 		g_free(pcKey);
761 		/* break into individual macro data */
762 		pcMacroCommands=g_strsplit(pcTemp,",",0);
763 		/* can now free up pcTemp */
764 		g_free(pcTemp);
765 		/* now go through macro data generating macros */
766 		for(k=0,m->MacroEvents=NULL;pcMacroCommands[k]!=NULL;)
767 			m->MacroEvents=g_slist_prepend(m->MacroEvents,
768 			                               GetMacroEventFromString(pcMacroCommands,
769 		                                       &k));
770 
771 		/* list created in reverse as more efficient, now turn it around */
772 		m->MacroEvents=g_slist_reverse(m->MacroEvents);
773 		/* macro now complete, add it to the list */
774 		AddMacroToList(m);
775 		/* free up memory used by pcMacroCommands */
776 		g_strfreev(pcMacroCommands);
777 	}
778 
779 	/* free memory */
780 	g_free(config_file);
781 	g_key_file_free(config);
782 }
783 
784 
785 /* handle button presses in the preferences dialog box */
on_configure_response(GtkDialog * dialog,gint response,gpointer user_data)786 static void on_configure_response(GtkDialog *dialog, gint response, gpointer user_data)
787 {
788 	gboolean bSettingsChanged;
789 	GtkCheckButton *cb1,*cb2;
790 
791 	if(response!=GTK_RESPONSE_OK && response!=GTK_RESPONSE_APPLY)
792 		return;
793 
794 	/* retreive pointers to check boxes */
795 	cb1=(GtkCheckButton*)(g_object_get_data(G_OBJECT(dialog),"GeanyMacros_cb1"));
796 	cb2=(GtkCheckButton*)(g_object_get_data(G_OBJECT(dialog),"GeanyMacros_cb2"));
797 
798 	/* first see if settings are going to change */
799 	bSettingsChanged=(bSaveMacros!=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb1)));
800 	bSettingsChanged|=(bQueryOverwriteMacros!=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb2)));
801 
802 	/* set new settings settings */
803 	bSaveMacros=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb1));
804 	bQueryOverwriteMacros=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb2));
805 
806 	/* now save new settings if they have changed */
807 	if(bSettingsChanged)
808 		SaveSettings();
809 }
810 
811 
812 /* return a widget containing settings for plugin that can be changed */
plugin_configure(GtkDialog * dialog)813 GtkWidget *plugin_configure(GtkDialog *dialog)
814 {
815 	GtkWidget *vbox;
816 	GtkWidget *cb1,*cb2;
817 
818 	vbox=gtk_vbox_new(FALSE, 6);
819 
820 	cb1=gtk_check_button_new_with_label(_("Save Macros when close Geany"));
821 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb1),bSaveMacros);
822 	gtk_box_pack_start(GTK_BOX(vbox),cb1,FALSE,FALSE,2);
823 	/* save pointer to check_button */
824 	g_object_set_data(G_OBJECT(dialog),"GeanyMacros_cb1",cb1);
825 
826 	cb2=gtk_check_button_new_with_label(_("Ask before replacing existing Macros"));
827 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb2),bQueryOverwriteMacros);
828 	gtk_box_pack_start(GTK_BOX(vbox),cb2,FALSE,FALSE,2);
829 	/* save pointer to check_button */
830 	g_object_set_data(G_OBJECT(dialog),"GeanyMacros_cb2",cb2);
831 
832 	gtk_widget_show_all(vbox);
833 
834 	g_signal_connect(dialog,"response",G_CALLBACK(on_configure_response),NULL);
835 
836 	return vbox;
837 }
838 
839 
840 /* display help box */
plugin_help(void)841 void plugin_help(void)
842 {
843 	GtkWidget *dialog,*label,*scroll;
844 	gchar *cText;
845 
846 	/* create dialog box */
847 	dialog=gtk_dialog_new_with_buttons(_("Geany Macros help"),
848 		GTK_WINDOW(geany->main_widgets->window),
849 		GTK_DIALOG_DESTROY_WITH_PARENT,
850 		GTK_STOCK_OK,GTK_RESPONSE_ACCEPT,
851 		NULL);
852 
853 	/* setup help text */
854 	cText=g_strconcat(
855 _("This Plugin implements Macros in Geany.\n\n"),
856 _("This plugin allows you to record and use your own macros. "),
857 _("These are sequences of actions that can then be repeated with a single key combination. "),
858 _("So if you had dozens of lines where you wanted to delete the last 2 characters, you could simpl\
859 y start recording, press End, Backspace, Backspace, down line and then stop recording. "),
860 _("Then simply trigger the macro and it would automatically edit the line and move to the next. "),
861 _("Select Record Macro from the Tools menu and you will be prompted with a dialog box. "),
862 _("You need to specify a key combination that isn't being used, and a name for the macro to help y\
863 ou identify it. "),
864 _("Then press Record. "),
865 _("What you do in the editor is then recorded until you select Stop Recording Macro from the Tools\
866  menu. "),
867 _("Simply pressing the specified key combination will re-run the macro. "),
868 _("To edit the macros you have, select Edit Macro from the Tools menu. "),
869 _("You can select a macro and delete it, or re-record it. "),
870 _("You can also click on a macro's name and change it, or the key combination and re-define that a\
871 ssuming that it's not already in use. "),
872 _("Selecting the edit option allows you to view all the individual elements that make up the macro\
873 . "),
874 _("You can select a different command for each element, move them, add new elements, delete element\
875 s, or if it's replace/insert, you can edit the text that replaces the selected text, or is inserte\
876 d.\n\n"),
877 
878 _("The only thing to bear in mind is that undo and redo actions are not recorded, and won't be rep\
879 layed when the macro is re-run.\n\n"),
880 
881 _("You can alter the default behaviour of this plugin by selecting Plugin Manager under the Tools \
882 menu, selecting this plugin, and clicking Preferences. "),
883 _("You can change:\n"),
884 _("Save Macros when close Geany - If this is selected then Geany will save any recorded macros and\
885  reload them for use the next time you open Geany, if not they will be lost when Geany is closed.\
886 \n"),
887 _("Ask before replacing existing Macros - If this is selected then if you try recording a macro o\
888 ver an existing one it will check before over-writing it, giving you the option of trying a differ\
889 ent name or key trigger combination, otherwise it will simply erase any existing macros with the s\
890 ame name, or the same key trigger combination."),
891 NULL);
892 
893 	/* create label */
894 	label=gtk_label_new(cText);
895 	gtk_label_set_line_wrap(GTK_LABEL(label),TRUE);
896 	gtk_widget_show(label);
897 
898 	/* create scrolled window to display label */
899 	scroll=gtk_scrolled_window_new(NULL,NULL);
900 	gtk_scrolled_window_set_policy((GtkScrolledWindow*)scroll,GTK_POLICY_NEVER,
901 								GTK_POLICY_AUTOMATIC);
902 	gtk_scrolled_window_add_with_viewport((GtkScrolledWindow*)scroll,label);
903 
904 	gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),scroll);
905 	gtk_widget_show(scroll);
906 
907 	/* set dialog size (leave width default) */
908 	gtk_widget_set_size_request(dialog,-1,300);
909 
910 	/* display the dialog */
911 	gtk_dialog_run(GTK_DIALOG(dialog));
912 	gtk_widget_destroy(dialog);
913 
914 	/* free memory */
915 	g_free(cText);
916 }
917 
918 
919 /* handle key press
920  * used to see if macro is being triggered and to control numbered bookmarks
921 */
Key_Released_CallBack(GtkWidget * widget,GdkEventKey * ev,gpointer data)922 static gboolean Key_Released_CallBack(GtkWidget *widget, GdkEventKey *ev, gpointer data)
923 {
924 	Macro *m;
925 
926 	m=FindMacroByKey(ev->keyval,ev->state);
927 
928 	/* if it's a macro trigger then run macro */
929 	if(m!=NULL)
930 	{
931 		ReplayMacro(m);
932 /* ?is this needed */
933 /*    g_signal_stop_emission_by_name((GObject *)widget,"key-release-event"); */
934 		return TRUE;
935 	}
936 
937 	return FALSE;
938 }
939 
940 
941 /* return TRUE if this key combination is not bing used by anything else (and so can be used by
942  * this plugin as a macro trigger)
943 */
UseableAccel(guint key,guint mod)944 static gboolean UseableAccel(guint key,guint mod)
945 {
946 	gint i;
947 	guint u,t;
948 	GSList *gsl;
949 
950 	/* check if in use by accelerator groups */
951 	gsl=gtk_accel_groups_from_object((GObject*)geany->main_widgets->window);
952 	/* loop through all the accelerator groups until we either find one that matches the key (k!=0)
953 	 * or we don't (k==0)
954 	*/
955 	for(u=0;u<g_slist_length(gsl);u++)
956 	{
957 		gtk_accel_group_query((GtkAccelGroup*)((g_slist_nth(gsl,u))->data),key,mod,&t);
958 		if(t!=0)
959 			return FALSE; /* combination in use so don't accept as macro trigger */
960 	}
961 
962 	/* now check to see if numbered bookmark key is atempted
963 	 *
964 	 * control and number pressed
965 	*/
966 	if(mod==4)
967 	{
968 		i=((gint)key)-'0';
969 		if(i>=0 && i<=9)
970 			return FALSE;
971 	}
972 
973 	/* control+shift+number */
974 	if(mod==5)
975 	{
976 		/* could use hardware keycode instead of keyvals but if unable to get keyode then don't
977 		 * have a logical default to fall back on
978 		*/
979 		for(i=0;i<10;i++) if((gint)key==iShiftNumbers[i])
980 			return FALSE;
981 	}
982 
983 	/* there are a lot of unmodified keys, and shift+key combinations in use by the editor.
984 	 * Rather than list them all individually that the editor uses, ban all except the
985 	 * shift+function keys
986 	*/
987 	if(mod==1 || mod==0)
988 	{
989 		if(key<GDK_F1 || key>GDK_F35)
990 			return FALSE;
991 	}
992 
993 	/* there are a several control keys that could be pressed but should not be used on their own
994 	 * check for them
995 	*/
996 	if(key==GDK_Shift_L || key==GDK_Shift_R || key==GDK_Control_L || key==GDK_Control_R ||
997 		key==GDK_Caps_Lock || key==GDK_Shift_Lock || key==GDK_Meta_L || key==GDK_Meta_R ||
998 		key==GDK_Alt_L || key==GDK_Alt_R || key==GDK_Super_L || key==GDK_Super_R ||
999 		key==GDK_Hyper_L || key==GDK_Hyper_R)
1000 		return FALSE;
1001 
1002 	/* ctrl+M is used by normal bookmarks */
1003 	if(mod==4 && key=='m')
1004 		return FALSE;
1005 
1006 	/* now should have a valid keyval/state combination */
1007 	return TRUE;
1008 }
1009 
1010 
1011 /* handle changes to an Entry dialog so that it handles accelerator key combinations */
Entry_Key_Pressed_CallBack(GtkWidget * widget,GdkEventKey * ev,gpointer data)1012 static gboolean Entry_Key_Pressed_CallBack(GtkWidget *widget, GdkEventKey *ev, gpointer data)
1013 {
1014 	gchar *cName;
1015 
1016 	/* make sure that tab is handled by regular handler to ensure can tab through entry boxes and
1017 	 * buttons
1018 	*/
1019 	if((ev->state==0 || ev->state==1) && ev->keyval==GDK_Tab)
1020 		return FALSE;
1021 
1022 	/* first see if key combination is valid for use in macros */
1023 	if(UseableAccel(ev->keyval,ev->state)==FALSE)
1024 		return TRUE;
1025 
1026 	cName=GetPretyKeyName(ev->keyval,ev->state);
1027 	/* set text in entry */
1028 	gtk_entry_set_text((GtkEntry*)widget,cName);
1029 
1030 	/* tidy up memory */
1031 	g_free(cName);
1032 
1033 	/* make note of keys pressed */
1034 	RecordingMacro->keyval=ev->keyval;
1035 	RecordingMacro->state=ev->state;
1036 
1037 	return TRUE;
1038 }
1039 
1040 
1041 /* display dialog and handle the gathering of data prior to recording a macro */
InitializeMacroRecord(void)1042 static gboolean InitializeMacroRecord(void)
1043 {
1044 	GtkWidget *dialog,*gtke,*gtke2,*hbox,*gtkl;
1045 	gint iReply=GTK_RESPONSE_OK;
1046 	Macro *m;
1047 	gboolean bReplaceName,bReplaceTrigger;
1048 
1049 	/* ensure have empty recording macro */
1050 	FreeMacro(RecordingMacro);
1051 	RecordingMacro=CreateMacro();
1052 	/* set with default values */
1053 	RecordingMacro->keyval=0;
1054 	RecordingMacro->state=0;
1055 
1056 	/* create dialog box */
1057 	dialog=gtk_dialog_new_with_buttons(_("Record Macro"),
1058 		GTK_WINDOW(geany->main_widgets->window),
1059 		GTK_DIALOG_DESTROY_WITH_PARENT,
1060 		NULL);
1061 
1062 	/* create buttons */
1063 	gtk_dialog_add_button(GTK_DIALOG(dialog),_("Record"),GTK_RESPONSE_OK);
1064 	gtk_dialog_add_button(GTK_DIALOG(dialog),_("Cancel"),GTK_RESPONSE_CANCEL);
1065 
1066 	/* create box to hold macro trigger entry box and label */
1067 	hbox=gtk_hbox_new(FALSE,0);
1068 	gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),hbox);
1069 	gtk_widget_show(hbox);
1070 
1071 	gtkl=gtk_label_new(_("Macro Trigger:"));
1072 	gtk_box_pack_start(GTK_BOX(hbox),gtkl,FALSE,FALSE,2);
1073 	gtk_widget_show(gtkl);
1074 
1075 	gtke=gtk_entry_new();
1076 	g_signal_connect(gtke,"key-press-event",G_CALLBACK(Entry_Key_Pressed_CallBack),NULL);
1077 	gtk_box_pack_start(GTK_BOX(hbox),gtke,FALSE,FALSE,2);
1078 	gtk_widget_show(gtke);
1079 
1080 	/* create box to hold macro name entry box, and label */
1081 	hbox=gtk_hbox_new(FALSE,0);
1082 	gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),hbox);
1083 	gtk_widget_show(hbox);
1084 
1085 	gtkl=gtk_label_new(_("Macro Name:"));
1086 	gtk_box_pack_start(GTK_BOX(hbox),gtkl,FALSE,FALSE,2);
1087 	gtk_widget_show(gtkl);
1088 
1089 	gtke2=gtk_entry_new();
1090 	gtk_box_pack_start(GTK_BOX(hbox),gtke2,FALSE,FALSE,2);
1091 	gtk_widget_show(gtke2);
1092 
1093 	/* run dialog, and wait for box to be canceled or for user to press record.
1094 	 * Check to make sure you're not over-writing existing macro trigger or name
1095 	*/
1096 	while(iReply==GTK_RESPONSE_OK)
1097 	{
1098 		iReply=gtk_dialog_run(GTK_DIALOG(dialog));
1099 
1100 		if(iReply==GTK_RESPONSE_OK)
1101 		{
1102 			/* first check if any trigger has been defined, and don't go further until we have a
1103 			 * trigger
1104 			*/
1105 			if(RecordingMacro->keyval==0 && RecordingMacro->state==0)
1106 			{
1107 				dialogs_show_msgbox(GTK_MESSAGE_INFO,("You must define a key trigger combination"));
1108 				continue;
1109 			}
1110 
1111 			bReplaceName=FALSE;
1112 			bReplaceTrigger=FALSE;
1113 			m=FindMacroByName((gchar*)gtk_entry_get_text((GtkEntry*)gtke2));
1114 			/* check if macro name already exists */
1115 			if(m!=NULL)
1116 			{
1117 				if(bQueryOverwriteMacros==FALSE)
1118 					bReplaceName=TRUE;
1119 				else
1120 					bReplaceName=dialogs_show_question(
1121 						_("Macro name \"%s\"\n is already in use.\nReplace?"),
1122 						gtk_entry_get_text((GtkEntry*)gtke2));
1123 
1124 				/* don't want to replace so loop back to allow user to change or cancel */
1125 				if(bReplaceName==FALSE)
1126 					continue;
1127 			}
1128 
1129 			/* check if trigger key combination is already used */
1130 			m=FindMacroByKey(RecordingMacro->keyval,RecordingMacro->state);
1131 			if(m!=NULL)
1132 			{
1133 				if(bQueryOverwriteMacros==FALSE)
1134 					bReplaceTrigger=TRUE;
1135 				else
1136 					bReplaceTrigger=dialogs_show_question(
1137 						_("Macro trigger \"%s\"\n is already in use.\nReplace?"),
1138 						gtk_entry_get_text((GtkEntry*)gtke));
1139 
1140 				/* don't want to replace so loop back to allow user to change or cancel */
1141 				if(bReplaceTrigger==FALSE)
1142 					continue;
1143 			}
1144 
1145 			/* remove old macros. By now will definately want to replace either */
1146 			if(bReplaceName==TRUE)
1147 			{
1148 				m=FindMacroByName((gchar*)gtk_entry_get_text((GtkEntry*)gtke2));
1149 				RemoveMacroFromList(m);
1150 				FreeMacro(m);
1151 			}
1152 
1153 			if(bReplaceTrigger==TRUE)
1154 			{
1155 				m=FindMacroByKey(RecordingMacro->keyval,RecordingMacro->state);
1156 				RemoveMacroFromList(m);
1157 				FreeMacro(m);
1158 			}
1159 
1160 			/* record macro name, trigger keys already recorded */
1161 			RecordingMacro->name=g_strdup((gchar*)gtk_entry_get_text((GtkEntry*)gtke2));
1162 			/* break out of loop */
1163 			break;
1164 		}
1165 	}
1166 
1167 	/* tidy up */
1168 	gtk_widget_destroy(dialog);
1169 
1170 	/* clear macro details if not going to record a macro */
1171 	if(iReply!=GTK_RESPONSE_OK)
1172 		RecordingMacro=FreeMacro(RecordingMacro);
1173 
1174 	return (iReply==GTK_RESPONSE_OK);
1175 }
1176 
1177 
1178 /* function to start the macro recording process */
StartRecordingMacro(void)1179 static void StartRecordingMacro(void)
1180 {
1181 	/* start recording process, but quit if error, or user cancels */
1182 	if(!InitializeMacroRecord())
1183 		return;
1184 
1185 	/* start actual recording */
1186 	scintilla_send_message(document_get_current()->editor->sci,SCI_STARTRECORD,0,0);
1187 	gtk_widget_hide(Record_Macro_menu_item);
1188 	gtk_widget_show(Stop_Record_Macro_menu_item);
1189 }
1190 
1191 
1192 /* function to finish recording a macro */
StopRecordingMacro(void)1193 static void StopRecordingMacro(void)
1194 {
1195 	scintilla_send_message(document_get_current()->editor->sci,SCI_STOPRECORD,0,0);
1196 	/* Recorded in reverse as more efficient */
1197 	RecordingMacro->MacroEvents=g_slist_reverse(RecordingMacro->MacroEvents);
1198 	/* add macro to list */
1199 	AddMacroToList(RecordingMacro);
1200 	/* set ready to record new macro (don't free as macro has been saved in macrolist) */
1201 	RecordingMacro=NULL;
1202 	gtk_widget_show(Record_Macro_menu_item);
1203 	gtk_widget_hide(Stop_Record_Macro_menu_item);
1204 
1205 	/* Macros have been changed */
1206 	bMacrosHaveChanged=TRUE;
1207 }
1208 
1209 
1210 /* check to see if we are recording a macro and stop it if we are */
on_document_close(GObject * obj,GeanyDocument * doc,gpointer user_data)1211 static void on_document_close(GObject *obj, GeanyDocument *doc, gpointer user_data)
1212 {
1213 	if(RecordingMacro!=NULL)
1214 		StopRecordingMacro();
1215 }
1216 
1217 
1218 PluginCallback plugin_callbacks[] =
1219 {
1220 	{ "editor-notify", (GCallback) &Notification_Handler, FALSE, NULL },
1221 	{ "document-close", (GCallback) &on_document_close, FALSE, NULL },
1222 	{ NULL, NULL, FALSE, NULL }
1223 };
1224 
1225 
1226 /* handle starting and stopping macro recording */
DoMacroRecording(GtkMenuItem * menuitem,gpointer gdata)1227 static void DoMacroRecording(GtkMenuItem *menuitem, gpointer gdata)
1228 {
1229 	/* can't record if in an empty editor */
1230 	if(!DocumentPresent())
1231 		return;
1232 
1233 	if(RecordingMacro==NULL)
1234 		StartRecordingMacro();
1235 	else
1236 		StopRecordingMacro();
1237 }
1238 
1239 
1240 /* handle a change in a macro name in the edit macro dialog */
Name_Render_Edited_CallBack(GtkCellRendererText * cell,gchar * iter_id,gchar * new_text,gpointer data)1241 static void Name_Render_Edited_CallBack(GtkCellRendererText *cell,gchar *iter_id,gchar *new_text,
1242                                         gpointer data)
1243 {
1244 	GtkTreeView *treeview=(GtkTreeView *)data;
1245 	GtkTreeModel *model;
1246 	GtkTreeIter iter;
1247 	Macro *m,*mTemp;
1248 	GSList *gsl=mList;
1249 
1250 	/* Get the iterator */
1251 	model=gtk_tree_view_get_model(treeview);
1252 	gtk_tree_model_get_iter_from_string(model,&iter,iter_id);
1253 
1254 	/* get Macro for this line */
1255 	gtk_tree_model_get(model,&iter,2,&m,-1);
1256 
1257 	/* return if line is uneditable */
1258 	if(m==NULL)
1259 		return;
1260 
1261 	/* now check that no other macro is using this name */
1262 	while(gsl!=NULL)
1263 	{
1264 		mTemp=(Macro*)(gsl->data);
1265 		if(mTemp!=m && strcmp(new_text,mTemp->name)==0)
1266 			return;
1267 
1268 		gsl=g_slist_next(gsl);
1269 	}
1270 
1271 	/* set new name */
1272 	m->name=g_strdup(new_text);
1273 
1274 	/* Update the model */
1275 	gtk_list_store_set(GTK_LIST_STORE(model),&iter,0,new_text,-1);
1276 
1277 	bMacrosHaveChanged=TRUE;
1278 }
1279 
1280 
1281 /* handle a change in macro trigger accelerator key in the edit macro dialog */
Accel_Render_Edited_CallBack(GtkCellRendererAccel * cell,gchar * iter_id,guint key,GdkModifierType mods,guint keycode,gpointer data)1282 static void Accel_Render_Edited_CallBack(GtkCellRendererAccel *cell,gchar *iter_id,guint key,
1283                                          GdkModifierType mods,guint keycode,gpointer data)
1284 {
1285 	gchar *cTemp;
1286 	GtkTreeView *treeview=(GtkTreeView *)data;
1287 	GtkTreeModel *model;
1288 	GtkTreeIter iter;
1289 	Macro *m,*mTemp;
1290 	GSList *gsl=mList;
1291 
1292 	/* check if is useable accelerator */
1293 	if(UseableAccel(key,mods)==FALSE)
1294 		return;
1295 
1296 	/* Get the iterator */
1297 	model=gtk_tree_view_get_model(treeview);
1298 	gtk_tree_model_get_iter_from_string(model,&iter,iter_id);
1299 
1300 	/* get Macro for this line */
1301 	gtk_tree_model_get(model,&iter,2,&m,-1);
1302 
1303 	/* return if line is uneditable */
1304 	if(m==NULL)
1305 		return;
1306 
1307 	/* now check that no other macro is using this key combination */
1308 	while(gsl!=NULL)
1309 	{
1310 		mTemp=(Macro*)(gsl->data);
1311 		if(mTemp!=m && mTemp->keyval==key && mTemp->state==mods)
1312 			return;
1313 
1314 		gsl=g_slist_next(gsl);
1315 	}
1316 
1317 	/* set new trigger values */
1318 	m->keyval=key;
1319 	m->state=mods;
1320 
1321 	/* Update the model */
1322 	cTemp=GetPretyKeyName(key,mods);
1323 	gtk_list_store_set(GTK_LIST_STORE(model),&iter,1,cTemp,-1);
1324 	g_free(cTemp);
1325 
1326 	bMacrosHaveChanged=TRUE;
1327 }
1328 
1329 
1330 /* Get Search Description string with search text and flags at end*/
GetSearchDescription(gint message,gchar * text,gint flags)1331 static gchar * GetSearchDescription(gint message,gchar *text,gint flags)
1332 {
1333 	return g_strdup_printf(_("Search %s, looking for %s%s%s.%s%s%s%s%s"),
1334 		message==SCI_SEARCHNEXT?"forewards":"backwards",
1335 		text==NULL?"":"\"",
1336 		text==NULL?"clipboard contents":text,
1337 		text==NULL?"":"\"",
1338 		(flags&SCFIND_MATCHCASE)==SCFIND_MATCHCASE?" Match case.":"",
1339 		(flags&SCFIND_WHOLEWORD)==SCFIND_WHOLEWORD?" Match whole word.":"",
1340 		(flags&SCFIND_WORDSTART)==SCFIND_WORDSTART?" Match start of word.":"",
1341 		(flags&SCFIND_REGEXP)==SCFIND_REGEXP?" Search by Regular Expression.":"",
1342 		(flags&SCFIND_POSIX)==SCFIND_POSIX?" Regular Expression is POSIX.":"");
1343 }
1344 
1345 
1346 /* handle button presses in the preferences dialog box */
on_search_toggle(GtkToggleButton * cb,gpointer user_data)1347 static void on_search_toggle(GtkToggleButton *cb,gpointer user_data)
1348 {
1349 	GtkEntry *gtke;
1350 	GtkLabel *gtkl;
1351 	gboolean bUseClipboard;
1352 
1353 	/* retreive pointers to entry & label */
1354 	gtke=(GtkEntry*)(g_object_get_data(G_OBJECT(cb),"GeanyMacros_e"));
1355 	gtkl=(GtkLabel*)(g_object_get_data(G_OBJECT(cb),"GeanyMacros_l"));
1356 
1357 	/* find out what we're searching for */
1358 	bUseClipboard=gtk_toggle_button_get_active(cb);
1359 
1360 	/* set entry & label depending on if we're looking for text or not */
1361 	gtk_widget_set_sensitive((GtkWidget*)gtke,!bUseClipboard);
1362 	gtk_widget_set_sensitive((GtkWidget*)gtkl,!bUseClipboard);
1363 }
1364 
1365 
1366 /* Handle editing of options for search */
EditSearchOptions(GtkTreeModel * model,GtkTreeIter * iter)1367 static void EditSearchOptions(GtkTreeModel *model,GtkTreeIter *iter)
1368 {
1369 	GtkWidget *dialog,*gtke,*hbox,*gtkl;
1370 	gchar *cTemp,*cData,*cText,*cTemp2;
1371 	gint iReply=GTK_RESPONSE_OK,i;
1372 	GtkWidget *vbox,*gtkcb;
1373 	GtkWidget *cbA,*cbB,*cbC,*cbD,*cbE,*cbF;
1374 	MacroDetailEntry *mde;
1375 	gulong flags;
1376 
1377 	/* get MacroDetail and data for this line */
1378 	gtk_tree_model_get(model,iter,2,&mde,3,&cData,-1);
1379 
1380 	/* make cText point to search text */
1381 	cText=strchr(cData,',');
1382 	cText++;
1383 
1384 	/* get search flags */
1385 	flags=strtoll(cData,NULL,10);
1386 
1387 	/* create dialog box */
1388 	dialog=gtk_dialog_new_with_buttons(_("Search Options:"),
1389 	                                   GTK_WINDOW(geany->main_widgets->window),
1390 	                                   GTK_DIALOG_DESTROY_WITH_PARENT,NULL);
1391 
1392 	/* create buttons */
1393 	gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Ok"),GTK_RESPONSE_OK);
1394 	gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Cancel"),GTK_RESPONSE_CANCEL);
1395 
1396 	/* create box to hold widgets */
1397 	vbox=gtk_vbox_new(FALSE, 6);
1398 	gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),vbox);
1399 	gtk_widget_show(vbox);
1400 
1401 	/* create combobox to hold search direction */
1402 	gtkcb=gtk_combo_box_text_new();
1403 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gtkcb),_("Search Forwards"));
1404 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gtkcb),_("Search Backwards"));
1405 	gtk_combo_box_set_active(GTK_COMBO_BOX(gtkcb),(mde->message==SCI_SEARCHNEXT)?0:1);
1406 	gtk_box_pack_start(GTK_BOX(vbox),gtkcb,FALSE,FALSE,2);
1407 	gtk_widget_show(gtkcb);
1408 
1409 	/* create checkbox to check for search options */
1410 	cbA=gtk_check_button_new_with_label(_("Search for contents of clipboard"));
1411 	/* if search text is empty then to search for clipboard contents */
1412 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cbA),(*cText)==0);
1413 	gtk_box_pack_start(GTK_BOX(vbox),cbA,FALSE,FALSE,2);
1414 	/* ensure we monitor for change in this button */
1415 	g_signal_connect(cbA,"toggled",G_CALLBACK(on_search_toggle),dialog);
1416 	gtk_widget_show(cbA);
1417 
1418 
1419 	/* create box to hold search text entry box, and label */
1420 	hbox=gtk_hbox_new(FALSE,0);
1421 	gtk_box_pack_start(GTK_BOX(vbox),hbox,FALSE,FALSE,2);
1422 	gtk_widget_show(hbox);
1423 
1424 	gtkl=gtk_label_new(_("Search for:"));
1425 	gtk_box_pack_start(GTK_BOX(hbox),gtkl,FALSE,FALSE,2);
1426 	gtk_widget_show(gtkl);
1427 	/* save pointer to label */
1428 	g_object_set_data(G_OBJECT(cbA),"GeanyMacros_l",gtkl);
1429 	gtk_widget_set_sensitive((GtkWidget*)gtkl,(*cText)!=0);
1430 
1431 	gtke=gtk_entry_new();
1432 	if((*cText)!=0)
1433 		gtk_entry_set_text(GTK_ENTRY(gtke),cText);
1434 
1435 	gtk_box_pack_start(GTK_BOX(hbox),gtke,FALSE,FALSE,2);
1436 	gtk_widget_show(gtke);
1437 	/* save pointer to entry */
1438 	g_object_set_data(G_OBJECT(cbA),"GeanyMacros_e",gtke);
1439 	gtk_widget_set_sensitive((GtkWidget*)gtke,(*cText)!=0);
1440 
1441 	/* create checkbox to check for search options */
1442 	cbB=gtk_check_button_new_with_label(_("Search is case sensitive"));
1443 	/* if search text is empty then to search for clipboard contents */
1444 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cbB),(flags&SCFIND_MATCHCASE)==SCFIND_MATCHCASE);
1445 	gtk_box_pack_start(GTK_BOX(vbox),cbB,FALSE,FALSE,2);
1446 
1447 	cbC=gtk_check_button_new_with_label(_("Search for whole word"));
1448 	/* if search text is empty then to search for clipboard contents */
1449 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cbC),(flags&SCFIND_WHOLEWORD)==SCFIND_WHOLEWORD);
1450 	gtk_box_pack_start(GTK_BOX(vbox),cbC,FALSE,FALSE,2);
1451 
1452 	cbD=gtk_check_button_new_with_label(_("Search for start of word"));
1453 	/* if search text is empty then to search for clipboard contents */
1454 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cbD),(flags&SCFIND_WORDSTART)==SCFIND_WORDSTART);
1455 	gtk_box_pack_start(GTK_BOX(vbox),cbD,FALSE,FALSE,2);
1456 
1457 	cbE=gtk_check_button_new_with_label(_("Search text is regular expression"));
1458 	/* if search text is empty then to search for clipboard contents */
1459 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cbE),(flags&SCFIND_REGEXP)==SCFIND_REGEXP);
1460 	gtk_box_pack_start(GTK_BOX(vbox),cbE,FALSE,FALSE,2);
1461 
1462 	cbF=gtk_check_button_new_with_label(_("Search text is POSIX compatible"));
1463 	/* if search text is empty then to search for clipboard contents */
1464 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cbF),(flags&SCFIND_POSIX)==SCFIND_POSIX);
1465 	gtk_box_pack_start(GTK_BOX(vbox),cbF,FALSE,FALSE,2);
1466 
1467 	gtk_widget_show_all(vbox);
1468 
1469 	while(iReply==GTK_RESPONSE_OK)
1470 	{
1471 		iReply=gtk_dialog_run(GTK_DIALOG(dialog));
1472 
1473 		if(iReply==GTK_RESPONSE_OK)
1474 		{
1475 			/* handle change in options */
1476 
1477 			/* check search direction 0=foreward, 1=backwards */
1478 			iReply=gtk_combo_box_get_active((GtkComboBox*)gtkcb);
1479 
1480 			/* calculate macro detail of relavent detail */
1481 			i=0;
1482 			while(MacroDetails[i].message!=SCI_SEARCHNEXT) i++;
1483 			mde=(MacroDetailEntry *)(&MacroDetails[i+iReply]);
1484 
1485 			/* calculate flags */
1486 			flags=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cbB))?SCFIND_MATCHCASE:0;
1487 			flags|=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cbC))?SCFIND_WHOLEWORD:0;
1488 			flags|=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cbD))?SCFIND_WORDSTART:0;
1489 			flags|=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cbE))?SCFIND_REGEXP:0;
1490 			flags|=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cbF))?SCFIND_POSIX:0;
1491 
1492 			/* get search string or NULL if using clipboard */
1493 			cText=(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cbA)))?
1494 			           NULL:(gchar*)gtk_entry_get_text((GtkEntry*)(gtke));
1495 
1496 			/* get new data */
1497 			cData=g_strdup_printf("%lu,%s",flags,(cText==NULL)?"":cText);
1498 
1499 			/* get new text */
1500 			cText=GetSearchDescription(mde->message,cText,flags);
1501 
1502 			/* get old data for this line */
1503 			gtk_tree_model_get(model,iter,0,&cTemp,3,&cTemp2,-1);
1504 
1505 			/* set text and macro detail */
1506 			gtk_list_store_set(GTK_LIST_STORE(model),iter,0,cText,2,mde,3,cData,-1);
1507 
1508 			/* free up old text */
1509 			g_free(cTemp);
1510 			g_free(cTemp2);
1511 
1512 			/* break out of loop */
1513 			break;
1514 		}
1515 
1516 	}
1517 
1518 	/* tidy up */
1519 	gtk_widget_destroy(dialog);
1520 }
1521 
1522 
1523 /* Handle editing of text for SCI_REPLACESEL */
EditSCIREPLACESELText(GtkTreeModel * model,GtkTreeIter * iter)1524 static void EditSCIREPLACESELText(GtkTreeModel *model,GtkTreeIter *iter)
1525 {
1526 	GtkWidget *dialog,*gtke,*hbox,*gtkl;
1527 	gchar *cTemp,*cTemp2;
1528 	gint iReply=GTK_RESPONSE_OK;
1529 
1530 	/* create dialog box */
1531 	dialog=gtk_dialog_new_with_buttons(_("Edit Insert/Replace Text"),
1532 	                                   GTK_WINDOW(geany->main_widgets->window),
1533 	                                   GTK_DIALOG_DESTROY_WITH_PARENT,NULL);
1534 
1535 	/* create buttons */
1536 	gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Ok"),GTK_RESPONSE_OK);
1537 	gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Cancel"),GTK_RESPONSE_CANCEL);
1538 
1539 	/* create box to hold macro name entry box, and label */
1540 	hbox=gtk_hbox_new(FALSE,0);
1541 	gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),hbox);
1542 	gtk_widget_show(hbox);
1543 
1544 	gtkl=gtk_label_new(_("Text:"));
1545 	gtk_box_pack_start(GTK_BOX(hbox),gtkl,FALSE,FALSE,2);
1546 	gtk_widget_show(gtkl);
1547 
1548 	gtke=gtk_entry_new();
1549 	gtk_tree_model_get(model,iter,3,&cTemp,-1);
1550 	if(cTemp!=NULL)
1551 		gtk_entry_set_text(GTK_ENTRY(gtke),cTemp);
1552 
1553 	gtk_box_pack_start(GTK_BOX(hbox),gtke,FALSE,FALSE,2);
1554 	gtk_widget_show(gtke);
1555 
1556 	while(iReply==GTK_RESPONSE_OK)
1557 	{
1558 		iReply=gtk_dialog_run(GTK_DIALOG(dialog));
1559 
1560 		if(iReply==GTK_RESPONSE_OK)
1561 		{
1562 			/* handle change in text */
1563 
1564 			/* first free old text */
1565 			gtk_tree_model_get(model,iter,3,&cTemp,-1);
1566 			g_free(cTemp);
1567 
1568 			/* get new text */
1569 			cTemp=g_strdup((gchar*)gtk_entry_get_text((GtkEntry*)(gtke)));
1570 			cTemp2=g_strdup_printf(_("Insert/replace with \"%s\""),cTemp);
1571 
1572 			/* set text */
1573 			gtk_list_store_set(GTK_LIST_STORE(model),iter,0,cTemp2,3,cTemp,-1);
1574 
1575 			g_free(cTemp2);
1576 
1577 			/* break out of loop */
1578 			break;
1579 		}
1580 
1581 	}
1582 
1583 	/* tidy up */
1584 	gtk_widget_destroy(dialog);
1585 }
1586 
1587 
1588 /* handle change in macro event in macro editor */
combo_edited(GtkCellRendererText * cell,gchar * iter_id,gchar * new_text,gpointer data)1589 static void combo_edited(GtkCellRendererText *cell,gchar *iter_id,gchar *new_text,gpointer data)
1590 {
1591 	GtkTreeView *treeview=(GtkTreeView *)data;
1592 	GtkTreeModel *model;
1593 	GtkTreeIter iter;
1594 	MacroDetailEntry *mde;
1595 	gint i;
1596 	gchar *cTemp,*cTemp2;
1597 	gboolean bNeedButtonUpdate=FALSE;
1598 
1599 	/* find MacroDetails that has the setting of new setting */
1600 	i=0;
1601 	while(strcmp(_(MacroDetails[i].description),new_text)!=0)
1602 		i++;
1603 
1604 	/* Get the iterator for treeview*/
1605 	model=gtk_tree_view_get_model(treeview);
1606 	gtk_tree_model_get_iter_from_string(model,&iter,iter_id);
1607 
1608 	/* get MacroDetail and any additional string for this line */
1609 	gtk_tree_model_get(model,&iter,0,&cTemp2,2,&mde,3,&cTemp,-1);
1610 
1611 	/* handle freeing of string if needed */
1612 	g_free(cTemp);
1613 	if(mde->message==SCI_REPLACESEL ||
1614 	   mde->message==SCI_SEARCHNEXT ||
1615 	   mde->message==SCI_SEARCHPREV)
1616 	{
1617 		g_free(cTemp2);
1618 		bNeedButtonUpdate=TRUE;
1619 	}
1620 
1621 	/* see what text will have to change into */
1622 	cTemp2=NULL;
1623 	if(MacroDetails[i].message==SCI_REPLACESEL)
1624 	{
1625 		cTemp=g_strdup_printf(_("Insert/replace with \"\""));
1626 		bNeedButtonUpdate=TRUE;
1627 	}
1628 	else if(MacroDetails[i].message==SCI_SEARCHNEXT ||
1629 	   MacroDetails[i].message==SCI_SEARCHPREV)
1630 	{
1631 		cTemp=GetSearchDescription(MacroDetails[i].message,NULL,0);
1632 		cTemp2=g_strdup("0,");
1633 		bNeedButtonUpdate=TRUE;
1634 	}
1635 	else
1636 		cTemp=g_strdup(_(MacroDetails[i].description));
1637 
1638 	/* Update the model */
1639 	gtk_list_store_set(GTK_LIST_STORE(model),&iter,0,cTemp,2,&(MacroDetails[i]),3,cTemp2,-1);
1640 
1641 	g_free(cTemp);
1642 
1643 	/* check if changing to or from SCI_REPLACESEL and enable/disable edit button as needed */
1644 	if(bNeedButtonUpdate)
1645 		g_signal_emit_by_name(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)),"changed",
1646 		                      G_TYPE_NONE);
1647 }
1648 
1649 
1650 /* handle change in selection in DoEditMacro list */
DoEditMacroElementsSelectionChanged(GtkTreeSelection * selection,gpointer data)1651 static void DoEditMacroElementsSelectionChanged(GtkTreeSelection *selection,gpointer data)
1652 {
1653 	GtkTreeModel *model;
1654 	GtkTreeIter iter,iter2;
1655 	MacroDetailEntry *mde;
1656 	GtkWidget *button;
1657 	GtkDialog *dialog;
1658 	GtkTreePath *tpTemp;
1659 
1660 	dialog=GTK_DIALOG(data);
1661 
1662 	/* check if have item selected & adjust buttons acordingly */
1663 	if(gtk_tree_selection_get_selected(selection,&model,&iter))
1664 	{
1665 		/* get details about this node */
1666 		gtk_tree_model_get(GTK_TREE_MODEL(model),&iter,2,&mde,-1);
1667 
1668 		/* find delete button & enable it*/
1669 		button=(GtkWidget*)(g_object_get_data(G_OBJECT(dialog),"GeanyMacros_bD"));
1670 		gtk_widget_set_sensitive(button,TRUE);
1671 
1672 		/* find edit text button & enable it if looking at a SCI_REPLACESEL item*/
1673 		button=(GtkWidget*)(g_object_get_data(G_OBJECT(dialog),"GeanyMacros_bC"));
1674 		gtk_widget_set_sensitive(button,mde->message==SCI_REPLACESEL ||
1675 		                                mde->message==SCI_SEARCHNEXT ||
1676 		                                mde->message==SCI_SEARCHPREV);
1677 
1678 		/* get copy of iteraton */
1679 		iter2=iter;
1680 		/* if can move to next node then it's not the last. use to set Move down */
1681 		button=(GtkWidget*)(g_object_get_data(G_OBJECT(dialog),"GeanyMacros_bB"));
1682 		gtk_widget_set_sensitive(button,gtk_tree_model_iter_next(GTK_TREE_MODEL(model),&iter2));
1683 
1684 		/* find Move up button & enable/disable it */
1685 		button=(GtkWidget*)(g_object_get_data(G_OBJECT(dialog),"GeanyMacros_bA"));
1686 		/* get the path of the current selected line */
1687 		tpTemp=gtk_tree_model_get_path(GTK_TREE_MODEL(model),&iter);
1688 		/* if has previous then can be moved up a line */
1689 		gtk_widget_set_sensitive(button,gtk_tree_path_prev(tpTemp));
1690 		/* free Tree path */
1691 		gtk_tree_path_free(tpTemp);
1692 	}
1693 	/* no item selected */
1694 	else
1695 	{
1696 		/* find delete button & diable it*/
1697 		button=(GtkWidget*)(g_object_get_data(G_OBJECT(dialog),"GeanyMacros_bD"));
1698 		gtk_widget_set_sensitive(button,FALSE);
1699 
1700 		/* find edit text button & diable it*/
1701 		button=(GtkWidget*)(g_object_get_data(G_OBJECT(dialog),"GeanyMacros_bC"));
1702 		gtk_widget_set_sensitive(button,FALSE);
1703 
1704 		/* find Move up button & diable it*/
1705 		button=(GtkWidget*)(g_object_get_data(G_OBJECT(dialog),"GeanyMacros_bA"));
1706 		gtk_widget_set_sensitive(button,FALSE);
1707 
1708 		/* find Move Down button & diable it*/
1709 		button=(GtkWidget*)(g_object_get_data(G_OBJECT(dialog),"GeanyMacros_bB"));
1710 		gtk_widget_set_sensitive(button,FALSE);
1711 	}
1712 
1713 }
1714 
1715 
1716 /* edit individual existing macro */
EditMacroElements(Macro * m)1717 static void EditMacroElements(Macro *m)
1718 {
1719 	GtkWidget *table,*dialog,*button;
1720 	GtkTreeViewColumn *column;
1721 	GtkCellRenderer *renderer;
1722 	GtkTreeSelection *selection;
1723 	GtkTreeIter iter,iterNew;
1724 	GtkListStore *ls;
1725 	GtkTreePath *gtkPath;
1726 	gint i;
1727 	GSList *gsl=m->MacroEvents;
1728 	gchar *cTitle,*cTemp,*cTemp2;
1729 	MacroEvent *me;
1730 	GtkListStore *lsCombo;
1731 	MacroDetailEntry *mde;
1732 	gboolean bHaveIter;
1733 
1734 	/* create dialog box */
1735 	cTitle=g_strdup_printf(_("Edit: %s"),m->name);
1736 	dialog=gtk_dialog_new_with_buttons(cTitle,GTK_WINDOW(geany->main_widgets->window),
1737 		GTK_DIALOG_DESTROY_WITH_PARENT,NULL);
1738 
1739 	/* create store to hold table data (2nd column holds number of macro value)
1740 	*/
1741 	ls=gtk_list_store_new(4,G_TYPE_STRING,G_TYPE_UINT,G_TYPE_POINTER,G_TYPE_POINTER);
1742 
1743 	while(gsl!=NULL)
1744 	{
1745  		me=(MacroEvent*)(gsl->data);
1746 		i=0;
1747 		while(MacroDetails[i].description!=NULL)
1748 		{
1749 			if(MacroDetails[i].message==me->message) break;
1750 			i++;
1751 		}
1752 
1753 		gtk_list_store_append(ls,&iter);  /*  Acquire an iterator */
1754 		/* set text, pointer to macro detail, and any ascociated string */
1755 		cTemp2=NULL;
1756 		if(me->message==SCI_REPLACESEL)
1757 		{
1758 			cTemp=g_strdup_printf(_("Insert/replace with \"%s\""),
1759 			                      (gchar*)(me->lparam));
1760 			cTemp2=g_strdup((gchar*)(me->lparam));
1761 		}
1762 		else if(MacroDetails[i].message==SCI_SEARCHNEXT ||
1763 		        MacroDetails[i].message==SCI_SEARCHPREV)
1764 		{
1765 			cTemp=GetSearchDescription(MacroDetails[i].message,(gchar*)(me->lparam),
1766 			                           me->wparam);
1767 			cTemp2=g_strdup_printf("%lu,%s",me->wparam,((gchar*)(me->lparam)==NULL)?
1768 			                       "":((gchar*)(me->lparam)));
1769 		}
1770 		else
1771 			cTemp=g_strdup(_(MacroDetails[i].description));
1772 
1773 		gtk_list_store_set(ls,&iter,0,cTemp,2,&(MacroDetails[i]),3,cTemp2,-1);
1774 		gsl=g_slist_next(gsl);
1775 
1776 		g_free(cTemp);
1777 	}
1778 
1779 	/* create list store for combo renderer */
1780 	lsCombo=gtk_list_store_new(2,G_TYPE_STRING,G_TYPE_POINTER);
1781 	i=0;
1782 	while(MacroDetails[i].description!=NULL)
1783 	{
1784 		gtk_list_store_append(lsCombo,&iter);
1785 		gtk_list_store_set(lsCombo,&iter,0,_(MacroDetails[i].description),1,
1786 		                   &(MacroDetails[i]),-1);
1787 		i++;
1788 	}
1789 
1790 	/* create combo renderer */
1791 	renderer=gtk_cell_renderer_combo_new();
1792 
1793 	/* add list store */
1794 	g_object_set(renderer,"model",lsCombo,"editable",TRUE,"has-entry",FALSE,NULL);
1795 
1796 	/* create table */
1797 	table=gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls));
1798 	gtk_tree_view_set_grid_lines(GTK_TREE_VIEW(table),GTK_TREE_VIEW_GRID_LINES_BOTH);
1799 
1800 	/* add column */
1801 	column=gtk_tree_view_column_new_with_attributes(_("Event"),renderer,"text",0,"text-column"
1802 	                                                ,1,NULL);
1803 	g_signal_connect(renderer,"edited",G_CALLBACK(combo_edited),table);
1804 	gtk_tree_view_append_column(GTK_TREE_VIEW(table),column);
1805 
1806 	/* set selection mode */
1807 	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(table)),
1808 	                            GTK_SELECTION_SINGLE);
1809 
1810 	/* add table to dialog */
1811 	gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),table);
1812 /*	gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),table,FALSE,FALSE,2);*/
1813 	gtk_widget_show(table);
1814 
1815 	/* add buttons */
1816 	button=gtk_dialog_add_button(GTK_DIALOG(dialog),_("Move _Up"),GEANY_MACRO_BUTTON_UP);
1817 	g_object_set_data(G_OBJECT(dialog),"GeanyMacros_bA",button);
1818 	button=gtk_dialog_add_button(GTK_DIALOG(dialog),_("Move Do_wn"),GEANY_MACRO_BUTTON_DOWN);
1819 	g_object_set_data(G_OBJECT(dialog),"GeanyMacros_bB",button);
1820 	gtk_dialog_add_button(GTK_DIALOG(dialog),_("New _Above"),GEANY_MACRO_BUTTON_ABOVE);
1821 	gtk_dialog_add_button(GTK_DIALOG(dialog),_("New _Below"),GEANY_MACRO_BUTTON_BELOW);
1822 	button=gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Edit"),GEANY_MACRO_BUTTON_EDIT);
1823 	g_object_set_data(G_OBJECT(dialog),"GeanyMacros_bC",button);
1824 	button=gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Delete"),GEANY_MACRO_BUTTON_DELETE);
1825 	g_object_set_data(G_OBJECT(dialog),"GeanyMacros_bD",button);
1826 	gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Ok"),GEANY_MACRO_BUTTON_APPLY);
1827 	gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Cancel"),GEANY_MACRO_BUTTON_CANCEL);
1828 
1829 	/* listen for changes in selection */
1830 	selection=gtk_tree_view_get_selection(GTK_TREE_VIEW(table));
1831 	g_signal_connect(G_OBJECT(selection),"changed",
1832 	                 G_CALLBACK(DoEditMacroElementsSelectionChanged),dialog);
1833 
1834 	/* call callback: this will set buttons acordingly */
1835 	DoEditMacroElementsSelectionChanged(selection,dialog);
1836 
1837 	i=GTK_RESPONSE_REJECT;
1838 	while(i!=GEANY_MACRO_BUTTON_CANCEL)
1839 	{
1840 		/* wait for button to be pressed */
1841 		i=gtk_dialog_run(GTK_DIALOG(dialog));
1842 
1843 		/* exit if cancel pressed */
1844 		if(i==GEANY_MACRO_BUTTON_CANCEL)
1845 			break;
1846 
1847 		/* check if line has been selected */
1848 		if(gtk_tree_selection_get_selected(selection,NULL,&iter))
1849 		{
1850 			/* handle delete element */
1851 			if(i==GEANY_MACRO_BUTTON_DELETE)
1852 			{
1853 				/* see if need to free non-static string */
1854 				gtk_tree_model_get(GTK_TREE_MODEL(ls),&iter,0,&cTemp,2,&mde,3,&cTemp2,-1);
1855 				if(mde->message==SCI_REPLACESEL ||
1856 				   mde->message==SCI_SEARCHNEXT ||
1857 				   mde->message==SCI_SEARCHPREV)
1858 				{
1859 					g_free(cTemp);
1860 					g_free(cTemp2);
1861 				}
1862 
1863 				/* remove element */
1864 				gtk_list_store_remove(ls,&iter);
1865 
1866 				/* call callback: this will update buttons acordingly */
1867 				DoEditMacroElementsSelectionChanged(selection,dialog);
1868 			}
1869 
1870 			/* handle moving element up before element previous to it */
1871 			if(i==GEANY_MACRO_BUTTON_UP)
1872 			{
1873 				/* get path of selected iteration */
1874 				gtkPath=gtk_tree_model_get_path(GTK_TREE_MODEL(ls),&iter);
1875 
1876 				/* move path to previous node & if sucessful try swapping */
1877 				if(gtk_tree_path_prev(gtkPath)==TRUE)
1878 				{
1879 					gtk_tree_model_get_iter(GTK_TREE_MODEL(ls),&iterNew,gtkPath);
1880 					gtk_list_store_swap(ls,&iter,&iterNew);
1881 				}
1882 
1883 				/* free path of selected iteration */
1884 				gtk_tree_path_free(gtkPath);
1885 
1886 				/* call callback: this will update buttons acordingly */
1887 				DoEditMacroElementsSelectionChanged(selection,dialog);
1888 			}
1889 
1890 			/* handle move element to after element after it */
1891 			if(i==GEANY_MACRO_BUTTON_DOWN)
1892 			{
1893 				/* create copy of curent position */
1894 				iterNew=iter;
1895 
1896 				/* if there is a node after the current node then swap */
1897 				if(gtk_tree_model_iter_next(GTK_TREE_MODEL(ls),&iterNew)==TRUE)
1898 					gtk_list_store_swap(ls,&iter,&iterNew);
1899 
1900 				/* call callback: this will update buttons acordingly */
1901 				DoEditMacroElementsSelectionChanged(selection,dialog);
1902 			}
1903 
1904 			/* handle insert new element before curent element */
1905 			if(i==GEANY_MACRO_BUTTON_ABOVE)
1906 			{
1907 				gtk_list_store_append(ls,&iterNew);
1908 				gtk_list_store_set(ls,&iterNew,0,_(MacroDetails[0].description),2,&(MacroDetails[0]),3,NULL,
1909 				                   -1);
1910 				gtk_list_store_move_before(ls,&iterNew,&iter);
1911 
1912 				/* call callback: this will update buttons acordingly */
1913 				DoEditMacroElementsSelectionChanged(selection,dialog);
1914 			}
1915 
1916 			/* handle insert new element after curent element */
1917 			if(i==GEANY_MACRO_BUTTON_BELOW)
1918 			{
1919 				gtk_list_store_append(ls,&iterNew);
1920 				gtk_list_store_set(ls,&iterNew,0,_(MacroDetails[0].description),2,&(MacroDetails[0]),3,NULL,
1921 				                   -1);
1922 				gtk_list_store_move_after(ls,&iterNew,&iter);
1923 
1924 				/* call callback: this will update buttons acordingly */
1925 				DoEditMacroElementsSelectionChanged(selection,dialog);
1926 			}
1927 
1928 			/* handle editing SCI_REPLACESEL text */
1929 			if(i==GEANY_MACRO_BUTTON_EDIT)
1930 			{
1931 				gtk_tree_model_get(GTK_TREE_MODEL(ls),&iter,2,&mde,-1);
1932 				if(mde->message==SCI_REPLACESEL)
1933 					EditSCIREPLACESELText(GTK_TREE_MODEL(ls),&iter);
1934 				else if(mde->message==SCI_SEARCHNEXT || mde->message==SCI_SEARCHPREV)
1935 					EditSearchOptions(GTK_TREE_MODEL(ls),&iter);
1936 
1937 			}
1938 
1939 		} /* end of commands that require line to be selected */
1940 		/* if no elements to insert above or below,just insert new element */
1941 		else if((i==GEANY_MACRO_BUTTON_ABOVE || i==GEANY_MACRO_BUTTON_BELOW) &&
1942 		        gtk_tree_model_iter_n_children(GTK_TREE_MODEL(ls),NULL)==0)
1943 		{
1944 			gtk_list_store_append(ls,&iterNew);
1945 			gtk_list_store_set(ls,&iterNew,0,_(MacroDetails[0].description),2,&(MacroDetails[0]),3,NULL,-1);
1946 
1947 			/* call callback: this will update buttons acordingly */
1948 			DoEditMacroElementsSelectionChanged(selection,dialog);
1949 		}
1950 
1951 
1952 		/* user has hit apply button. check for changes & implement */
1953 		if(i==GEANY_MACRO_BUTTON_APPLY)
1954 		{
1955 			/* clear old macro */
1956 			m->MacroEvents=ClearMacroList(m->MacroEvents);
1957 
1958 			/* go through list adding macro events */
1959 			bHaveIter=gtk_tree_model_get_iter_first(GTK_TREE_MODEL(ls),&iter);
1960 			while(bHaveIter)
1961 			{
1962 				/* get Macro event for this line */
1963 				gtk_tree_model_get(GTK_TREE_MODEL(ls),&iter,2,&mde,3,&cTemp,-1);
1964 
1965 				/* create new macro event */
1966 				me=g_new0(MacroEvent,1);
1967 
1968 				me->message=mde->message;
1969 				me->lparam=0;
1970 				me->wparam=0;
1971 
1972 				/* Special handling for text inserting, duplicate inserted string */
1973 				if(me->message==SCI_REPLACESEL)
1974 					me->lparam=(sptr_t)((cTemp!=NULL)?g_strdup(cTemp):g_strdup(""));
1975 
1976 				/* Special handling for search */
1977 				if(me->message==SCI_SEARCHNEXT || me->message==SCI_SEARCHPREV)
1978 				{
1979 					cTemp2=strchr(cTemp,',');
1980 					cTemp2++;
1981 
1982 					me->lparam=(sptr_t)(((*cTemp2)==0)?NULL:g_strdup(cTemp2));
1983 					me->wparam=strtoll(cTemp,NULL,10);
1984 				}
1985 
1986 				/* more efficient to create reverse list and reverse it at the end */
1987 				m->MacroEvents=g_slist_prepend(m->MacroEvents,me);
1988 
1989 				/* get next event */
1990 				bHaveIter=gtk_tree_model_iter_next(GTK_TREE_MODEL(ls),&iter);
1991 			}
1992 
1993 			/* was more efficient to record in reverse direction, so now reverse */
1994 			m->MacroEvents=g_slist_reverse(m->MacroEvents);
1995 
1996 			break;
1997 		}
1998 
1999 	} /* end look responding to dialog buttons */
2000 
2001 	/* clear any memory allocated for strings */
2002 	bHaveIter=gtk_tree_model_get_iter_first(GTK_TREE_MODEL(ls),&iter);
2003 	while(bHaveIter)
2004 	{
2005 		/* get Macro event for this line */
2006 		gtk_tree_model_get(GTK_TREE_MODEL(ls),&iter,0,&cTemp2,2,&mde,3,&cTemp,-1);
2007 
2008 		/* free any non-static text */
2009 		g_free((void*)(cTemp));
2010 		if(mde->message==SCI_REPLACESEL || mde->message==SCI_SEARCHNEXT ||
2011 		   mde->message==SCI_SEARCHPREV)
2012 			g_free(cTemp2);
2013 
2014 		/* get next event */
2015 		bHaveIter=gtk_tree_model_iter_next(GTK_TREE_MODEL(ls),&iter);
2016 	}
2017 
2018 	/* tidy up */
2019 	gtk_widget_destroy(dialog);
2020 
2021 	/* free memory */
2022 	g_free(cTitle);
2023 }
2024 
2025 
2026 /* handle change in selection in DoEditMacro list */
DoEditMacroSelectionChanged(GtkTreeSelection * selection,gpointer data)2027 static void DoEditMacroSelectionChanged(GtkTreeSelection *selection,gpointer data)
2028 {
2029 	GtkTreeModel *model;
2030 	GtkTreeIter iter;
2031 	GtkWidget *button;
2032 	GtkDialog *dialog;
2033 	gboolean bHasItemSelected;
2034 
2035 	dialog=GTK_DIALOG(data);
2036 
2037 	/* find out if anything has been selected */
2038 	bHasItemSelected=gtk_tree_selection_get_selected(selection,&model,&iter);
2039 
2040 	/* now set button sensitive or not depending if there is something for them to act on */
2041 	button=(GtkWidget*)(g_object_get_data(G_OBJECT(dialog),"GeanyMacros_bA"));
2042 	gtk_widget_set_sensitive(button,bHasItemSelected);
2043 	button=(GtkWidget*)(g_object_get_data(G_OBJECT(dialog),"GeanyMacros_bB"));
2044 	gtk_widget_set_sensitive(button,bHasItemSelected);
2045 	button=(GtkWidget*)(g_object_get_data(G_OBJECT(dialog),"GeanyMacros_bC"));
2046 	gtk_widget_set_sensitive(button,bHasItemSelected);
2047 }
2048 
2049 
2050 /* do editing of existing macros */
DoEditMacro(GtkMenuItem * menuitem,gpointer gdata)2051 static void DoEditMacro(GtkMenuItem *menuitem, gpointer gdata)
2052 {
2053 	GtkWidget *table,*dialog,*button;
2054 	GtkTreeViewColumn *column;
2055 	GtkCellRenderer *renderer;
2056 	GtkTreeSelection *selection;
2057 	GtkTreeIter iter;
2058 	GtkListStore *ls;
2059 	gint i;
2060 	GSList *gsl=mList;
2061 	Macro *m;
2062 	gchar *cTemp;
2063 
2064 	/* create dialog box */
2065 	dialog=gtk_dialog_new_with_buttons(_("Edit Macros"),GTK_WINDOW(geany->main_widgets->window),
2066 		GTK_DIALOG_DESTROY_WITH_PARENT,NULL);
2067 
2068 	/* create store to hold table data (3rd column holds pointer to macro or NULL if not editable)
2069 	*/
2070 	ls=gtk_list_store_new(3,G_TYPE_STRING,G_TYPE_STRING,G_TYPE_POINTER);
2071 
2072 	/* add data to store either empty line, or list of macros */
2073 	if(gsl==NULL)
2074 	{
2075 		gtk_list_store_append(ls,&iter);  /* Acquire an iterator  */
2076 		gtk_list_store_set(ls,&iter,0,"",1,"",2,NULL,-1);
2077 	}
2078 
2079 	while(gsl!=NULL)
2080 	{
2081 		gtk_list_store_append(ls,&iter);  /*  Acquire an iterator */
2082 		m=(Macro*)(gsl->data);
2083 		cTemp=GetPretyKeyName(m->keyval,m->state);
2084 		gtk_list_store_set(ls,&iter,0,m->name,1,cTemp,2,m,-1);
2085 		g_free(cTemp);
2086 		gsl=g_slist_next(gsl);
2087 	}
2088 
2089 	/* create table */
2090 	table=gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls));
2091 	gtk_tree_view_set_grid_lines(GTK_TREE_VIEW(table),GTK_TREE_VIEW_GRID_LINES_BOTH);
2092 
2093 	/* add columns */
2094 	renderer=gtk_cell_renderer_text_new();
2095 	column=gtk_tree_view_column_new_with_attributes(_("Macro Name"),renderer,"text",0,NULL);
2096 	g_signal_connect(renderer,"edited",G_CALLBACK(Name_Render_Edited_CallBack),table);
2097 	g_object_set(renderer,"editable",TRUE,NULL);
2098 	gtk_tree_view_append_column(GTK_TREE_VIEW(table),column);
2099 
2100 	renderer= gtk_cell_renderer_accel_new();
2101 	column=gtk_tree_view_column_new_with_attributes(_("Key Trigger"),renderer,"text",1,NULL);
2102 	g_signal_connect(renderer,"accel-edited",G_CALLBACK(Accel_Render_Edited_CallBack),table);
2103 	g_object_set(renderer,"editable",TRUE,NULL);
2104 
2105 	gtk_tree_view_append_column(GTK_TREE_VIEW(table),column);
2106 
2107 	/* set selection mode */
2108 	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(table)),
2109 	                            GTK_SELECTION_SINGLE);
2110 
2111 	/* add table to dialog */
2112 	gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),table,FALSE,FALSE,2);
2113 	gtk_widget_show(table);
2114 
2115 	/* add buttons */
2116 	button=gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Re-Record"),GEANY_MACRO_BUTTON_RERECORD);
2117 	g_object_set_data(G_OBJECT(dialog),"GeanyMacros_bA",button);
2118 	button=gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Edit"),GEANY_MACRO_BUTTON_EDIT);
2119 	g_object_set_data(G_OBJECT(dialog),"GeanyMacros_bB",button);
2120 	button=gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Delete"),GEANY_MACRO_BUTTON_DELETE);
2121 	g_object_set_data(G_OBJECT(dialog),"GeanyMacros_bC",button);
2122 	gtk_dialog_add_button(GTK_DIALOG(dialog),_("_Ok"),GEANY_MACRO_BUTTON_CANCEL);
2123 
2124 	/* listen for changes in selection */
2125 	selection=gtk_tree_view_get_selection(GTK_TREE_VIEW(table));
2126 	g_signal_connect(G_OBJECT(selection),"changed",G_CALLBACK(DoEditMacroSelectionChanged),dialog);
2127 
2128 	/* call callback: this will set buttons acordingly */
2129 	DoEditMacroSelectionChanged(selection,dialog);
2130 
2131 	i=GTK_RESPONSE_REJECT;
2132 	while(i==GEANY_MACRO_BUTTON_RERECORD || i==GEANY_MACRO_BUTTON_EDIT ||
2133 	      i==GEANY_MACRO_BUTTON_DELETE || i==GTK_RESPONSE_REJECT)
2134 	{
2135 		/* wait for button to be pressed */
2136 		i=gtk_dialog_run(GTK_DIALOG(dialog));
2137 
2138 		/* exit if not doing any action */
2139 		if(i!=GEANY_MACRO_BUTTON_RERECORD && i!=GEANY_MACRO_BUTTON_EDIT && i!=GEANY_MACRO_BUTTON_DELETE)
2140 			break;
2141 
2142 		/* check if line has been selected */
2143 		if(gtk_tree_selection_get_selected(selection,NULL,&iter))
2144 		{
2145 			/* get macro name */
2146 			gtk_tree_model_get(GTK_TREE_MODEL(ls),&iter,2,&m,-1);
2147 			/* handle delete macro */
2148 			if(i==GEANY_MACRO_BUTTON_DELETE && m)
2149 			{
2150 				/* remove from table */
2151 				gtk_list_store_remove(GTK_LIST_STORE(ls),&iter);
2152 				/* remove macro */
2153 				RemoveMacroFromList(m);
2154 				FreeMacro(m);
2155 				/* Signal that macros have changed (and need to be saved) */
2156 				bMacrosHaveChanged=TRUE;
2157 
2158 				/* call callback: this will update buttons acordingly */
2159 				DoEditMacroElementsSelectionChanged(selection,dialog);
2160 			}
2161 
2162 			/* handle re-record macro */
2163 			if(i==GEANY_MACRO_BUTTON_RERECORD && m && DocumentPresent())
2164 			{
2165 				/* ensure have empty recording macro */
2166 				FreeMacro(RecordingMacro);
2167 				RecordingMacro=CreateMacro();
2168 				/* set values */
2169 				RecordingMacro->keyval=m->keyval;
2170 				RecordingMacro->state=m->state;
2171 				RecordingMacro->name=g_strdup(m->name);
2172 				/* remove existing macro (so newly recorded one takes it's place) */
2173 				RemoveMacroFromList(m);
2174 				FreeMacro(m);
2175 				/* start actual recording */
2176 				scintilla_send_message(document_get_current()->editor->sci,SCI_STARTRECORD,0,0);
2177 				gtk_widget_hide(Record_Macro_menu_item);
2178 				gtk_widget_show(Stop_Record_Macro_menu_item);
2179 
2180 				break;
2181 			}
2182 
2183 			/* handle edit macro */
2184 			if(i==GEANY_MACRO_BUTTON_EDIT && m)
2185 			{
2186 				EditMacroElements(m);
2187 				/* Signal that macros have changed (and need to be saved) */
2188 				bMacrosHaveChanged=TRUE;
2189 			}
2190 		}
2191 
2192 	}
2193 
2194 	gtk_widget_destroy(dialog);
2195 }
2196 
2197 
2198 /* set up this plugin */
plugin_init(GeanyData * data)2199 void plugin_init(GeanyData *data)
2200 {
2201 	gint i,k,iResults=0;
2202 	GdkKeymapKey *gdkkmkResults;
2203 	GdkKeymap *gdkKeyMap=gdk_keymap_get_default();
2204 
2205 	/* Load settings */
2206 	LoadSettings();
2207 
2208 	/* Calculate what shift '0' to '9 will be (£ is above 3 on uk keyboard, but it's # or ~ on us
2209 	 * keyboard.)
2210 	 * there must be an easier way than this of working this out, but I've not figured it out.
2211 	 * This is needed to play nicely with the Geany Numbered Bookmarks plugin
2212 	*/
2213 
2214 	/* go through '0' to '9', work out hardware keycode, then find out what shift+this keycode
2215 	 * results in
2216 	*/
2217 	for(i=0;i<10;i++)
2218 	{
2219 		/* Get keymapkey data for number key */
2220 		k=gdk_keymap_get_entries_for_keyval(gdkKeyMap,'0'+i,&gdkkmkResults,&iResults);
2221 		/* error retrieving hardware keycode, so leave as standard uk character for shift + number */
2222 		if(k==0)
2223 			continue;
2224 
2225 		/* unsure, just in case it does return 0 results but reserve memory  */
2226 		if(iResults==0)
2227 		{
2228 			g_free(gdkkmkResults);
2229 			continue;
2230 		}
2231 
2232 		/* now use k to indicate GdkKeymapKey we're after */
2233 		k=0; /* default if only one hit found */
2234 		if(iResults>1)
2235 			/* cycle through results if more than one matches */
2236 			for(k=0;k<iResults;k++)
2237 				/* have found number without using shift, ctrl, Alt etc, so shold be it. */
2238 				if(gdkkmkResults[k].level==0)
2239 					break;
2240 
2241 		/* error figuring out which keycode to use so default to standard uk */
2242 		if(k==iResults)
2243 		{
2244 			g_free(gdkkmkResults);
2245 			continue;
2246 		}
2247 
2248 		/* set shift pressed */
2249 		gdkkmkResults[k].level=1;
2250 		/* now get keycode for shift + number */
2251 		iResults=gdk_keymap_lookup_key(gdkKeyMap,&(gdkkmkResults[k]));
2252 		/* if valid keycode, enter into list of shift + numbers */
2253 		if(iResults!=0)
2254 			iShiftNumbers[i]=iResults;
2255 
2256 		/* free resources */
2257 		g_free(gdkkmkResults);
2258 	}
2259 
2260 	/* add record macro menu entry */
2261 	Record_Macro_menu_item=gtk_menu_item_new_with_mnemonic(_("Record _Macro"));
2262 	gtk_widget_show(Record_Macro_menu_item);
2263 	gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu),Record_Macro_menu_item);
2264 	g_signal_connect(Record_Macro_menu_item,"activate",G_CALLBACK(DoMacroRecording),NULL);
2265 
2266 	/* add stop record macromenu entry */
2267 	Stop_Record_Macro_menu_item=gtk_menu_item_new_with_mnemonic(_("Stop Recording _Macro"));
2268 	gtk_widget_hide(Stop_Record_Macro_menu_item);
2269 	gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu),Stop_Record_Macro_menu_item);
2270 	g_signal_connect(Stop_Record_Macro_menu_item,"activate",G_CALLBACK(DoMacroRecording),NULL);
2271 
2272 	/* add Edit Macro menu entry */
2273 	Edit_Macro_menu_item=gtk_menu_item_new_with_mnemonic(_("_Edit Macros"));
2274 	gtk_widget_show(Edit_Macro_menu_item);
2275 	gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu),Edit_Macro_menu_item);
2276 	g_signal_connect(Edit_Macro_menu_item,"activate",G_CALLBACK(DoEditMacro),NULL);
2277 
2278 	/* set key press monitor handle */
2279 	key_release_signal_id=g_signal_connect(geany->main_widgets->window,"key-release-event",
2280 										G_CALLBACK(Key_Released_CallBack),NULL);
2281 }
2282 
2283 
2284 /* clean up on exiting this plugin */
plugin_cleanup(void)2285 void plugin_cleanup(void)
2286 {
2287 	/* if macros have changed then save off */
2288 	if(bMacrosHaveChanged==TRUE && bSaveMacros==TRUE)
2289 		SaveSettings();
2290 
2291 	/* uncouple keypress monitor */
2292 	g_signal_handler_disconnect(geany->main_widgets->window,key_release_signal_id);
2293 
2294 	/* clear menu entries */
2295 	gtk_widget_destroy(Record_Macro_menu_item);
2296 	gtk_widget_destroy(Stop_Record_Macro_menu_item);
2297 	gtk_widget_destroy(Edit_Macro_menu_item);
2298 
2299 	/* Clear any macros that are recording */
2300 	RecordingMacro=FreeMacro(RecordingMacro);
2301 
2302 	/* clean up memory used by macros */
2303 	ClearAllMacros();
2304 }
2305