File indexing completed on 2025-01-05 05:19:56
0001 /* 0002 * SPDX-FileCopyrightText: 2022 Pablo Rauzy <r .at. uzy .dot. me> 0003 * SPDX-License-Identifier: LGPL-2.0-or-later 0004 */ 0005 0006 #include <QAction> 0007 #include <QInputDialog> 0008 #include <QKeySequence> 0009 #include <QLineEdit> 0010 #include <QMessageBox> 0011 #include <QRegularExpression> 0012 0013 #include <KActionCollection> 0014 #include <KLocalizedString> 0015 #include <KStringHandler> 0016 #include <KXMLGUIFactory> 0017 0018 #include <KTextEditor/Editor> 0019 #include <KTextEditor/Message> 0020 #include <KTextEditor/Plugin> 0021 0022 #include "keyboardmacrospluginview.h" 0023 0024 KeyboardMacrosPluginView::KeyboardMacrosPluginView(KeyboardMacrosPlugin *plugin, KTextEditor::MainWindow *mainwindow) 0025 : QObject(mainwindow) 0026 , m_plugin(plugin) 0027 , m_mainWindow(mainwindow) 0028 { 0029 // setup XML GUI 0030 KXMLGUIClient::setComponentName(QStringLiteral("keyboardmacros"), i18n("Keyboard Macros")); 0031 setXMLFile(QStringLiteral("ui.rc")); 0032 0033 KActionMenu *menu = new KActionMenu(i18n("&Keyboard Macros"), this); 0034 menu->setIcon(QIcon::fromTheme(QStringLiteral("input-keyboard"))); 0035 actionCollection()->addAction(QStringLiteral("keyboardmacros"), menu); 0036 menu->setToolTip(i18n("Record and play keyboard macros.")); 0037 menu->setEnabled(true); 0038 0039 // create record action 0040 m_recordAction = actionCollection()->addAction(QStringLiteral("keyboardmacros_record")); 0041 m_recordAction->setText(i18n("&Record Macro...")); 0042 m_recordAction->setIcon(QIcon::fromTheme(QStringLiteral("media-record"))); 0043 m_recordAction->setToolTip(i18n("Start/stop recording a macro (i.e., keyboard action sequence).")); 0044 actionCollection()->setDefaultShortcut(m_recordAction, QKeySequence(QStringLiteral("Ctrl+Shift+K"), QKeySequence::PortableText)); 0045 connect(m_recordAction, &QAction::triggered, plugin, [this] { 0046 slotRecord(); 0047 }); 0048 menu->addAction(m_recordAction); 0049 0050 // create cancel action 0051 m_cancelAction = actionCollection()->addAction(QStringLiteral("keyboardmacros_cancel")); 0052 m_cancelAction->setText(i18n("&Cancel Macro Recording")); 0053 m_cancelAction->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); 0054 m_cancelAction->setToolTip(i18n("Cancel ongoing recording (and keep the previous macro as the current one).")); 0055 actionCollection()->setDefaultShortcut(m_cancelAction, QKeySequence(QStringLiteral("Ctrl+Alt+Shift+K"), QKeySequence::PortableText)); 0056 m_cancelAction->setEnabled(false); 0057 connect(m_cancelAction, &QAction::triggered, plugin, [this] { 0058 slotCancel(); 0059 }); 0060 menu->addAction(m_cancelAction); 0061 0062 // create play action 0063 m_playAction = actionCollection()->addAction(QStringLiteral("keyboardmacros_play")); 0064 m_playAction->setText(i18n("&Play Macro")); 0065 m_playAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); 0066 m_playAction->setToolTip(i18n("Play current macro (i.e., execute the last recorded keyboard action sequence).")); 0067 actionCollection()->setDefaultShortcut(m_playAction, QKeySequence(QStringLiteral("Ctrl+Alt+K"), QKeySequence::PortableText)); 0068 m_playAction->setEnabled(false); 0069 connect(m_playAction, &QAction::triggered, plugin, [this] { 0070 slotPlay(); 0071 }); 0072 menu->addAction(m_playAction); 0073 0074 // create save action 0075 m_saveAction = actionCollection()->addAction(QStringLiteral("keyboardmacros_save")); 0076 m_saveAction->setText(i18n("&Save Current Macro")); 0077 m_saveAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playlist-append"))); 0078 m_saveAction->setToolTip(i18n("Give a name to the current macro and persistently save it.")); 0079 actionCollection()->setDefaultShortcut(m_saveAction, QKeySequence(QStringLiteral("Alt+Shift+K"), QKeySequence::PortableText)); 0080 m_saveAction->setEnabled(false); 0081 connect(m_saveAction, &QAction::triggered, plugin, [this] { 0082 slotSave(); 0083 }); 0084 menu->addAction(m_saveAction); 0085 0086 // add separator 0087 menu->addSeparator(); 0088 0089 // create load named menu 0090 m_loadMenu = new KActionMenu(i18n("&Load Named Macro..."), menu); 0091 m_loadMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); 0092 actionCollection()->addAction(QStringLiteral("keyboardmacros_named_load"), m_loadMenu); 0093 m_loadMenu->setToolTip(i18n("Load a named macro as the current one.")); 0094 m_loadMenu->setEnabled(!plugin->m_namedMacros.empty()); 0095 menu->addAction(m_loadMenu); 0096 0097 // create play named menu 0098 m_playMenu = new KActionMenu(i18n("&Play Named Macro..."), menu); 0099 m_playMenu->setIcon(QIcon::fromTheme(QStringLiteral("auto-type"))); 0100 actionCollection()->addAction(QStringLiteral("keyboardmacros_named_play"), m_playMenu); 0101 m_playMenu->setToolTip(i18n("Play a named macro without loading it.")); 0102 m_playMenu->setEnabled(!plugin->m_namedMacros.empty()); 0103 menu->addAction(m_playMenu); 0104 0105 // create wipe named menu 0106 m_wipeMenu = new KActionMenu(i18n("&Wipe Named Macro..."), menu); 0107 m_wipeMenu->setIcon(QIcon::fromTheme(QStringLiteral("delete"))); 0108 actionCollection()->addAction(QStringLiteral("keyboardmacros_named_wipe"), m_wipeMenu); 0109 m_wipeMenu->setToolTip(i18n("Wipe a named macro.")); 0110 m_wipeMenu->setEnabled(!plugin->m_namedMacros.empty()); 0111 menu->addAction(m_wipeMenu); 0112 0113 // add named macros to our menus 0114 for (const auto &[name, macro] : plugin->m_namedMacros) { 0115 addNamedMacro(name, macro.toString()); 0116 } 0117 0118 // update current state if necessary 0119 if (plugin->m_recording) { 0120 recordingOn(); 0121 } 0122 if (!plugin->m_macro.isEmpty()) { 0123 macroLoaded(true); 0124 } 0125 0126 // add Keyboard Macros actions to the GUI 0127 mainwindow->guiFactory()->addClient(this); 0128 } 0129 0130 KeyboardMacrosPluginView::~KeyboardMacrosPluginView() 0131 { 0132 // remove Keyboard Macros actions from the GUI 0133 m_mainWindow->guiFactory()->removeClient(this); 0134 // deregister this view from the plugin 0135 m_plugin->m_pluginViews.removeOne(this); 0136 } 0137 0138 QKeySequence KeyboardMacrosPluginView::recordActionShortcut() const 0139 { 0140 return m_recordAction->shortcut(); 0141 } 0142 0143 QKeySequence KeyboardMacrosPluginView::playActionShortcut() const 0144 { 0145 return m_playAction->shortcut(); 0146 } 0147 0148 void KeyboardMacrosPluginView::recordingOn() 0149 { 0150 m_recordAction->setText(i18n("End Macro &Recording")); 0151 m_recordAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop"))); 0152 m_cancelAction->setEnabled(true); 0153 m_playAction->setEnabled(true); 0154 } 0155 0156 void KeyboardMacrosPluginView::recordingOff() 0157 { 0158 m_recordAction->setText(i18n("&Record Macro...")); 0159 m_recordAction->setIcon(QIcon::fromTheme(QStringLiteral("media-record"))); 0160 m_cancelAction->setEnabled(false); 0161 } 0162 0163 void KeyboardMacrosPluginView::macroLoaded(bool enable) 0164 { 0165 m_playAction->setEnabled(enable); 0166 m_saveAction->setEnabled(enable); 0167 } 0168 0169 void KeyboardMacrosPluginView::addNamedMacro(const QString &name, const QString &description) 0170 { 0171 QAction *action; 0172 // avoid unwanted accelerators 0173 QString label = KLocalizedString::removeAcceleratorMarker(KStringHandler::rsqueeze(name + QStringLiteral(": ") + description, 50)); 0174 0175 // add load action 0176 action = new QAction(i18n("Load %1", label), this); 0177 action->setToolTip(i18n("Load the '%1' macro as the current one.", name)); 0178 action->setEnabled(true); 0179 connect(action, &QAction::triggered, m_plugin, [this, name] { 0180 slotLoadNamed(name); 0181 }); 0182 m_loadMenu->addAction(action); 0183 // remember load action pointer 0184 m_namedMacrosLoadActions.insert_or_assign(name, action); 0185 // update load menu state 0186 m_loadMenu->setEnabled(true); 0187 0188 // add play action 0189 action = new QAction(i18n("Play %1", label), this); 0190 action->setToolTip(i18n("Play the '%1' macro without loading it.", name)); 0191 action->setEnabled(true); 0192 connect(action, &QAction::triggered, m_plugin, [this, name] { 0193 slotPlayNamed(name); 0194 }); 0195 m_playMenu->addAction(action); 0196 // add the play action to the collection (a user may want to set a shortcut for a macro they use very often) 0197 actionCollection()->addAction(QStringLiteral("keyboardmacros_named_play_") + name, action); 0198 // remember play action pointer 0199 m_namedMacrosPlayActions.insert_or_assign(name, action); 0200 // update play menu state 0201 m_playMenu->setEnabled(true); 0202 0203 // add wipe action 0204 action = new QAction(i18n("Wipe %1", label), this); 0205 action->setToolTip(i18n("Wipe the '%1' macro.", name)); 0206 action->setEnabled(true); 0207 connect(action, &QAction::triggered, m_plugin, [this, name] { 0208 slotWipeNamed(name); 0209 }); 0210 m_wipeMenu->addAction(action); 0211 // remember wipe action pointer 0212 m_namedMacrosWipeActions.insert_or_assign(name, action); 0213 // update wipe menu state 0214 m_wipeMenu->setEnabled(true); 0215 } 0216 0217 void KeyboardMacrosPluginView::removeNamedMacro(const QString &name) 0218 { 0219 QAction *action; 0220 0221 // remove load action 0222 auto it = m_namedMacrosLoadActions.find(name); 0223 action = (it == m_namedMacrosLoadActions.end()) ? nullptr : it->second; 0224 m_loadMenu->removeAction(action); 0225 actionCollection()->removeAction(action); 0226 // forget load action pointer 0227 m_namedMacrosLoadActions.erase(name); 0228 // update load menu state 0229 m_loadMenu->setEnabled(!m_namedMacrosLoadActions.empty()); 0230 0231 // remove play action 0232 it = m_namedMacrosPlayActions.find(name); 0233 action = (it == m_namedMacrosPlayActions.end()) ? nullptr : it->second; 0234 m_playMenu->removeAction(action); 0235 actionCollection()->removeAction(action); 0236 // forget play action pointer 0237 m_namedMacrosPlayActions.erase(name); 0238 // update play menu state 0239 m_playMenu->setEnabled(!m_namedMacrosPlayActions.empty()); 0240 0241 // remove wipe action 0242 it = m_namedMacrosWipeActions.find(name); 0243 action = (it == m_namedMacrosWipeActions.end()) ? nullptr : it->second; 0244 m_wipeMenu->removeAction(action); 0245 actionCollection()->removeAction(action); 0246 // forget wipe action pointer 0247 m_namedMacrosWipeActions.erase(name); 0248 // update wipe menu state 0249 m_wipeMenu->setEnabled(!m_namedMacrosWipeActions.empty()); 0250 } 0251 0252 // BEGIN Action slots 0253 0254 void KeyboardMacrosPluginView::slotRecord() 0255 { 0256 if (m_plugin->m_recording) { 0257 m_plugin->stop(true); 0258 } else { 0259 m_plugin->record(); 0260 } 0261 } 0262 0263 void KeyboardMacrosPluginView::slotPlay() 0264 { 0265 if (m_plugin->m_recording) { 0266 m_plugin->stop(true); 0267 } 0268 m_plugin->play(); 0269 } 0270 0271 void KeyboardMacrosPluginView::slotCancel() 0272 { 0273 if (!m_plugin->m_recording) { 0274 return; 0275 } 0276 m_plugin->cancel(); 0277 } 0278 0279 void KeyboardMacrosPluginView::slotSave() 0280 { 0281 if (m_plugin->m_recording) { 0282 return; 0283 } 0284 bool ok; 0285 QString name = 0286 QInputDialog::getText(m_mainWindow->window(), i18n("Keyboard Macros"), i18n("Save current macro as?"), QLineEdit::Normal, QStringLiteral(""), &ok); 0287 if (!ok || name.isEmpty()) { 0288 return; 0289 } 0290 m_plugin->save(name); 0291 } 0292 0293 void KeyboardMacrosPluginView::slotLoadNamed(const QString &name) 0294 { 0295 if (m_plugin->m_recording) { 0296 return; 0297 } 0298 if (name.isEmpty()) { 0299 return; 0300 } 0301 m_plugin->load(name); 0302 } 0303 0304 void KeyboardMacrosPluginView::slotPlayNamed(const QString &name) 0305 { 0306 if (m_plugin->m_recording) { 0307 return; 0308 } 0309 if (name.isEmpty()) { 0310 return; 0311 } 0312 m_plugin->play(name); 0313 } 0314 0315 void KeyboardMacrosPluginView::slotWipeNamed(const QString &name) 0316 { 0317 if (m_plugin->m_recording) { 0318 return; 0319 } 0320 if (QMessageBox::question(m_mainWindow->window(), i18n("Keyboard Macros"), i18n("Wipe the '%1' macro?", name)) != QMessageBox::Yes) { 0321 return; 0322 } 0323 m_plugin->wipe(name); 0324 } 0325 0326 // END 0327 0328 #include "moc_keyboardmacrospluginview.cpp"