1 /*
2 For general Scribus (>=1.3.2) copyright and licensing information please refer
3 to the COPYING file provided with the program. Following this notice may exist
4 a copyright and/or license notice that predates the release of Scribus 1.3.2
5 for which a new license (GPL+exception) is in place.
6 */
7
8 #include <QDebug>
9 #include <QDomDocument>
10 #include <QFileDialog>
11 #include <QInputDialog>
12 #include <QMessageBox>
13
14 #include "actionmanager.h"
15 #include "commonstrings.h"
16 #include "iconmanager.h"
17 #include "pluginmanager.h"
18 #include "prefscontext.h"
19 #include "prefsfile.h"
20 #include "prefsmanager.h"
21 #include "prefsstructs.h"
22 #include "scpaths.h"
23 #include "scplugin.h"
24 #include "scraction.h"
25 #include "ui/prefs_keyboardshortcuts.h"
26 #include "ui/scmessagebox.h"
27 #include "util.h"
28
29
Prefs_KeyboardShortcuts(QWidget * parent,ScribusDoc * doc)30 Prefs_KeyboardShortcuts::Prefs_KeyboardShortcuts(QWidget* parent, ScribusDoc* doc)
31 : Prefs_Pane(parent)
32 {
33 setupUi(this);
34 languageChange();
35
36 m_caption = tr("Keyboard Shortcuts");
37 m_icon = "16/preferences-desktop-keyboard-shortcuts.png";
38
39 defMenus=ActionManager::defaultMenus();
40 defNonMenuActions=ActionManager::defaultNonMenuActions();
41
42 QVector< QPair<QString, QStringList> >::Iterator itnmenua = defNonMenuActions->begin();
43 PluginManager& pluginManager(PluginManager::instance());
44 QStringList pluginNames(pluginManager.pluginNames(false));
45 ScPlugin* plugin = nullptr;
46 ScActionPlugin* ixplug = nullptr;
47 QString pName;
48 for (int i = 0; i < pluginNames.count(); ++i)
49 {
50 pName = pluginNames.at(i);
51 plugin = pluginManager.getPlugin(pName, true);
52 Q_ASSERT(plugin); // all the returned names should represent loaded plugins
53 if (plugin->inherits("ScActionPlugin"))
54 {
55 ixplug = qobject_cast<ScActionPlugin*>(plugin);
56 Q_ASSERT(ixplug);
57 ScActionPlugin::ActionInfo ai(ixplug->actionInfo());
58 itnmenua->second << ai.name;
59 }
60 }
61
62 Q_CHECK_PTR(defMenus);
63 lviToActionMap.clear();
64 lviToMenuMap.clear();
65 keyTable->clear();
66 keyMap.clear();
67 keyCode = 0;
68 keyDisplay->setMinimumWidth(fontMetrics().horizontalAdvance("CTRL+ALT+SHIFT+W"));
69 keyDisplay->setText("");
70 selectedLVI = nullptr;
71
72 clearSearchButton->setIcon(IconManager::instance().loadIcon("clear_right.png"));
73 // signals and slots connections
74 connect( keyTable, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this, SLOT(dispKey(QTreeWidgetItem*, QTreeWidgetItem*)));
75 connect( noKey, SIGNAL(clicked()), this, SLOT(setNoKey()));
76 connect( setKeyButton, SIGNAL(clicked()), this, SLOT(setKeyText()));
77 connect( loadSetButton, SIGNAL(clicked()), this, SLOT(loadKeySetFile()));
78 connect( importSetButton, SIGNAL(clicked()), this, SLOT(importKeySetFile()));
79 connect( exportSetButton, SIGNAL(clicked()), this, SLOT(exportKeySetFile()));
80 connect( resetSetButton, SIGNAL(clicked()), this, SLOT(resetKeySet()));
81 connect( clearSearchButton, SIGNAL(clicked()), this, SLOT(clearSearchString()));
82 connect( searchTextLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(applySearch(const QString&)));
83
84 }
85
86 Prefs_KeyboardShortcuts::~Prefs_KeyboardShortcuts() = default;
87
languageChange()88 void Prefs_KeyboardShortcuts::languageChange()
89 {
90 }
91
restoreDefaults(struct ApplicationPrefs * prefsData)92 void Prefs_KeyboardShortcuts::restoreDefaults(struct ApplicationPrefs *prefsData)
93 {
94 keyMap=prefsData->keyShortcutPrefs.KeyActions;
95 loadableSets->clear();
96 loadableSets->addItems(scanForSets());
97 insertActions();
98 dispKey(nullptr);
99 }
100
saveGuiToPrefs(struct ApplicationPrefs * prefsData) const101 void Prefs_KeyboardShortcuts::saveGuiToPrefs(struct ApplicationPrefs *prefsData) const
102 {
103 prefsData->keyShortcutPrefs.KeyActions=keyMap;
104 }
105
setNoKey()106 void Prefs_KeyboardShortcuts::setNoKey()
107 {
108 if (noKey->isChecked())
109 {
110 if (selectedLVI!=nullptr)
111 {
112 selectedLVI->setText(1,"");
113 keyMap[lviToActionMap[selectedLVI]].keySequence=QKeySequence();
114 }
115 keyDisplay->setText("");
116 noKey->setChecked(true);
117 }
118 }
119
loadKeySetFile()120 void Prefs_KeyboardShortcuts::loadKeySetFile()
121 {
122 if (keySetList.contains(loadableSets->currentText()))
123 importKeySet(keySetList[loadableSets->currentText()]);
124 }
125
importKeySetFile()126 void Prefs_KeyboardShortcuts::importKeySetFile()
127 {
128 PrefsContext* dirs = PrefsManager::instance().prefsFile->getContext("dirs");
129 QString currentPath = dirs->get("keymapprefs_import", ScPaths::instance().shareDir() + "keysets/");
130 QString s = QFileDialog::getOpenFileName(this, tr("Select a Key set file to read"), currentPath, tr("Key Set XML Files (*.xml)"));
131 if (!s.isEmpty())
132 importKeySet(s);
133 }
exportKeySetFile()134 void Prefs_KeyboardShortcuts::exportKeySetFile()
135 {
136 PrefsContext* dirs = PrefsManager::instance().prefsFile->getContext("dirs");
137 QString currentPath= dirs->get("keymapprefs_export", ".");
138 QString s = QFileDialog::getSaveFileName(this, tr("Select a Key set file to save to"), currentPath, tr("Key Set XML Files (*.xml)") );
139 if (!s.isEmpty())
140 exportKeySet(s);
141 }
142
importKeySet(const QString & filename)143 void Prefs_KeyboardShortcuts::importKeySet(const QString& filename)
144 {
145 searchTextLineEdit->clear();
146 QFileInfo fi = QFileInfo(filename);
147 if (fi.exists())
148 {
149 //import the file into qdomdoc
150 QDomDocument doc( "keymapentries" );
151 QFile file1( filename );
152 if ( !file1.open( QIODevice::ReadOnly ) )
153 return;
154 QTextStream ts(&file1);
155 ts.setCodec("UTF-8");
156 QString errorMsg;
157 int eline;
158 int ecol;
159 if ( !doc.setContent( ts.readAll(), &errorMsg, &eline, &ecol ))
160 {
161 qDebug("%s", QString("Could not open key set file: %1\nError:%2 at line: %3, row: %4").arg(filename, errorMsg).arg(eline).arg(ecol).toLatin1().constData());
162 file1.close();
163 return;
164 }
165 file1.close();
166 //load the file now
167 QDomElement docElem = doc.documentElement();
168 if (docElem.tagName()=="shortcutset" && docElem.hasAttribute("name"))
169 {
170 QDomAttr keysetAttr = docElem.attributeNode( "name" );
171
172 //clear current menu entries
173 for (QMap<QString,Keys>::Iterator it=keyMap.begin(); it!=keyMap.end(); ++it)
174 it.value().keySequence = QKeySequence();
175
176 //load in new set
177 QDomNode n = docElem.firstChild();
178 while (!n.isNull())
179 {
180 QDomElement e = n.toElement(); // try to convert the node to an element.
181 if (!e.isNull())
182 {
183 if (e.hasAttribute("name") && e.hasAttribute( "shortcut" ))
184 {
185 QDomAttr nameAttr = e.attributeNode( "name" );
186 QDomAttr shortcutAttr = e.attributeNode( "shortcut" );
187 if (keyMap.contains(nameAttr.value()))
188 keyMap[nameAttr.value()].keySequence=QKeySequence(shortcutAttr.value());
189 }
190 }
191 n = n.nextSibling();
192 }
193 }
194 }
195 insertActions();
196 }
197
exportKeySet(const QString & filename)198 bool Prefs_KeyboardShortcuts::exportKeySet(const QString& filename)
199 {
200 QString exportFileName;
201 if (filename.endsWith(".xml"))
202 exportFileName = filename;
203 else
204 exportFileName = filename+".xml";
205 if (overwrite(this, exportFileName))
206 {
207 bool ok;
208 QString setName = QInputDialog::getText(this, tr("Export Keyboard Shortcuts to File"), tr("Enter the name of the shortcut set:"), QLineEdit::Normal, QString(), &ok);
209 if (!( ok && !setName.isEmpty()) )
210 return false;
211
212 QDomDocument doc( "keymapentries" );
213 QString keyset=QString("<shortcutset name=\"%1\"></shortcutset>").arg(setName);
214 doc.setContent(keyset);
215 QDomElement keySetElement=doc.documentElement();
216 QMap<QString,Keys>::Iterator itEnd=keyMap.end();
217 for (QMap<QString,Keys>::Iterator it=keyMap.begin(); it!=itEnd; ++it)
218 {
219 if (it.value().keySequence.isEmpty() && it.key().isEmpty())
220 continue;
221 QDomElement function_shortcut = doc.createElement("function");
222 function_shortcut.setAttribute("name", it.key());
223 function_shortcut.setAttribute("shortcut", getKeyText(it.value().keySequence));
224 keySetElement.appendChild(function_shortcut);
225 }
226 QFile f(filename);
227 if(!f.open(QIODevice::WriteOnly))
228 return false;
229 QDataStream s(&f);
230 QByteArray xmltag("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
231 s.writeRawData(xmltag.data(), xmltag.length());
232 QByteArray xmldoc = doc.toByteArray(4);
233 s.writeRawData(xmldoc, xmldoc.length());
234 f.close();
235 }
236 return true;
237 }
238
resetKeySet()239 void Prefs_KeyboardShortcuts::resetKeySet()
240 {
241 QString location=ScPaths::instance().shareDir();
242 QString defaultKeySetFileName=QDir::toNativeSeparators(location+"keysets/scribus15.xml");
243 importKeySet(defaultKeySetFileName);
244 }
245
scanForSets()246 QStringList Prefs_KeyboardShortcuts::scanForSets()
247 {
248 keySetList.clear();
249 QString location(ScPaths::instance().shareDir());
250 QDir keySetsDir(QDir::toNativeSeparators(location+"keysets/"), "*.xml", QDir::Name, QDir::Files | QDir::NoSymLinks);
251 if ((!keySetsDir.exists()) || (keySetsDir.count() <= 0))
252 return QStringList();
253
254 QStringList appNames;
255 for (uint fileCounter = 0; fileCounter < keySetsDir.count(); ++fileCounter)
256 {
257 QString filename(QDir::toNativeSeparators(location+"keysets/"+keySetsDir[fileCounter]));
258
259 QDomDocument doc( "keymapentries" );
260 QFile file( filename );
261 if (!file.open( QIODevice::ReadOnly))
262 continue;
263 QString errorMsg;
264 int eline;
265 int ecol;
266
267 if (!doc.setContent( &file, &errorMsg, &eline, &ecol ))
268 {
269 qDebug("%s", QString("Could not open key set file: %1\nError:%2 at line: %3, row: %4").arg(keySetsDir[fileCounter], errorMsg).arg(eline).arg(ecol).toLatin1().constData());
270 file.close();
271 continue;
272 }
273 file.close();
274
275 QDomElement docElem = doc.documentElement();
276 if (docElem.tagName() == "shortcutset" && docElem.hasAttribute("name"))
277 {
278 QDomAttr nameAttr = docElem.attributeNode( "name" );
279 appNames.append(nameAttr.value());
280 keySetList.insert(nameAttr.value(), filename);
281 }
282 }
283 return QStringList(appNames);
284 }
285
getKeyText(const QKeySequence & KeyC)286 QString Prefs_KeyboardShortcuts::getKeyText(const QKeySequence& KeyC)
287 {
288 return KeyC.toString();
289 }
290
getTrKeyText(const QKeySequence & KeyC)291 QString Prefs_KeyboardShortcuts::getTrKeyText(const QKeySequence& KeyC)
292 {
293 return KeyC.toString(QKeySequence::NativeText);
294 }
295
setKeyText()296 void Prefs_KeyboardShortcuts::setKeyText()
297 {
298 if (keyTable->currentItem()==nullptr)
299 {
300 setKeyButton->setChecked(false);
301 return;
302 }
303 if (setKeyButton->isChecked())
304 {
305 keyCode = 0;
306 grabKeyboard();
307 }
308 else
309 releaseKeyboard();
310 }
311
insertActions()312 void Prefs_KeyboardShortcuts::insertActions()
313 {
314 lviToActionMap.clear();
315 lviToMenuMap.clear();
316 keyTable->clear();
317 bool first, firstMenu=true;
318 QTreeWidgetItem *currLVI = nullptr;
319 QTreeWidgetItem *currMenuLVI = nullptr;
320 QTreeWidgetItem *prevLVI = nullptr;
321 QTreeWidgetItem *prevMenuLVI = nullptr;
322 for (int i = 0; i < defMenus->count(); ++i)
323 {
324 const QPair<QString, QStringList> &actionStrings = defMenus->at(i);
325 if (firstMenu)
326 {
327 currMenuLVI = new QTreeWidgetItem(keyTable);
328 firstMenu = false;
329 }
330 else
331 currMenuLVI = new QTreeWidgetItem(keyTable, prevMenuLVI);
332 Q_CHECK_PTR(currMenuLVI);
333 lviToMenuMap.append(currMenuLVI);
334 currMenuLVI->setText(0, actionStrings.first);
335 currMenuLVI->setExpanded(true);
336 currMenuLVI->setFlags(Qt::ItemIsEnabled);
337 prevMenuLVI=currMenuLVI;
338 first=true;
339 currLVI=nullptr;
340 prevLVI=nullptr;
341 for (int j = 0; j < actionStrings.second.count(); ++j)
342 {
343 QString actionName = actionStrings.second.at(j);
344 if (!keyMap.contains(actionName))
345 {
346 qDebug() << "The action " << actionName << " is not defined in shortcut map";
347 continue;
348 }
349 const Keys &actionKeys = keyMap[actionName];
350 if (actionKeys.cleanMenuText.isEmpty())
351 continue;
352 if (first)
353 {
354 currLVI = new QTreeWidgetItem(currMenuLVI);
355 first = false;
356 }
357 else
358 currLVI = new QTreeWidgetItem(currMenuLVI, prevLVI);
359 Q_CHECK_PTR(currLVI);
360 lviToActionMap.insert(currLVI, actionName);
361 currLVI->setText(0, actionKeys.cleanMenuText);
362 currLVI->setText(1, actionKeys.keySequence.toString(QKeySequence::NativeText));
363 prevLVI=currLVI;
364 }
365 }
366 //Non menu actions
367 for (int i = 0; i < defNonMenuActions->count(); ++i)
368 {
369 const QPair<QString, QStringList> &actionStrings = defNonMenuActions->at(i);
370 if (firstMenu)
371 {
372 currMenuLVI = new QTreeWidgetItem(keyTable);
373 firstMenu = false;
374 }
375 else
376 currMenuLVI = new QTreeWidgetItem(keyTable, prevMenuLVI);
377 Q_CHECK_PTR(currMenuLVI);
378 lviToMenuMap.append(currMenuLVI);
379 currMenuLVI->setText(0, actionStrings.first);
380 currMenuLVI->setExpanded(true);
381 currMenuLVI->setFlags(Qt::ItemIsEnabled);
382 prevMenuLVI=currMenuLVI;
383 first=true;
384 currLVI=nullptr;
385 prevLVI=nullptr;
386 for (int j = 0; j < actionStrings.second.count(); ++j)
387 {
388 QString actionName = actionStrings.second.at(j);
389 if (!keyMap.contains(actionName))
390 {
391 qDebug() << "The action " << actionName << " is not defined in shortcut map";
392 continue;
393 }
394 const Keys &actionKeys = keyMap[actionName];
395 if (actionKeys.cleanMenuText.isEmpty())
396 continue;
397 if (first)
398 {
399 currLVI=new QTreeWidgetItem(currMenuLVI);
400 first=false;
401 }
402 else
403 currLVI=new QTreeWidgetItem(currMenuLVI, prevLVI);
404 Q_CHECK_PTR(currLVI);
405 lviToActionMap.insert(currLVI, actionName);
406 currLVI->setText(0, actionKeys.cleanMenuText);
407 currLVI->setText(1, actionKeys.keySequence.toString(QKeySequence::NativeText));
408 prevLVI=currLVI;
409 }
410 }
411 keyTable->resizeColumnToContents(0);
412 }
413
applySearch(const QString & newss)414 void Prefs_KeyboardShortcuts::applySearch( const QString & newss )
415 {
416 //Must run this as if newss is not empty and we go to the next for loop, the set visible doesn't work
417 for (auto it = lviToMenuMap.begin(); it != lviToMenuMap.end(); ++it)
418 (*it)->setHidden(false);
419 if (newss.isEmpty())
420 {
421 for (auto it = lviToActionMap.begin(); it != lviToActionMap.end(); ++it)
422 it.key()->setHidden(false);
423 return;
424 }
425 //Seem to need to do this.. isOpen doesn't seem to do what it says
426 for (auto it = lviToActionMap.begin(); it != lviToActionMap.end(); ++it)
427 {
428 if (it.key()->text(0).contains(newss, Qt::CaseInsensitive))
429 it.key()->setHidden(false);
430 else
431 it.key()->setHidden(true);
432 }
433 }
434
dispKey(QTreeWidgetItem * qlvi,QTreeWidgetItem *)435 void Prefs_KeyboardShortcuts::dispKey(QTreeWidgetItem* qlvi, QTreeWidgetItem*)
436 {
437 if (setKeyButton->isChecked())
438 {
439 releaseKeyboard();
440 setKeyButton->setChecked(false);
441 }
442 if (qlvi!=nullptr && lviToActionMap.contains(qlvi))
443 {
444 selectedLVI = qlvi;
445 QString actionName=lviToActionMap[qlvi];
446 if (actionName.isEmpty())
447 return;
448 keyDisplay->setText(keyMap[actionName].keySequence.toString(QKeySequence::NativeText));
449 if (keyMap[actionName].keySequence.isEmpty())
450 noKey->setChecked(true);
451 else
452 userDef->setChecked(true);
453 }
454 else
455 {
456 noKey->setChecked(true);
457 keyDisplay->setText("");
458 selectedLVI = nullptr;
459 }
460 noKey->setEnabled(selectedLVI != nullptr);
461 userDef->setEnabled(selectedLVI != nullptr);
462 setKeyButton->setEnabled(selectedLVI != nullptr);
463 keyDisplay->setEnabled(selectedLVI != nullptr);
464 }
465
event(QEvent * ev)466 bool Prefs_KeyboardShortcuts::event( QEvent* ev )
467 {
468 bool ret = QWidget::event( ev );
469 if ( ev->type() == QEvent::KeyPress )
470 keyPressEvent((QKeyEvent*)ev);
471 if ( ev->type() == QEvent::KeyRelease )
472 keyReleaseEvent((QKeyEvent*)ev);
473 return ret;
474 }
475
keyPressEvent(QKeyEvent * k)476 void Prefs_KeyboardShortcuts::keyPressEvent(QKeyEvent *k)
477 {
478 if (setKeyButton->isChecked())
479 {
480 switch (k->key())
481 {
482 case Qt::Key_Meta:
483 keyCode |= Qt::META;
484 break;
485 case Qt::Key_Shift:
486 keyCode |= Qt::SHIFT;
487 break;
488 case Qt::Key_Alt:
489 keyCode |= Qt::ALT;
490 break;
491 case Qt::Key_Control:
492 keyCode |= Qt::CTRL;
493 break;
494 default:
495 keyCode |= k->key();
496 keyDisplay->setText(getTrKeyText(keyCode));
497 releaseKeyboard();
498 if (selectedLVI)
499 {
500 QString actionName = lviToActionMap[selectedLVI];
501 if (checkKey(keyCode))
502 {
503 ScMessageBox::information(this, CommonStrings::trWarning, tr("The %1 key sequence is already in use by \"%2\"").arg(getTrKeyText(keyCode),getAction(keyCode)));
504 selectedLVI->setText(1,keyMap[actionName].keySequence.toString(QKeySequence::NativeText));
505 keyDisplay->setText(keyMap[actionName].keySequence.toString(QKeySequence::NativeText));
506 }
507 else
508 {
509 QKeySequence newKeySequence(keyCode);
510 selectedLVI->setText(1, newKeySequence.toString(QKeySequence::NativeText));
511 keyMap[actionName].keySequence=newKeySequence;
512 userDef->setChecked(true);
513 }
514 }
515 setKeyButton->setChecked(false);
516 }
517 }
518 if (setKeyButton->isChecked())
519 keyDisplay->setText(getTrKeyText(keyCode));
520 }
521
keyReleaseEvent(QKeyEvent * k)522 void Prefs_KeyboardShortcuts::keyReleaseEvent(QKeyEvent *k)
523 {
524 if (setKeyButton->isChecked())
525 {
526 if (k->key() == Qt::Key_Meta)
527 keyCode &= ~Qt::META;
528 if (k->key() == Qt::Key_Shift)
529 keyCode &= ~Qt::SHIFT;
530 if (k->key() == Qt::Key_Alt)
531 keyCode &= ~Qt::ALT;
532 if (k->key() == Qt::Key_Control)
533 keyCode &= ~Qt::CTRL;
534 keyDisplay->setText(getTrKeyText(keyCode));
535 }
536 }
537
getAction(int code)538 QString Prefs_KeyboardShortcuts::getAction(int code)
539 {
540 QString ret;
541 QKeySequence key = QKeySequence(code);
542 for (QMap<QString,Keys>::Iterator it=keyMap.begin(); it!=keyMap.end(); ++it)
543 {
544 if (key.matches(it.value().keySequence) != QKeySequence::NoMatch)
545 {
546 ret = it->cleanMenuText;
547 break;
548 }
549 }
550 return ret;
551 }
552
checkKey(int code)553 bool Prefs_KeyboardShortcuts::checkKey(int code)
554 {
555 bool ret = false;
556 QKeySequence key = QKeySequence(code);
557 for (QMap<QString,Keys>::Iterator it=keyMap.begin(); it!=keyMap.end(); ++it)
558 {
559 if (key.matches(it.value().keySequence) != QKeySequence::NoMatch)
560 {
561 ret = true;
562 break;
563 }
564 }
565 return ret;
566 }
567
clearSearchString()568 void Prefs_KeyboardShortcuts::clearSearchString( )
569 {
570 searchTextLineEdit->clear();
571 }
572
573