Skip to content

Commit

Permalink
Add event handlers
Browse files Browse the repository at this point in the history
Allows to use scripting to handle some events:

- items added/removed/changed
- tab items loaded
- tab selected

Fixes #59
  • Loading branch information
hluk committed Dec 14, 2023
1 parent bfcc880 commit 7fa973a
Show file tree
Hide file tree
Showing 14 changed files with 378 additions and 25 deletions.
42 changes: 42 additions & 0 deletions docs/scripting-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1622,6 +1622,48 @@ unlike in GUI, where row numbers start from 1 by default.
config("style", styleName)
.. js:function:: onItemsAdded()

Called when items are added to a tab.

The target tab is returned by `selectedTab()`.

The new items can be accessed with `selectedItemsData()`,
`selectedItemData()`, `selectedItems()` and `ItemSelection().current()`.

.. js:function:: onItemsRemoved()

Called when items are being removed from a tab.

The target tab is returned by `selectedTab()`.

The removed items can be accessed with `selectedItemsData()`,
`selectedItemData()`, `selectedItems()` and `ItemSelection().current()`.

.. js:function:: onItemsChanged()

Called when data in items change.

The target tab is returned by `selectedTab()`.

The modified items can be accessed with `selectedItemsData()`,
`selectedItemData()`, `selectedItems()` and `ItemSelection().current()`.

.. js:function:: onTabSelected()

Called when another tab is opened.

The newly selected tab is returned by `selectedTab()`.

The changed items can be accessed with `selectedItemsData()`,
`selectedItemData()`, `selectedItems()` and `ItemSelection().current()`.

.. js:function:: onItemsLoaded()

Called when all items are loaded into a tab.

The target tab is returned by `selectedTab()`.

Types
-----

Expand Down
4 changes: 2 additions & 2 deletions src/app/clipboardserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ ClipboardServer::ClipboardServer(QApplication *app, const QString &sessionName)
connect( m_wnd, &MainWindow::commandsSaved,
this, &ClipboardServer::onCommandsSaved );

m_server->start();

{
AppConfig appConfig;
loadSettings(&appConfig);
Expand All @@ -173,8 +175,6 @@ ClipboardServer::ClipboardServer(QApplication *app, const QString &sessionName)

qApp->installEventFilter(this);

m_server->start();

// Ignore global shortcut key presses in any widget.
m_ignoreKeysTimer.setInterval(100);
m_ignoreKeysTimer.setSingleShot(true);
Expand Down
2 changes: 1 addition & 1 deletion src/common/action.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ bool Action::waitForFinished(int msecs)
t.setSingleShot(true);
t.start(msecs);
}
loop.exec(QEventLoop::ExcludeUserInputEvents);
loop.exec(QEventLoop::AllEvents);

// Loop stopped because application is exiting?
while ( self && isRunning() && (msecs < 0 || t.isActive()) )
Expand Down
1 change: 1 addition & 0 deletions src/gui/clipboardbrowser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1671,6 +1671,7 @@ bool ClipboardBrowser::loadItems()
return false;

d.rowsInserted(QModelIndex(), 0, m.rowCount());
emit itemsLoaded(this);
if ( hasFocus() )
setCurrent(0);
onItemCountChanged();
Expand Down
2 changes: 2 additions & 0 deletions src/gui/clipboardbrowser.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ class ClipboardBrowser final : public QListView

void itemsChanged(const ClipboardBrowser *self);

void itemsLoaded(const ClipboardBrowser *self);

void itemSelectionChanged(const ClipboardBrowser *self);

