All Classes Namespaces Functions Variables Enumerations Properties Pages
shortcutspage.cpp
1 /*
2 
3 Pencil2D - Traditional Animation Software
4 Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5 Copyright (C) 2012-2020 Matthew Chiawen Chang
6 
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; version 2 of the License.
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 */
17 #include "shortcutspage.h"
18 #include "ui_shortcutspage.h"
19 
20 #include <QDebug>
21 #include <QStringRef>
22 #include <QSettings>
23 #include <QStandardItemModel>
24 #include <QKeyEvent>
25 #include <QKeySequence>
26 #include <QMessageBox>
27 #include "pencilsettings.h"
28 #include <QFile>
29 #include <QFileDialog>
30 
31 
32 static const int ACT_NAME_COLUMN = 0;
33 static const int KEY_SEQ_COLUMN = 1;
34 
35 static QString getHumanReadableShortcutName(const QString&);
36 
37 ShortcutsPage::ShortcutsPage( QWidget* parent )
38  : QWidget(parent),
39  ui( new Ui::ShortcutsPage )
40 {
41  ui->setupUi(this);
42  m_treeModel = new QStandardItemModel(this);
43  m_treeModel->setColumnCount(2);
44  m_treeModel->setHorizontalHeaderLabels({ tr("Action", "Shortcut table header"), tr("Shortcut", "Shortcut table header") });
45  treeModelLoadShortcutsSetting();
46 
47  ui->treeView->setModel(m_treeModel);
48  ui->treeView->resizeColumnToContents(0);
49 
50  connect( ui->treeView, &QTreeView::clicked, this, &ShortcutsPage::tableItemClicked );
51  connect( ui->keySequenceEdit, &QKeySequenceEdit::editingFinished, this, &ShortcutsPage::keyCapLineEditTextChanged );
52  connect( ui->restoreShortcutsButton, &QPushButton::clicked, this, &ShortcutsPage::restoreShortcutsButtonClicked );
53  connect(ui->btnSaveShortcuts, &QPushButton::clicked, this, &ShortcutsPage::saveShortcutsButtonClicked);
54  connect(ui->btnLoadShortcuts, &QPushButton::clicked, this, &ShortcutsPage::loadShortcutsButtonClicked);
55  connect( ui->clearButton, &QPushButton::clicked, this, &ShortcutsPage::clearButtonClicked );
56 
57  ui->treeView->selectionModel()->select(QItemSelection(m_treeModel->index(0, 0), m_treeModel->index(0, m_treeModel->columnCount() - 1)), QItemSelectionModel::Select);
58  tableItemClicked(m_treeModel->index(0, 0));
59 }
60 
61 ShortcutsPage::~ShortcutsPage() {
62  delete ui;
63 }
64 
65 void ShortcutsPage::tableItemClicked( const QModelIndex& modelIndex )
66 {
67  int row = modelIndex.row();
68 
69  // extract action name
70  QStandardItem* actionItem = m_treeModel->item(row, ACT_NAME_COLUMN);
71  ui->actionNameLabel->setText(actionItem->text());
72 
73  // extract key sequence
74  QStandardItem* keySeqItem = m_treeModel->item(row, KEY_SEQ_COLUMN);
75  ui->keySequenceEdit->setKeySequence(keySeqItem->text());
76 
77  qDebug() << "Command Selected:" << actionItem->text();
78 
79  m_currentItemIndex = modelIndex;
80 
81  ui->keySequenceEdit->setFocus();
82 }
83 
84 void ShortcutsPage::keyCapLineEditTextChanged()
85 {
86  QKeySequence keySequence = ui->keySequenceEdit->keySequence();
87  if (!m_currentItemIndex.isValid())
88  {
89  return;
90  }
91 
92  int row = m_currentItemIndex.row();
93  QStandardItem* actionItem = m_treeModel->item(row, ACT_NAME_COLUMN);
94  QStandardItem* keySeqItem = m_treeModel->item(row, KEY_SEQ_COLUMN);
95 
96  QString strCmdName = actionItem->data().toString();
97  QString strKeySeq = keySequence.toString( QKeySequence::PortableText );
98 
99  QSettings setting( PENCIL2D, PENCIL2D );
100  setting.beginGroup("shortcuts");
101 
102  if (isKeySequenceExist(setting, strCmdName, keySequence))
103  {
104  QMessageBox msgBox(this);
105  msgBox.setText( tr("Shortcut Conflict!"));
106  msgBox.setInformativeText( tr("%1 is already used, overwrite?").arg(keySequence.toString(QKeySequence::NativeText)) );
107  msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
108  msgBox.setDefaultButton(QMessageBox::No);
109  msgBox.setIcon( QMessageBox::Warning );
110 
111  int result = msgBox.exec();
112 
113  if ( result != QMessageBox::Yes )
114  {
115  ui->keySequenceEdit->setKeySequence( keySeqItem->text() );
116  return;
117  }
118  removeDuplicateKeySequence(&setting, keySequence);
119  }
120 
121  setting.setValue(strCmdName, strKeySeq);
122  setting.endGroup();
123  setting.sync();
124 
125  treeModelLoadShortcutsSetting();
126 
127  qDebug() << "Shortcut " << strCmdName << " = " << strKeySeq;
128 }
129 
130 void ShortcutsPage::saveShortcutsButtonClicked()
131 {
132  QSettings settings( PENCIL2D, PENCIL2D );
133  settings.beginGroup( "LastSavePath" );
134 
135  QString fDir = settings.value("Shortcuts").toString();
136  if (fDir.isEmpty())
137  fDir = QDir::homePath();
138 
139  QString fileName = QFileDialog::getSaveFileName(this,
140  tr("Save Pencil2D Shortcut file"),
141  fDir + "/" + tr("untitled.pcls"),
142  tr("Pencil2D Shortcut File(*.pcls)"));
143  settings.setValue("Shortcuts", fileName);
144  settings.endGroup();
145 
146  QSettings out(fileName, QSettings::IniFormat);
147  settings.beginGroup( "shortcuts" );
148  out.beginGroup("shortcuts");
149 
150  foreach (QString key, settings.allKeys())
151  {
152  out.setValue(key, settings.value(key));
153  }
154 }
155 
156 void ShortcutsPage::loadShortcutsButtonClicked()
157 {
158  QSettings settings( PENCIL2D, PENCIL2D );
159  settings.beginGroup("LastSavePath");
160  QString fDir = settings.value("Shortcuts").toString();
161  settings.endGroup();
162  if (fDir.isEmpty())
163  fDir = QDir::homePath();
164 
165  QString fileName = QFileDialog::getOpenFileName(this,
166  tr("Open Pencil2D Shortcut file"),
167  fDir,
168  tr("Pencil2D Shortcut File(*.pcls)"));
169 
170  if (!QFileInfo(fileName).isReadable())
171  {
172  qDebug() << "Shortcut file not readable";
173  return;
174  }
175 
176  QSettings input(fileName, QSettings::IniFormat);
177  if (input.status() != QSettings::NoError || !input.childGroups().contains("shortcuts"))
178  {
179  qDebug() << "Error accessing or parsing shortcut file" << input.status();
180  return;
181  }
182 
183  input.beginGroup("shortcuts");
184  settings.beginGroup("shortcuts");
185  foreach (QString pShortcutsKey, input.allKeys())
186  {
187  settings.setValue(pShortcutsKey, input.value(pShortcutsKey));
188  }
189 
190  treeModelLoadShortcutsSetting();
191 }
192 
193 void ShortcutsPage::restoreShortcutsButtonClicked()
194 {
195  restoreShortcutsToDefault();
196  treeModelLoadShortcutsSetting();
197 }
198 
199 bool ShortcutsPage::isKeySequenceExist(const QSettings& settings, QString strTargetCmdName, QKeySequence targetkeySeq)
200 {
201  foreach (QString strCmdName, settings.allKeys())
202  {
203  if (strTargetCmdName == strCmdName)
204  {
205  continue;
206  }
207 
208  QString strCmdKeySeq = settings.value(strCmdName).toString();
209  if (QKeySequence(strCmdKeySeq) == targetkeySeq)
210  {
211  return true;
212  }
213  }
214  return false;
215 }
216 
217 void ShortcutsPage::removeDuplicateKeySequence(QSettings* settings, QKeySequence keySeq)
218 {
219  foreach(QString strCmdName, settings->allKeys())
220  {
221  QString strCmdKeySeq = settings->value(strCmdName).toString();
222 
223  if ( strCmdKeySeq == keySeq.toString(QKeySequence::PortableText))
224  {
225  settings->setValue(strCmdName, "");
226  }
227  }
228 }
229 
230 void ShortcutsPage::treeModelLoadShortcutsSetting()
231 {
232  // Load shortcuts from settings
233  QSettings settings( PENCIL2D, PENCIL2D );
234  settings.beginGroup("shortcuts");
235 
236  m_treeModel->setRowCount(settings.allKeys().size());
237  Q_ASSERT(m_treeModel->columnCount() == 2);
238 
239  int row = 0;
240  foreach (QString strCmdName, settings.allKeys())
241  {
242  const QString &strShortcutName = getHumanReadableShortcutName(strCmdName);
243  if (strShortcutName.isEmpty()) {
244  // Shortcut not supported by this version of Pencil2D
245  continue;
246  }
247 
248  QString strKeySequence = settings.value(strCmdName).toString();
249 
250  //convert to native format
251  strKeySequence = QKeySequence(strKeySequence).toString(QKeySequence::NativeText);
252 
253  if (m_treeModel->item(row , ACT_NAME_COLUMN) == nullptr)
254  m_treeModel->setItem(row, ACT_NAME_COLUMN, new QStandardItem());
255  if (m_treeModel->item(row, KEY_SEQ_COLUMN) == nullptr)
256  m_treeModel->setItem(row, KEY_SEQ_COLUMN, new QStandardItem());
257 
258  m_treeModel->item(row, ACT_NAME_COLUMN)->setData(strCmdName);
259  m_treeModel->item(row, ACT_NAME_COLUMN)->setText(strShortcutName);
260  m_treeModel->item(row, ACT_NAME_COLUMN)->setEditable(false);
261  m_treeModel->item(row, KEY_SEQ_COLUMN)->setText(strKeySequence);
262  m_treeModel->item(row, KEY_SEQ_COLUMN)->setEditable(false);
263 
264  row++;
265  }
266  m_treeModel->setRowCount(row);
267  settings.endGroup();
268 
269  ui->treeView->resizeColumnToContents( 0 );
270 }
271 
272 void ShortcutsPage::clearButtonClicked()
273 {
274  if ( !m_currentItemIndex.isValid() )
275  {
276  return;
277  }
278 
279  int row = m_currentItemIndex.row();
280  QStandardItem* actionItem = m_treeModel->item(row, ACT_NAME_COLUMN);
281 
282  QString strCmdName = QString("shortcuts/%1").arg( actionItem->data().toString() );
283 
284  QSettings setting( PENCIL2D, PENCIL2D );
285  setting.setValue( strCmdName, "" );
286  setting.sync();
287 
288  ui->keySequenceEdit->clear();
289 
290  treeModelLoadShortcutsSetting();
291 }
292 
299 static QString getHumanReadableShortcutName(const QString& cmdName)
300 {
301  static QHash<QString, QString> humanReadableShortcutNames = QHash<QString, QString>{
302  {CMD_ADD_FRAME, ShortcutsPage::tr("Add Frame", "Shortcut")},
303  {CMD_CLEAR_FRAME, ShortcutsPage::tr("Clear Frame", "Shortcut")},
304  {CMD_COPY, ShortcutsPage::tr("Copy", "Shortcut")},
305  {CMD_PASTE_FROM_PREVIOUS, ShortcutsPage::tr("Paste from Previous Keyframe", "Shortcut")},
306  {CMD_CUT, ShortcutsPage::tr("Cut", "Shortcut")},
307  {CMD_DELETE_CUR_LAYER, ShortcutsPage::tr("Delete Current Layer", "Shortcut")},
308  {CMD_DESELECT_ALL, ShortcutsPage::tr("Deselect All", "Shortcut")},
309  {CMD_DUPLICATE_FRAME, ShortcutsPage::tr("Duplicate Frame", "Shortcut")},
310  {CMD_EXIT, ShortcutsPage::tr("Exit", "Shortcut")},
311  {CMD_EXPORT_IMAGE, ShortcutsPage::tr("Export Image", "Shortcut")},
312  {CMD_EXPORT_IMAGE_SEQ, ShortcutsPage::tr("Export Image Sequence", "Shortcut")},
313  {CMD_EXPORT_MOVIE, ShortcutsPage::tr("Export Movie", "Shortcut")},
314  {CMD_EXPORT_PALETTE, ShortcutsPage::tr("Export Palette", "Shortcut")},
315  {CMD_EXPORT_SOUND, ShortcutsPage::tr("Export Sound", "Shortcut")},
316  {CMD_FLIP_HORIZONTAL, ShortcutsPage::tr("Horizontal Flip", "Shortcut")},
317  {CMD_FLIP_INBETWEEN, ShortcutsPage::tr("Flip In-Between", "Shortcut")},
318  {CMD_FLIP_ROLLING, ShortcutsPage::tr("Flip Rolling", "Shortcut")},
319  {CMD_FLIP_VERTICAL, ShortcutsPage::tr("Vertical Flip", "Shortcut")},
320  {CMD_GOTO_NEXT_FRAME, ShortcutsPage::tr("Next Frame", "Shortcut")},
321  {CMD_GOTO_NEXT_KEY_FRAME, ShortcutsPage::tr("Next Keyframe", "Shortcut")},
322  {CMD_GOTO_PREV_FRAME, ShortcutsPage::tr("Previous Frame", "Shortcut")},
323  {CMD_GOTO_PREV_KEY_FRAME, ShortcutsPage::tr("Previous Keyframe", "Shortcut")},
324  {CMD_SELECTION_ADD_FRAME_EXPOSURE, ShortcutsPage::tr("Selection: Add Frame Exposure", "Shortcut")},
325  {CMD_SELECTION_SUBTRACT_FRAME_EXPOSURE, ShortcutsPage::tr("Selection: Subtract Frame Exposure", "Shortcut")},
326  {CMD_REVERSE_SELECTED_FRAMES, ShortcutsPage::tr("Selection: Reverse Keyframes", "Shortcut")},
327  {CMD_REMOVE_SELECTED_FRAMES, ShortcutsPage::tr("Selection: Remove Keyframes", "Shortcut")},
328  {CMD_GRID, ShortcutsPage::tr("Toggle Grid", "Shortcut")},
329  {CMD_IMPORT_IMAGE, ShortcutsPage::tr("Import Image", "Shortcut")},
330  {CMD_IMPORT_IMAGE_SEQ, ShortcutsPage::tr("Import Image Sequence", "Shortcut")},
331  {CMD_IMPORT_SOUND, ShortcutsPage::tr("Import Sound", "Shortcut")},
332  {CMD_ALL_LAYER_VISIBILITY, ShortcutsPage::tr("Show All Layers", "Shortcut")},
333  {CMD_CURRENT_LAYER_VISIBILITY, ShortcutsPage::tr("Show Current Layer Only", "Shortcut")},
334  {CMD_RELATIVE_LAYER_VISIBILITY, ShortcutsPage::tr("Show Layers Relative to Current Layer", "Shortcut")},
335  {CMD_LOOP, ShortcutsPage::tr("Toggle Loop", "Shortcut")},
336  {CMD_MOVE_FRAME_BACKWARD, ShortcutsPage::tr("Move Frame Backward", "Shortcut")},
337  {CMD_MOVE_FRAME_FORWARD, ShortcutsPage::tr("Move Frame Forward", "Shortcut")},
338  {CMD_NEW_BITMAP_LAYER, ShortcutsPage::tr("New Bitmap Layer", "Shortcut")},
339  {CMD_NEW_CAMERA_LAYER, ShortcutsPage::tr("New Camera Layer", "Shortcut")},
340  {CMD_NEW_FILE, ShortcutsPage::tr("New File", "Shortcut")},
341  {CMD_NEW_SOUND_LAYER, ShortcutsPage::tr("New Sound Layer", "Shortcut")},
342  {CMD_NEW_VECTOR_LAYER, ShortcutsPage::tr("New Vector Layer", "Shortcut")},
343  {CMD_ONIONSKIN_NEXT, ShortcutsPage::tr("Toggle Next Onion Skin", "Shortcut")},
344  {CMD_ONIONSKIN_PREV, ShortcutsPage::tr("Toggle Previous Onion Skin", "Shortcut")},
345  {CMD_OPEN_FILE, ShortcutsPage::tr("Open File", "Shortcut")},
346  {CMD_PASTE, ShortcutsPage::tr("Paste", "Shortcut")},
347  {CMD_PLAY, ShortcutsPage::tr("Play/Stop", "Shortcut")},
348  {CMD_PREFERENCE, ShortcutsPage::tr("Preferences", "Shortcut")},
349  {CMD_PREVIEW, ShortcutsPage::tr("Preview", "Shortcut")},
350  {CMD_REDO, ShortcutsPage::tr("Redo", "Shortcut")},
351  {CMD_REMOVE_FRAME, ShortcutsPage::tr("Remove Frame", "Shortcut")},
352  {CMD_RESET_WINDOWS, ShortcutsPage::tr("Reset Windows", "Shortcut")},
353  {CMD_RESET_ZOOM_ROTATE, ShortcutsPage::tr("Reset View", "Shortcut")},
354  {CMD_CENTER_VIEW, ShortcutsPage::tr("Center View", "Shortcut")},
355  {CMD_ROTATE_ANTI_CLOCK, ShortcutsPage::tr("Rotate Anticlockwise", "Shortcut")},
356  {CMD_ROTATE_CLOCK, ShortcutsPage::tr("Rotate Clockwise", "Shortcut")},
357  {CMD_RESET_ROTATION, ShortcutsPage::tr("Reset Rotation", "Shortcut")},
358  {CMD_SAVE_AS, ShortcutsPage::tr("Save File As", "Shortcut")},
359  {CMD_SAVE_FILE, ShortcutsPage::tr("Save File", "Shortcut")},
360  {CMD_SELECT_ALL, ShortcutsPage::tr("Select All", "Shortcut")},
361  {CMD_TOGGLE_STATUS_BAR, ShortcutsPage::tr("Toggle Status Bar Visibility", "Shortcut")},
362  {CMD_TOGGLE_COLOR_INSPECTOR, ShortcutsPage::tr("Toggle Color Inspector Window Visibility", "Shortcut")},
363  {CMD_TOGGLE_COLOR_LIBRARY, ShortcutsPage::tr("Toggle Color Palette Window Visibility", "Shortcut")},
364  {CMD_TOGGLE_COLOR_WHEEL, ShortcutsPage::tr("Toggle Color Box Window Visibility", "Shortcut")},
365  {CMD_TOGGLE_DISPLAY_OPTIONS, ShortcutsPage::tr("Toggle Display Window Visibility", "Shortcut")},
366  {CMD_TOGGLE_ONION_SKIN, ShortcutsPage::tr("Toggle Onion Skins Window Visibility", "Shortcut")},
367  {CMD_TOGGLE_TIMELINE, ShortcutsPage::tr("Toggle Timeline Window Visibility", "Shortcut")},
368  {CMD_TOGGLE_TOOLBOX, ShortcutsPage::tr("Toggle Tools Window Visibility", "Shortcut")},
369  {CMD_TOGGLE_TOOL_OPTIONS, ShortcutsPage::tr("Toggle Options Window Visibility", "Shortcut")},
370  {CMD_TOOL_BRUSH, ShortcutsPage::tr("Brush Tool", "Shortcut")},
371  {CMD_TOOL_BUCKET, ShortcutsPage::tr("Bucket Tool", "Shortcut")},
372  {CMD_TOOL_ERASER, ShortcutsPage::tr("Eraser Tool", "Shortcut")},
373  {CMD_TOOL_EYEDROPPER, ShortcutsPage::tr("Eyedropper Tool", "Shortcut")},
374  {CMD_TOOL_HAND, ShortcutsPage::tr("Hand Tool", "Shortcut")},
375  {CMD_TOOL_MOVE, ShortcutsPage::tr("Move Tool", "Shortcut")},
376  {CMD_TOOL_PEN, ShortcutsPage::tr("Pen Tool", "Shortcut")},
377  {CMD_TOOL_PENCIL, ShortcutsPage::tr("Pencil Tool", "Shortcut")},
378  {CMD_TOOL_POLYLINE, ShortcutsPage::tr("Polyline Tool", "Shortcut")},
379  {CMD_TOOL_SELECT, ShortcutsPage::tr("Select Tool", "Shortcut")},
380  {CMD_TOOL_SMUDGE, ShortcutsPage::tr("Smudge Tool", "Shortcut")},
381  {CMD_UNDO, ShortcutsPage::tr("Undo", "Shortcut")},
382  {CMD_ZOOM_100, ShortcutsPage::tr("Set Zoom to 100%", "Shortcut")},
383  {CMD_ZOOM_200, ShortcutsPage::tr("Set Zoom to 200%", "Shortcut")},
384  {CMD_ZOOM_25, ShortcutsPage::tr("Set Zoom to 25%", "Shortcut")},
385  {CMD_ZOOM_300, ShortcutsPage::tr("Set Zoom to 300%", "Shortcut")},
386  {CMD_ZOOM_33, ShortcutsPage::tr("Set Zoom to 33%", "Shortcut")},
387  {CMD_ZOOM_400, ShortcutsPage::tr("Set Zoom to 400%", "Shortcut")},
388  {CMD_ZOOM_50, ShortcutsPage::tr("Set Zoom to 50%", "Shortcut")},
389  {CMD_ZOOM_IN, ShortcutsPage::tr("Zoom In", "Shortcut")},
390  {CMD_ZOOM_OUT, ShortcutsPage::tr("Zoom Out", "Shortcut")},
391  };
392 
393  return humanReadableShortcutNames.value(cmdName, QString());
394 }
void setupUi(QWidget *widget)
void endGroup()
QString homePath()
QString tr(const char *sourceText, const char *disambiguation, int n)
QString text() const const
int size() const const
virtual void setData(const QVariant &value, int role)
void editingFinished()
void setValue(const QString &key, const QVariant &value)
bool isValid() const const
void setItem(int row, int column, QStandardItem *item)
bool isEmpty() const const
void clicked(bool checked)
int row() const const
virtual int columnCount(const QModelIndex &parent) const const override
const T value(const Key &key) const const
QStandardItem * item(int row, int column) const const
void setText(const QString &text)
QVariant value(const QString &key, const QVariant &defaultValue) const const
QString toString(QKeySequence::SequenceFormat format) const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QStringList allKeys() const const
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
void clicked(const QModelIndex &index)
void keySequence(QWindow *window, const QKeySequence &keySequence)
void setRowCount(int rows)
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString toString() const const
virtual QVariant data(int role) const const
void setEditable(bool editable)
void beginGroup(const QString &prefix)