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