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"