1 /*
2  * LoadBindings
3  * Copyright (C) 2007 by Martin Sevior
4  * Copyright (C) 2007 by Marc 'Foddex' Oude Kotte
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  * 02110-1301 USA.
20  */
21 
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <sys/types.h>
26 #include <errno.h>
27 #include <sys/stat.h>
28 #include <glib.h>
29 #include <set>
30 #include <libxml/xmlreader.h>
31 #include <libxml/xmlerror.h>
32 #if !defined(LIBXML_READER_ENABLED)
33 #error LIBXML reader not enabled! Recompile your libxml library binaries!
34 #endif
35 
36 #include "xap_Module.h"
37 #include "xap_App.h"
38 #include "xap_Frame.h"
39 #include "fv_View.h"
40 #include "ev_EditMethod.h"
41 #include "ie_imp.h"
42 #include "ie_exp.h"
43 #include "ie_types.h"
44 #include "ap_Convert.h"
45 #include "ap_EditMethods.h"
46 #include "ev_EditBits.h"
47 #include "LoadBindings.h"
48 #include "ap_LoadBindings.h"
49 #include "ut_path.h"
50 #include "ap_Menu_Id.h"
51 #include "ev_Menu_Actions.h"
52 #include "ev_Menu.h"
53 #include "ev_Menu_Layouts.h"
54 #include "ev_Menu_Labels.h"
55 #include "xap_Menu_Layouts.h"
56 #include "xap_DialogFactory.h"
57 #include "xap_Dlg_FileOpenSaveAs.h"
58 #include "xap_Dialog_Id.h"
59 #include "ev_NamedVirtualKey.h"
60 
61 #ifdef ABI_PLUGIN_BUILTIN
62 #define abi_plugin_register abipgn_loadbindings_register
63 #define abi_plugin_unregister abipgn_loadbindings_unregister
64 #define abi_plugin_supports_version abipgn_loadbindings_supports_version
65 // dll exports break static linking
66 #define ABI_BUILTIN_FAR_CALL extern "C"
67 #else
68 #define ABI_BUILTIN_FAR_CALL ABI_FAR_CALL
69 ABI_PLUGIN_DECLARE (LoadBindings)
70 #endif
71 
72 #define RES_TO_STATUS(a) ((a) ? 0 : -1)
73 
74 // -----------------------------------------------------------------------
75 //      Edit methods and menu items
76 // -----------------------------------------------------------------------
77 
78 static bool LoadBindingsDlg_invoke (AV_View * v, EV_EditMethodCallData * d);
79 static bool LoadBindingsFromURI_invoke (AV_View * v, EV_EditMethodCallData * d);
80 static bool LoadBindingsFromMemory_invoke (AV_View * v, EV_EditMethodCallData * d);
81 static bool DumpEditMethods_invoke (AV_View * v, EV_EditMethodCallData * d);
82 static bool SaveBindings_invoke(AV_View * v, EV_EditMethodCallData* d);
83 
84 #if defined(DEBUG)
85 static XAP_Menu_Id loadBindingID;
86 static XAP_Menu_Id dumpEditMethodsID;
87 static XAP_Menu_Id saveBindingID;
88 static const char * szLoadBinding = "Load keybindings";
89 static const char * szLoadBindingStatus = "Load keybindings from a file";
90 static const char * szDumpEditMethods = "Dump edit methods";
91 static const char * szDumpEditMethodsStatus = "Dump edit methods to your console";
92 static const char * szSaveBinding = "Save keybindings";
93 static const char * szSaveBindingStatus = "Save keybindings to your profile directory";
94 #endif
95 
LoadBindings_registerMethod()96 static void LoadBindings_registerMethod ()
97 {
98 	// prepare
99 	XAP_App *pApp = XAP_App::getApp ();
100 	EV_EditMethodContainer *pEMC = pApp->getEditMethodContainer ();
101 	EV_EditMethod *myEditMethod;
102 
103 	// add edit methods
104 	myEditMethod = new EV_EditMethod ("com.abisource.abiword.loadbindings.loadBindingsDlg", LoadBindingsDlg_invoke, 0, "" );
105 	pEMC->addEditMethod (myEditMethod);
106 
107 	myEditMethod = new EV_EditMethod ("com.abisource.abiword.loadbindings.fromURI", LoadBindingsFromURI_invoke, 0, "" );
108 	pEMC->addEditMethod (myEditMethod);
109 
110 	myEditMethod = new EV_EditMethod ("com.abisource.abiword.loadbindings.fromMemory", LoadBindingsFromMemory_invoke, 0, "" );
111 	pEMC->addEditMethod (myEditMethod);
112 
113 	myEditMethod = new EV_EditMethod ("com.abisource.abiword.loadbindings.dumpEditMethods", DumpEditMethods_invoke, 0, "" );
114 	pEMC->addEditMethod (myEditMethod);
115 
116 	myEditMethod = new EV_EditMethod ("com.abisource.abiword.loadbindings.saveCurrent", SaveBindings_invoke, 0, "" );
117 	pEMC->addEditMethod (myEditMethod);
118 
119 #if defined(DEBUG)
120 	XAP_Menu_Factory* pFact = pApp->getMenuFactory();
121 
122 	// add menu item
123 	loadBindingID = pFact->addNewMenuAfter("Main",NULL,AP_MENU_ID_FMT_STYLIST,EV_MLF_Normal);	// named _FMT_, but actually in tools menu
124 	pFact->addNewLabel(NULL,loadBindingID,szLoadBinding,szLoadBindingStatus);
125 	EV_Menu_Action* myLoadBindingAction = new EV_Menu_Action(
126 		loadBindingID,          // id that the layout said we could use
127 		0,                      // no, we don't have a sub menu.
128 		1,                      // yes, we raise a dialog.
129 		0,                      // no, we don't have a checkbox.
130 		0,                      // no radio buttons for me, thank you
131 		"com.abisource.abiword.loadbindings.loadBindingsDlg",  // callback function to call.
132 		NULL,                   // don't know/care what this is for
133 		NULL                    // don't know/care what this is for
134 		);
135     pApp->getMenuActionSet()->addAction(myLoadBindingAction);
136 
137 	// add menu item
138 	dumpEditMethodsID = pFact->addNewMenuAfter("Main",NULL,loadBindingID,EV_MLF_Normal);
139 	pFact->addNewLabel(NULL,dumpEditMethodsID,szDumpEditMethods,szDumpEditMethodsStatus);
140 	EV_Menu_Action* myDumpEditMethodsAction = new EV_Menu_Action(
141 		dumpEditMethodsID,        // id that the layout said we could use
142 		0,                      // no, we don't have a sub menu.
143 		0,                      // no, we don't raise a dialog.
144 		0,                      // no, we don't have a checkbox.
145 		0,                      // no radio buttons for me, thank you
146 		"com.abisource.abiword.loadbindings.dumpEditMethods",  // callback function to call.
147 		NULL,                   // don't know/care what this is for
148 		NULL                    // don't know/care what this is for
149 		);
150     pApp->getMenuActionSet()->addAction(myDumpEditMethodsAction);
151 
152 	// add menu item
153 	saveBindingID = pFact->addNewMenuAfter("Main",NULL,dumpEditMethodsID,EV_MLF_Normal);
154 	pFact->addNewLabel(NULL,saveBindingID,szSaveBinding,szSaveBindingStatus);
155 	EV_Menu_Action* mySaveBindingAction = new EV_Menu_Action(
156 		saveBindingID,          // id that the layout said we could use
157 		0,                      // no, we don't have a sub menu.
158 		0,                      // no, we don't raise a dialog.
159 		0,                      // no, we don't have a checkbox.
160 		0,                      // no radio buttons for me, thank you
161 		"com.abisource.abiword.loadbindings.saveCurrent",  // callback function to call.
162 		NULL,                   // don't know/care what this is for
163 		NULL                    // don't know/care what this is for
164 		);
165     pApp->getMenuActionSet()->addAction(mySaveBindingAction);
166 #endif
167 }
168 
LoadBindings_RemoveFromMethods()169 static void LoadBindings_RemoveFromMethods ()
170 {
171 	// prepare
172 	XAP_App *pApp = XAP_App::getApp ();
173 	EV_EditMethodContainer *pEMC = pApp->getEditMethodContainer ();
174 	EV_EditMethod *pEM;
175 
176 	// remove edit methods
177 	pEM = ev_EditMethod_lookup ("com.abisource.abiword.loadbindings.dumpEditMethods");
178 	pEMC->removeEditMethod (pEM);
179 	DELETEP (pEM);
180 
181 	pEM = ev_EditMethod_lookup ("com.abisource.abiword.loadbindings.fromMemory");
182 	pEMC->removeEditMethod (pEM);
183 	DELETEP (pEM);
184 
185 	pEM = ev_EditMethod_lookup ("com.abisource.abiword.loadbindings.fromURI");
186 	pEMC->removeEditMethod (pEM);
187 	DELETEP (pEM);
188 
189 	pEM = ev_EditMethod_lookup ("com.abisource.abiword.loadbindings.loadBindingsDlg");
190 	pEMC->removeEditMethod (pEM);
191 	DELETEP (pEM);
192 
193 	pEM = ev_EditMethod_lookup ("com.abisource.abiword.loadbindings.saveCurrent");
194 	pEMC->removeEditMethod (pEM);
195 	DELETEP (pEM);
196 
197 #if defined(DEBUG)
198 	XAP_Menu_Factory * pFact = pApp->getMenuFactory();
199 	// remove menu items
200 	pFact->removeMenuItem("Main",NULL,loadBindingID);
201 	pFact->removeMenuItem("Main",NULL,dumpEditMethodsID);
202 	pFact->removeMenuItem("Main",NULL,saveBindingID);
203 #endif
204 }
205 
206 // -----------------------------------------------------------------------
207 //      Abiword Plugin Interface
208 // -----------------------------------------------------------------------
209 
LoadKeybindings(const char * uri)210 static void LoadKeybindings(const char* uri)
211 {
212 	UT_return_if_fail(uri);
213 	UT_DEBUGMSG(("[LoadBindings] trying file %s\n", uri));
214 
215 	// find out if the file exists at all
216 	GsfInput* in = UT_go_file_open(uri, NULL);
217 	if (in)
218 	{
219 		// it seems to exist, cleanup after ourselves ...
220 		g_object_unref(G_OBJECT(in));
221 		// ... and let LoadBindings_invoke do its thing
222 		UT_DEBUGMSG(("[LoadBindings] invoking loader on %s\n", uri));
223 		EV_EditMethodCallData userFileData(uri, strlen(uri));
224 		LoadBindingsFromURI_invoke(NULL, &userFileData);
225 	}
226 }
227 
abi_plugin_register(XAP_ModuleInfo * mi)228 ABI_BUILTIN_FAR_CALL int abi_plugin_register (XAP_ModuleInfo * mi)
229 {
230 	mi->name = "LoadBindings";
231 	mi->desc = "This allows Keybindings to be loaded from an Ascii file";
232 	mi->version = ABI_VERSION_STRING;
233 	mi->author =
234 		"Original version by Martin Sevior <msevior@physics.unimelb.edu.au>\n"
235 		"Refactored to support XML by Marc 'Foddex' Oude Kotte <foddex@foddex.net>";
236 	mi->usage = "LoadBindingsDlg_invoke";
237 
238 	LoadBindings_registerMethod ();
239 
240 	// load the keybindings.xml file from the application directory, if present
241 	UT_UTF8String appFile = XAP_App::getApp()->getAbiSuiteAppDir();
242 	appFile += "/keybindings.xml";
243 	char * appUri = UT_go_filename_to_uri(appFile.utf8_str());
244 	if (appUri)
245 	{
246 		LoadKeybindings(appUri);
247 		FREEP(appUri);
248 	}
249 
250 	// load the keybindings.xml file from the user's home directory, if present
251 	UT_UTF8String userFile = XAP_App::getApp()->getUserPrivateDirectory();
252 	userFile += "/keybindings.xml";
253 	char * userUri = UT_go_filename_to_uri(userFile.utf8_str());
254 	if (userUri)
255 	{
256 		LoadKeybindings(userUri);
257 		FREEP(userUri);
258 	}
259 
260 	return 1;
261 }
262 
abi_plugin_unregister(XAP_ModuleInfo * mi)263 ABI_BUILTIN_FAR_CALL int abi_plugin_unregister (XAP_ModuleInfo * mi)
264 {
265 	mi->name = 0;
266 	mi->desc = 0;
267 	mi->version = 0;
268 	mi->author = 0;
269 	mi->usage = 0;
270 
271 	LoadBindings_RemoveFromMethods ();
272 	return 1;
273 }
274 
abi_plugin_supports_version(UT_uint32,UT_uint32,UT_uint32)275 ABI_BUILTIN_FAR_CALL int abi_plugin_supports_version (UT_uint32 /*major*/, UT_uint32 /*minor*/, UT_uint32 /*release*/)
276 {
277 	return 1;
278 }
279 
280 // -----------------------------------------------------------------------
281 //     LoadBindings Invocation Code
282 // -----------------------------------------------------------------------
283 
284 // Utility function for Abiword that first shows an open file dialog,
285 // then automatically invokes the LoadBindings and SetBindings functions
LoadBindingsDlg_invoke(AV_View *,EV_EditMethodCallData *)286 static bool LoadBindingsDlg_invoke (AV_View *, EV_EditMethodCallData * /*d*/)
287 {
288 	// ask user what file to open
289 	XAP_Frame *pFrame = XAP_App::getApp()->getLastFocussedFrame();
290 	XAP_DialogFactory * pDialogFactory = static_cast<XAP_DialogFactory *>(XAP_App::getApp()->getDialogFactory());
291 	XAP_Dialog_FileOpenSaveAs * pDialog = static_cast<XAP_Dialog_FileOpenSaveAs *>(pDialogFactory->requestDialog(XAP_DIALOG_ID_FILE_OPEN));
292 	UT_return_val_if_fail (pDialog, false);
293 	pDialog->setSuggestFilename(false);
294 	pDialog->runModal( pFrame );
295 	XAP_Dialog_FileOpenSaveAs::tAnswer ans = pDialog->getAnswer();
296 	bool bOK = (ans == XAP_Dialog_FileOpenSaveAs::a_OK);
297 	std::string resultPathname = bOK ? pDialog->getPathname() : "";
298 	pDialogFactory->releaseDialog(pDialog);
299 
300 	// call LoadBindings_invoke
301 	EV_EditMethodCallData d2( resultPathname.c_str(), UT_uint32(resultPathname.size()) );
302 	return LoadBindingsFromURI_invoke( NULL, &d2 );
303 }
304 
305 // Loads the keybindings from an XML file to a named set of keybindings.
LoadBindingsFromURI_invoke(AV_View *,EV_EditMethodCallData * d)306 static bool LoadBindingsFromURI_invoke (AV_View *, EV_EditMethodCallData * d)
307 {
308 	LoadBindings loadBindings(d, FROM_URI);
309 	if (!loadBindings.Load()) return false;
310 	return loadBindings.Set();
311 }
312 
313 // Loads the keybindings from memory to a named set of keybindings.
LoadBindingsFromMemory_invoke(AV_View *,EV_EditMethodCallData * d)314 static bool LoadBindingsFromMemory_invoke (AV_View *, EV_EditMethodCallData * d)
315 {
316 	LoadBindings loadBindings(d, FROM_MEMORY);
317 	if (!loadBindings.Load()) return false;
318 	return loadBindings.Set();
319 }
320 
compareEditMethods(const EV_EditMethod * lhs,const EV_EditMethod * rhs)321 static bool compareEditMethods( const EV_EditMethod* lhs, const EV_EditMethod* rhs )
322 {
323 	return strcmp( lhs->getName(), rhs->getName() ) < 0;
324 }
325 
326 // Dumps (debug only) all edit methods on the console
DumpEditMethods_invoke(AV_View *,EV_EditMethodCallData *)327 static bool DumpEditMethods_invoke(AV_View *, EV_EditMethodCallData *)
328 {
329 	EV_EditMethodContainer *pEMC = XAP_App::getApp()->getEditMethodContainer ();
330 
331 	// fill vector with bindable edit methods
332 	std::vector<EV_EditMethod*> list;
333 	for (UT_uint32 i=0; i<pEMC->countEditMethods(); ++i) {
334 		EV_EditMethod* method = pEMC->getNthEditMethod(i);
335 		if (method) {
336 			if (!(method->getType() & EV_EMT_REQUIREDATA)) {
337 				list.push_back( method );
338 			}
339 		}
340 	}
341 
342 	// qsort them by name
343 	std::sort( list.begin(), list.end(), compareEditMethods );
344 
345 	// print them
346 	printf("%zu bindable edit methods (don't require data)\n", list.size());
347 	for (size_t i=0; i<list.size(); ++i) printf("%s\n", list[i]->getName());
348 
349 	return true;
350 }
351 
SaveBindings_invoke(AV_View *,EV_EditMethodCallData * d)352 static bool SaveBindings_invoke(AV_View * /*v*/, EV_EditMethodCallData* d)
353 {
354 	// get binding set
355 	AP_BindingSet* pBSet = static_cast<AP_BindingSet *>(XAP_App::getApp()->getBindingSet());
356 	UT_return_val_if_fail(pBSet,false);
357 
358 	// get current map
359 	const char* curMapName = XAP_App::getApp()->getInputMode();
360 	UT_return_val_if_fail(curMapName,false);
361 	EV_EditBindingMap* curMap = pBSet->getMap( curMapName );
362 	UT_return_val_if_fail(curMap,false);
363 
364 	// get target filename
365 	std::string targetFilename;
366 	if (d->m_pData && d->m_dataLength) {
367 		UT_UCS4String ucs4(reinterpret_cast<const UT_UCS4Char *>(d->m_pData),d->m_dataLength);
368 		targetFilename = ucs4.utf8_str();
369 	} else {
370 		targetFilename = XAP_App::getApp()->getUserPrivateDirectory();
371 		targetFilename += '/';
372 		targetFilename += "keybindings-";
373 		targetFilename += curMapName;
374 		targetFilename += "-";
375 		targetFilename += UT_UTF8String_sprintf( "%u", time(0) ).utf8_str();
376 		targetFilename += ".xml";
377 	}
378 	UT_DEBUGMSG(("Saving current keybindings %s to %s\n", curMapName, targetFilename.c_str()));
379 
380 	// open file
381 	GsfOutput* file;
382 	file = UT_go_file_create( targetFilename.c_str(), NULL );
383 	if (!file) {
384 		const char* URI = UT_go_filename_to_uri(targetFilename.c_str());
385 		file = UT_go_file_create( URI, NULL );
386 		FREEP(URI);
387 	}
388 	UT_return_val_if_fail(file,false);
389 
390 	// generate file
391 	std::string contents;
392 	contents += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
393 	contents += UT_UTF8String_sprintf(
394 		"<editbindings name=\"%s\" mode=\"replace\" "
395 		"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
396 		"xsi:noNamespaceSchemaLocation=\"http://www.abisource.com/abiword-keybindings-1.0.xsd\">\n",
397 		curMapName ).utf8_str();
398 	std::map<EV_EditBits,const char*> map;
399 	curMap->getAll( map );
400 	std::set<EV_EditBits> doneMouseContexts;
401 
402 	// walk in reverse order, so mousecontexts come first
403 	// this is because our XSD demands mousecontexts come before keystrokes
404 	for (std::map<EV_EditBits,const char*>::reverse_iterator it=map.rbegin(); it!=map.rend(); ++it) {
405 		// prepare general stuff
406 		EV_EditBits bits = (*it).first;
407 		const char* methodName = (*it).second;
408 		std::string modAtt;
409 		if (bits & EV_EMS_CONTROL) modAtt += "control=\"true\"";
410 		if (bits & EV_EMS_SHIFT) {
411 			if (modAtt.size()) modAtt += " ";	// yes, I'm anal
412 			modAtt += " shift=\"true\"";
413 		}
414 		if (bits & EV_EMS_ALT) {
415 			if (modAtt.size()) modAtt += " ";
416 			modAtt += " alt=\"true\"";
417 		}
418 
419 		// if it's a mouse context
420 		if (EV_IsMouse(bits)) {
421 			// have we seen this one yet in another run?
422 			if (doneMouseContexts.find( bits & ~EV_EMO__MASK__ )!=doneMouseContexts.end()) continue;
423 			doneMouseContexts.insert( bits & ~EV_EMO__MASK__ );
424 
425 			// get button
426 			int button = 0;
427 			switch (bits & EV_EMB__MASK__) {
428 				case EV_EMB_BUTTON0: button = 0; break;
429 				case EV_EMB_BUTTON1: button = 1; break;
430 				case EV_EMB_BUTTON2: button = 2; break;
431 				case EV_EMB_BUTTON3: button = 3; break;
432 				case EV_EMB_BUTTON4: button = 4; break;
433 				case EV_EMB_BUTTON5: button = 5; break;
434 				default:
435 					UT_DEBUGMSG(("XXX: bits=0x%x, EMB=%x\n", bits, bits & EV_EMB__MASK__));
436 					UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
437 					break;
438 			}
439 			// get context
440 			std::string context;
441 			switch (bits & EV_EMC__MASK__) {
442 				case EV_EMC_UNKNOWN: 		context = "EV_EMC_UNKNOWN"; break;
443 				case EV_EMC_TEXT:			context = "EV_EMC_TEXT"; break;
444 				case EV_EMC_LEFTOFTEXT:		context = "EV_EMC_LEFTOFTEXT"; break;
445 				case EV_EMC_MISSPELLEDTEXT:	context = "EV_EMC_MISSPELLEDTEXT"; break;
446 				case EV_EMC_IMAGE:			context = "EV_EMC_IMAGE"; break;
447 				case EV_EMC_IMAGESIZE:		context = "EV_EMC_IMAGESIZE"; break;
448 				case EV_EMC_FIELD:			context = "EV_EMC_FIELD"; break;
449 				case EV_EMC_HYPERLINK:		context = "EV_EMC_HYPERLINK"; break;
450 				case EV_EMC_RIGHTOFTEXT:	context = "EV_EMC_RIGHTOFTEXT"; break;
451 				case EV_EMC_REVISION:		context = "EV_EMC_REVISION"; break;
452 				case EV_EMC_VLINE:			context = "EV_EMC_VLINE"; break;
453 				case EV_EMC_HLINE:			context = "EV_EMC_HLINE"; break;
454 				case EV_EMC_FRAME:			context = "EV_EMC_FRAME"; break;
455 				case EV_EMC_VISUALTEXTDRAG:	context = "EV_EMC_VISUALTEXTDRAG"; break;
456 				case EV_EMC_TOPCELL:		context = "EV_EMC_TOPCELL"; break;
457 				case EV_EMC_TOC:			context = "EV_EMC_TOC"; break;
458 				case EV_EMC_POSOBJECT:		context = "EV_EMC_POSOBJECT"; break;
459 				case EV_EMC_MATH:			context = "EV_EMC_MATH"; break;
460 				case EV_EMC_EMBED:			context = "EV_EMC_EMBED"; break;
461 				default:
462 					UT_DEBUGMSG(("XXX: bits=0x%x, EMC=%x\n", bits, bits & EV_EMC__MASK__));
463 					UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
464 					break;
465 			}
466 			contents += UT_UTF8String_sprintf(
467 					"\t<mousecontext context=\"%s\" button=\"%d\"%s%s>\n",
468 					/* context */ context.c_str(),
469 					/* button */ button,
470 					/* modifiers */ modAtt.size()?" ":"", modAtt.c_str()).utf8_str();
471 			size_t seen = 0;
472 			for (EV_EditMouseOp op=EV_EMO_SINGLECLICK; op<=EV_EMO_DOUBLERELEASE; op+=EV_EMO_SINGLECLICK) {
473 				std::map<EV_EditBits,const char*>::iterator it2 = map.find( (bits & ~EV_EMO__MASK__)|op );
474 				if (it2!=map.end()) {
475 
476 					// get type
477 					std::string type;
478 					switch (op) {
479 						case EV_EMO_SINGLECLICK:	type = "click"; break;
480 						case EV_EMO_DOUBLECLICK:	type = "doubleclick"; break;
481 						case EV_EMO_DRAG:			type = "drag"; break;
482 						case EV_EMO_DOUBLEDRAG:		type = "doubledrag"; break;
483 						case EV_EMO_RELEASE:		type = "release"; break;
484 						case EV_EMO_DOUBLERELEASE:	type = "doublerelease"; break;
485 						default:
486 							UT_DEBUGMSG(("XXX: bits=0x%x, EMO=%x\n", bits, bits & EV_EMO__MASK__));
487 							UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
488 							break;
489 
490 					}
491 					// add to contents
492 					contents += UT_UTF8String_sprintf(
493 						"\t\t<operation type=\"%s\" handler=\"%s\"/>\n",
494 						/* type */ type.c_str(),
495 						/* handler */ (*it2).second ).utf8_str();
496 					++seen;
497 				}
498 			}
499 			UT_ASSERT(seen>0);
500 			contents += "\t</mousecontext>\n";
501 		}
502 		// if it's a key
503 		else if (EV_IsKeyboard(bits)) {
504 			std::string key;
505 			if (bits & EV_EKP_NAMEDKEY) {
506 				const char* keyName = EV_NamedVirtualKey::getName( bits & 0xFF );
507 				if (!keyName) continue; // whoopsie?
508 				key = keyName;
509 			} else {
510 				key = UT_UTF8String_sprintf("0x%x", bits & 0xFF).utf8_str();
511 			}
512 			contents += UT_UTF8String_sprintf("\t<keystroke key=\"%s\" handler=\"%s\"%s%s/>\n",
513 				key.c_str(), methodName, modAtt.size()?" ":"", modAtt.c_str() ).utf8_str();
514 		}
515 		// whuh?
516 		else {
517 			UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
518 		}
519 	}
520 	contents += "</editbindings>\n";
521 
522 	// save file
523 	gsf_output_write(file, contents.size(), reinterpret_cast<const guint8*>( contents.c_str() ));
524 	gsf_output_close(file);
525 	g_object_unref(G_OBJECT(file));
526 
527 	// done!
528 	return true;
529 }
530 
531 // -----------------------------------------------------------------------
532 // 		LoadBindings class
533 // -----------------------------------------------------------------------
534 
LoadBindings(EV_EditMethodCallData * d,_FROM_URI)535 LoadBindings::LoadBindings (EV_EditMethodCallData * d, _FROM_URI )
536 : m_pApp( XAP_App::getApp() )
537 , m_pXMLDoc( NULL )
538 , m_bReplace( false )
539 {
540 	UT_UCS4String ucs4(reinterpret_cast<const UT_UCS4Char *>(d->m_pData),d->m_dataLength);
541 	const char* input = ucs4.utf8_str();
542 
543 	// check for regular filename
544 	struct stat buf;
545 	GsfInput* in = NULL;
546 	if (stat(input, &buf)==0) {
547 		m_pXMLDoc = xmlReadFile( input, NULL, XML_PARSE_NOBLANKS );
548 	}
549 	// check for URI
550 	else if ( (in = UT_go_file_open(input, NULL)) ) {
551 		size_t fileSize = gsf_input_size(in);
552 		guint8 const* contents = gsf_input_read(in, fileSize, NULL);
553 		if (contents) {
554 			m_pXMLDoc = xmlReadMemory( reinterpret_cast<const char*>(contents), fileSize, "", NULL, XML_PARSE_NOBLANKS );
555 		}
556 		g_object_unref(G_OBJECT(in));
557 	}
558 }
559 
LoadBindings(EV_EditMethodCallData * d,_FROM_MEMORY)560 LoadBindings::LoadBindings (EV_EditMethodCallData * d, _FROM_MEMORY )
561 : m_pApp( XAP_App::getApp() )
562 , m_pXMLDoc( NULL )
563 , m_bReplace( false )
564 {
565 	UT_UCS4String ucs4(reinterpret_cast<const UT_UCS4Char *>(d->m_pData),d->m_dataLength);
566 	const char* input = ucs4.utf8_str();
567 	m_pXMLDoc = xmlReadMemory( input, strlen(input), "", NULL, XML_PARSE_NOBLANKS );
568 }
569 
~LoadBindings()570 LoadBindings::~LoadBindings()
571 {
572 	if (m_pXMLDoc) {
573 		xmlFreeDoc( m_pXMLDoc );
574 		m_pXMLDoc = NULL;
575 	}
576 }
577 
Load()578 bool LoadBindings::Load()
579 {
580 	// check input
581 	if (!m_pXMLDoc) {
582 		ReportError( "XML file failed to load" );
583 		return false;
584 	}
585 
586 	// find the document node
587 	xmlNodePtr node = (xmlNodePtr)m_pXMLDoc;
588 	while (node && node->type!=XML_DOCUMENT_NODE) node = node->next;
589 	if (!node) {
590 		ReportError("couldn't find document node");
591 		return false;
592 	}
593 
594 	// first child should be element node named editbindings
595 	node = node->children;
596 	if (!node || node->type!=XML_ELEMENT_NODE || strcmp( node->name, "editbindings" )) {
597 		ReportError("expected editbindings node");
598 		return false;
599 	}
600 
601 	// get mode and name
602 	const char* nameStr = FindAttribute( node, "name" );
603 	if (!nameStr) {
604 		ReportError("editbinding missing mandatory name attribute");
605 		return false;
606 	}
607 	m_sName = nameStr;
608 	const char* modeStr = FindAttribute( node, "mode" );
609 	if (!modeStr) {
610 		ReportError("editbinding missing mandatory mode attribute");
611 	}
612 	if (!strcmp( modeStr, "replace" )) {
613 		m_bReplace = true;
614 	}
615 	else if (!strcmp( modeStr, "append" )) {
616 		m_bReplace = false;
617 	}
618 	else {
619 		ReportError("invalid mode attribute value %s, expected replace or append", modeStr);
620 		return false;
621 	}
622 
623 	// parse mouse and keystrokes
624 	node = node->children;
625 	while (node) {
626 		// bool get keyboard modifiers (alt/control/shift)
627 		EV_EditModifierState modifiers = GetModifiers( node );
628 
629 		// check type
630 		if (!strcmp( node->name, "mousecontext" )) {
631 			// get mouse context
632 			EV_EditMouseContext context = 0;
633 			const char* contextStr = FindAttribute( node, "context" );
634 			if (!contextStr) {
635 				ReportError("mousecontext requires a context attribute");
636 				return false;
637 			}
638 			if (!strcmp("EV_EMC_UNKNOWN", contextStr)) context = EV_EMC_UNKNOWN;
639 			else if (!strcmp("EV_EMC_TEXT", contextStr)) context = EV_EMC_TEXT;
640 			else if (!strcmp("EV_EMC_LEFTOFTEXT", contextStr)) context = EV_EMC_LEFTOFTEXT;
641 			else if (!strcmp("EV_EMC_MISSPELLEDTEXT", contextStr)) context = EV_EMC_MISSPELLEDTEXT;
642 			else if (!strcmp("EV_EMC_IMAGE", contextStr)) context = EV_EMC_IMAGE;
643 			else if (!strcmp("EV_EMC_IMAGESIZE", contextStr)) context = EV_EMC_IMAGESIZE;
644 			else if (!strcmp("EV_EMC_FIELD", contextStr)) context = EV_EMC_FIELD;
645 			else if (!strcmp("EV_EMC_HYPERLINK", contextStr)) context = EV_EMC_HYPERLINK;
646 			else if (!strcmp("EV_EMC_RIGHTOFTEXT", contextStr)) context = EV_EMC_RIGHTOFTEXT;
647 			else if (!strcmp("EV_EMC_REVISION", contextStr)) context = EV_EMC_REVISION;
648 			else if (!strcmp("EV_EMC_VLINE", contextStr)) context = EV_EMC_VLINE;
649 			else if (!strcmp("EV_EMC_HLINE", contextStr)) context = EV_EMC_HLINE;
650 			else if (!strcmp("EV_EMC_FRAME", contextStr)) context = EV_EMC_FRAME;
651 			else if (!strcmp("EV_EMC_VISUALTEXTDRAG", contextStr)) context = EV_EMC_VISUALTEXTDRAG;
652 			else if (!strcmp("EV_EMC_TOPCELL", contextStr)) context = EV_EMC_TOPCELL;
653 			else if (!strcmp("EV_EMC_TOC", contextStr)) context = EV_EMC_TOC;
654 			else if (!strcmp("EV_EMC_POSOBJECT", contextStr)) context = EV_EMC_POSOBJECT;
655 			else if (!strcmp("EV_EMC_MATH", contextStr)) context = EV_EMC_MATH;
656 			else if (!strcmp("EV_EMC_EMBED", contextStr)) context = EV_EMC_EMBED;
657 			else {
658 				ReportError("unrecognized context attribute value %s in mousecontext element", contextStr);
659 				return false;
660 			}
661 			// get mouse button
662 			EV_EditMouseButton button = 0;
663 			const char* buttonStr = FindAttribute( node, "button" );
664 			if (buttonStr) {
665 				switch (atoi( buttonStr )) {
666 					case 0: break;
667 					case 1: button = EV_EMB_BUTTON1; break;
668 					case 2: button = EV_EMB_BUTTON2; break;
669 					case 3: button = EV_EMB_BUTTON3; break;
670 					case 4: button = EV_EMB_BUTTON4; break;
671 					case 5: button = EV_EMB_BUTTON5; break;
672 					default:
673 						ReportError("button value must be 0<=x<=5, found %d", atoi(buttonStr));
674 						return false;
675 				}
676 			}
677 			if (!button) button = EV_EMB_BUTTON0;
678 			// now find handlers for the various operations
679 			xmlNodePtr operationNode = node->children;
680 			while (operationNode) {
681 				// get operation
682 				if (strcmp( operationNode->name, "operation" )) {
683 					ReportError("mousecontext element may only have operation sub elements, found %s element", operationNode->name);
684 					return false;
685 				}
686 				const char* opType = FindAttribute( operationNode, "type" );
687 				if (!opType) {
688 					ReportError("operation element missing type attribute");
689 					return false;
690 				}
691 				EV_EditMouseOp op = 0;
692 				if (!strcmp( opType, "click" )) op = EV_EMO_SINGLECLICK;
693 				else if (!strcmp( opType, "doubleclick" )) op = EV_EMO_DOUBLECLICK;
694 				else if (!strcmp( opType, "drag" )) op = EV_EMO_DRAG;
695 				else if (!strcmp( opType, "doubledrag" )) op = EV_EMO_DOUBLEDRAG;
696 				else if (!strcmp( opType, "release" )) op = EV_EMO_RELEASE;
697 				else if (!strcmp( opType, "doublerelease" )) op = EV_EMO_DOUBLERELEASE;
698 				else {
699 					ReportError("unrecognized type attribute value %s in operation element",opType);
700 					return false;
701 				}
702 
703 				// get command to invoke
704 				const char* command = FindAttribute( operationNode, "handler" );
705 				if (!command) {
706 					ReportError("operation element missing handler attribute");
707 					return false;
708 				}
709 
710 				// save to internal map
711 				if (!AddMapping( op | button | context | modifiers, command )) return false;
712 
713 				// next
714 				operationNode = operationNode->next;
715 			}
716 		}
717 		else if (!strcmp( node->name, "keystroke" )) {
718 			const char* handlerStr = FindAttribute( node, "handler" );
719 			if (!handlerStr) {
720 				ReportError("keystroke element missing mandatory handler attribute");
721 				return false;
722 			}
723 			const char* keyStr = FindAttribute( node, "key" );
724 			if (!keyStr) {
725 				ReportError("keystroke element missing mandatory key attribute");
726 				return false;
727 			}
728 			if (!strncmp( keyStr, "0x", 2 )) {
729 				// handle key press
730 				EV_EditKeyPress key;
731 				sscanf(keyStr,"%x",&key);
732 				if (!AddMapping( modifiers | key | EV_EKP_PRESS, handlerStr )) return false;
733 			}
734 			else {
735 				// handle named virtual key
736 				EV_EditBits vkey = EV_NamedVirtualKey::getEB( keyStr );
737 				if (!vkey) {
738 					ReportError("unrecognized named virtual key %s", keyStr);
739 					return false;
740 				}
741 				if (!AddMapping( modifiers | vkey | EV_EKP_NAMEDKEY, handlerStr )) return false;
742 			}
743 		}
744 		else if (!strcmp( node->name, "unbind-mappings" )) {
745 			const char* handlerStr = FindAttribute( node, "handler" );
746 			if (!handlerStr) {
747 				ReportError("unbind-mappings element missing mandatory handler attribute");
748 				return false;
749 			}
750 			UT_uint8 unbind = 0;
751 			const char* keystrokesStr = FindAttribute( node, "keystrokes" );
752 			if (keystrokesStr && !strcmp( keystrokesStr, "true" )) {
753 				unbind |= DONT_UNBIND_KEYSTROKES;
754 			}
755 			const char* mousecontextsStr = FindAttribute( node, "mousecontexts" );
756 			if (mousecontextsStr && !strcmp( mousecontextsStr, "true" )) {
757 				unbind |= DONT_UNBIND_MOUSECONTEXTS;
758 			}
759 			if ((unbind & DONT_UNBIND_ANYTHING)==DONT_UNBIND_ANYTHING) {
760 				ReportError("got unbind-mappings for handler %s that unbinds nothing", handlerStr);
761 				return false;
762 			}
763 			RemoveMapping( handlerStr, unbind );
764 		}
765 		else {
766 			ReportError("unrecognized element %s, expecting 'mousecontext', 'keystroke' or 'unbind-mappings'", node->name);
767 			return false;
768 		}
769 		// next sibling
770 		node = node->next;
771 	}
772 	return true;
773 }
774 
GetModifiers(xmlNodePtr node)775 EV_EditModifierState LoadBindings::GetModifiers( xmlNodePtr node )
776 {
777 	EV_EditModifierState mod = 0;
778 	_xmlAttr* prop = node->properties;
779 	while (prop) {
780 		if (prop->name && prop->children && prop->children->content) {
781 			if (!strcmp( prop->name, "control" )) {
782 				if (!strcmp( prop->children->content, "true" )) {
783 					mod |= EV_EMS_CONTROL;
784 				}
785 			}
786 			else if (!strcmp( prop->name, "alt" )) {
787 				if (!strcmp( prop->children->content, "true" )) {
788 					mod |= EV_EMS_ALT;
789 				}
790 			}
791 			else if (!strcmp( prop->name, "shift" )) {
792 				if (!strcmp( prop->children->content, "true" )) {
793 					mod |= EV_EMS_SHIFT;
794 				}
795 			}
796 		}
797 		prop = prop->next;
798 	}
799 	return mod;
800 }
801 
FindAttribute(xmlNodePtr node,const char * name)802 const char* LoadBindings::FindAttribute( xmlNodePtr node, const char* name )
803 {
804 	_xmlAttr* prop = node->properties;
805 	while (prop) {
806 		if (prop->name && prop->children) {
807 			if (!strcmp( prop->name, name )) {
808 				return reinterpret_cast<const char*>(prop->children->content);
809 			}
810 		}
811 		prop = prop->next;
812 	}
813 	return NULL;
814 }
815 
AddMapping(UT_uint32 binding,const char * command)816 bool LoadBindings::AddMapping( UT_uint32 binding, const char* command )
817 {
818 	UT_DEBUGMSG(("[LoadBindings] Adding 0x%08x: '%s'\n", binding, command));
819 	if (!m_BindMap.insert( BindingMap::value_type( binding, command ) ).second) {
820 		ReportError("overlapping mappings detected for binding 0x%x (see command %s)", binding, command);
821 		return false;
822 	}
823 	return true;
824 }
825 
RemoveMapping(const char * command,UT_uint8 unbinding)826 bool LoadBindings::RemoveMapping( const char* command, UT_uint8 unbinding )
827 {
828 	UT_DEBUGMSG(("[LoadBindings] Removing 0x%02x: '%s'\n", unbinding, command));
829 	if (!m_UnbindMap.insert( UnbindMap::value_type( command, unbinding) ).second) {
830 		ReportWarning("duplicate unbind-mappings detected for command %s", command);
831 	}
832 	return true;
833 }
834 
ReportError(const char * format,...) const835 void LoadBindings::ReportError( const char* format, ... ) const
836 {
837 	fprintf( stderr, "[LoadBindings] Error: " );
838 	va_list args;
839 	va_start(args, format);
840 	vfprintf( stderr, format, args );
841 	va_end(args);
842 	fprintf( stderr, "\n" );
843 }
844 
ReportWarning(const char * format,...) const845 void LoadBindings::ReportWarning( const char* format, ... ) const
846 {
847 	fprintf( stderr, "[LoadBindings] Warning: " );
848 	va_list args;
849 	va_start(args, format);
850 	vfprintf( stderr, format, args );
851 	va_end(args);
852 	fprintf( stderr, "\n" );
853 }
854 
Set() const855 bool LoadBindings::Set() const
856 {
857 	AP_BindingSet* pBSet = static_cast<AP_BindingSet *>(m_pApp->getBindingSet());
858 	UT_return_val_if_fail(pBSet,false);
859 
860 	EV_EditBindingMap* map;
861 	if (m_bReplace) {
862 		// we're replacing a map, if it doesn't exist yet
863 		// this doesn't matter
864 		map = pBSet->getMap(m_sName.c_str());
865 		if (!map) {
866 			map = pBSet->createMap(m_sName.c_str());
867 			UT_return_val_if_fail(map,false);
868 		} else {
869 			// reset map!
870 			map->resetAll();
871 		}
872 	}
873 	else {
874 		// we're appending to an existing map, if it doesn't exist
875 		// this is a failure!
876 		map = pBSet->getMap(m_sName.c_str());
877 		UT_return_val_if_fail(map,false);
878 	}
879 
880 	// set bindings!
881 	for (BindingMap::const_iterator it=m_BindMap.begin(); it!=m_BindMap.end(); ++it) {
882 		// first remove current command for the editbits
883 		// we MUST do this, or setBinding might fail!
884 		map->removeBinding( static_cast<EV_EditBits>( (*it).first ) );
885 		// then set our new command
886 		if (!map->setBinding( static_cast<EV_EditBits>( (*it).first ), (*it).second.c_str() )) {
887 			ReportWarning( "Failed to set binding for EV 0x%x handler %s", (*it).first, (*it).second.c_str() );
888 		}
889 	}
890 	// remove bindings
891 	for (UnbindMap::const_iterator it=m_UnbindMap.begin(); it!=m_UnbindMap.end(); ++it) {
892 		std::vector<EV_EditBits> editBits;
893 		map->findEditBits( (*it).first.c_str(), editBits );
894 		for (size_t i=0; i<editBits.size(); ++i) {
895 			if (EV_IsMouse( editBits[i] )) {
896 				if (!((*it).second & DONT_UNBIND_MOUSECONTEXTS)) {
897 					if (!map->removeBinding( editBits[i] )) {
898 						ReportWarning( "Failed to remove binding for EV 0x%x handler %s", editBits[i], (*it).first.c_str() );
899 					}
900 				}
901 			}
902 			else if (EV_IsKeyboard( editBits[i] )) {
903 				if (!((*it).second & DONT_UNBIND_KEYSTROKES)) {
904 					if (!map->removeBinding( editBits[i] )) {
905 						ReportWarning( "Failed to remove binding for EV 0x%x handler %s", editBits[i], (*it).first.c_str() );
906 					}
907 				}
908 			}
909 			else {
910 				if (!map->removeBinding( editBits[i] )) {
911 					ReportWarning( "Failed to remove binding for EV 0x%x handler %s", editBits[i], (*it).first.c_str() );
912 				}
913 			}
914 		}
915 	}
916 	return m_pApp->setInputMode( m_sName.c_str(), true /* force update! */ )>=0;
917 }
918 
919 // -----------------------------------------------------------------------
920 //      EV_NamedVirtualKey - moved from Abi to here, unused in Abi
921 // -----------------------------------------------------------------------
922 
923 static const char * s_Abi_NVKTable[] =
924 {	"",				// must be at index zero
925 	"backspace",
926 	"space",
927 	"tab",
928 	"return",
929 	"escape",
930 	"pageup",
931 	"pagedown",
932 	"end",
933 	"home",
934 	"left",
935 	"up",
936 	"right",
937 	"down",
938 	"insert",
939 	"delete",
940 	"help",
941 	"f1",
942 	"f2",
943 	"f3",
944 	"f4",
945 	"f5",
946 	"f6",
947 	"f7",
948 	"f8",
949 	"f9",
950 	"f10",
951 	"f11",
952 	"f12",
953 	"f13",
954 	"f14",
955 	"f15",
956 	"f16",
957 	"f17",
958 	"f18",
959 	"f19",
960 	"f20",
961 	"f21",
962 	"f22",
963 	"f23",
964 	"f24",
965 	"f25",
966 	"f26",
967 	"f27",
968 	"f28",
969 	"f29",
970 	"f30",
971 	"f31",
972 	"f32",
973 	"f33",
974 	"f34",
975 	"f35",
976 	"DeadGrave",
977 	"DeadAcute",
978 	"DeadCircumflex",
979 	"DeadTilde",
980 	"DeadMacron",
981 	"DeadBreve",
982 	"DeadAboveDot",
983 	"DeadDiaeresis",
984 	"DeadDoubleAcute",
985 	"DeadCaron",
986 	"DeadCedilla",
987 	"DeadOgonek",
988 	"DeadIota",
989 	"MenuShortCut"
990 	// TODO as other items are added to ev_NamedVirtualKey, add items here.
991 };
992 
getName(EV_EditBits eb)993 const char * EV_NamedVirtualKey::getName(EV_EditBits eb)
994 {
995 	UT_ASSERT((g_ascii_strcasecmp(s_Abi_NVKTable[EV_NVK_F35&~EV_EKP_NAMEDKEY],"f35")==0));
996 	UT_ASSERT((G_N_ELEMENTS(s_Abi_NVKTable) == EV_COUNT_NVK));
997 
998 	EV_EditVirtualKey evk = eb & ~EV_EKP_NAMEDKEY;
999 	if (evk < G_N_ELEMENTS(s_Abi_NVKTable))
1000 		return s_Abi_NVKTable[evk];
1001 	return 0;
1002 }
1003 
getEB(const char * szName)1004 EV_EditBits EV_NamedVirtualKey::getEB(const char * szName)
1005 {
1006 	UT_ASSERT((g_ascii_strcasecmp(s_Abi_NVKTable[EV_NVK_F35&~EV_EKP_NAMEDKEY],"f35")==0));
1007 	UT_ASSERT((G_N_ELEMENTS(s_Abi_NVKTable) == EV_COUNT_NVK));
1008 
1009 	for (UT_uint32 k=1; k<G_N_ELEMENTS(s_Abi_NVKTable); k++)
1010 		if (g_ascii_strcasecmp(s_Abi_NVKTable[k],szName)==0)
1011 			return EV_NamedKey(k);
1012 	return 0;
1013 }
1014