1 /* Copyright (C) 2007-2008 by Xyhthyx <xyhthyx@gmail.com>
2  *
3  * This file is part of Parcellite.
4  *
5  * Parcellite is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * Parcellite is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "parcellite.h"
20 #include <sys/wait.h>
21 
22 void setup_icon( void ); /**in main.c  */
23 
24 #define MAX_HISTORY 1000
25 
26 #define INIT_HISTORY_KEY      NULL
27 #define INIT_ACTIONS_KEY      NULL
28 #define INIT_MENU_KEY         NULL
29 
30 #define DEF_USE_COPY          TRUE
31 #define DEF_USE_PRIMARY       FALSE
32 #define DEF_SYNCHRONIZE       FALSE
33 #define DEF_SAVE_HISTORY      TRUE
34 #define DEF_HISTORY_LIMIT     25
35 #define DEF_HYPERLINKS_ONLY   FALSE
36 #define DEF_CONFIRM_CLEAR     TRUE
37 #define DEF_SINGLE_LINE       TRUE
38 #define DEF_REVERSE_HISTORY   FALSE
39 #define DEF_ITEM_LENGTH       50
40 #define DEF_ITEM_LENGTH_MAX   200
41 #define DEF_ELLIPSIZE         2
42 #define DEF_PHISTORY_KEY      "<Ctrl><Alt>X"
43 #define DEF_HISTORY_KEY       "<Ctrl><Alt>H"
44 #define DEF_ACTIONS_KEY       "<Ctrl><Alt>A"
45 #define DEF_MENU_KEY          "<Ctrl><Alt>P"
46 #define DEF_NO_ICON           FALSE
47 
48 /**allow lower nibble to become the number of items of this type  */
49 #define PREF_TYPE_TOGGLE 0x10
50 #define PREF_TYPE_SPIN   0x20
51 #define PREF_TYPE_COMBO  0x30
52 #define PREF_TYPE_ENTRY  0x40 /**gchar *  */
53 #define PREF_TYPE_ALIGN  0x50 /**label, then align box  */
54 #define PREF_TYPE_SPACER 0x60
55 #define PREF_TYPE_FRAME  0x70 /**frame for each section  */
56 #define PREF_TYPE_MASK	 0xF0
57 #define PREF_TYPE_NMASK	 0xF
58 #define PREF_TYPE_SINGLE_LINE 1
59 
60 #define PREF_SEC_NONE 0
61 #define PREF_SEC_CLIP 1
62 #define PREF_SEC_HIST 2
63 #define PREF_SEC_MISC 3
64 #define PREF_SEC_DISP 4
65 
66 #define PREF_SEC_ACT	5
67 #define PREF_SEC_XMISC 6
68 
69 #define RC_VERSION_NAME "RCVersion"
70 #define RC_VERSION      1
71 
72 struct myadj {
73 	gdouble lower;
74 	gdouble upper;
75 	gdouble step;
76 	gdouble page;
77 };
78 static char *icon_name="parcellite";
79 struct myadj align_hist_x={1,100,1,10};
80 struct myadj align_hist_y={1,100,1,10};
81 struct myadj align_data_lim={0,1000000,1,10};
82 struct myadj align_hist_lim={5, MAX_HISTORY, 1, 10};
83 struct myadj align_line_lim={5, DEF_ITEM_LENGTH_MAX, 1, 5};
84 struct pref_item {
85 	gchar *name;		/**name/id to find pref  */
86 	gint32 val;			/**int val  */
87 	float fval;
88 	gchar *cval;	 /**char val  */
89 	GtkWidget *w;	 /**widget in menu  */
90 	gint type;		/**PREF_TYPE_  */
91 	gchar *desc; /**shows up in menu  */
92 	gchar *tip; /**tooltip */
93 	gint sec;	/**clipboard,history, misc,display,hotkeys  */
94 	gchar *sig;			 /**signal, if any  */
95 	GCallback sfunc; /**function to call  */
96 	struct myadj *adj;
97 };
98 static struct pref_item dummy[2];
99 static void check_toggled(GtkToggleButton *togglebutton, gpointer user_data);
100 static void search_toggled(GtkToggleButton *b, gpointer user);
101 static gint dbg=0;
102 static int tool_bitfield=0;
103 static int tool_bitfield_check=0;
104 struct pref2int *pref2int_mapper=NULL;
105 struct tool_flag tool_flags[]={
106 	{.flag=TOOL_XDOTOOL,.name="xdotool"},
107 	{.flag=0,.name=NULL },
108 	/*{.flag=,.name= }, */
109 };
110 /**hot key list, mainly for easy sanity checks.  */
111 struct keys keylist[]={
112 	{.name="menu_key",.keyval=DEF_MENU_KEY,.keyfunc=(void *)menu_hotkey},
113 	{.name="history_key",.keyval=DEF_HISTORY_KEY,.keyfunc=(void *)history_hotkey},
114 	{.name="phistory_key",.keyval=DEF_PHISTORY_KEY,.keyfunc=(void *)phistory_hotkey},
115 	{.name="actions_key",.keyval=DEF_ACTIONS_KEY,.keyfunc=(void *)actions_hotkey},
116 	{.name=NULL,.keyval=NULL,.keyfunc=(void *)0},
117 };
118 /**must be in same order as above struct array  */
119 gchar *def_keyvals[]={ DEF_MENU_KEY,DEF_HISTORY_KEY,DEF_PHISTORY_KEY,DEF_ACTIONS_KEY};
120 struct pref_item myprefs[]={
121 /**Behaviour  */
122 	/**Clipboards  */
123 	{.adj=NULL,.cval=NULL,.sig=NULL,.sfunc=NULL,.sec=PREF_SEC_CLIP,.name=NULL,.type=PREF_TYPE_FRAME,.desc="<b>Clipboards</b>",.tip=NULL,.val=0},
124   {.adj=NULL,.cval=NULL,.sig="toggled",.sfunc=(GCallback)check_toggled,.sec=PREF_SEC_CLIP,.name="use_copy",.type=PREF_TYPE_TOGGLE,.desc="Use _Copy (Ctrl-C)",.tip="If checked, Use the clipboard, which is Ctrl-C, Ctrl-V",.val=DEF_USE_COPY},
125   {.adj=NULL,.cval=NULL,.sig="toggled",.sfunc=(GCallback)check_toggled,.sec=PREF_SEC_CLIP,.name="use_primary",.type=PREF_TYPE_TOGGLE,.desc="Use _Primary (Selection)",.tip="If checked, Use the primary clipboard (mouse highlight-copy, middle mouse button paste)",.val=DEF_USE_PRIMARY},
126   {.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_CLIP,.name="synchronize",.type=PREF_TYPE_TOGGLE,.desc="S_ynchronize clipboards",.tip="If checked, will keep both clipboards with the same content. If primary is pasted, then copy will have the same data.",.val=DEF_SYNCHRONIZE},
127   /**History  */
128   {.adj=NULL,.cval=NULL,.sig=NULL,.sfunc=NULL,.sec=PREF_SEC_HIST,.name=NULL,.type=PREF_TYPE_FRAME,.desc="<b>History</b>",.tip=NULL,.val=0},
129   {.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="save_history",.type=PREF_TYPE_TOGGLE,.desc="Save history",.tip="Save history to a file.",.val=DEF_SAVE_HISTORY},
130   {.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="history_pos",.type=PREF_TYPE_TOGGLE|PREF_TYPE_SINGLE_LINE,.desc="Position history",.tip="If checked, use X, Y to position the history list",.val=0},
131   {.adj=&align_hist_x,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="history_x",.type=PREF_TYPE_SPIN|PREF_TYPE_SINGLE_LINE,.desc="<b>X</b>",.tip=NULL,.val=1},
132   {.adj=&align_hist_y,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="history_y",.type=PREF_TYPE_SPIN|PREF_TYPE_SINGLE_LINE,.desc="<b>Y</b>",.tip="Position in pixels from the top of the screen",.val=1},
133 	{.adj=&align_hist_lim,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="history_limit",.type=PREF_TYPE_SPIN,.desc="Items in history",.tip="Maximum number of clipboard entries to keep",.val=DEF_HISTORY_LIMIT},
134   {.adj=&align_data_lim,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="data_size",.type=PREF_TYPE_SPIN,.desc="Max Data Size(KB)",.tip="Maximum data size of entire history list",.val=0},
135   {.adj=&align_hist_lim,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="item_size",.type=PREF_TYPE_SPIN,.desc="Max Item Size(KB)",.tip="Maximum data size of one item",.val=0},
136 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="nop",.type=PREF_TYPE_SPACER,.desc=" ",.tip=NULL},
137 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="automatic_paste",.type=PREF_TYPE_TOGGLE|PREF_TYPE_SINGLE_LINE,.desc="Auto Paste",.tip="If checked, will use xdotool to paste wherever the mouse is.\nNOTE! Package xdotool MUST BE INSTALLED for this to work.",.val=0},
138 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="auto_key",.type=PREF_TYPE_TOGGLE|PREF_TYPE_SINGLE_LINE,.desc="Key",.tip="If checked, will use Ctrl-V paste.",.val=0},
139 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="auto_mouse",.type=PREF_TYPE_TOGGLE|PREF_TYPE_SINGLE_LINE,.desc="Mouse",.tip="If checked, will use middle mouse to paste.",.val=1},
140 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="key_input",.type=PREF_TYPE_TOGGLE,.desc="Keyboard Input",.tip="If checked, will emit the history entry via the keyboard entry.",.val=0},
141 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="restore_empty",.type=PREF_TYPE_TOGGLE,.desc="Restore Empty",.tip="If checked, will restore clipboard entry on application exit.",.val=1},
142 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_HIST,.name="rc_edit",.type=PREF_TYPE_TOGGLE,.desc="Right Click Edit",.tip="If checked, edit the history item clicked on.",.val=0},
143 
144   /**Miscellaneous  */
145 	{.adj=NULL,.cval=NULL,.sig=NULL,.sfunc=NULL,.sec=PREF_SEC_MISC,.name=NULL,.type=PREF_TYPE_FRAME,.desc="<b>Miscellaneous</b>",.tip=NULL,.val=0},
146 	{.adj=NULL,.cval=NULL,.sig="toggled",.sfunc=(GCallback)search_toggled,.sec=PREF_SEC_MISC,.name="type_search",.type=PREF_TYPE_TOGGLE,.desc="Search As You Type",.tip="If checked, does a search-as-you-type. Turns red if not found. Goes to top (Alt-E) line when no chars are entered for search"},
147   {.adj=NULL,.cval=NULL,.sig="toggled",.sfunc=(GCallback)search_toggled,.sec=PREF_SEC_MISC,.name="case_search",.type=PREF_TYPE_TOGGLE,.desc="Case Sensitive Search",.tip="If checked, does case sensitive search"},
148 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_MISC,.name="ignore_whiteonly",.type=PREF_TYPE_TOGGLE,.desc="Ignore Whitespace Only",.tip="If checked, will ignore any clipboard additions that contain only whitespace."},
149 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_MISC,.name="trim_wspace_begend",.type=PREF_TYPE_TOGGLE,.desc="Trim Whitespace",.tip="If checked, will trim whitespace from beginning and end of entry."},
150 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_MISC,.name="trim_newline",.type=PREF_TYPE_TOGGLE,.desc="Trim Newlines",.tip="If checked, will replace newlines with spaces."},
151 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_MISC,.name="hyperlinks_only",.type=PREF_TYPE_TOGGLE,.desc="Capture hyperlinks only",.tip=NULL,.val=DEF_HYPERLINKS_ONLY},
152 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_MISC,.name="confirm_clear",.type=PREF_TYPE_TOGGLE,.desc="Confirm before clearing history",.tip=NULL,.val=DEF_CONFIRM_CLEAR},
153 
154 /**Display  add icon here...*/
155 	{.adj=NULL,.cval=NULL,.sig=NULL,.sfunc=NULL,.sec=PREF_SEC_DISP,.name=NULL,.type=PREF_TYPE_FRAME,.desc="<b>Items</b>",.tip=NULL,.val=0},
156 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_DISP,.name="current_on_top",.type=PREF_TYPE_TOGGLE,.desc="Current entry on top",.tip="If checked, places current clipboard entry at top of list. If not checked, history does not get sorted.",.val=TRUE},
157   {.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_DISP,.name="single_line",.type=PREF_TYPE_TOGGLE,.desc="Show in a single line",.tip=NULL,.val=DEF_SINGLE_LINE},
158   {.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_DISP,.name="reverse_history",.type=PREF_TYPE_TOGGLE,.desc="Show in reverse order",.tip="If checked, the current entry will be on the bottom instead of the top.",.val=DEF_REVERSE_HISTORY},
159 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_DISP,.name="nop",.type=PREF_TYPE_SPACER,.desc=" ",.tip=NULL},
160 	{.adj=&align_line_lim,.cval=NULL,.sig=NULL,.sec=PREF_SEC_DISP,.name="item_length",.type=PREF_TYPE_SPIN,.desc="Character length of items",.tip=NULL,.val=DEF_ITEM_LENGTH},
161   {.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_DISP,.name="nop",.type=PREF_TYPE_SPACER,.desc=" ",.tip=NULL},
162 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_DISP,.name="persistent_history",.type=PREF_TYPE_TOGGLE,.desc="Persistent History",.tip="If checked, enables the persistent history.",.val=FALSE},
163 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_DISP,.name="persistent_separate",.type=PREF_TYPE_TOGGLE,.desc="Persistent As Separate List",.tip="If checked, puts the persistent history in a new list and enables the Persistent History HotKey. You will need to use the persistent history hotkey to see the history.",.val=FALSE},
164 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_DISP,.name="persistent_on_top",.type=PREF_TYPE_TOGGLE,.desc="Persistent On Top",.tip="If checked, puts the persistent history at the top of the history list. If the option above is checked, this option has no effect.",.val=FALSE},
165 	{.adj=NULL,.cval="\\n",.sig=NULL,.sec=PREF_SEC_DISP,.name="persistent_delim",.type=PREF_TYPE_ENTRY,.desc="Paste All Entry Delimiter",.tip="This string will be inserted between each line of history for paste all."},
166 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_DISP,.name="nonprint_disp",.type=PREF_TYPE_TOGGLE,.desc="Alternate Non-printing Display",.tip="If checked, will display tabs with Right arrow (utf8,\\2192), newlines with pharagrph(\\204b), and spaces with square-u(\\2423).", .val=FALSE},
167 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_DISP,.name="nop",.type=PREF_TYPE_SPACER,.desc=" ",.tip=NULL},
168   {.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_NONE,.name="ellipsize",.type=PREF_TYPE_COMBO,.desc="Omit items in the:",.tip=NULL,.val=DEF_ELLIPSIZE},
169 
170 /**miscellaneous that doesn't fit elswhew  */
171   {.adj=NULL,.cval=NULL,.sig=NULL,.sfunc=NULL,.sec=PREF_SEC_XMISC,.name=NULL,.type=PREF_TYPE_FRAME,.desc="<b>Miscellaneous</b>",.tip=NULL,.val=0},
172 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_XMISC,.name="multi_user",.type=PREF_TYPE_TOGGLE,.desc="Multiuser",.tip="If checked, enables checking multiple concurrent user logic. Use if several different users are logged in at the same time.",.val=TRUE},
173 	{.adj=NULL,.cval=PARCELLITE_ICON,.sig=NULL,.sec=PREF_SEC_XMISC,.name="icon_name",.type=PREF_TYPE_ENTRY,.desc="Parcellite Icon Name",.tip="Name of Parcellite icon. If this is mis-typed, icon will not appear.",.val=TRUE},
174 #ifdef	DEBUG_UPDATE
175 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_XMISC,.name="debug_update",.type=PREF_TYPE_TOGGLE,.desc="DebugUpdate",.tip="If checked, enables debug prints on clipboard update logic. This only takes effect when enabled at start up, and may be disabled at compile time.",.val=FALSE},
176 #endif
177 
178 /**Action Keys  */
179 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_ACT,.name="menu_key",.type=PREF_TYPE_ENTRY,.desc="Menu key combination",.tip=NULL},
180   {.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_ACT,.name="history_key",.type=PREF_TYPE_ENTRY,.desc="History key combination:",.tip=NULL},
181 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_ACT,.name="phistory_key",.type=PREF_TYPE_ENTRY,.desc="Persistent history key:",.tip=NULL},
182   {.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_ACT,.name="actions_key",.type=PREF_TYPE_ENTRY,.desc="Actions key combination:",.tip=NULL},
183 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_NONE,.name="no_icon",.val=FALSE},
184 	{.adj=NULL,.cval=NULL,.sig=NULL,.sec=PREF_SEC_NONE,.name=NULL,.desc=NULL},
185 };
186 
187 GtkListStore* actions_list;
188 GtkTreeSelection* actions_selection;
189 
190 /***************************************************************************/
191 /** Set up which tools are available on the system.
192 \n\b Arguments:
193 \n\b Returns:
194 ****************************************************************************/
check_for_tools_exit(GPid pid,gint status,gpointer data)195 static void check_for_tools_exit(GPid pid, gint status, gpointer data)
196 {
197 	int flag= GPOINTER_TO_INT(data);
198   g_spawn_close_pid(pid);
199 	if( WIFEXITED(status) ){
200 		if(0 ==WEXITSTATUS(status) )
201 			tool_bitfield|=flag;
202 
203 	}
204 	tool_bitfield_check &= ~(flag);
205 	g_fprintf(stderr,"Flag 0x%04x, status %d, EXIT %d STAT %d\n",flag,status,WIFEXITED(status),WEXITSTATUS(status) );
206 
207 }
208 /***************************************************************************/
209 /** Check for installed tools and set flags accordingly.
210 \n\b Arguments:
211 \n\b Returns:
212 ****************************************************************************/
check_for_tools(void)213 void check_for_tools(void )
214 {
215 	GPid pid;
216   gchar **argv;
217 	int i;
218 	for (i=0; NULL != tool_flags[i].name; ++i){
219 		gchar cmd[100];
220 		tool_bitfield_check|=tool_flags[i].flag;
221 		sprintf(cmd,"/bin/sh -c 'which %s'>/dev/null\n",tool_flags[i].name);
222 		g_shell_parse_argv(cmd, NULL, &argv, NULL);
223 		g_spawn_async(NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL);
224 		g_child_watch_add(pid, (GChildWatchFunc)check_for_tools_exit, GINT_TO_POINTER(tool_flags[i].flag));
225 		g_strfreev(argv);
226 	}
227 }
228 
229 /***************************************************************************/
230 /** .
231 \n\b Arguments:
232 \n\b Returns: Structure of item
233 ****************************************************************************/
get_pref(char * name)234 struct pref_item* get_pref(char *name)
235 {
236 	int i;
237 	for (i=0;NULL != myprefs[i].desc; ++i){
238 		if(NULL == myprefs[i].name)
239 			continue;
240 		if(!g_strcmp0(myprefs[i].name,name))
241 			return &myprefs[i];
242 	}
243 	return &dummy[0];
244 }
245 /***************************************************************************/
246 /** Set an array that is updated every time the preferences are changed.
247 \n\b Arguments:
248 \n\b Returns:
249 ****************************************************************************/
pref_mapper(struct pref2int * m,int mode)250 void pref_mapper (struct pref2int *m, int mode)
251 {
252 	int i;
253 	if(PM_INIT == mode){
254 		pref2int_mapper=m;
255 		return;
256 	}
257 	for (i=0; pref2int_mapper[i].name != NULL && pref2int_mapper[i].val != NULL; ++i){
258 		struct pref_item *p=get_pref(pref2int_mapper[i].name);
259 		*pref2int_mapper[i].val=p->val;
260 	}
261 
262 }
263 /***************************************************************************/
264 /** .
265 \n\b Arguments: pointer of widget
266 \n\b Returns: Structure of item
267 ****************************************************************************/
get_pref_by_widget(GtkWidget * w)268 struct pref_item* get_pref_by_widget(GtkWidget *w)
269 {
270 	int i;
271 	for (i=0;NULL != myprefs[i].desc; ++i){
272 		if(NULL == myprefs[i].name)
273 			continue;
274 		if(w == myprefs[i].w)
275 			return &myprefs[i];
276 	}
277 	return &dummy[0];
278 }
279 /***************************************************************************/
280 /** Find first item in section.
281 \n\b Arguments:
282 \n\b Returns: index into struct where found, or end of list
283 ****************************************************************************/
get_first_pref(int section)284 int get_first_pref(int section)
285 {
286 	int i;
287 	for (i=0;NULL != myprefs[i].desc; ++i){
288 		if(section == myprefs[i].sec){
289 			if(dbg)g_printf("gfp:returning sec %d, '%s\n",section, myprefs[i].desc);
290 			return i;
291 		}
292 
293 	}
294 	if(dbg)g_printf("Didn't find section %d\n",i);
295 	return i;
296 }
297 /***************************************************************************/
298 /** .
299 \n\b Arguments:
300 \n\b Returns:
301 ****************************************************************************/
init_pref(void)302 int init_pref( void )
303 {
304 	GdkScreen *s;
305 	gint sx,sy;
306 	s=gdk_screen_get_default();
307 	sx= gdk_screen_get_width(s);
308 	sy= gdk_screen_get_height(s);
309 	dummy[0].cval="dummy String";
310 	dummy[0].w=NULL;
311 	dummy[0].desc="Dummy desc";
312 	dummy[0].tip="Dummy tip";
313 	dummy[1].name=NULL;
314 	dummy[1].sec=PREF_SEC_NONE;
315 	align_hist_y.upper=sy-100;
316 	align_hist_x.upper=sx-100;
317 	return 0;
318 }
319 /***************************************************************************/
320 /** set the wideget of item.
321 \n\b Arguments:
322 \n\b Returns:
323 ****************************************************************************/
set_pref_widget(char * name,GtkWidget * w)324 int set_pref_widget (char *name, GtkWidget *w)
325 {
326 	struct pref_item *p=get_pref(name);
327 	if(NULL == p)
328 		return -1;
329 	p->w=w;
330 	return 0;
331 }
332 /***************************************************************************/
333 /** get the char * value of string.
334 \n\b Arguments:
335 \n\b Returns:
336 ****************************************************************************/
get_pref_widget(char * name)337 GtkWidget *get_pref_widget (char *name)
338 {
339 	struct pref_item *p=get_pref(name);
340 	if(NULL == p)
341 		return dummy[0].w;
342 	return p->w;
343  }
344 /***************************************************************************/
345 /** Set the int value of name.
346 \n\b Arguments:
347 \n\b Returns:
348 ****************************************************************************/
set_pref_int32(char * name,gint32 val)349 int set_pref_int32(char *name, gint32 val)
350 {
351 	struct pref_item *p=get_pref(name);
352 	if(NULL == p)
353 		return -1;
354 	p->val=val;
355 	return 0;
356 }
357 
358 /***************************************************************************/
359 /** get the int value of name.
360 \n\b Arguments:
361 \n\b Returns:
362 ****************************************************************************/
get_pref_int32(char * name)363 gint32 get_pref_int32 (char *name)
364 {
365 	struct pref_item *p=get_pref(name);
366 	if(NULL == p)
367 		return -1;
368 	return p->val;
369 }
370 /***************************************************************************/
371 /** Set the float value of name.
372 \n\b Arguments:
373 \n\b Returns:
374 ****************************************************************************/
set_pref_float(char * name,float fval)375 int set_pref_float(char *name, float fval)
376 {
377 	struct pref_item *p=get_pref(name);
378 	if(NULL == p)
379 		return -1;
380 	p->fval=fval;
381 	return 0;
382 }
383 
384 /***************************************************************************/
385 /** get the float value of name.
386 \n\b Arguments:
387 \n\b Returns:
388 ****************************************************************************/
get_pref_float(char * name)389 float get_pref_float (char *name)
390 {
391 	struct pref_item *p=get_pref(name);
392 	if(NULL == p)
393 		return -1;
394 	return p->fval;
395 }
396 
397 /***************************************************************************/
398 /** set the char *value of string.
399 \n\b Arguments:
400 \n\b Returns:
401 ****************************************************************************/
set_pref_string(char * name,char * string)402 int set_pref_string (char *name, char *string)
403 {
404 	struct pref_item *p=get_pref(name);
405 	if(NULL == p)
406 		return -1;
407 	if(p->cval != NULL)
408 		g_free(p->cval);
409 	p->cval=g_strdup(string);
410 	return 0;
411 }
412 
413 /***************************************************************************/
414 /** get the char * value of string.
415 \n\b Arguments:
416 \n\b Returns:
417 ****************************************************************************/
get_pref_string(char * name)418 gchar *get_pref_string (char *name)
419 {
420 	struct pref_item *p=get_pref(name);
421 	if(NULL == p)
422 		return dummy[0].cval;
423 	return p->cval;
424  }
425 
426 /***************************************************************************/
427 /** .
428 \n\b Arguments:
429 \n\b Returns:
430 ****************************************************************************/
unbind_itemkey(char * name,void * fhk)431 void unbind_itemkey(char *name, void *fhk )
432 {
433 
434 	struct pref_item *p=get_pref(name);
435 	if(NULL == p){
436 		if(dbg)g_printf("pref:null found for %s\n",name);
437 		return;
438 	}
439 	keybinder_unbind(p->cval, fhk);
440 	g_free(p->cval);
441 	p->cval=NULL;
442 
443 }
444 
445 /***************************************************************************/
446 /** .
447 \n\b Arguments:
448 \n\b Returns:
449 ****************************************************************************/
bind_itemkey(char * name,void (fhk)(char *,gpointer))450 void bind_itemkey(char *name, void (fhk)(char *, gpointer) )
451 {
452 	struct pref_item *p=get_pref(name);
453 	if(NULL ==p){
454 		if(dbg)g_printf("pref2:null found for %s\n",name);
455 		return;
456 	}
457 	if(NULL != p->cval && 0 != p->cval)
458 		keybinder_bind(p->cval, fhk, NULL);
459 }
460 
461 
462 /***************************************************************************/
463 /** .
464 \n\b Arguments:
465 \n\b Returns:
466 ****************************************************************************/
set_key_entry(gchar * name,gchar * val)467 void set_key_entry(gchar *name, gchar *val)
468 {
469 	int i;
470 	for (i=0;NULL != keylist[i].name; ++i){
471 		if(!g_strcmp0(keylist[i].name,name)){
472 		  if(NULL == val)
473 		  	keylist[i].keyval="";
474 			else
475 				keylist[i].keyval=val;
476 			return;
477 		}
478 	}
479 }
480 
481 /***************************************************************************/
482 /** .
483 \n\b Arguments:
484 \n\b Returns:
485 ****************************************************************************/
set_keys_from_prefs(void)486 void set_keys_from_prefs( void )
487 {
488 	int i,l;
489 	for (i=0;NULL != keylist[i].name; ++i){
490 		/**NOTE: do not set up default keys here! User may WANT them null */
491 			/** call egg_accelerator_parse_virtual to validate? */
492 		set_key_entry(keylist[i].name,get_pref_string(keylist[i].name));
493 		/*g_fprintf(stderr,"key '%s' val '%s'\n",keylist[i].name, keylist[i].keyval); */
494 	}
495 	/**now go through and make sure we have no duplicates */
496 	for (i=0;NULL != keylist[i].name; ++i){
497 		if(0 !=  keylist[i].keyval[0]){
498 			/**see if it exists elsewhere  */
499 			for (l=0;NULL != keylist[l].name; ++l){
500 				if(l!=i && 0 != keylist[l].keyval[0]){
501 					if(!g_strcmp0(keylist[i].keyval, keylist[l].keyval)) { /**conflict!, delete second  */
502 						g_fprintf(stderr,"Error! Hot keys have same key '%s': '%s' and '%s'. Ignoring second entry\n",keylist[i].keyval,keylist[i].name,keylist[l].name);
503 						set_key_entry(keylist[l].name,"");
504 						set_pref_string(keylist[l].name,"");
505 					}
506 				}
507 			}
508 		}
509 	}
510 
511 }
512 /***************************************************************************/
513 /** .
514 \n\b Arguments:
515 mode - if 0, do not display helper missing dialog.
516 \n\b Returns:
517 ****************************************************************************/
check_sanity(int mode)518 void check_sanity(int mode)
519 {
520 	gint32 x,y;
521 	gchar *val;
522 	check_for_tools(); /**update the list of tools parcellite needs.  */
523 	while(tool_bitfield_check){
524 		g_main_context_iteration(NULL, TRUE);
525 		usleep(100000);
526 	}
527 	val=get_pref_string("icon_name");
528 	if(NULL != val && strcmp(icon_name, val)){
529 		setup_icon( );
530 		icon_name=strdup(val);
531 	}
532 	x=get_pref_int32("history_x");
533 	y=get_pref_int32("history_y");
534 	postition_history(NULL,&x,&y,NULL, 0); /**have function limit x,y according to screen limits */
535   set_pref_int32("history_x",x);
536   set_pref_int32("history_y",y);
537  	x=get_pref_int32("history_limit");
538   if ((!x) || (x > MAX_HISTORY) || (x < 0))
539     set_pref_int32("history_limit",DEF_HISTORY_LIMIT);
540 	x=get_pref_int32("item_length");
541   if ((!x) || (x > DEF_ITEM_LENGTH_MAX) || (x < 0))
542     set_pref_int32("item_length",DEF_ITEM_LENGTH);
543 	x=get_pref_int32("ellipsize");
544   if ((!x) || (x > 3) || (x < 0))
545      set_pref_int32("ellipsize",DEF_ELLIPSIZE);
546 	set_keys_from_prefs();
547 
548 
549 	if(get_pref_int32("persistent_history")){
550 	 	if(get_pref_int32("persistent_separate"))
551 	 		set_pref_int32("persistent_on_top",0);
552 	}else{
553 		set_pref_int32("persistent_separate",0);
554 	  set_pref_int32("persistent_on_top",0);
555 	}
556 	if(get_pref_int32("automatic_paste")){
557 		if(!(tool_bitfield&TOOL_XDOTOOL)){
558 			g_fprintf(stderr,"tool_bitfield=0x%x\n",tool_bitfield);
559 			set_pref_int32("automatic_paste",0);
560 			if( mode )
561 				show_gtk_dialog("xdotool is not installed\nParcellite's auto-paste will not function without xdotool.","xdotool Not Installed" );
562 		} else{
563 			if(get_pref_int32("auto_key") && get_pref_int32("auto_mouse"))
564 				set_pref_int32("auto_key",0);
565 			if(!get_pref_int32("auto_key") && !get_pref_int32("auto_mouse"))
566 				set_pref_int32("auto_mouse",1);
567 		}
568 	}
569 }
570 /* Apply the new preferences */
apply_preferences()571 static void apply_preferences()
572 {
573 	int i;
574   /* Unbind the keys before binding new ones */
575 	for (i=0;NULL != keylist[i].name; ++i)
576 	  unbind_itemkey(keylist[i].name,keylist[i].keyfunc);
577 
578 
579   for (i=0;NULL != myprefs[i].desc; ++i){
580 		if(NULL == myprefs[i].name)
581 			continue;
582 		switch(myprefs[i].type&PREF_TYPE_MASK){
583 			case PREF_TYPE_TOGGLE:
584 				myprefs[i].val=gtk_toggle_button_get_active((GtkToggleButton*)myprefs[i].w);
585 				break;
586 			case PREF_TYPE_SPIN:
587 				myprefs[i].val=gtk_spin_button_get_value_as_int((GtkSpinButton*)myprefs[i].w);
588 				myprefs[i].fval=(float)gtk_spin_button_get_value((GtkSpinButton*)myprefs[i].w);
589 				break;
590 			case PREF_TYPE_COMBO:
591 				myprefs[i].val=gtk_combo_box_get_active((GtkComboBox*)myprefs[i].w) + 1;
592 				break;
593 			case PREF_TYPE_ENTRY:
594 				myprefs[i].cval=g_strdup(gtk_entry_get_text((GtkEntry*)myprefs[i].w ));
595 				break;
596 			case PREF_TYPE_SPACER:
597 				break;
598 			default: if(dbg)g_printf("apply_pref:don't know how to handle type %d\n",myprefs[i].type);
599 				break;
600 		}
601 	}
602   check_sanity(1);
603   /* Bind keys and apply the new history limit */
604 	for (i=0;NULL != keylist[i].name; ++i)
605 	  bind_itemkey(keylist[i].name,keylist[i].keyfunc);
606   truncate_history();
607 	pref_mapper(NULL, PM_UPDATE);
608 }
609 
610 
611 /***************************************************************************/
612 /** .
613 \n\b Arguments:
614 \n\b Returns:
615 ****************************************************************************/
save_preferences()616 static void save_preferences()
617 {
618 	int i;
619   /* Create key */
620   GKeyFile* rc_key = g_key_file_new();
621 	g_key_file_set_integer(rc_key, "rc", RC_VERSION_NAME, RC_VERSION);
622   if(0 == get_pref_int32("type_search"))
623     set_pref_int32("case_search",0);
624   /* Add values */
625 	for (i=0;NULL != myprefs[i].desc; ++i){
626 		if(NULL == myprefs[i].name)
627 			continue;
628 		switch(myprefs[i].type&PREF_TYPE_MASK){
629 			case PREF_TYPE_TOGGLE:
630 				g_key_file_set_boolean(rc_key, "rc", myprefs[i].name, myprefs[i].val);
631 				break;
632 			case PREF_TYPE_COMBO:
633 			case PREF_TYPE_SPIN:
634 				g_key_file_set_integer(rc_key, "rc", myprefs[i].name, myprefs[i].val);
635 				break;
636 			case PREF_TYPE_ENTRY:
637 				g_key_file_set_string(rc_key, "rc", myprefs[i].name, myprefs[i].cval);
638 				break;
639 			case PREF_TYPE_SPACER:
640 				break;
641 			default: if(dbg)g_printf("save_pref:don't know how to handle type %d\n",myprefs[i].type);
642 				break;
643 		}
644 	}
645   /* Check config and data directories */
646   check_dirs();
647   /* Save key to file */
648   gchar* rc_file = g_build_filename(g_get_user_config_dir(), PREFERENCES_FILE, NULL);
649   g_file_set_contents(rc_file, g_key_file_to_data(rc_key, NULL, NULL), -1, NULL);
650   g_key_file_free(rc_key);
651   g_free(rc_file);
652 }
653 
654 
655 /***************************************************************************/
656 /** Read the parcelliterc file.
657 \n\b Arguments:
658 mode - 0 to not display missing helper warnings
659 \n\b Returns:
660 ****************************************************************************/
read_preferences(int mode)661 void read_preferences(int mode)
662 {
663 	gchar *c,*rc_file = g_build_filename(g_get_user_config_dir(), PREFERENCES_FILE, NULL);
664   gint32 z;
665   GError *err=NULL;
666 	struct pref_item *p;
667 	init_pref();
668   /* Create key */
669   GKeyFile* rc_key = g_key_file_new();
670   if (g_key_file_load_from_file(rc_key, rc_file, G_KEY_FILE_NONE, NULL)) {
671 		int i;
672 		i=g_key_file_get_integer(rc_key, "rc", RC_VERSION_NAME,&err); /**this begins in 1.1.8  */
673 		/* Load values */
674 		for (i=0;NULL != myprefs[i].desc; ++i){
675 			if(NULL == myprefs[i].name)
676 				continue;
677 		  err=NULL;
678 			switch(myprefs[i].type&PREF_TYPE_MASK){
679 				case PREF_TYPE_TOGGLE:
680 				  z=g_key_file_get_boolean(rc_key, "rc", myprefs[i].name,&err);
681 				  if( NULL ==err)
682 							myprefs[i].val=z;
683 					break;
684 				case PREF_TYPE_COMBO:
685 				case PREF_TYPE_SPIN:
686 				  z=g_key_file_get_integer(rc_key, "rc", myprefs[i].name,&err);
687 				  if( NULL ==err)
688 							myprefs[i].val=z;
689 					break;
690 				case PREF_TYPE_ENTRY:
691 				  c=g_key_file_get_string(rc_key, "rc", myprefs[i].name, &err);
692 				  if( NULL ==err)
693 						myprefs[i].cval=c;
694 					break;
695 				case PREF_TYPE_SPACER:
696 				break;
697 				default:
698 					if(dbg) g_printf("read_pref:don't know how to handle type %d for '%s'\n",myprefs[i].type,myprefs[i].name);
699 					continue;
700 					break;
701 			}
702 			if(NULL != err)
703 				g_printf("Unable to load pref '%s'\n",myprefs[i].name);
704 			if(dbg)g_printf("rp:Set '%s' to %d (%s)\n",myprefs[i].name, myprefs[i].val, myprefs[i].cval);
705 		}
706     p=get_pref("type_search");
707     if(0 == p->val){
708 			p=get_pref("case_search");
709 			p->val=0;
710 		}
711 
712     /* Check for errors and set default values if any */
713     check_sanity(mode);
714   }
715   else  { /* Init default keys on error */
716 	  int i;
717 
718 		for (i=0;NULL != keylist[i].name; ++i)
719 			set_pref_string(keylist[i].name,def_keyvals[i]);
720   }
721 	pref_mapper(NULL, PM_UPDATE);
722   g_key_file_free(rc_key);
723   g_free(rc_file);
724 }
725 
726 /* Read ~/.parcellite/actions into the treeview */
read_actions()727 static void read_actions()
728 {
729   /* Open the file for reading */
730   gchar* path = g_build_filename(g_get_user_data_dir(), ACTIONS_FILE, NULL);
731   FILE* actions_file = fopen(path, "rb");
732   g_free(path);
733   /* Check that it opened and begin read */
734   if (actions_file)
735   {
736     /* Keep a row reference */
737     GtkTreeIter row_iter;
738     /* Read the size of the first item */
739     gint size=0;
740     if(0 ==fread(&size, 4, 1, actions_file)) g_print("P1:0 Items read\n");
741     /* Continue reading until size is 0 */
742     while (size != 0)
743     {
744       /* Read name */
745       gchar* name = (gchar*)g_malloc(size + 1);
746       if(0 ==fread(name, size, 1, actions_file))  g_print("P1:0 Items read\n");
747       name[size] = '\0';
748       if(0 ==fread(&size, 4, 1, actions_file))  g_print("P1:0 Items read\n");
749       /* Read command */
750       gchar* command = (gchar*)g_malloc(size + 1);
751       if(0 ==fread(command, size, 1, actions_file))  g_print("P1:0 Items read\n");
752       command[size] = '\0';
753       if(0 ==fread(&size, 4, 1, actions_file))  g_print("P1:0 Items read\n");
754       /* Append the read action */
755       gtk_list_store_append(actions_list, &row_iter);
756       gtk_list_store_set(actions_list, &row_iter, 0, name, 1, command, -1);
757       g_free(name);
758       g_free(command);
759     }
760     fclose(actions_file);
761   }
762 }
763 
764 /* Save the actions treeview to ~/.local/share/parcellite/actions */
save_actions()765 static void save_actions()
766 {
767   /* Check config and data directories */
768   check_dirs();
769   /* Open the file for writing */
770   gchar* path = g_build_filename(g_get_user_data_dir(), ACTIONS_FILE, NULL);
771   FILE* actions_file = fopen(path, "wb");
772   g_free(path);
773   /* Check that it opened and begin write */
774   if (actions_file)
775   {
776     GtkTreeIter action_iter;
777     /* Get and check if there's a first iter */
778     if (gtk_tree_model_get_iter_first((GtkTreeModel*)actions_list, &action_iter))
779     {
780       do
781       {
782         /* Get name and command */
783         gchar *name, *command;
784         gtk_tree_model_get((GtkTreeModel*)actions_list, &action_iter, 0, &name, 1, &command, -1);
785         GString* s_name = g_string_new(name);
786         GString* s_command = g_string_new(command);
787         g_free(name);
788         g_free(command);
789         /* Check that there's text to save */
790         if ((s_name->len == 0) || (s_command->len == 0))
791         {
792           /* Free strings and skip iteration */
793           g_string_free(s_name, TRUE);
794           g_string_free(s_command, TRUE);
795           continue;
796         }
797         else
798         {
799           /* Save action */
800           fwrite(&(s_name->len), 4, 1, actions_file);
801           fputs(s_name->str, actions_file);
802           fwrite(&(s_command->len), 4, 1, actions_file);
803           fputs(s_command->str, actions_file);
804           /* Free strings */
805           g_string_free(s_name, TRUE);
806           g_string_free(s_command, TRUE);
807         }
808       }
809       while(gtk_tree_model_iter_next((GtkTreeModel*)actions_list, &action_iter));
810     }
811     /* End of file write */
812     gint end = 0;
813     fwrite(&end, 4, 1, actions_file);
814     fclose(actions_file);
815   }
816 }
817 
818 /* Called when clipboard checks are pressed */
check_toggled(GtkToggleButton * togglebutton,gpointer user_data)819 static void check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
820 {
821   if (gtk_toggle_button_get_active((GtkToggleButton*)get_pref_widget("use_copy")) &&
822       gtk_toggle_button_get_active((GtkToggleButton*)get_pref_widget("use_primary")))
823   {
824     /* Only allow synchronize option if both primary and clipboard are enabled */
825     gtk_widget_set_sensitive((GtkWidget*)get_pref_widget("synchronize"), TRUE);
826   }
827   else
828   {
829     /* Disable synchronize option */
830     gtk_toggle_button_set_active((GtkToggleButton*)get_pref_widget("synchronize"), FALSE);
831     gtk_widget_set_sensitive((GtkWidget*)get_pref_widget("synchronize"), FALSE);
832 
833   }
834 }
835 
search_toggled(GtkToggleButton * b,gpointer user)836 static void search_toggled(GtkToggleButton *b, gpointer user)
837 {
838 	struct pref_item *u,*p;
839 	u=get_pref_by_widget((GtkWidget *)user);
840 	p=get_pref("case_search");
841 
842   if(u == p){
843     if(TRUE == gtk_toggle_button_get_active((GtkToggleButton*)p->w) &&
844       FALSE == gtk_toggle_button_get_active((GtkToggleButton*)get_pref_widget("type_search")) )
845       gtk_toggle_button_set_active((GtkToggleButton*)get_pref_widget("type_search"), TRUE);
846   }else if( u == get_pref("type_search")){
847     if(FALSE == gtk_toggle_button_get_active((GtkToggleButton*)get_pref_widget("type_search")) &&
848       TRUE == gtk_toggle_button_get_active((GtkToggleButton*)p->w) )
849       gtk_toggle_button_set_active((GtkToggleButton*)p->w, FALSE);
850   }
851 
852 }
853 
854 /* Called when Add... button is clicked */
add_action(GtkButton * button,gpointer user_data)855 static void add_action(GtkButton *button, gpointer user_data)
856 {
857   /* Append new item */
858   GtkTreeIter row_iter;
859   gtk_list_store_append(actions_list, &row_iter);
860   /* Add a %s to the command */
861   gtk_list_store_set(actions_list, &row_iter, 1, "%s", -1);
862   /* Set the first column to editing */
863   GtkTreePath* row_path = gtk_tree_model_get_path((GtkTreeModel*)actions_list, &row_iter);
864   GtkTreeView* treeview = gtk_tree_selection_get_tree_view(actions_selection);
865   GtkTreeViewColumn* column = gtk_tree_view_get_column(treeview, 0);
866   gtk_tree_view_set_cursor(treeview, row_path, column, TRUE);
867   gtk_tree_path_free(row_path);
868 }
869 
870 /* Called when Remove button is clicked */
remove_action(GtkButton * button,gpointer user_data)871 static void remove_action(GtkButton *button, gpointer user_data)
872 {
873   GtkTreeIter sel_iter;
874   /* Check if selected */
875   if (gtk_tree_selection_get_selected(actions_selection, NULL, &sel_iter))
876   {
877     /* Delete selected and select next */
878     GtkTreePath* tree_path = gtk_tree_model_get_path((GtkTreeModel*)actions_list, &sel_iter);
879     gtk_list_store_remove(actions_list, &sel_iter);
880     gtk_tree_selection_select_path(actions_selection, tree_path);
881     /* Select previous if the last row was deleted */
882     if (!gtk_tree_selection_path_is_selected(actions_selection, tree_path))
883     {
884       if (gtk_tree_path_prev(tree_path))
885         gtk_tree_selection_select_path(actions_selection, tree_path);
886     }
887     gtk_tree_path_free(tree_path);
888   }
889 }
890 
891 /* Called when Up button is clicked */
move_action_up(GtkButton * button,gpointer user_data)892 static void move_action_up(GtkButton *button, gpointer user_data)
893 {
894   GtkTreeIter sel_iter;
895   /* Check if selected */
896   if (gtk_tree_selection_get_selected(actions_selection, NULL, &sel_iter))
897   {
898     /* Create path to previous row */
899     GtkTreePath* tree_path = gtk_tree_model_get_path((GtkTreeModel*)actions_list, &sel_iter);
900     /* Check if previous row exists */
901     if (gtk_tree_path_prev(tree_path))
902     {
903       /* Swap rows */
904       GtkTreeIter prev_iter;
905       gtk_tree_model_get_iter((GtkTreeModel*)actions_list, &prev_iter, tree_path);
906       gtk_list_store_swap(actions_list, &sel_iter, &prev_iter);
907     }
908     gtk_tree_path_free(tree_path);
909   }
910 }
911 
912 /* Called when Down button is clicked */
move_action_down(GtkButton * button,gpointer user_data)913 static void move_action_down(GtkButton *button, gpointer user_data)
914 {
915   GtkTreeIter sel_iter;
916   /* Check if selected */
917   if (gtk_tree_selection_get_selected(actions_selection, NULL, &sel_iter))
918   {
919     /* Create iter to next row */
920     GtkTreeIter next_iter = sel_iter;
921     /* Check if next row exists */
922     if (gtk_tree_model_iter_next((GtkTreeModel*)actions_list, &next_iter))
923       /* Swap rows */
924       gtk_list_store_swap(actions_list, &sel_iter, &next_iter);
925   }
926 }
927 
928 /* Called when delete key is pressed */
delete_key_pressed(GtkWidget * widget,GdkEventKey * event,gpointer user_data)929 static void delete_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
930 {
931   /* Check if DEL key was pressed (keyval: 65535) */
932   if (event->keyval == 65535)
933     remove_action(NULL, NULL);
934 }
935 
936 /* Called when a cell is edited */
edit_action(GtkCellRendererText * renderer,gchar * path,gchar * new_text,gpointer cell)937 static void edit_action(GtkCellRendererText *renderer, gchar *path, gchar *new_text,  gpointer cell)
938 {
939   GtkTreeIter sel_iter;
940   /* Check if selected */
941   if (gtk_tree_selection_get_selected(actions_selection, NULL, &sel_iter))
942   {
943     /* Apply changes */
944     gtk_list_store_set(actions_list, &sel_iter, GPOINTER_TO_INT(cell), new_text, -1);
945   }
946 }
947 
948 /***************************************************************************/
949 /** .
950 \n\b Arguments:
951 \n\b Returns:
952 ****************************************************************************/
update_pref_widgets(void)953 int update_pref_widgets( void)
954 {
955 	int i,rtn=0;
956 	for (i=0;NULL !=myprefs[i].desc; ++i){
957 		if(NULL != myprefs[i].name){
958 			switch (myprefs[i].type&PREF_TYPE_MASK){
959 				case PREF_TYPE_TOGGLE:
960 					gtk_toggle_button_set_active((GtkToggleButton*)myprefs[i].w, myprefs[i].val);
961 					break;
962 				case PREF_TYPE_SPIN:
963 					gtk_spin_button_set_value((GtkSpinButton*)myprefs[i].w, (gdouble)myprefs[i].val);
964 					break;
965 				case PREF_TYPE_COMBO:
966 					gtk_combo_box_set_active((GtkComboBox*)myprefs[i].w, myprefs[i].val - 1);
967 					break;
968 				case PREF_TYPE_ENTRY:
969 					gtk_entry_set_text((GtkEntry*)myprefs[i].w, myprefs[i].cval);
970 					break;
971 				case PREF_TYPE_SPACER:
972 					break;
973 				default:
974 					if(dbg)g_printf("apply_pref:don't know how to handle type %d\n",myprefs[i].type);
975 					rtn=-1;
976 					continue;
977 					break;
978 			}
979 			if(dbg)g_printf("up:Set '%s' to %d (%s)\n",myprefs[i].name, myprefs[i].val, myprefs[i].cval);
980 		}
981 
982 	}
983 
984 	return rtn;
985 }
986 /***************************************************************************/
987 /** .
988 \n\b Arguments:
989 section is the section to add, parent is the box to put it in.
990 \n\b Returns: -1 on error;
991 ****************************************************************************/
add_section(int sec,GtkWidget * parent)992 int add_section(int sec, GtkWidget *parent)
993 {
994 	int i,rtn=0;
995 	int single_st, single_is;
996 	gint connect;
997 	GtkWidget *hbox, *label, *child, *vbox, *alignment;
998 	GtkWidget* packit;
999 	vbox=parent;
1000 	single_st=single_is=0;
1001 	for (i=get_first_pref(sec);sec==myprefs[i].sec; ++i){
1002 		connect=1;
1003 		single_st=(myprefs[i].type&(PREF_TYPE_NMASK|PREF_TYPE_SINGLE_LINE)); /**deterimine if we are in single line  */
1004 
1005 		if(single_st && !single_is){ /**start of single line  */
1006 			hbox = gtk_hbox_new(FALSE, 2);  /**create hbox  */
1007 			/*g_printf("alloc %p hbox\n",hbox); */
1008 			single_is=1;
1009 		}
1010 
1011 		switch (myprefs[i].type&PREF_TYPE_MASK){
1012 			case PREF_TYPE_FRAME:/**must be first in section, since it sets vbox.  */
1013 					myprefs[i].w= gtk_frame_new(NULL);
1014 				  gtk_frame_set_shadow_type((GtkFrame*)	myprefs[i].w, GTK_SHADOW_NONE);
1015 				  label = gtk_label_new(NULL);							/**<b>myprefs[i].desc  */
1016 				  gtk_label_set_markup((GtkLabel*)label, _(myprefs[i].desc));
1017 				  gtk_frame_set_label_widget((GtkFrame*)	myprefs[i].w, label);
1018 				  alignment = gtk_alignment_new(0.50, 0.50, 1.0, 1.0);
1019 				  gtk_alignment_set_padding((GtkAlignment*)alignment, 12, 0, 12, 0);
1020 				  gtk_container_add((GtkContainer*)	myprefs[i].w, alignment);
1021 				  vbox = gtk_vbox_new(FALSE, 2);
1022 				  gtk_container_add((GtkContainer*)alignment, vbox);
1023 					gtk_box_pack_start((GtkBox*)parent,	myprefs[i].w,FALSE,FALSE,0);
1024 				continue;
1025 				break;
1026 			case PREF_TYPE_TOGGLE:
1027 				myprefs[i].w=gtk_check_button_new_with_mnemonic(_(myprefs[i].desc));
1028 				packit=myprefs[i].w;
1029 				break;
1030 
1031 			case PREF_TYPE_SPIN:
1032 				if(!single_is)
1033 					hbox = gtk_hbox_new(FALSE, 4);
1034   			label = gtk_label_new(NULL);
1035 				gtk_label_set_markup((GtkLabel*)label, _(myprefs[i].desc));
1036 				gtk_box_pack_start((GtkBox*)hbox, label, FALSE, FALSE, 0);
1037   			myprefs[i].w=gtk_spin_button_new((GtkAdjustment*)gtk_adjustment_new ( \
1038 				myprefs[i].val,myprefs[i].adj->lower,myprefs[i].adj->upper,myprefs[i].adj->step,myprefs[i].adj->page,0 ),10,0);
1039   			gtk_box_pack_start((GtkBox*)hbox, myprefs[i].w, FALSE, FALSE, 0);
1040   			gtk_spin_button_set_update_policy((GtkSpinButton*)myprefs[i].w, GTK_UPDATE_IF_VALID);
1041   			if(NULL != myprefs[i].tip) gtk_widget_set_tooltip_text(label, _(myprefs[i].tip));
1042   			packit=hbox;
1043 				break;
1044 
1045 			case PREF_TYPE_ENTRY:
1046 				if(!single_is)
1047 					hbox = gtk_hbox_new(TRUE, 4);
1048 			  label = gtk_label_new(NULL);
1049 				gtk_label_set_markup((GtkLabel*)label, _(myprefs[i].desc));
1050 			  gtk_misc_set_alignment((GtkMisc*)label, 0.0, 0.50);
1051 			  gtk_box_pack_start((GtkBox*)hbox, label, TRUE, TRUE, 0);
1052 			  myprefs[i].w = gtk_entry_new();
1053 			  gtk_entry_set_width_chars((GtkEntry*)myprefs[i].w, 10);
1054 				gtk_box_pack_end((GtkBox*)hbox,myprefs[i].w, TRUE, TRUE, 0);
1055 			  packit=hbox;
1056 				break;
1057 			case PREF_TYPE_COMBO: /**handled in show_preferences, only one so  */
1058 				continue;
1059 				break;
1060 			case PREF_TYPE_SPACER:
1061 				packit=myprefs[i].w=gtk_menu_item_new_with_label("");
1062 				child=gtk_bin_get_child((GtkBin *)packit);
1063 				gtk_misc_set_padding((GtkMisc *)child,0,0);
1064 				gtk_label_set_markup ((GtkLabel *)child, "<span size=\"0\"> </span>");
1065 				break;
1066 
1067 			default:
1068 				if(dbg)g_printf("add_sec:don't know how to handle type %d\n",myprefs[i].type);
1069 				rtn=-1;
1070 				continue;
1071 				break;
1072 		}
1073 
1074 		/**tooltips are set on the label of the spin box, not the widget and are handled above */
1075 		if(PREF_TYPE_SPIN != myprefs[i].type && NULL != myprefs[i].tip)
1076 		  gtk_widget_set_tooltip_text(myprefs[i].w, _(myprefs[i].tip));
1077 
1078 		if(NULL != myprefs[i].sig && connect)
1079 			g_signal_connect((GObject*)myprefs[i].w, myprefs[i].sig, (GCallback)myprefs[i].sfunc, myprefs[i].w);
1080 
1081 		if(dbg)g_printf("Packing %s\n",myprefs[i].name);
1082 		if(single_is){
1083 			if(packit != hbox){
1084 				/*g_printf("Packed a slwidget %p<-%p\n",hbox, myprefs[i].w); */
1085 				gtk_box_pack_start((GtkBox*)hbox,myprefs[i].w , FALSE, FALSE, 0);
1086 			}
1087 			/**else already packed above.  */
1088 		}	else
1089 																							/**espand fill padding  */
1090 			gtk_box_pack_start((GtkBox*)vbox, packit, TRUE, TRUE, 0);
1091 	/**check for end of single line.  */
1092 		single_st=(myprefs[i+1].type&(PREF_TYPE_NMASK|PREF_TYPE_SINGLE_LINE)); /**deterimine if we are in single line  */
1093 		if(single_is && !single_st){/**end of single line  */
1094 																							/**exp fill padding  */
1095 			gtk_box_pack_start((GtkBox*)vbox, hbox, TRUE, TRUE, 0);		/**pack the hbox into parent  */
1096 			/*g_printf("pack %p<-%p hbox\n",vbox,hbox); */
1097 			single_is=0;
1098 		}
1099 	}
1100 	if(dbg)g_printf("Ending on %d '%s'\n",i,myprefs[i].name);
1101 	return rtn;
1102 
1103 }
1104 
1105 /* Shows the preferences dialog on the given tab */
show_preferences(gint tab)1106 void show_preferences(gint tab)
1107 {
1108   /* Declare some variables */
1109   GtkWidget *frame,*label,*alignment,*hbox, *vbox;
1110 	struct pref_item *p;
1111   GtkTreeViewColumn *tree_column;
1112 	init_pref();
1113 
1114   /* Create the dialog */
1115   GtkWidget* dialog = gtk_dialog_new_with_buttons(_("Preferences"),     NULL,
1116                                                    (GTK_DIALOG_MODAL  + GTK_DIALOG_NO_SEPARATOR),
1117                                                     GTK_STOCK_CANCEL,   GTK_RESPONSE_REJECT,
1118                                                     GTK_STOCK_OK,       GTK_RESPONSE_ACCEPT, NULL);
1119 
1120   gtk_window_set_icon((GtkWindow*)dialog, gtk_widget_render_icon(dialog, GTK_STOCK_PREFERENCES, -1, NULL));
1121   gtk_window_set_resizable((GtkWindow*)dialog, FALSE);
1122 
1123   /* Create notebook */
1124   GtkWidget* notebook = gtk_notebook_new();
1125 #if GTK_CHECK_VERSION (2,14,0)
1126   gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area (GTK_DIALOG(dialog))), notebook, TRUE, TRUE, 2);
1127 #else
1128   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), notebook, TRUE, TRUE, 2);
1129 #endif
1130 
1131 /* Build the behavior page */
1132   GtkWidget* page_behavior = gtk_alignment_new(0.50, 0.50, 1.0, 1.0);
1133   gtk_alignment_set_padding((GtkAlignment*)page_behavior, 12, 6, 12, 6);
1134   gtk_notebook_append_page((GtkNotebook*)notebook, page_behavior, gtk_label_new(_("Behavior")));
1135   GtkWidget* vbox_behavior = gtk_vbox_new(FALSE, 12);
1136   gtk_container_add((GtkContainer*)page_behavior, vbox_behavior);
1137 
1138   /* Build the clipboards frame & copy section */
1139 	add_section(PREF_SEC_CLIP,vbox_behavior);
1140 	/* Build the history frame */
1141 	add_section(PREF_SEC_HIST,vbox_behavior);
1142   /* Build the miscellaneous frame */
1143 	add_section(PREF_SEC_MISC,vbox_behavior);
1144 
1145   /* Build the display page */
1146   GtkWidget* page_display = gtk_alignment_new(0.50, 0.50, 1.0, 1.0);
1147   gtk_alignment_set_padding((GtkAlignment*)page_display, 12, 6, 12, 6);
1148   gtk_notebook_append_page((GtkNotebook*)notebook, page_display, gtk_label_new(_("Display")));
1149   GtkWidget* vbox_display = gtk_vbox_new(FALSE, 12);
1150   gtk_container_add((GtkContainer*)page_display, vbox_display);
1151 
1152   /* Build the items frame */
1153 	add_section(PREF_SEC_DISP,vbox_display);
1154 
1155   /* Build the omitting frame */
1156   frame = gtk_frame_new(NULL);
1157   gtk_frame_set_shadow_type((GtkFrame*)frame, GTK_SHADOW_NONE);
1158   label = gtk_label_new(NULL);
1159   gtk_label_set_markup((GtkLabel*)label, _("<b>Omitting</b>"));
1160   gtk_frame_set_label_widget((GtkFrame*)frame, label);
1161   alignment = gtk_alignment_new(0.50, 0.50, 1.0, 1.0);
1162   gtk_alignment_set_padding((GtkAlignment*)alignment, 12, 0, 12, 0);
1163   gtk_container_add((GtkContainer*)frame, alignment);
1164   vbox = gtk_vbox_new(FALSE, 2);
1165   gtk_container_add((GtkContainer*)alignment, vbox);
1166 
1167 
1168   hbox = gtk_hbox_new(FALSE, 4);
1169   gtk_box_pack_start((GtkBox*)vbox, hbox, FALSE, FALSE, 0);
1170 	p=get_pref("ellipsize");
1171   label = gtk_label_new(_(p->desc));
1172   gtk_misc_set_alignment((GtkMisc*)label, 0.0, 0.50);
1173   gtk_box_pack_start((GtkBox*)hbox, label, FALSE, FALSE, 0);
1174   p->w = gtk_combo_box_new_text();
1175   gtk_combo_box_append_text((GtkComboBox*)p->w, _("Beginning"));
1176   gtk_combo_box_append_text((GtkComboBox*)p->w, _("Middle"));
1177   gtk_combo_box_append_text((GtkComboBox*)p->w, _("End"));
1178   gtk_box_pack_start((GtkBox*)hbox, p->w, FALSE, FALSE, 0);
1179   gtk_box_pack_start((GtkBox*)vbox_display, frame, FALSE, FALSE, 0);
1180 
1181 	/* Build the misc Display frame */
1182 	add_section(PREF_SEC_XMISC,vbox_display);
1183 
1184   /* Build the actions page */
1185   GtkWidget* page_actions = gtk_alignment_new(0.50, 0.50, 1.0, 1.0);
1186   gtk_alignment_set_padding((GtkAlignment*)page_actions, 6, 6, 6, 6);
1187   gtk_notebook_append_page((GtkNotebook*)notebook, page_actions, gtk_label_new(_("Actions")));
1188   GtkWidget* vbox_actions = gtk_vbox_new(FALSE, 6);
1189   gtk_container_add((GtkContainer*)page_actions, vbox_actions);
1190 
1191   /* Build the actions label */
1192   label = gtk_label_new(_("Control-click Parcellite's tray icon to use actions,"));
1193 	gtk_widget_set_tooltip_text(label, _("Use %% if you want a literal %. See the man page for gnuC printf (man 2 printf) for more information."));
1194   gtk_label_set_line_wrap((GtkLabel*)label, TRUE);
1195   gtk_misc_set_alignment((GtkMisc*)label, 0.0, 0.50);
1196   gtk_box_pack_start((GtkBox*)vbox_actions, label, FALSE, FALSE, 0);
1197 
1198   /* Build the actions treeview */
1199   GtkWidget* scrolled_window = gtk_scrolled_window_new(
1200                                (GtkAdjustment*)gtk_adjustment_new(0, 0, 0, 0, 0, 0),
1201                                (GtkAdjustment*)gtk_adjustment_new(0, 0, 0, 0, 0, 0));
1202 
1203   gtk_scrolled_window_set_policy((GtkScrolledWindow*)scrolled_window, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1204   gtk_scrolled_window_set_shadow_type((GtkScrolledWindow*)scrolled_window, GTK_SHADOW_ETCHED_OUT);
1205   GtkWidget* treeview = gtk_tree_view_new();
1206   gtk_tree_view_set_reorderable((GtkTreeView*)treeview, TRUE);
1207   gtk_tree_view_set_rules_hint((GtkTreeView*)treeview, TRUE);
1208   actions_list = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
1209   gtk_tree_view_set_model((GtkTreeView*)treeview, (GtkTreeModel*)actions_list);
1210   GtkCellRenderer* name_renderer = gtk_cell_renderer_text_new();
1211   g_object_set(name_renderer, "editable", TRUE, NULL);
1212   g_signal_connect((GObject*)name_renderer, "edited", (GCallback)edit_action, (gpointer)0);
1213 
1214 	label=gtk_label_new(_("Action"));
1215 	gtk_widget_set_tooltip_text(label,"This is the Action Name. \nDO NOT put commands here.");
1216 	gtk_widget_show (label);
1217   tree_column = gtk_tree_view_column_new_with_attributes(_("Action"), name_renderer, "text", 0, NULL);
1218   gtk_tree_view_column_set_widget(tree_column,label);
1219 
1220   gtk_tree_view_column_set_resizable(tree_column, TRUE);
1221   gtk_tree_view_append_column((GtkTreeView*)treeview, tree_column);
1222   GtkCellRenderer* command_renderer = gtk_cell_renderer_text_new();
1223   g_object_set(command_renderer, "editable", TRUE, NULL);
1224   g_object_set(command_renderer, "ellipsize-set", TRUE, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1225   g_signal_connect((GObject*)command_renderer, "edited", (GCallback)edit_action, (gpointer)1);
1226 
1227 	label=gtk_label_new(_("Command"));
1228 	gtk_widget_set_tooltip_text(label,"Put the full commands here. ex echo \"parcellite gave me %s\">>$HOME/ptest");
1229 	gtk_widget_show (label);
1230   tree_column = gtk_tree_view_column_new_with_attributes(_("Command"), command_renderer, "text", 1, NULL);
1231 	gtk_tree_view_column_set_widget(tree_column,label);
1232   gtk_tree_view_column_set_expand(tree_column, TRUE);
1233   gtk_tree_view_append_column((GtkTreeView*)treeview, tree_column);
1234   gtk_container_add((GtkContainer*)scrolled_window, treeview);
1235   gtk_box_pack_start((GtkBox*)vbox_actions, scrolled_window, TRUE, TRUE, 0);
1236 
1237   /* Edit selection and connect treeview related signals */
1238   actions_selection = gtk_tree_view_get_selection((GtkTreeView*)treeview);
1239   gtk_tree_selection_set_mode(actions_selection, GTK_SELECTION_BROWSE);
1240   g_signal_connect((GObject*)treeview, "key-press-event", (GCallback)delete_key_pressed, NULL);
1241 
1242   /* Build the buttons */
1243   GtkWidget* hbbox = gtk_hbutton_box_new();
1244   gtk_box_set_spacing((GtkBox*)hbbox, 6);
1245   gtk_button_box_set_layout((GtkButtonBox*)hbbox, GTK_BUTTONBOX_START);
1246   GtkWidget* add_button = gtk_button_new_with_label(_("Add..."));
1247   gtk_button_set_image((GtkButton*)add_button, gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU));
1248   g_signal_connect((GObject*)add_button, "clicked", (GCallback)add_action, NULL);
1249   gtk_box_pack_start((GtkBox*)hbbox, add_button, FALSE, TRUE, 0);
1250   GtkWidget* remove_button = gtk_button_new_with_label(_("Remove"));
1251   gtk_button_set_image((GtkButton*)remove_button, gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU));
1252   g_signal_connect((GObject*)remove_button, "clicked", (GCallback)remove_action, NULL);
1253   gtk_box_pack_start((GtkBox*)hbbox, remove_button, FALSE, TRUE, 0);
1254   GtkWidget* up_button = gtk_button_new();
1255   gtk_button_set_image((GtkButton*)up_button, gtk_image_new_from_stock(GTK_STOCK_GO_UP, GTK_ICON_SIZE_MENU));
1256   g_signal_connect((GObject*)up_button, "clicked", (GCallback)move_action_up, NULL);
1257   gtk_box_pack_start((GtkBox*)hbbox, up_button, FALSE, TRUE, 0);
1258   GtkWidget* down_button = gtk_button_new();
1259   gtk_button_set_image((GtkButton*)down_button, gtk_image_new_from_stock(GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_MENU));
1260   g_signal_connect((GObject*)down_button, "clicked", (GCallback)move_action_down, NULL);
1261   gtk_box_pack_start((GtkBox*)hbbox, down_button, FALSE, TRUE, 0);
1262   gtk_box_pack_start((GtkBox*)vbox_actions, hbbox, FALSE, FALSE, 0);
1263 
1264   /* Build the hotkeys page */
1265   GtkWidget* page_extras = gtk_alignment_new(0.50, 0.50, 1.0, 1.0);
1266   gtk_alignment_set_padding((GtkAlignment*)page_extras, 12, 6, 12, 6);
1267   gtk_notebook_append_page((GtkNotebook*)notebook, page_extras, gtk_label_new(_("Hotkeys")));
1268   GtkWidget* vbox_extras = gtk_vbox_new(FALSE, 12);
1269   gtk_container_add((GtkContainer*)page_extras, vbox_extras);
1270 
1271   /* Build the hotkeys frame */
1272   frame = gtk_frame_new(NULL);
1273   gtk_frame_set_shadow_type((GtkFrame*)frame, GTK_SHADOW_NONE);
1274   label = gtk_label_new(NULL);
1275   gtk_label_set_markup((GtkLabel*)label, _("<b>Hotkeys</b>"));
1276   gtk_frame_set_label_widget((GtkFrame*)frame, label);
1277   alignment = gtk_alignment_new(0.50, 0.50, 1.0, 1.0);
1278   gtk_alignment_set_padding((GtkAlignment*)alignment, 12, 0, 12, 0);
1279   gtk_container_add((GtkContainer*)frame, alignment);
1280   vbox = gtk_vbox_new(FALSE, 2);
1281   gtk_container_add((GtkContainer*)alignment, vbox);
1282 	add_section(PREF_SEC_ACT,vbox);
1283 	gtk_box_pack_start((GtkBox*)vbox_extras,frame,FALSE,FALSE,0);
1284 
1285   /* Make widgets reflect current preferences */
1286 	update_pref_widgets();
1287 
1288   /* Read actions */
1289   read_actions();
1290 
1291   /* Run the dialog */
1292   gtk_widget_show_all(dialog);
1293   gtk_notebook_set_current_page((GtkNotebook*)notebook, tab);
1294   if (gtk_dialog_run((GtkDialog*)dialog) == GTK_RESPONSE_ACCEPT)
1295   {
1296     /* Apply and save preferences */
1297     apply_preferences();
1298     save_preferences();
1299     save_actions();
1300   }
1301   gtk_widget_destroy(dialog);
1302 }
1303 
1304