void internalEditorStateChanged(const ClipboardBrowser *self);
Expand Down
6 changes: 6 additions & 0 deletions src/gui/commandcompleterdocumentation.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ void addDocumentation(AddDocumentationCallback addDocumentation)
addDocumentation("open", "open(url, ...) -> bool", "Tries to open URLs in appropriate applications.");
addDocumentation("execute", "execute(argument, ..., null, stdinData, ...) -> `FinishedCommand` or `undefined`", "Executes a command.");
addDocumentation("currentWindowTitle", "String currentWindowTitle() -> string", "Returns window title of currently focused window.");
addDocumentation("currentClipboardOwner", "String currentClipboardOwner() -> string", "Returns name of the current clipboard owner.");
addDocumentation("dialog", "dialog(...)", "Shows messages or asks user for input.");
addDocumentation("menuItems", "menuItems(text...) -> string", "Opens menu with given items and returns selected item or an empty string.");
addDocumentation("menuItems", "menuItems(items[]) -> int", "Opens menu with given items and returns index of selected item or -1.");
Expand Down Expand Up @@ -171,6 +172,11 @@ void addDocumentation(AddDocumentationCallback addDocumentation)
addDocumentation("hideDataNotification", "hideDataNotification()", "Hide notification for current data.");
addDocumentation("setClipboardData", "setClipboardData()", "Sets clipboard data for menu commands.");
addDocumentation("styles", "styles() -> array of strings", "List available styles for `style` option.");
addDocumentation("onItemsAdded", "onItemsAdded()", "Called when items are added to a tab.");
addDocumentation("onItemsRemoved", "onItemsRemoved()", "Called when items are being removed from a tab.");
addDocumentation("onItemsChanged", "onItemsChanged()", "Called when data in items change.");
addDocumentation("onTabSelected", "onTabSelected()", "Called when another tab is open.");
addDocumentation("onItemsLoaded", "onItemsLoaded()", "Called when items a loaded to a tab.");
addDocumentation("ByteArray", "ByteArray", "Wrapper for QByteArray Qt class.");
addDocumentation("File", "File", "Wrapper for QFile Qt class.");
addDocumentation("Dir", "Dir", "Wrapper for QDir Qt class.");
Expand Down
114 changes: 98 additions & 16 deletions src/gui/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,18 @@ void disableActionWhenTabGroupSelected(WidgetOrAction *action, MainWindow *windo
action, &WidgetOrAction::setDisabled );
}

void addSelectionData(
QVariantMap *result,
const QModelIndexList &selectedIndexes)
{
QList<QPersistentModelIndex> selected;
selected.reserve(selectedIndexes.size());
for (const auto &index : selectedIndexes)
selected.append(index);
std::sort(selected.begin(), selected.end());
result->insert(mimeSelectedItems, QVariant::fromValue(selected));
}

/// Adds information about current tab and selection if command is triggered by user.
QVariantMap addSelectionData(
const ClipboardBrowser &c,
Expand All @@ -203,12 +215,7 @@ QVariantMap addSelectionData(
}

if ( !selectedIndexes.isEmpty() ) {
QList<QPersistentModelIndex> selected;
selected.reserve(selectedIndexes.size());
for (const auto &index : selectedIndexes)
selected.append(index);
std::sort(selected.begin(), selected.end());
result.insert(mimeSelectedItems, QVariant::fromValue(selected));
addSelectionData(&result, selectedIndexes);
}

return result;
Expand Down Expand Up @@ -1065,15 +1072,21 @@ bool MainWindow::isItemPreviewVisible() const
return m_showItemPreview;
}

void MainWindow::setScriptOverrides(const QVector<int> &overrides)
void MainWindow::setScriptOverrides(const QVector<int> &overrides, int actionId)
{
if (!m_actionCollectOverrides || m_actionCollectOverrides->id() != actionId)
return;

m_overrides = overrides;
std::sort(m_overrides.begin(), m_overrides.end());
}

bool MainWindow::isScriptOverridden(int id) const
{
return std::binary_search(m_overrides.begin(), m_overrides.end(), id);
return
// Assume everything is overridden until collectOverrides() finishes
(m_actionCollectOverrides && m_actionCollectOverrides->isRunning() && m_overrides.isEmpty())
|| std::binary_search(m_overrides.begin(), m_overrides.end(), id);
}

void MainWindow::onAboutToQuit()
Expand Down Expand Up @@ -1253,6 +1266,8 @@ void MainWindow::onBrowserCreated(ClipboardBrowser *browser)
this, &MainWindow::onSearchShowRequest );
connect( browser, &ClipboardBrowser::itemWidgetCreated,
this, &MainWindow::onItemWidgetCreated );
connect( browser, &ClipboardBrowser::itemsLoaded,
this, &MainWindow::onBrowserItemsLoaded );

