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