1 
2 
3 #include "toonzqt/menubarcommand.h"
4 //#include "menubarcommandids.h"
5 #include "toonzqt/dvdialog.h"
6 #include "toonzqt/gutil.h"
7 #include "toonz/toonzfolders.h"
8 #include "tsystem.h"
9 #include <assert.h>
10 #include <QObject>
11 #include <QAction>
12 #include <QSettings>
13 #include <QKeySequence>
14 #include <QApplication>
15 
16 #include <sys/types.h>
17 
18 using namespace DVGui;
19 
20 //---------------------------------------------------------
21 
22 namespace {
23 
updateToolTip(QAction * action)24 void updateToolTip(QAction *action) {
25   QString tooltip  = action->text();
26   QString shortcut = action->shortcut().toString();
27   if (shortcut != "") tooltip += " (" + shortcut + ")";
28   action->setToolTip(tooltip);
29 }
30 
31 }  // namespace
32 
33 //=========================================================
34 
AuxActionsCreator()35 AuxActionsCreator::AuxActionsCreator() {
36   AuxActionsCreatorManager::instance()->addAuxActionsCreator(this);
37 }
38 //-----------------------------------------------------------------------------
39 
AuxActionsCreatorManager()40 AuxActionsCreatorManager::AuxActionsCreatorManager()
41     : m_auxActionsCreated(false) {}
42 
instance()43 AuxActionsCreatorManager *AuxActionsCreatorManager::instance() {
44   static AuxActionsCreatorManager _instance;
45   return &_instance;
46 }
47 
addAuxActionsCreator(AuxActionsCreator * auxActionsCreator)48 void AuxActionsCreatorManager::addAuxActionsCreator(
49     AuxActionsCreator *auxActionsCreator) {
50   m_auxActionsCreators.push_back(auxActionsCreator);
51 }
52 
createAuxActions(QObject * parent)53 void AuxActionsCreatorManager::createAuxActions(QObject *parent) {
54   if (m_auxActionsCreated) return;
55   m_auxActionsCreated = true;
56   for (int i = 0; i < (int)m_auxActionsCreators.size(); i++)
57     m_auxActionsCreators[i]->createActions(parent);
58 }
59 
60 //=========================================================
61 
CommandManager()62 CommandManager::CommandManager() {}
63 
64 //---------------------------------------------------------
65 
~CommandManager()66 CommandManager::~CommandManager() {
67   std::map<std::string, Node *>::iterator it;
68   for (it = m_idTable.begin(); it != m_idTable.end(); ++it) delete it->second;
69 }
70 
71 //---------------------------------------------------------
72 
instance()73 CommandManager *CommandManager::instance() {
74   static CommandManager _instance;
75   return &_instance;
76 }
77 
78 //---------------------------------------------------------
79 
80 //
81 // command id => command
82 //
getNode(CommandId id,bool createIfNeeded)83 CommandManager::Node *CommandManager::getNode(CommandId id,
84                                               bool createIfNeeded) {
85   AuxActionsCreatorManager::instance()->createAuxActions(qApp);
86   std::map<std::string, Node *>::iterator it = m_idTable.find(id);
87   if (it != m_idTable.end()) return it->second;
88   if (createIfNeeded) {
89     Node *node    = new Node(id);
90     m_idTable[id] = node;
91     return node;
92   } else
93     return 0;
94 }
95 
96 //---------------------------------------------------------
97 
setShortcut(CommandId id,QAction * action,std::string shortcutString)98 void CommandManager::setShortcut(CommandId id, QAction *action,
99                                  std::string shortcutString) {
100   if (shortcutString != "")
101     action->setShortcut(QKeySequence(QString::fromStdString(shortcutString)));
102   else
103     action->setShortcut(QKeySequence());
104   TFilePath fp = ToonzFolder::getMyModuleDir() + TFilePath("shortcuts.ini");
105   QSettings settings(toQString(fp), QSettings::IniFormat);
106   settings.beginGroup("shortcuts");
107   settings.setValue(QString(id), QString::fromStdString(shortcutString));
108   settings.endGroup();
109 }
110 
111 //---------------------------------------------------------
112 
define(CommandId id,CommandType type,std::string defaultShortcutString,QAction * qaction)113 void CommandManager::define(CommandId id, CommandType type,
114                             std::string defaultShortcutString,
115                             QAction *qaction) {
116   assert(type != UndefinedCommandType);
117   assert(qaction != 0);
118   assert(m_qactionTable.count(qaction) == 0);
119 
120   Node *node = getNode(id);
121   if (node->m_type != UndefinedCommandType) {
122     assert(!"Duplicate command id");
123   }
124   node->m_type    = type;
125   node->m_qaction = qaction;
126   node->m_qaction->setEnabled(
127       (node->m_enabled &&
128           (node->m_handler || node->m_qaction->actionGroup() != 0)) ||
129       node->m_type == MiscCommandType ||
130       node->m_type == ToolModifierCommandType);
131 
132   m_qactionTable[qaction] = node;
133   qaction->setShortcutContext(Qt::ApplicationShortcut);
134   // user defined shortcuts will be loaded afterwards in loadShortcuts()
135   QString defaultShortcutQString =
136       QString::fromStdString(defaultShortcutString);
137   if (!defaultShortcutQString.isEmpty()) {
138     qaction->setShortcut(QKeySequence(defaultShortcutQString));
139     m_shortcutTable[defaultShortcutString] = node;
140   }
141 
142   if (type == ToolCommandType) updateToolTip(qaction);
143 }
144 
145 //---------------------------------------------------------
146 
147 //
148 // set handler (id, handler)
149 //   possibly changes enable/disable qaction state
150 //
setHandler(CommandId id,CommandHandlerInterface * handler)151 void CommandManager::setHandler(CommandId id,
152                                 CommandHandlerInterface *handler) {
153   Node *node = getNode(id);
154   if (node->m_handler != handler) {
155     delete node->m_handler;
156     node->m_handler = handler;
157   }
158   if (node->m_qaction) {
159     node->m_qaction->setEnabled(
160         node->m_enabled &&
161         (!!node->m_handler || node->m_qaction->actionGroup() != 0));
162   }
163 }
164 
165 //---------------------------------------------------------
166 //
167 // qaction -> command; execute
168 //
169 
execute(QAction * qaction)170 void CommandManager::execute(QAction *qaction) {
171   assert(qaction);
172   std::map<QAction *, Node *>::iterator it = m_qactionTable.find(qaction);
173   assert(it != m_qactionTable.end());
174   if (it != m_qactionTable.end() && it->second->m_handler) {
175     it->second->m_handler->execute();
176   }
177 }
178 
179 //---------------------------------------------------------
180 
execute(QAction * action,QAction * menuAction)181 void CommandManager::execute(QAction *action, QAction *menuAction) {
182   std::map<QAction *, Node *>::iterator it = m_qactionTable.find(action);
183   if (it != m_qactionTable.end())
184     execute(action);
185   else
186     execute(menuAction);
187 }
188 
189 //---------------------------------------------------------
190 
execute(CommandId id)191 void CommandManager::execute(CommandId id) {
192   Node *node = getNode(id, false);
193   if (node && node->m_handler) {
194     QAction *action = node->m_qaction;
195     if (action && action->isCheckable()) {
196       // principalmente per i tool
197       action->setChecked(true);
198     }
199     node->m_handler->execute();
200   }
201 }
202 
203 //---------------------------------------------------------
204 
getActions(CommandType type,std::vector<QAction * > & actions)205 void CommandManager::getActions(CommandType type,
206                                 std::vector<QAction *> &actions) {
207   AuxActionsCreatorManager::instance()->createAuxActions(qApp);
208   std::map<QAction *, Node *>::iterator it;
209   for (it = m_qactionTable.begin(); it != m_qactionTable.end(); ++it)
210     if (it->second->m_type == type) actions.push_back(it->first);
211 }
212 
213 //---------------------------------------------------------
214 
getActionFromShortcut(std::string shortcutString)215 QAction *CommandManager::getActionFromShortcut(std::string shortcutString) {
216   std::map<std::string, Node *>::iterator it =
217       m_shortcutTable.find(shortcutString);
218   return it != m_shortcutTable.end() ? it->second->m_qaction : 0;
219 }
220 
221 //---------------------------------------------------------
222 
getShortcutFromAction(QAction * action)223 std::string CommandManager::getShortcutFromAction(QAction *action) {
224   std::map<std::string, Node *>::iterator it = m_shortcutTable.begin();
225   for (; it != m_shortcutTable.end(); ++it) {
226     if (it->second->m_qaction == action) return it->first;
227   }
228   return "";
229 }
230 
231 //---------------------------------------------------------
232 
getShortcutFromId(const char * id)233 std::string CommandManager::getShortcutFromId(const char *id) {
234   QAction *action = getAction(id);
235   assert(action);
236   if (!action) return "";
237   return getShortcutFromAction(action);
238 }
239 
240 //---------------------------------------------------------
241 
getKeyFromShortcut(const std::string & shortcut)242 int CommandManager::getKeyFromShortcut(const std::string &shortcut) {
243   QString qShortcut = QString::fromStdString(shortcut);
244   if (qShortcut == "") return 0;
245 
246   QKeySequence ks(qShortcut);
247   assert(ks.count() == 1);
248   return ks[0];
249 }
250 
251 //---------------------------------------------------------
252 
getKeyFromId(const char * id)253 int CommandManager::getKeyFromId(const char *id) {
254   return getKeyFromShortcut(getShortcutFromId(id));
255 }
256 
257 //---------------------------------------------------------
258 
setShortcut(QAction * action,std::string shortcutString,bool keepDefault)259 void CommandManager::setShortcut(QAction *action, std::string shortcutString,
260                                  bool keepDefault) {
261   QString shortcut = QString::fromStdString(shortcutString);
262 
263   std::string oldShortcutString = action->shortcut().toString().toStdString();
264   if (oldShortcutString == shortcutString) return;
265 
266   // Cerco il nodo corrispondente ad action. Deve esistere
267   std::map<QAction *, Node *>::iterator it = m_qactionTable.find(action);
268   Node *node = it != m_qactionTable.end() ? it->second : 0;
269   assert(node);
270   assert(node->m_qaction == action);
271 
272   QKeySequence ks(shortcut);
273   assert(ks.count() == 1 || ks.count() == 0 && shortcut == "");
274 
275   if (node->m_type == ZoomCommandType && ks.count() > 1) {
276     DVGui::warning(
277         QObject::tr("It is not possible to assign a shortcut with modifiers to "
278                     "the visualization commands."));
279     return;
280   }
281   // lo shortcut e' gia' assegnato?
282   QString oldActionId;
283   std::map<std::string, Node *>::iterator sit =
284       m_shortcutTable.find(shortcutString);
285   if (sit != m_shortcutTable.end()) {
286     // la vecchia azione non ha piu' shortcut
287     oldActionId = QString::fromStdString(sit->second->m_id);
288     sit->second->m_qaction->setShortcut(QKeySequence());
289     if (sit->second->m_type == ToolCommandType)
290       updateToolTip(sit->second->m_qaction);
291   }
292   // assegno lo shortcut all'azione
293   action->setShortcut(
294       QKeySequence::fromString(QString::fromStdString(shortcutString)));
295   if (node->m_type == ToolCommandType) updateToolTip(action);
296 
297   // Aggiorno la tabella shortcut -> azioni
298   // Cancello il riferimento all'eventuale vecchio shortcut di action
299   if (oldShortcutString != "") m_shortcutTable.erase(oldShortcutString);
300   // e aggiungo il nuovo legame
301   m_shortcutTable[shortcutString] = node;
302 
303   // registro il tutto
304   TFilePath fp = ToonzFolder::getMyModuleDir() + TFilePath("shortcuts.ini");
305   QSettings settings(toQString(fp), QSettings::IniFormat);
306   settings.beginGroup("shortcuts");
307   settings.setValue(QString::fromStdString(node->m_id),
308                     QString::fromStdString(shortcutString));
309   if (keepDefault) {
310     if (oldActionId != "") settings.remove(oldActionId);
311   }
312   settings.endGroup();
313 }
314 
315 //---------------------------------------------------------
316 
enable(CommandId id,bool enabled)317 void CommandManager::enable(CommandId id, bool enabled) {
318   Node *node = getNode(id, false);
319   if (!node) return;
320   if (node->m_enabled == enabled) return;
321   node->m_enabled = enabled;
322   if (node->m_qaction)
323     node->m_qaction->setEnabled(
324         node->m_enabled &&
325         (!!node->m_handler || node->m_qaction->actionGroup() != 0));
326 }
327 
328 //---------------------------------------------------------
329 
setChecked(CommandId id,bool checked)330 void CommandManager::setChecked(CommandId id, bool checked) {
331   Node *node = getNode(id, false);
332   if (!node) return;
333   if (node->m_qaction) {
334     node->m_qaction->setChecked(checked);
335     if (node->m_handler) node->m_handler->execute();
336   }
337 }
338 
339 //---------------------------------------------------------
340 
getAction(CommandId id,bool createIfNeeded)341 QAction *CommandManager::getAction(CommandId id, bool createIfNeeded) {
342   Node *node = getNode(id, createIfNeeded);
343   if (node) {
344     return node->m_qaction;
345   } else {
346     return 0;
347   }
348 }
349 
350 //---------------------------------------------------------
351 
createAction(CommandId id,QObject * parent,bool state)352 QAction *CommandManager::createAction(CommandId id, QObject *parent,
353                                       bool state) {
354   Node *node = getNode(id, false);
355   if (!node) return 0;
356   QAction *refAction = node->m_qaction;
357   if (!refAction) return 0;
358   QString text = refAction->text();
359   if (node->m_onText != "" && node->m_offText != "")
360     text          = state ? node->m_onText : node->m_offText;
361   QAction *action = new QAction(text, parent);
362   action->setShortcut(refAction->shortcut());
363   return action;
364 }
365 
366 //---------------------------------------------------------
367 
setToggleTexts(CommandId id,const QString & onText,const QString & offText)368 void CommandManager::setToggleTexts(CommandId id, const QString &onText,
369                                     const QString &offText) {
370   Node *node = getNode(id, false);
371   if (node) {
372     node->m_onText  = onText;
373     node->m_offText = offText;
374   }
375 }
376 
377 //---------------------------------------------------------
378 
getIdFromAction(QAction * action)379 std::string CommandManager::getIdFromAction(QAction *action) {
380   std::map<QAction *, Node *>::iterator it = m_qactionTable.find(action);
381   if (it != m_qactionTable.end())
382     return it->second->m_id;
383   else
384     return "";
385 }
386 
387 //---------------------------------------------------------
388 // load user defined shortcuts
389 
loadShortcuts()390 void CommandManager::loadShortcuts() {
391   TFilePath fp = ToonzFolder::getMyModuleDir() + TFilePath("shortcuts.ini");
392   if (!TFileStatus(fp).doesExist()) {
393     // if user shortcut file does not exist, then try to load from template
394     TFilePath tmplFp =
395         ToonzFolder::getTemplateModuleDir() + TFilePath("shortcuts.ini");
396     if (TFileStatus(tmplFp).doesExist()) TSystem::copyFile(fp, tmplFp);
397     // if neither settings exist, do nothing and return
398     else
399       return;
400   }
401 
402   QSettings settings(toQString(fp), QSettings::IniFormat);
403   settings.beginGroup("shortcuts");
404   QStringList ids = settings.allKeys();
405   for (int i = 0; i < ids.size(); i++) {
406     std::string id   = ids.at(i).toStdString();
407     QString shortcut = settings.value(ids.at(i), "").toString();
408     QAction *action  = getAction(&id[0], false);
409     if (action) {
410       QString oldShortcut = action->shortcut().toString();
411       if (oldShortcut == shortcut) continue;
412       if (!oldShortcut.isEmpty())
413         m_shortcutTable.erase(oldShortcut.toStdString());
414       if (!shortcut.isEmpty()) {
415         QAction *other = getActionFromShortcut(shortcut.toStdString());
416         if (other) other->setShortcut(QKeySequence());
417         m_shortcutTable[shortcut.toStdString()] = getNode(&id[0]);
418       }
419       action->setShortcut(QKeySequence(shortcut));
420     }
421   }
422   settings.endGroup();
423 }
424 
425 /*
426 //---------------------------------------------------------
427 
428 QString CommandManager::getFullText(CommandId id, bool state)
429 {
430   std::map<std::string, Node*>::iterator it;
431   it = m_idTable.find(id);
432   if(it == m_idTable.end()) return "";
433   Node *node = it->second;
434   QAction *action = it->second->m_qaction;
435   QString text = action->text();
436   if(node->m_onText != "" && node->m_offText != "")
437     text = state ? node->m_onText : node->m_offText;
438 
439   QString shortcut = QString::fromStdString(getShortcutFromAction(action));
440   if(shortcut != "") text += " " + shortcut;
441   return text;
442 }
443 */
444 
445 //---------------------------------------------------------
446 
MenuItemHandler(const char * cmdId)447 MenuItemHandler::MenuItemHandler(const char *cmdId) {
448   CommandManager::instance()->setHandler(
449       cmdId, new CommandHandlerHelper<MenuItemHandler>(
450                  this, &MenuItemHandler::execute));
451 }
452 
453 //=============================================================================
454 // DVAction
455 //-----------------------------------------------------------------------------
456 
DVAction(const QString & text,QObject * parent)457 DVAction::DVAction(const QString &text, QObject *parent)
458     : QAction(text, parent) {
459   connect(this, SIGNAL(triggered()), this, SLOT(onTriggered()));
460 }
461 
462 //-----------------------------------------------------------------------------
463 
DVAction(const QIcon & icon,const QString & text,QObject * parent)464 DVAction::DVAction(const QIcon &icon, const QString &text, QObject *parent)
465     : QAction(icon, text, parent) {
466   connect(this, SIGNAL(triggered()), this, SLOT(onTriggered()));
467 }
468 
469 //-----------------------------------------------------------------------------
470 
onTriggered()471 void DVAction::onTriggered() { CommandManager::instance()->execute(this); }
472 
473 //=============================================================================
474 // DVMenuAction
475 //-----------------------------------------------------------------------------
476 /*! It is a menu' with action defined in \b actions.
477                 Actions can contain CommandId or simple action name.
478                 In first case action triggered is connected with action command
479    execute directly.
480                 In second case action triggered is connected with menu action
481    command execute;
482                 is helpful to use \b m_triggeredActionIndex to distingue action.
483 */
DVMenuAction(const QString & text,QWidget * parent,QList<QString> actions)484 DVMenuAction::DVMenuAction(const QString &text, QWidget *parent,
485                            QList<QString> actions)
486     : QMenu(text, parent), m_triggeredActionIndex(-1) {
487   int i;
488   for (i = 0; i < actions.size(); i++) addAction(actions.at(i));
489   connect(this, SIGNAL(triggered(QAction *)), this,
490           SLOT(onTriggered(QAction *)));
491 }
492 
493 //-----------------------------------------------------------------------------
494 /*! Set a new list of action to menu'.
495                 NB. If action list is composed by action menaged and action to
496    create pay
497                 attention to inserted order.*/
setActions(QList<QString> actions)498 void DVMenuAction::setActions(QList<QString> actions) {
499   if (m_triggeredActionIndex != -1)  // Sta facendo l'onTriggered
500     return;
501   clear();
502   int i;
503   for (i = 0; i < actions.size(); i++) {
504     QString actionId = actions.at(i);
505     // Se l'azione e' definita da un CommandId la aggiungo.
506     QAction *action =
507         CommandManager::instance()->getAction(actionId.toStdString().c_str());
508     if (action) {
509       addAction(action);
510       continue;
511     }
512     // Altrimenti creo una nuova azione e la aggiungo.
513     action = addAction(actionId);
514     action->setData(QVariant(i));
515   }
516 }
517 
518 //-----------------------------------------------------------------------------
519 
520 namespace {
changeStringNumber(QString str,int index)521 QString changeStringNumber(QString str, int index) {
522   QString newStr     = str;
523   int n              = 3;
524   if (index >= 10) n = 4;
525   QString number;
526   newStr.replace(0, n, number.number(index + 1) + QString(". "));
527   return newStr;
528 }
529 }
530 
531 //-----------------------------------------------------------------------------
532 
onTriggered(QAction * action)533 void DVMenuAction::onTriggered(QAction *action) {
534   QVariant data                              = action->data();
535   if (data.isValid()) m_triggeredActionIndex = data.toInt();
536   CommandManager::instance()->execute(action, menuAction());
537   int oldIndex = m_triggeredActionIndex;
538   if (m_triggeredActionIndex != -1) m_triggeredActionIndex = -1;
539   QString str                                              = data.toString();
540   QAction *tableAction =
541       CommandManager::instance()->getAction(str.toStdString().c_str());
542   if (tableAction || oldIndex == 0) return;
543   QList<QAction *> acts = actions();
544   removeAction(action);
545   insertAction(acts[0], action);
546 
547   acts = actions();
548   int i;
549   for (i = 0; i <= oldIndex; i++) {
550     QAction *a     = acts.at(i);
551     QString newTxt = changeStringNumber(a->text(), i);
552     a->setText(newTxt);
553     a->setData(QVariant(i));
554   }
555   m_triggeredActionIndex = -1;
556 }
557 
558 //-----------------------------------------------------------------------------
559