if (browserOrNull() == browser) {
const int index = ui->tabWidget->currentIndex();
Expand All @@ -1268,6 +1283,34 @@ void MainWindow::onBrowserDestroyed(ClipboardBrowserPlaceholder *placeholder)
}
}

void MainWindow::onBrowserItemsLoaded(const ClipboardBrowser *browser)
{
if (isScriptOverridden(ScriptOverrides::OnItemsLoaded)) {
runEventHandlerScript(
QStringLiteral("onItemsLoaded()"),
createDataMap(mimeCurrentTab, browser->tabName()));
}

connect( browser->model(), &QAbstractItemModel::rowsAboutToBeRemoved,
browser, [this, browser](const QModelIndex &, int first, int last) {
if (isScriptOverridden(ScriptOverrides::OnItemsRemoved))
runItemHandlerScript(QStringLiteral("onItemsRemoved()"), browser, first, last);
} );
connect( browser->model(), &QAbstractItemModel::rowsInserted,
browser, [this, browser](const QModelIndex &, int first, int last) {
if (isScriptOverridden(ScriptOverrides::OnItemsAdded))
runItemHandlerScript(QStringLiteral("onItemsAdded()"), browser, first, last);
} );
connect( browser->model(), &QAbstractItemModel::dataChanged,
browser, [this, browser](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
if (isScriptOverridden(ScriptOverrides::OnItemsChanged)) {
runItemHandlerScript(
QStringLiteral("onItemsChanged()"),
browser, topLeft.row(), bottomRight.row());
}
} );
}

void MainWindow::onItemSelectionChanged(const ClipboardBrowser *browser)
{
if (browser == browserOrNull()) {
Expand Down Expand Up @@ -1346,7 +1389,7 @@ void MainWindow::runDisplayCommands()

if ( !isInternalActionId(m_displayActionId) ) {
m_currentDisplayItem = m_displayItemList.takeFirst();
const auto action = runScript("runDisplayCommands()", m_currentDisplayItem.data());
const auto action = runScript(QStringLiteral("runDisplayCommands()"), m_currentDisplayItem.data());
m_displayActionId = action->id();
}

Expand Down Expand Up @@ -1613,7 +1656,7 @@ void MainWindow::runMenuCommandFilters(MenuMatchCommands *menuMatchCommands, QVa
if (isRunning) {
m_sharedData->actions->setActionData(menuMatchCommands->actionId, data);
} else {
const auto act = runScript("runMenuCommandFilters()", data);
const auto act = runScript(QStringLiteral("runMenuCommandFilters()"), data);
menuMatchCommands->actionId = act->id();
}

Expand Down Expand Up @@ -1884,7 +1927,7 @@ void MainWindow::activateMenuItem(ClipboardBrowserPlaceholder *placeholder, cons
if ( m_options.trayItemPaste && !omitPaste && canPaste() ) {
if (isScriptOverridden(ScriptOverrides::Paste)) {
COPYQ_LOG("Pasting item with paste()");
runScript("paste()");
runScript(QStringLiteral("paste()"));
} else if (lastWindow) {
COPYQ_LOG( QStringLiteral("Pasting item from tray menu to: %1")
.arg(lastWindow->getTitle()) );
Expand Down Expand Up @@ -2306,6 +2349,9 @@ void MainWindow::updateEnabledCommands()

void MainWindow::updateCommands(QVector<Command> allCommands, bool forceSave)
{
m_overrides = {};
m_actionCollectOverrides = runScript(QStringLiteral("collectScriptOverrides()"));

m_automaticCommands.clear();
m_menuCommands.clear();
m_scriptCommands.clear();
Expand Down Expand Up @@ -2348,10 +2394,9 @@ void MainWindow::updateCommands(QVector<Command> allCommands, bool forceSave)
reloadBrowsers();
}

runScript("collectOverrides()");

updateContextMenu(contextMenuUpdateIntervalMsec);
updateTrayMenuCommands();

emit commandsSaved(commands);
}

Expand Down Expand Up @@ -2404,12 +2449,43 @@ const Theme &MainWindow::theme() const
Action *MainWindow::runScript(const QString &script, const QVariantMap &data)
{
auto act = new Action();
act->setCommand(QStringList() << "copyq" << "eval" << "--" << script);
act->setCommand(
{QStringLiteral("copyq"), QStringLiteral("eval"), QStringLiteral("--"), script});
act->setData(data);
runInternalAction(act);
return act;
}

void MainWindow::runEventHandlerScript(const QString &script, const QVariantMap &data)
{
if (m_maxEventHandlerScripts == 0)
return;

--m_maxEventHandlerScripts;
if (m_maxEventHandlerScripts == 0)
log("Event handler maximum recursion reached", LogWarning);

const auto action = runScript(script, data);
action->waitForFinished();
++m_maxEventHandlerScripts;
}

void MainWindow::runItemHandlerScript(
const QString &script, const ClipboardBrowser *browser, int firstRow, int lastRow)
{
QModelIndexList indexes;
indexes.reserve(lastRow - firstRow + 1);
for (int row = firstRow; row <= lastRow; ++row) {
const auto index = browser->model()->index(row, 0);
if (index.isValid())
indexes.append(index);
}

QVariantMap data = createDataMap(mimeCurrentTab, browser->tabName());
addSelectionData(&data, indexes);
runEventHandlerScript(script, data);
}

int MainWindow::findTabIndex(const QString &name)
{
TabWidget *w = ui->tabWidget;
Expand Down Expand Up @@ -2984,6 +3060,12 @@ void MainWindow::tabChanged(int current, int)
}

setTabOrder(ui->searchBar, c);

if (isScriptOverridden(ScriptOverrides::OnTabSelected)) {
runEventHandlerScript(
QStringLiteral("onTabSelected()"),
createDataMap(mimeCurrentTab, c->tabName()));
}
}
}

Expand Down Expand Up @@ -3333,7 +3415,7 @@ void MainWindow::activateCurrentItemHelper()
if (paste) {
if (isScriptOverridden(ScriptOverrides::Paste)) {
COPYQ_LOG("Pasting item with paste()");
runScript("paste()");
runScript(QStringLiteral("paste()"));
} else if (lastWindow) {
COPYQ_LOG( QStringLiteral("Pasting item from main window to: %1")
.arg(lastWindow->getTitle()) );
Expand Down Expand Up @@ -3366,7 +3448,7 @@ void MainWindow::disableClipboardStoring(bool disable)

updateIcon();

runScript("setTitle(); showDataNotification()");
runScript(QStringLiteral("setTitle(); showDataNotification()"));

COPYQ_LOG( QString("Clipboard monitoring %1.")
.arg(m_clipboardStoringDisabled ? "disabled" : "enabled") );
Expand Down
8 changes: 7 additions & 1 deletion src/gui/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ class MainWindow final : public QMainWindow
void setItemPreviewVisible(bool visible);
bool isItemPreviewVisible() const;

void setScriptOverrides(const QVector<int> &overrides);
void setScriptOverrides(const QVector<int> &overrides, int actionId);
bool isScriptOverridden(int id) const;

signals:
Expand Down Expand Up @@ -500,6 +500,7 @@ class MainWindow final : public QMainWindow

void onBrowserCreated(ClipboardBrowser *browser);
void onBrowserDestroyed(ClipboardBrowserPlaceholder *placeholder);
void onBrowserItemsLoaded(const ClipboardBrowser *browser);

void onItemSelectionChanged(const ClipboardBrowser *browser);
void onItemsChanged(const ClipboardBrowser *browser);
Expand Down Expand Up @@ -630,6 +631,9 @@ class MainWindow final : public QMainWindow
const Theme &theme() const;

Action *runScript(const QString &script, const QVariantMap &data = QVariantMap());
void runEventHandlerScript(const QString &script, const QVariantMap &data = QVariantMap());
void runItemHandlerScript(
const QString &script, const ClipboardBrowser *browser, int firstRow, int lastRow);

void activateCurrentItemHelper();
void onItemClicked();
Expand Down Expand Up @@ -699,6 +703,8 @@ class MainWindow final : public QMainWindow
bool m_enteringSearchMode = false;

QVector<int> m_overrides;
int m_maxEventHandlerScripts = 10;
QPointer<Action> m_actionCollectOverrides;
};

#endif // MAINWINDOW_H
Loading

0 comments on commit 7fa973a

Please sign in to comment.