1 /*
2 Copyright (c) 2020, Lukas Holecek <hluk@email.cz>
3
4 This file is part of CopyQ.
5
6 CopyQ is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 CopyQ 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 You should have received a copy of the GNU General Public License
17 along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "scriptableproxy.h"
21
22 #include "common/action.h"
23 #include "common/appconfig.h"
24 #include "common/command.h"
25 #include "common/commandstatus.h"
26 #include "common/commandstore.h"
27 #include "common/common.h"
28 #include "common/config.h"
29 #include "common/contenttype.h"
30 #include "common/display.h"
31 #include "common/log.h"
32 #include "common/mimetypes.h"
33 #include "common/settings.h"
34 #include "common/sleeptimer.h"
35 #include "common/textdata.h"
36 #include "gui/clipboardbrowser.h"
37 #include "gui/filedialog.h"
38 #include "gui/iconfactory.h"
39 #include "gui/icons.h"
40 #include "gui/mainwindow.h"
41 #include "gui/notification.h"
42 #include "gui/pixelratio.h"
43 #include "gui/screen.h"
44 #include "gui/tabicons.h"
45 #include "gui/traymenu.h"
46 #include "gui/windowgeometryguard.h"
47 #include "item/serialize.h"
48 #include "platform/platformnativeinterface.h"
49 #include "platform/platformwindow.h"
50
51 #include <QDialog>
52 #include <QHBoxLayout>
53 #include <QVBoxLayout>
54
55 #include <QApplication>
56 #include <QBuffer>
57 #include <QDataStream>
58 #include <QCheckBox>
59 #include <QComboBox>
60 #include <QCursor>
61 #include <QDateTimeEdit>
62 #include <QDesktopServices>
63 #include <QDialogButtonBox>
64 #include <QFile>
65 #include <QFileInfo>
66 #include <QFileDialog>
67 #include <QWidget>
68 #include <QLabel>
69 #include <QLineEdit>
70 #include <QListWidget>
71 #include <QMetaMethod>
72 #include <QMetaType>
73 #include <QMimeData>
74 #include <QPainter>
75 #include <QPaintEvent>
76 #include <QPen>
77 #include <QPixmap>
78 #include <QPushButton>
79 #include <QScreen>
80 #include <QShortcut>
81 #include <QSpinBox>
82 #include <QStyleFactory>
83 #include <QTextEdit>
84 #include <QUrl>
85
86 #ifdef HAS_TESTS
87 # include <QTest>
88 #endif
89
90 #include <type_traits>
91
92 namespace {
93
94 const quint32 serializedFunctionCallMagicNumber = 0x58746908;
95 const quint32 serializedFunctionCallVersion = 2;
96
registerMetaTypes()97 void registerMetaTypes() {
98 static bool registered = false;
99 if (registered)
100 return;
101
102 qRegisterMetaType< QPointer<QWidget> >("QPointer<QWidget>");
103 qRegisterMetaTypeStreamOperators<ClipboardMode>("ClipboardMode");
104 qRegisterMetaTypeStreamOperators<Command>("Command");
105 qRegisterMetaTypeStreamOperators<NamedValueList>("NamedValueList");
106 qRegisterMetaTypeStreamOperators<NotificationButtons>("NotificationButtons");
107 qRegisterMetaTypeStreamOperators<ScriptablePath>("ScriptablePath");
108 qRegisterMetaTypeStreamOperators<QVector<int>>("QVector<int>");
109 qRegisterMetaTypeStreamOperators<QVector<Command>>("QVector<Command>");
110 qRegisterMetaTypeStreamOperators<QVector<QVariantMap>>("QVector<QVariantMap>");
111 qRegisterMetaTypeStreamOperators<Qt::KeyboardModifiers>("Qt::KeyboardModifiers");
112
113 registered = true;
114 }
115
116 template<typename Predicate>
selectionRemoveIf(QList<QPersistentModelIndex> * indexes,Predicate predicate)117 void selectionRemoveIf(QList<QPersistentModelIndex> *indexes, Predicate predicate)
118 {
119 indexes->erase(
120 std::remove_if(indexes->begin(), indexes->end(), predicate),
121 indexes->end());
122 }
123
selectionRemoveInvalid(QList<QPersistentModelIndex> * indexes)124 void selectionRemoveInvalid(QList<QPersistentModelIndex> *indexes)
125 {
126 selectionRemoveIf(
127 indexes,
128 [](const QPersistentModelIndex &index){
129 return !index.isValid();
130 });
131 }
132
133 } // namespace
134
135 #define BROWSER(tabName, call) \
136 ClipboardBrowser *c = fetchBrowser(tabName); \
137 if (c) \
138 (c->call)
139
140 #define STR(str) str
141
142 #define INVOKE_(function, arguments, functionCallId) do { \
143 static const auto f = FunctionCallSerializer(QByteArrayLiteral(STR(#function))).withSlotArguments arguments; \
144 const auto args = f.argumentList arguments; \
145 emit sendMessage(f.serialize(functionCallId, args), CommandFunctionCall); \
146 } while(false)
147
148 #define INVOKE(FUNCTION, ARGUMENTS) do { \
149 if (!m_wnd) { \
150 using Result = decltype(FUNCTION ARGUMENTS); \
151 const auto functionCallId = ++m_lastFunctionCallId; \
152 INVOKE_(FUNCTION, ARGUMENTS, functionCallId); \
153 const auto result = waitForFunctionCallFinished(functionCallId); \
154 return result.value<Result>(); \
155 } \
156 } while(false)
157
158 #define INVOKE2(FUNCTION, ARGUMENTS) do { \
159 if (!m_wnd) { \
160 const auto functionCallId = ++m_lastFunctionCallId; \
161 INVOKE_(FUNCTION, ARGUMENTS, functionCallId); \
162 waitForFunctionCallFinished(functionCallId); \
163 return; \
164 } \
165 } while(false)
166
167 Q_DECLARE_METATYPE(QFile*)
168
169 QDataStream &operator<<(QDataStream &out, const NotificationButton &button)
170 {
171 out << button.name
172 << button.script
173 << button.data;
174 Q_ASSERT(out.status() == QDataStream::Ok);
175 return out;
176 }
177
operator >>(QDataStream & in,NotificationButton & button)178 QDataStream &operator>>(QDataStream &in, NotificationButton &button)
179 {
180 in >> button.name
181 >> button.script
182 >> button.data;
183 Q_ASSERT(in.status() == QDataStream::Ok);
184 return in;
185 }
186
operator <<(QDataStream & out,const NamedValueList & list)187 QDataStream &operator<<(QDataStream &out, const NamedValueList &list)
188 {
189 out << list.size();
190 for (const auto &item : list)
191 out << item.name << item.value;
192 Q_ASSERT(out.status() == QDataStream::Ok);
193 return out;
194 }
195
operator >>(QDataStream & in,NamedValueList & list)196 QDataStream &operator>>(QDataStream &in, NamedValueList &list)
197 {
198 int size;
199 in >> size;
200 for (int i = 0; i < size; ++i) {
201 NamedValue item;
202 in >> item.name >> item.value;
203 list.append(item);
204 }
205 Q_ASSERT(in.status() == QDataStream::Ok);
206 return in;
207 }
208
operator <<(QDataStream & out,const Command & command)209 QDataStream &operator<<(QDataStream &out, const Command &command)
210 {
211 out << command.name
212 << command.re
213 << command.wndre
214 << command.matchCmd
215 << command.cmd
216 << command.sep
217 << command.input
218 << command.output
219 << command.wait
220 << command.automatic
221 << command.display
222 << command.inMenu
223 << command.isGlobalShortcut
224 << command.isScript
225 << command.transform
226 << command.remove
227 << command.hideWindow
228 << command.enable
229 << command.icon
230 << command.shortcuts
231 << command.globalShortcuts
232 << command.tab
233 << command.outputTab;
234 Q_ASSERT(out.status() == QDataStream::Ok);
235 return out;
236 }
237
operator >>(QDataStream & in,Command & command)238 QDataStream &operator>>(QDataStream &in, Command &command)
239 {
240 in >> command.name
241 >> command.re
242 >> command.wndre
243 >> command.matchCmd
244 >> command.cmd
245 >> command.sep
246 >> command.input
247 >> command.output
248 >> command.wait
249 >> command.automatic
250 >> command.display
251 >> command.inMenu
252 >> command.isGlobalShortcut
253 >> command.isScript
254 >> command.transform
255 >> command.remove
256 >> command.hideWindow
257 >> command.enable
258 >> command.icon
259 >> command.shortcuts
260 >> command.globalShortcuts
261 >> command.tab
262 >> command.outputTab;
263 Q_ASSERT(in.status() == QDataStream::Ok);
264 return in;
265 }
266
operator <<(QDataStream & out,ClipboardMode mode)267 QDataStream &operator<<(QDataStream &out, ClipboardMode mode)
268 {
269 const int modeId = static_cast<int>(mode);
270 out << modeId;
271 Q_ASSERT(out.status() == QDataStream::Ok);
272 return out;
273 }
274
operator >>(QDataStream & in,ClipboardMode & mode)275 QDataStream &operator>>(QDataStream &in, ClipboardMode &mode)
276 {
277 int modeId;
278 in >> modeId;
279 Q_ASSERT(in.status() == QDataStream::Ok);
280 mode = static_cast<ClipboardMode>(modeId);
281 return in;
282 }
283
operator <<(QDataStream & out,const ScriptablePath & path)284 QDataStream &operator<<(QDataStream &out, const ScriptablePath &path)
285 {
286 return out << path.path;
287 }
288
operator >>(QDataStream & in,ScriptablePath & path)289 QDataStream &operator>>(QDataStream &in, ScriptablePath &path)
290 {
291 return in >> path.path;
292 }
293
operator <<(QDataStream & out,Qt::KeyboardModifiers value)294 QDataStream &operator<<(QDataStream &out, Qt::KeyboardModifiers value)
295 {
296 return out << static_cast<int>(value);
297 }
298
operator >>(QDataStream & in,Qt::KeyboardModifiers & value)299 QDataStream &operator>>(QDataStream &in, Qt::KeyboardModifiers &value)
300 {
301 int valueInt;
302 in >> valueInt;
303 Q_ASSERT(in.status() == QDataStream::Ok);
304 value = static_cast<Qt::KeyboardModifiers>(valueInt);
305 return in;
306 }
307
308 namespace {
309
310 const char propertyWidgetName[] = "CopyQ_widget_name";
311 const char propertyWidgetProperty[] = "CopyQ_widget_property";
312
313 struct InputDialog {
314 QPointer<QDialog> dialog;
315 QVariant defaultChoice; /// Default text for list widgets.
316 };
317
318 class FunctionCallSerializer final {
319 public:
FunctionCallSerializer(QByteArray functionName)320 explicit FunctionCallSerializer(QByteArray functionName)
321 : m_slotName(std::move(functionName))
322 {
323 }
324
325 template<typename ...Ts>
withSlotArguments(Ts...arguments)326 FunctionCallSerializer &withSlotArguments(Ts... arguments)
327 {
328 QByteArray args;
329 for (const auto argType : std::initializer_list<const char *>{ argumentType(arguments)... }) {
330 args.append(argType);
331 args.append(',');
332 }
333 args.chop(1);
334 setSlotArgumentTypes(args);
335 return *this;
336 }
337
serialize(int functionCallId,const QVector<QVariant> args) const338 QByteArray serialize(int functionCallId, const QVector<QVariant> args) const
339 {
340 QByteArray bytes;
341 QDataStream stream(&bytes, QIODevice::WriteOnly);
342 stream.setVersion(QDataStream::Qt_5_0);
343 stream << serializedFunctionCallMagicNumber << serializedFunctionCallVersion
344 << functionCallId << m_slotName << args;
345 return bytes;
346 }
347
348 template<typename ...Ts>
argumentList(Ts...arguments)349 static QVector<QVariant> argumentList(Ts... arguments)
350 {
351 return { QVariant::fromValue(arguments)... };
352 }
353
354 template<typename T>
argumentType(const T &)355 static const char *argumentType(const T &)
356 {
357 if ( std::is_same<QVariant, T>::value )
358 return "QVariant";
359
360 return QMetaType::typeName(qMetaTypeId<T>());
361 }
362
363 private:
setSlotArgumentTypes(const QByteArray & args)364 void setSlotArgumentTypes(const QByteArray &args)
365 {
366 m_slotName += "(" + args + ")";
367 const int slotIndex = ScriptableProxy::staticMetaObject.indexOfSlot(m_slotName);
368 if (slotIndex == -1) {
369 log("Failed to find scriptable proxy slot: " + m_slotName, LogError);
370 Q_ASSERT(false);
371 }
372 }
373
374 QByteArray m_slotName;
375 };
376
377 class ScreenshotRectWidget final : public QLabel {
378 public:
ScreenshotRectWidget(const QPixmap & pixmap)379 explicit ScreenshotRectWidget(const QPixmap &pixmap)
380 {
381 setWindowFlags(Qt::Widget | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
382 setCursor(Qt::CrossCursor);
383 setPixmap(pixmap);
384 }
385
paintEvent(QPaintEvent * ev)386 void paintEvent(QPaintEvent *ev) override
387 {
388 QLabel::paintEvent(ev);
389 if (selectionRect.isValid()) {
390 QPainter p(this);
391 const auto w = pointsToPixels(1);
392
393 p.setPen(QPen(Qt::white, w));
394 p.drawRect(selectionRect);
395
396 p.setPen(QPen(Qt::black, w));
397 p.drawRect(selectionRect.adjusted(-w, -w, w, w));
398 }
399 }
400
keyPressEvent(QKeyEvent * ev)401 void keyPressEvent(QKeyEvent *ev) override
402 {
403 QWidget::keyPressEvent(ev);
404 hide();
405 }
406
mousePressEvent(QMouseEvent * ev)407 void mousePressEvent(QMouseEvent *ev) override
408 {
409 if ( ev->button() == Qt::LeftButton ) {
410 m_pos = ev->pos();
411 selectionRect.setTopLeft(m_pos);
412 update();
413 } else {
414 hide();
415 }
416 }
417
mouseReleaseEvent(QMouseEvent *)418 void mouseReleaseEvent(QMouseEvent *) override
419 {
420 hide();
421 }
422
mouseMoveEvent(QMouseEvent * ev)423 void mouseMoveEvent(QMouseEvent *ev) override
424 {
425 if ( !ev->buttons().testFlag(Qt::LeftButton) )
426 return;
427
428 const auto pos = ev->pos();
429 // Types need to be explicitly specified because minmax() returns pair of references.
430 const std::pair<int,int> x = std::minmax(pos.x(), m_pos.x());
431 const std::pair<int,int> y = std::minmax(pos.y(), m_pos.y());
432 selectionRect = QRect( QPoint(x.first, y.first), QPoint(x.second, y.second) );
433 update();
434 }
435
436 QRect selectionRect;
437
438 private:
439 QPoint m_pos;
440 };
441
442 /// Load icon from icon font, path or theme.
loadIcon(const QString & idPathOrName)443 QIcon loadIcon(const QString &idPathOrName)
444 {
445 if (idPathOrName.size() == 1)
446 return createPixmap(idPathOrName[0].unicode(), Qt::white, 64);
447
448 if ( QFile::exists(idPathOrName) )
449 return QIcon(idPathOrName);
450
451 return QIcon::fromTheme(idPathOrName);
452 }
453
label(Qt::Orientation orientation,const QString & name,QWidget * w)454 QWidget *label(Qt::Orientation orientation, const QString &name, QWidget *w)
455 {
456 QWidget *parent = w->parentWidget();
457
458 if ( !name.isEmpty() ) {
459 QBoxLayout *layout;
460 if (orientation == Qt::Horizontal)
461 layout = new QHBoxLayout;
462 else
463 layout = new QVBoxLayout;
464
465 parent->layout()->addItem(layout);
466
467 QLabel *label = new QLabel(name + ":", parent);
468 label->setBuddy(w);
469 layout->addWidget(label);
470 layout->addWidget(w, 1);
471 }
472
473 w->setProperty(propertyWidgetName, name);
474
475 return w;
476 }
477
label(const QString & name,QWidget * w)478 QWidget *label(const QString &name, QWidget *w)
479 {
480 w->setProperty("text", name);
481 w->setProperty(propertyWidgetName, name);
482 return w;
483 }
484
485 template <typename Widget>
createAndSetWidget(const char * propertyName,const QVariant & value,QWidget * parent)486 Widget *createAndSetWidget(const char *propertyName, const QVariant &value, QWidget *parent)
487 {
488 auto w = new Widget(parent);
489 w->setProperty(propertyName, value);
490 w->setProperty(propertyWidgetProperty, propertyName);
491 parent->layout()->addWidget(w);
492 return w;
493 }
494
createDateTimeEdit(const QString & name,const char * propertyName,const QVariant & value,QWidget * parent)495 QWidget *createDateTimeEdit(
496 const QString &name, const char *propertyName, const QVariant &value, QWidget *parent)
497 {
498 QDateTimeEdit *w = createAndSetWidget<QDateTimeEdit>(propertyName, value, parent);
499 w->setCalendarPopup(true);
500 return label(Qt::Horizontal, name, w);
501 }
502
installShortcutToCloseDialog(QDialog * dialog,QWidget * shortcutParent,int shortcut)503 void installShortcutToCloseDialog(QDialog *dialog, QWidget *shortcutParent, int shortcut)
504 {
505 QShortcut *s = new QShortcut(QKeySequence(shortcut), shortcutParent);
506 QObject::connect(s, &QShortcut::activated, dialog, &QDialog::accept);
507 QObject::connect(s, &QShortcut::activatedAmbiguously, dialog, &QDialog::accept);
508 }
509
createListWidget(const QString & name,const QStringList & items,InputDialog * inputDialog)510 QWidget *createListWidget(const QString &name, const QStringList &items, InputDialog *inputDialog)
511 {
512 QDialog *parent = inputDialog->dialog;
513
514 const QString currentText = inputDialog->defaultChoice.isValid()
515 ? inputDialog->defaultChoice.toString()
516 : items.value(0);
517
518 const QLatin1String listPrefix(".list:");
519 if ( name.startsWith(listPrefix) ) {
520 QListWidget *w = createAndSetWidget<QListWidget>("currentRow", QVariant(), parent);
521 w->addItems(items);
522 const int i = items.indexOf(currentText);
523 if (i != -1)
524 w->setCurrentRow(i);
525 w->setAlternatingRowColors(true);
526 installShortcutToCloseDialog(parent, w, Qt::Key_Enter);
527 installShortcutToCloseDialog(parent, w, Qt::Key_Return);
528 return label(Qt::Vertical, name.mid(listPrefix.size()), w);
529 }
530
531 QComboBox *w = createAndSetWidget<QComboBox>("currentText", QVariant(), parent);
532 w->setEditable(true);
533 w->addItems(items);
534 w->setCurrentIndex(items.indexOf(currentText));
535 w->lineEdit()->setText(currentText);
536 w->lineEdit()->selectAll();
537 w->setMaximumWidth( pointsToPixels(400) );
538 installShortcutToCloseDialog(parent, w, Qt::Key_Enter);
539 installShortcutToCloseDialog(parent, w, Qt::Key_Return);
540
541 const QLatin1String comboPrefix(".combo:");
542 if ( name.startsWith(comboPrefix) ) {
543 w->setEditable(false);
544 return label(Qt::Horizontal, name.mid(comboPrefix.size()), w);
545 }
546
547 return label(Qt::Horizontal, name, w);
548 }
549
createSpinBox(const QString & name,const QVariant & value,QWidget * parent)550 QWidget *createSpinBox(const QString &name, const QVariant &value, QWidget *parent)
551 {
552 QSpinBox *w = createAndSetWidget<QSpinBox>("value", value, parent);
553 w->setRange(-1e9, 1e9);
554 return label(Qt::Horizontal, name, w);
555 }
556
createLineEdit(const QVariant & value,QWidget * parent)557 QLineEdit *createLineEdit(const QVariant &value, QWidget *parent)
558 {
559 QLineEdit *lineEdit = createAndSetWidget<QLineEdit>("text", value, parent);
560 lineEdit->selectAll();
561 return lineEdit;
562 }
563
createFileNameEdit(const QString & name,const QString & path,QWidget * parent)564 QWidget *createFileNameEdit(const QString &name, const QString &path, QWidget *parent)
565 {
566 QWidget *w = new QWidget(parent);
567 parent->layout()->addWidget(w);
568
569 auto layout = new QHBoxLayout(w);
570 layout->setContentsMargins(0, 0, 0, 0);
571
572 QLineEdit *lineEdit = createLineEdit(path, w);
573 lineEdit->setProperty(propertyWidgetName, name);
574
575 QPushButton *browseButton = new QPushButton("...");
576
577 FileDialog *dialog = new FileDialog(w, name, path);
578 QObject::connect( browseButton, &QAbstractButton::clicked,
579 dialog, &FileDialog::exec );
580 QObject::connect( dialog, &FileDialog::fileSelected,
581 lineEdit, &QLineEdit::setText );
582
583 layout->addWidget(lineEdit);
584 layout->addWidget(browseButton);
585
586 label(Qt::Vertical, name, w);
587
588 return lineEdit;
589 }
590
createTextEdit(const QString & name,const QVariant & value,QWidget * parent)591 QWidget *createTextEdit(const QString &name, const QVariant &value, QWidget *parent)
592 {
593 QTextEdit *w = createAndSetWidget<QTextEdit>("plainText", value, parent);
594 w->setTabChangesFocus(true);
595 return label(Qt::Vertical, name, w);
596 }
597
createWidget(const QString & name,const QVariant & value,InputDialog * inputDialog)598 QWidget *createWidget(const QString &name, const QVariant &value, InputDialog *inputDialog)
599 {
600 QDialog *parent = inputDialog->dialog;
601
602 switch ( value.type() ) {
603 case QVariant::Bool:
604 return label(name, createAndSetWidget<QCheckBox>("checked", value, parent));
605 case QVariant::Int:
606 return createSpinBox("value", value, parent);
607 case QVariant::Date:
608 return createDateTimeEdit(name, "date", value, parent);
609 case QVariant::Time:
610 return createDateTimeEdit(name, "time", value, parent);
611 case QVariant::DateTime:
612 return createDateTimeEdit(name, "dateTime", value, parent);
613 case QVariant::List:
614 case QVariant::StringList:
615 return createListWidget(name, value.toStringList(), inputDialog);
616 default:
617 if ( value.userType() == qMetaTypeId<ScriptablePath>() ) {
618 const auto path = value.value<ScriptablePath>();
619 return createFileNameEdit(name, path.path, parent);
620 }
621
622 const QString text = value.toString();
623 if (text.contains('\n'))
624 return createTextEdit(name, value.toStringList(), parent);
625
626 return label(Qt::Horizontal, name, createLineEdit(value, parent));
627 }
628 }
629
setGeometryWithoutSave(QWidget * window,QRect geometry)630 void setGeometryWithoutSave(QWidget *window, QRect geometry)
631 {
632 setGeometryGuardBlockedUntilHidden(window, true);
633
634 const auto pos = (geometry.x() == -1 && geometry.y() == -1)
635 ? QCursor::pos()
636 : geometry.topLeft();
637
638 const int w = pointsToPixels(geometry.width());
639 const int h = pointsToPixels(geometry.height());
640 if (w > 0 && h > 0)
641 window->resize(w, h);
642
643 moveWindowOnScreen(window, pos);
644 }
645
tabNotFoundError()646 QString tabNotFoundError()
647 {
648 return ScriptableProxy::tr("Tab with given name doesn't exist!");
649 }
650
tabNameEmptyError()651 QString tabNameEmptyError()
652 {
653 return ScriptableProxy::tr("Tab name cannot be empty!");
654 }
655
raiseWindow(QPointer<QWidget> window)656 void raiseWindow(QPointer<QWidget> window)
657 {
658 window->raise();
659 window->activateWindow();
660 QApplication::setActiveWindow(window);
661 QApplication::processEvents();
662 const auto wid = window->winId();
663 const auto platformWindow = platformNativeInterface()->getWindow(wid);
664 if (platformWindow)
665 platformWindow->raise();
666 }
667
668 } // namespace
669
670 #ifdef HAS_TESTS
671 class KeyClicker final : public QObject {
672 public:
KeyClicker(MainWindow * wnd,QObject * parent)673 KeyClicker(MainWindow *wnd, QObject *parent)
674 : QObject(parent)
675 , m_wnd(wnd)
676 {
677 }
678
keyClicksRetry(const QString & expectedWidgetName,const QString & keys,int delay,int retry)679 void keyClicksRetry(const QString &expectedWidgetName, const QString &keys, int delay, int retry)
680 {
681 if (retry > 0)
682 sendKeyClicks(expectedWidgetName, keys, delay + 100, retry - 1);
683 else
684 keyClicksFailed(expectedWidgetName);
685 }
686
keyClicksFailed(const QString & expectedWidgetName)687 void keyClicksFailed(const QString &expectedWidgetName)
688 {
689 auto actual = keyClicksTarget();
690 auto popup = QApplication::activePopupWidget();
691 auto widget = QApplication::focusWidget();
692 auto window = QApplication::activeWindow();
693 auto modal = QApplication::activeModalWidget();
694 const auto currentWindow = platformNativeInterface()->getCurrentWindow();
695 const auto currentWindowTitle = currentWindow ? currentWindow->getTitle() : QString();
696 log( QString("Failed to send key press to target widget")
697 + "\nExpected: " + (expectedWidgetName.isEmpty() ? "Any" : expectedWidgetName)
698 + "\nActual: " + keyClicksTargetDescription(actual)
699 + "\nPopup: " + keyClicksTargetDescription(popup)
700 + "\nWidget: " + keyClicksTargetDescription(widget)
701 + "\nWindow: " + keyClicksTargetDescription(window)
702 + "\nModal: " + keyClicksTargetDescription(modal)
703 + "\nTitle: " + currentWindowTitle
704 , LogError );
705
706 m_failed = true;
707 }
708
keyClicks(const QString & expectedWidgetName,const QString & keys,int delay,int retry)709 void keyClicks(const QString &expectedWidgetName, const QString &keys, int delay, int retry)
710 {
711 auto widget = keyClicksTarget();
712 if (!widget) {
713 keyClicksRetry(expectedWidgetName, keys, delay, retry);
714 return;
715 }
716
717 auto widgetName = keyClicksTargetDescription(widget);
718 if ( !expectedWidgetName.isEmpty() && !widgetName.contains(expectedWidgetName) ) {
719 keyClicksRetry(expectedWidgetName, keys, delay, retry);
720 return;
721 }
722
723 // Only verified focused widget.
724 if ( keys.isEmpty() ) {
725 m_succeeded = true;
726 return;
727 }
728
729 // There could be some animation/transition effect on check boxes
730 // so wait for checkbox to be set.
731 if ( qobject_cast<QCheckBox*>(widget) )
732 waitFor(100);
733
734 COPYQ_LOG( QString("Sending keys \"%1\" to %2.")
735 .arg(keys, widgetName) );
736
737 const auto popupMessage = QString::fromLatin1("%1 (%2)")
738 .arg( quoteString(keys), widgetName );
739 auto notification = m_wnd->createNotification(QLatin1String("tests"));
740 notification->setMessage(popupMessage);
741 notification->setIcon(IconKeyboard);
742 notification->setInterval(2000);
743
744 if ( keys.startsWith(":") ) {
745 const auto text = keys.mid(1);
746
747 QTest::keyClicks(widget, text, Qt::NoModifier, 0);
748
749 // Increment key clicks sequence number after typing all the text.
750 m_succeeded = true;
751 } else {
752 const QKeySequence shortcut(keys, QKeySequence::PortableText);
753
754 if ( shortcut.isEmpty() ) {
755 log( QString("Cannot parse shortcut \"%1\"!").arg(keys), LogError );
756 m_failed = true;
757 return;
758 }
759
760 // Increment key clicks sequence number before opening any modal dialogs.
761 m_succeeded = true;
762
763 const auto key = static_cast<uint>(shortcut[0]);
764 QTest::keyClick( widget,
765 Qt::Key(key & ~Qt::KeyboardModifierMask),
766 Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask),
767 0 );
768 }
769
770 COPYQ_LOG( QString("Key \"%1\" sent to %2.")
771 .arg(keys, widgetName) );
772 }
773
sendKeyClicks(const QString & expectedWidgetName,const QString & keys,int delay,int retry)774 void sendKeyClicks(const QString &expectedWidgetName, const QString &keys, int delay, int retry)
775 {
776 m_succeeded = false;
777 m_failed = false;
778
779 // Don't stop when modal window is open.
780 auto t = new QTimer(m_wnd);
781 t->setSingleShot(true);
782 QObject::connect( t, &QTimer::timeout, this, [=]() {
783 keyClicks(expectedWidgetName, keys, delay, retry);
784 t->deleteLater();
785 });
786 t->start(delay);
787 }
788
succeeded() const789 bool succeeded() const { return m_succeeded; }
failed() const790 bool failed() const { return m_failed; }
791
792 private:
keyClicksTargetDescription(QWidget * widget)793 static QString keyClicksTargetDescription(QWidget *widget)
794 {
795 if (widget == nullptr)
796 return "None";
797
798 const auto className = widget->metaObject()->className();
799
800 auto widgetName = QString::fromLatin1("%1:%2")
801 .arg(widget->objectName(), className);
802
803 const auto window = widget->window();
804 if (window && widget != window) {
805 widgetName.append(
806 QString::fromLatin1(" in %1:%2")
807 .arg(window->objectName(), window->metaObject()->className())
808 );
809 }
810
811 auto parent = widget->parentWidget();
812 while (parent) {
813 if ( parent != window && !parent->objectName().startsWith("qt_") ) {
814 widgetName.append(
815 QString::fromLatin1(" in %1:%2")
816 .arg(parent->objectName(), parent->metaObject()->className())
817 );
818 }
819 parent = parent->parentWidget();
820 }
821
822 return widgetName;
823 }
824
keyClicksTarget()825 QWidget *keyClicksTarget()
826 {
827 auto popup = QApplication::activePopupWidget();
828 if (popup)
829 return popup;
830
831 auto widget = QApplication::focusWidget();
832 if (widget)
833 return widget;
834
835 auto window = QApplication::activeWindow();
836 if (window)
837 return window->focusWidget();
838
839 auto modal = QApplication::activeModalWidget();
840 if (modal)
841 return modal->focusWidget();
842
843 #ifdef Q_OS_MAC
844 return m_wnd->focusWidget();
845 #else
846 return nullptr;
847 #endif
848 }
849
850
851 MainWindow *m_wnd = nullptr;
852 bool m_succeeded = true;
853 bool m_failed = false;
854 };
855 #endif // HAS_TESTS
856
ScriptableProxy(MainWindow * mainWindow,QObject * parent)857 ScriptableProxy::ScriptableProxy(MainWindow *mainWindow, QObject *parent)
858 : QObject(parent)
859 , m_wnd(mainWindow)
860 {
861 connect( this, &ScriptableProxy::clientDisconnected,
862 this, [this]() {
863 m_disconnected = true;
864 emit abortEvaluation();
865 } );
866 registerMetaTypes();
867 }
868
callFunction(const QByteArray & serializedFunctionCall)869 void ScriptableProxy::callFunction(const QByteArray &serializedFunctionCall)
870 {
871 if (m_shouldBeDeleted)
872 return;
873
874 ++m_functionCallStack;
875 auto t = new QTimer(this);
876 t->setSingleShot(true);
877 QObject::connect( t, &QTimer::timeout, this, [=]() {
878 const auto result = callFunctionHelper(serializedFunctionCall);
879 emit sendMessage(result, CommandFunctionCallReturnValue);
880 t->deleteLater();
881
882 --m_functionCallStack;
883 if (m_shouldBeDeleted && m_functionCallStack == 0)
884 deleteLater();
885 });
886 t->start(0);
887 }
888
callFunctionHelper(const QByteArray & serializedFunctionCall)889 QByteArray ScriptableProxy::callFunctionHelper(const QByteArray &serializedFunctionCall)
890 {
891 QVector<QVariant> arguments;
892 QByteArray slotName;
893 int functionCallId;
894 {
895 QDataStream stream(serializedFunctionCall);
896 stream.setVersion(QDataStream::Qt_5_0);
897
898 quint32 magicNumber;
899 quint32 version;
900 stream >> magicNumber >> version;
901 if (stream.status() != QDataStream::Ok) {
902 log("Failed to read scriptable proxy slot call preamble", LogError);
903 Q_ASSERT(false);
904 return QByteArray();
905 }
906
907 if (magicNumber != serializedFunctionCallMagicNumber) {
908 log("Unexpected scriptable proxy slot call preamble magic number", LogError);
909 Q_ASSERT(false);
910 return QByteArray();
911 }
912
913 if (version != serializedFunctionCallVersion) {
914 log("Unexpected scriptable proxy slot call preamble version", LogError);
915 Q_ASSERT(false);
916 return QByteArray();
917 }
918
919 stream >> functionCallId;
920 if (stream.status() != QDataStream::Ok) {
921 log("Failed to read scriptable proxy slot call ID", LogError);
922 Q_ASSERT(false);
923 return QByteArray();
924 }
925
926 stream >> slotName;
927 if (stream.status() != QDataStream::Ok) {
928 log("Failed to read scriptable proxy slot call name", LogError);
929 Q_ASSERT(false);
930 return QByteArray();
931 }
932
933 stream >> arguments;
934 if (stream.status() != QDataStream::Ok) {
935 log("Failed to read scriptable proxy slot call", LogError);
936 Q_ASSERT(false);
937 return QByteArray();
938 }
939 }
940
941 const auto slotIndex = metaObject()->indexOfSlot(slotName);
942 if (slotIndex == -1) {
943 log("Failed to find scriptable proxy slot: " + slotName, LogError);
944 Q_ASSERT(false);
945 return QByteArray();
946 }
947
948 const auto metaMethod = metaObject()->method(slotIndex);
949 const auto typeId = metaMethod.returnType();
950
951 QGenericArgument args[9];
952 for (int i = 0; i < arguments.size(); ++i) {
953 auto &value = arguments[i];
954 const int argumentTypeId = metaMethod.parameterType(i);
955 if (argumentTypeId == QMetaType::QVariant) {
956 args[i] = Q_ARG(QVariant, value);
957 } else if ( value.userType() == argumentTypeId ) {
958 args[i] = QGenericArgument( value.typeName(), static_cast<void*>(value.data()) );
959 } else {
960 log( QString("Bad argument type (at index %1) for scriptable proxy slot: %2")
961 .arg(i)
962 .arg(metaMethod.methodSignature().constData()), LogError);
963 Q_ASSERT(false);
964 return QByteArray();
965 }
966 }
967
968 QVariant returnValue;
969 bool called;
970
971 if (typeId == QMetaType::Void) {
972 called = metaMethod.invoke(
973 this, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
974 } else {
975 returnValue = QVariant(typeId, nullptr);
976 const auto genericReturnValue = returnValue.isValid()
977 ? QGenericReturnArgument(returnValue.typeName(), static_cast<void*>(returnValue.data()) )
978 : Q_RETURN_ARG(QVariant, returnValue);
979
980 called = metaMethod.invoke(
981 this, genericReturnValue,
982 args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
983 }
984
985 if (!called) {
986 log( QString("Bad scriptable proxy slot call: %1")
987 .arg(metaMethod.methodSignature().constData()), LogError);
988 Q_ASSERT(false);
989 }
990
991 QByteArray bytes;
992 {
993 QDataStream stream(&bytes, QIODevice::WriteOnly);
994 stream << functionCallId << returnValue;
995 }
996
997 return bytes;
998 }
999
setFunctionCallReturnValue(const QByteArray & bytes)1000 void ScriptableProxy::setFunctionCallReturnValue(const QByteArray &bytes)
1001 {
1002 QDataStream stream(bytes);
1003 int functionCallId;
1004 QVariant returnValue;
1005 stream >> functionCallId >> returnValue;
1006 if (stream.status() != QDataStream::Ok) {
1007 log("Failed to read scriptable proxy slot call return value", LogError);
1008 Q_ASSERT(false);
1009 return;
1010 }
1011 emit functionCallFinished(functionCallId, returnValue);
1012 }
1013
setInputDialogResult(const QByteArray & bytes)1014 void ScriptableProxy::setInputDialogResult(const QByteArray &bytes)
1015 {
1016 QDataStream stream(bytes);
1017 int dialogId;
1018 NamedValueList result;
1019 stream >> dialogId >> result;
1020 if (stream.status() != QDataStream::Ok) {
1021 log("Failed to read input dialog result", LogError);
1022 Q_ASSERT(false);
1023 return;
1024 }
1025 emit inputDialogFinished(dialogId, result);
1026 }
1027
safeDeleteLater()1028 void ScriptableProxy::safeDeleteLater()
1029 {
1030 m_shouldBeDeleted = true;
1031 if (m_functionCallStack == 0)
1032 deleteLater();
1033 }
1034
getActionData(int id)1035 QVariantMap ScriptableProxy::getActionData(int id)
1036 {
1037 INVOKE(getActionData, (id));
1038 m_actionData = m_wnd->actionData(id);
1039 m_actionId = id;
1040
1041 auto data = m_actionData;
1042 data.remove(mimeSelectedItems);
1043 data.remove(mimeCurrentItem);
1044 return data;
1045 }
1046
setActionData(int id,const QVariantMap & data)1047 void ScriptableProxy::setActionData(int id, const QVariantMap &data)
1048 {
1049 INVOKE2(setActionData, (id, data));
1050 m_wnd->setActionData(id, data);
1051 }
1052
exit()1053 void ScriptableProxy::exit()
1054 {
1055 INVOKE2(exit, ());
1056 qApp->quit();
1057 }
1058
close()1059 void ScriptableProxy::close()
1060 {
1061 INVOKE2(close, ());
1062 m_wnd->close();
1063 }
1064
focusPrevious()1065 bool ScriptableProxy::focusPrevious()
1066 {
1067 INVOKE(focusPrevious, ());
1068 return m_wnd->focusPrevious();
1069 }
1070
showWindow()1071 bool ScriptableProxy::showWindow()
1072 {
1073 INVOKE(showWindow, ());
1074 m_wnd->showWindow();
1075 return m_wnd->isVisible();
1076 }
1077
showWindowAt(QRect rect)1078 bool ScriptableProxy::showWindowAt(QRect rect)
1079 {
1080 INVOKE(showWindowAt, (rect));
1081 setGeometryWithoutSave(m_wnd, rect);
1082 return showWindow();
1083 }
1084
pasteToCurrentWindow()1085 bool ScriptableProxy::pasteToCurrentWindow()
1086 {
1087 INVOKE(pasteToCurrentWindow, ());
1088
1089 PlatformWindowPtr window = platformNativeInterface()->getCurrentWindow();
1090 if (!window)
1091 return false;
1092 window->pasteClipboard();
1093 return true;
1094 }
1095
copyFromCurrentWindow()1096 bool ScriptableProxy::copyFromCurrentWindow()
1097 {
1098 INVOKE(copyFromCurrentWindow, ());
1099
1100 PlatformWindowPtr window = platformNativeInterface()->getCurrentWindow();
1101 if (!window)
1102 return false;
1103 window->copy();
1104 return true;
1105 }
1106
isMonitoringEnabled()1107 bool ScriptableProxy::isMonitoringEnabled()
1108 {
1109 INVOKE(isMonitoringEnabled, ());
1110 return m_wnd->isMonitoringEnabled();
1111 }
1112
isMainWindowVisible()1113 bool ScriptableProxy::isMainWindowVisible()
1114 {
1115 INVOKE(isMainWindowVisible, ());
1116 return !m_wnd->isMinimized() && m_wnd->isVisible();
1117 }
1118
isMainWindowFocused()1119 bool ScriptableProxy::isMainWindowFocused()
1120 {
1121 INVOKE(isMainWindowFocused, ());
1122 return m_wnd->isActiveWindow();
1123 }
1124
preview(const QVariant & arg)1125 bool ScriptableProxy::preview(const QVariant &arg)
1126 {
1127 INVOKE(preview, (arg));
1128
1129 const bool wasVisible = m_wnd->isItemPreviewVisible();
1130
1131 if ( arg.isValid() ) {
1132 const bool enable =
1133 arg.canConvert<bool>() ? arg.toBool()
1134 : arg.canConvert<int>() ? arg.toInt() != 0
1135 : arg.toString() == QLatin1String("true");
1136 m_wnd->setItemPreviewVisible(enable);
1137 }
1138
1139 return wasVisible;
1140 }
1141
disableMonitoring(bool arg1)1142 void ScriptableProxy::disableMonitoring(bool arg1)
1143 {
1144 INVOKE2(disableMonitoring, (arg1));
1145 m_wnd->disableClipboardStoring(arg1);
1146 }
1147
setClipboard(const QVariantMap & data,ClipboardMode mode)1148 void ScriptableProxy::setClipboard(const QVariantMap &data, ClipboardMode mode)
1149 {
1150 INVOKE2(setClipboard, (data, mode));
1151 m_wnd->setClipboard(data, mode);
1152 }
1153
renameTab(const QString & arg1,const QString & arg2)1154 QString ScriptableProxy::renameTab(const QString &arg1, const QString &arg2)
1155 {
1156 INVOKE(renameTab, (arg1, arg2));
1157
1158 if ( arg1.isEmpty() || arg2.isEmpty() )
1159 return tabNameEmptyError();
1160
1161 const int i = m_wnd->findTabIndex(arg2);
1162 if (i == -1)
1163 return tabNotFoundError();
1164
1165 if ( m_wnd->findTabIndex(arg1) != -1 )
1166 return ScriptableProxy::tr("Tab with given name already exists!");
1167
1168 m_wnd->renameTab(arg1, i);
1169
1170 return QString();
1171 }
1172
removeTab(const QString & arg1)1173 QString ScriptableProxy::removeTab(const QString &arg1)
1174 {
1175 INVOKE(removeTab, (arg1));
1176
1177 if ( arg1.isEmpty() )
1178 return tabNameEmptyError();
1179
1180 const int i = m_wnd->findTabIndex(arg1);
1181 if (i == -1)
1182 return tabNotFoundError();
1183
1184 m_wnd->removeTab(false, i);
1185 return QString();
1186 }
1187
tabIcon(const QString & tabName)1188 QString ScriptableProxy::tabIcon(const QString &tabName)
1189 {
1190 INVOKE(tabIcon, (tabName));
1191 return getIconNameForTabName(tabName);
1192 }
1193
setTabIcon(const QString & tabName,const QString & icon)1194 void ScriptableProxy::setTabIcon(const QString &tabName, const QString &icon)
1195 {
1196 INVOKE2(setTabIcon, (tabName, icon));
1197 m_wnd->setTabIcon(tabName, icon);
1198 }
1199
unloadTabs(const QStringList & tabs)1200 QStringList ScriptableProxy::unloadTabs(const QStringList &tabs)
1201 {
1202 INVOKE(unloadTabs, (tabs));
1203 QStringList unloaded;
1204 for (const auto &tab : tabs) {
1205 if ( m_wnd->unloadTab(tab) )
1206 unloaded.append(tab);
1207 }
1208 return unloaded;
1209 }
1210
forceUnloadTabs(const QStringList & tabs)1211 void ScriptableProxy::forceUnloadTabs(const QStringList &tabs)
1212 {
1213 INVOKE2(forceUnloadTabs, (tabs));
1214 for (const auto &tab : tabs)
1215 m_wnd->forceUnloadTab(tab);
1216 }
1217
showBrowser(const QString & tabName)1218 bool ScriptableProxy::showBrowser(const QString &tabName)
1219 {
1220 INVOKE(showBrowser, (tabName));
1221 ClipboardBrowser *c = fetchBrowser(tabName);
1222 if (c)
1223 m_wnd->showBrowser(c);
1224 return m_wnd->isVisible();
1225 }
1226
showBrowserAt(const QString & tabName,QRect rect)1227 bool ScriptableProxy::showBrowserAt(const QString &tabName, QRect rect)
1228 {
1229 INVOKE(showBrowserAt, (tabName, rect));
1230 setGeometryWithoutSave(m_wnd, rect);
1231 return showBrowser(tabName);
1232 }
1233
action(const QVariantMap & arg1,const Command & arg2)1234 void ScriptableProxy::action(const QVariantMap &arg1, const Command &arg2)
1235 {
1236 INVOKE2(action, (arg1, arg2));
1237 m_wnd->action(arg1, arg2, QModelIndex());
1238 }
1239
runInternalAction(const QVariantMap & data,const QString & command)1240 void ScriptableProxy::runInternalAction(const QVariantMap &data, const QString &command)
1241 {
1242 INVOKE2(runInternalAction, (data, command));
1243 auto action = new Action();
1244 action->setCommand(command);
1245 action->setData(data);
1246 m_wnd->runInternalAction(action);
1247 }
1248
tryGetCommandOutput(const QString & command)1249 QByteArray ScriptableProxy::tryGetCommandOutput(const QString &command)
1250 {
1251 INVOKE(tryGetCommandOutput, (command));
1252
1253 for (int i = 0; i < 3; ++i) {
1254 Action action;
1255 action.setCommand(command);
1256 action.setReadOutput(true);
1257
1258 QByteArray output;
1259 connect( &action, &Action::actionOutput,
1260 this, [&output](const QByteArray &actionOutput) {
1261 output.append(actionOutput);
1262 } );
1263
1264 action.start();
1265 if ( !action.waitForFinished(5000) ) {
1266 if ( output.isEmpty() || !action.waitForFinished(30000) ) {
1267 action.terminate();
1268 continue;
1269 }
1270 }
1271
1272 if ( action.actionFailed() || action.exitCode() != 0 )
1273 continue;
1274
1275 return output;
1276 }
1277
1278 return QByteArray();
1279 }
1280
showMessage(const QString & title,const QString & msg,const QString & icon,int msec,const QString & notificationId,const NotificationButtons & buttons)1281 void ScriptableProxy::showMessage(const QString &title,
1282 const QString &msg,
1283 const QString &icon,
1284 int msec,
1285 const QString ¬ificationId,
1286 const NotificationButtons &buttons)
1287 {
1288 INVOKE2(showMessage, (title, msg, icon, msec, notificationId, buttons));
1289
1290 auto notification = m_wnd->createNotification(notificationId);
1291 notification->setTitle(title);
1292 notification->setMessage(msg, Qt::AutoText);
1293 notification->setIcon(icon);
1294 notification->setInterval(msec);
1295 notification->setButtons(buttons);
1296 }
1297
nextItem(const QString & tabName,int where)1298 QVariantMap ScriptableProxy::nextItem(const QString &tabName, int where)
1299 {
1300 INVOKE(nextItem, (tabName, where));
1301 ClipboardBrowser *c = fetchBrowser(tabName);
1302 if (!c)
1303 return QVariantMap();
1304
1305 const int row = qMax(0, c->currentIndex().row()) + where;
1306 const QModelIndex index = c->index(row);
1307
1308 if (!index.isValid())
1309 return QVariantMap();
1310
1311 c->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
1312 return c->copyIndex(index);
1313 }
1314
browserMoveToClipboard(const QString & tabName,int row)1315 void ScriptableProxy::browserMoveToClipboard(const QString &tabName, int row)
1316 {
1317 INVOKE2(browserMoveToClipboard, (tabName, row));
1318 ClipboardBrowser *c = fetchBrowser(tabName);
1319 m_wnd->moveToClipboard(c, row);
1320 }
1321
browserSetCurrent(const QString & tabName,int arg1)1322 void ScriptableProxy::browserSetCurrent(const QString &tabName, int arg1)
1323 {
1324 INVOKE2(browserSetCurrent, (tabName, arg1));
1325 BROWSER(tabName, setCurrent(arg1));
1326 }
1327
browserRemoveRows(const QString & tabName,QVector<int> rows)1328 QString ScriptableProxy::browserRemoveRows(const QString &tabName, QVector<int> rows)
1329 {
1330 INVOKE(browserRemoveRows, (tabName, rows));
1331 ClipboardBrowser *c = fetchBrowser(tabName);
1332 if (!c)
1333 return QLatin1String("Invalid tab");
1334
1335 std::sort( rows.begin(), rows.end(), std::greater<int>() );
1336
1337 QModelIndexList indexes;
1338 indexes.reserve(rows.size());
1339
1340 for (int row : rows) {
1341 const QModelIndex indexToRemove = c->index(row);
1342 if ( indexToRemove.isValid() )
1343 indexes.append(indexToRemove);
1344 }
1345
1346 const QPersistentModelIndex currentIndex = c->currentIndex();
1347
1348 QString error;
1349 const int lastRow = c->removeIndexes(indexes, &error);
1350
1351 if ( !error.isEmpty() )
1352 return error;
1353
1354 if ( !currentIndex.isValid() ) {
1355 const int currentRow = qMin(lastRow, c->length() - 1);
1356 c->setCurrent(currentRow);
1357 }
1358
1359 return QString();
1360 }
1361
browserMoveSelected(int targetRow)1362 void ScriptableProxy::browserMoveSelected(int targetRow)
1363 {
1364 INVOKE2(browserMoveSelected, (targetRow));
1365
1366 const QList<QPersistentModelIndex> selected = selectedIndexes();
1367 if ( selected.isEmpty() )
1368 return;
1369
1370 ClipboardBrowser *c = m_wnd->browserForItem(selected.first());
1371 if (c == nullptr)
1372 return;
1373
1374 QModelIndexList indexes;
1375 for (const auto &index : selected)
1376 indexes.append(index);
1377 c->move(indexes, targetRow);
1378 }
1379
browserEditRow(const QString & tabName,int arg1)1380 void ScriptableProxy::browserEditRow(const QString &tabName, int arg1)
1381 {
1382 INVOKE2(browserEditRow, (tabName, arg1));
1383 BROWSER(tabName, editRow(arg1));
1384 }
1385
browserEditNew(const QString & tabName,const QString & arg1,bool changeClipboard)1386 void ScriptableProxy::browserEditNew(const QString &tabName, const QString &arg1, bool changeClipboard)
1387 {
1388 INVOKE2(browserEditNew, (tabName, arg1, changeClipboard));
1389 BROWSER(tabName, editNew(arg1, changeClipboard));
1390 }
1391
tabs()1392 QStringList ScriptableProxy::tabs()
1393 {
1394 INVOKE(tabs, ());
1395 return m_wnd->tabs();
1396 }
1397
toggleVisible()1398 bool ScriptableProxy::toggleVisible()
1399 {
1400 INVOKE(toggleVisible, ());
1401 return m_wnd->toggleVisible();
1402 }
1403
toggleMenu(const QString & tabName,int maxItemCount,QPoint position)1404 bool ScriptableProxy::toggleMenu(const QString &tabName, int maxItemCount, QPoint position)
1405 {
1406 INVOKE(toggleMenu, (tabName, maxItemCount, position));
1407 return m_wnd->toggleMenu(tabName, maxItemCount, position);
1408 }
1409
toggleCurrentMenu()1410 bool ScriptableProxy::toggleCurrentMenu()
1411 {
1412 INVOKE(toggleCurrentMenu, ());
1413 return m_wnd->toggleMenu();
1414 }
1415
findTabIndex(const QString & arg1)1416 int ScriptableProxy::findTabIndex(const QString &arg1)
1417 {
1418 INVOKE(findTabIndex, (arg1));
1419 return m_wnd->findTabIndex(arg1);
1420 }
1421
menuItems(const QVector<QVariantMap> & items)1422 int ScriptableProxy::menuItems(const QVector<QVariantMap> &items)
1423 {
1424 INVOKE(menuItems, (items));
1425
1426 TrayMenu menu;
1427 menu.setObjectName("CustomMenu");
1428 menu.setRowIndexFromOne( AppConfig().option<Config::row_index_from_one>() );
1429
1430 const auto addMenuItems = [&](const QString &searchText) {
1431 menu.clearClipboardItems();
1432 for (const QVariantMap &data : items) {
1433 const QString text = getTextData(data);
1434 if ( text.contains(searchText, Qt::CaseInsensitive) )
1435 menu.addClipboardItemAction(data, true);
1436 }
1437 };
1438 addMenuItems(QString());
1439
1440 connect(&menu, &TrayMenu::searchRequest, addMenuItems);
1441
1442 const QPoint pos = QCursor::pos();
1443 QAction *act = menu.exec(pos);
1444 if (act == nullptr)
1445 return -1;
1446
1447 return items.indexOf(act->data().toMap());
1448 }
1449
openActionDialog(const QVariantMap & arg1)1450 void ScriptableProxy::openActionDialog(const QVariantMap &arg1)
1451 {
1452 INVOKE2(openActionDialog, (arg1));
1453 m_wnd->openActionDialog(arg1);
1454 }
1455
loadTab(const QString & arg1)1456 bool ScriptableProxy::loadTab(const QString &arg1)
1457 {
1458 INVOKE(loadTab, (arg1));
1459 return m_wnd->loadTab(arg1);
1460 }
1461
saveTab(const QString & tabName,const QString & arg1)1462 bool ScriptableProxy::saveTab(const QString &tabName, const QString &arg1)
1463 {
1464 INVOKE(saveTab, (tabName, arg1));
1465 ClipboardBrowser *c = fetchBrowser(tabName);
1466 if (!c)
1467 return false;
1468
1469 const int i = m_wnd->findTabIndex( c->tabName() );
1470 return m_wnd->saveTab(arg1, i);
1471 }
1472
importData(const QString & fileName)1473 bool ScriptableProxy::importData(const QString &fileName)
1474 {
1475 INVOKE(importData, (fileName));
1476 return m_wnd->importDataFrom(fileName, ImportOptions::All);
1477 }
1478
exportData(const QString & fileName)1479 bool ScriptableProxy::exportData(const QString &fileName)
1480 {
1481 INVOKE(exportData, (fileName));
1482 return m_wnd->exportAllData(fileName);
1483 }
1484
config(const QVariantList & nameValue)1485 QVariant ScriptableProxy::config(const QVariantList &nameValue)
1486 {
1487 INVOKE(config, (nameValue));
1488 return m_wnd->config(nameValue);
1489 }
1490
configDescription()1491 QString ScriptableProxy::configDescription()
1492 {
1493 INVOKE(configDescription, ());
1494 return m_wnd->configDescription();
1495 }
1496
toggleConfig(const QString & optionName)1497 QVariant ScriptableProxy::toggleConfig(const QString &optionName)
1498 {
1499 INVOKE(toggleConfig, (optionName));
1500
1501 QVariantList nameValue;
1502 nameValue.append(optionName);
1503 const auto values = m_wnd->config(nameValue);
1504 if ( values.type() == QVariant::StringList )
1505 return values;
1506
1507 const auto oldValue = values.toMap().constBegin().value();
1508 if ( oldValue.type() != QVariant::Bool )
1509 return QVariant();
1510
1511 const auto newValue = !QVariant(oldValue).toBool();
1512 nameValue.append(newValue);
1513 return m_wnd->config(nameValue).toMap().constBegin().value();
1514 }
1515
browserLength(const QString & tabName)1516 int ScriptableProxy::browserLength(const QString &tabName)
1517 {
1518 INVOKE(browserLength, (tabName));
1519 ClipboardBrowser *c = fetchBrowser(tabName);
1520 return c ? c->length() : 0;
1521 }
1522
browserOpenEditor(const QString & tabName,const QByteArray & arg1,bool changeClipboard)1523 bool ScriptableProxy::browserOpenEditor(const QString &tabName, const QByteArray &arg1, bool changeClipboard)
1524 {
1525 INVOKE(browserOpenEditor, (tabName, arg1, changeClipboard));
1526 ClipboardBrowser *c = fetchBrowser(tabName);
1527 return c && c->openEditor(arg1, changeClipboard);
1528 }
1529
browserInsert(const QString & tabName,int row,const QVector<QVariantMap> & items)1530 QString ScriptableProxy::browserInsert(const QString &tabName, int row, const QVector<QVariantMap> &items)
1531 {
1532 INVOKE(browserInsert, (tabName, row, items));
1533
1534 ClipboardBrowser *c = fetchBrowser(tabName);
1535 if (!c)
1536 return QLatin1String("Invalid tab");
1537
1538 if ( !c->allocateSpaceForNewItems(items.size()) )
1539 return QLatin1String("Tab is full (cannot remove any items)");
1540
1541 for (const auto &item : items) {
1542 if ( !c->add(item, row) )
1543 return QLatin1String("Failed to new add items");
1544 }
1545
1546 return QString();
1547 }
1548
browserChange(const QString & tabName,int row,const QVector<QVariantMap> & items)1549 QString ScriptableProxy::browserChange(const QString &tabName, int row, const QVector<QVariantMap> &items)
1550 {
1551 INVOKE(browserChange, (tabName, row, items));
1552
1553 ClipboardBrowser *c = fetchBrowser(tabName);
1554 if (!c)
1555 return QLatin1String("Invalid tab");
1556
1557 int currentRow = row;
1558 for (const auto &data : items) {
1559 const auto index = c->index(currentRow);
1560 QVariantMap itemData = c->model()->data(index, contentType::data).toMap();
1561 for (auto it = data.constBegin(); it != data.constEnd(); ++it) {
1562 if ( it.value().isValid() )
1563 itemData.insert( it.key(), it.value() );
1564 else
1565 itemData.remove( it.key() );
1566 }
1567 c->model()->setData(index, itemData, contentType::data);
1568 ++currentRow;
1569 }
1570
1571 return QString();
1572 }
1573
browserItemData(const QString & tabName,int arg1,const QString & arg2)1574 QByteArray ScriptableProxy::browserItemData(const QString &tabName, int arg1, const QString &arg2)
1575 {
1576 INVOKE(browserItemData, (tabName, arg1, arg2));
1577 return itemData(tabName, arg1, arg2);
1578 }
1579
browserItemData(const QString & tabName,int arg1)1580 QVariantMap ScriptableProxy::browserItemData(const QString &tabName, int arg1)
1581 {
1582 INVOKE(browserItemData, (tabName, arg1));
1583 return itemData(tabName, arg1);
1584 }
1585
setCurrentTab(const QString & tabName)1586 void ScriptableProxy::setCurrentTab(const QString &tabName)
1587 {
1588 INVOKE2(setCurrentTab, (tabName));
1589 m_wnd->addAndFocusTab(tabName);
1590 }
1591
tab(const QString & tabName)1592 QString ScriptableProxy::tab(const QString &tabName)
1593 {
1594 INVOKE(tab, (tabName));
1595 ClipboardBrowser *c = fetchBrowser(tabName);
1596 return c ? c->tabName() : QString();
1597 }
1598
currentItem()1599 int ScriptableProxy::currentItem()
1600 {
1601 INVOKE(currentItem, ());
1602
1603 const QPersistentModelIndex current =
1604 m_actionData.value(mimeCurrentItem).value<QPersistentModelIndex>();
1605 return current.isValid() ? current.row() : -1;
1606 }
1607
selectItems(const QString & tabName,const QVector<int> & rows)1608 bool ScriptableProxy::selectItems(const QString &tabName, const QVector<int> &rows)
1609 {
1610 INVOKE(selectItems, (tabName, rows));
1611 ClipboardBrowser *c = fetchBrowser(tabName);
1612 if (!c)
1613 return false;
1614
1615 c->clearSelection();
1616
1617 if ( !rows.isEmpty() ) {
1618 c->setCurrent(rows.last());
1619
1620 for (int i : rows) {
1621 const QModelIndex index = c->index(i);
1622 if ( index.isValid() && !c->isFiltered(i) )
1623 c->selectionModel()->select(index, QItemSelectionModel::Select);
1624 }
1625 }
1626
1627 return true;
1628 }
1629
selectedItems()1630 QVector<int> ScriptableProxy::selectedItems()
1631 {
1632 INVOKE(selectedItems, ());
1633
1634 QVector<int> selectedRows;
1635 const QList<QPersistentModelIndex> selected = selectedIndexes();
1636 selectedRows.reserve(selected.count());
1637 for (const auto &index : selected) {
1638 if (index.isValid())
1639 selectedRows.append(index.row());
1640 }
1641
1642 return selectedRows;
1643 }
1644
selectedItemData(int selectedIndex)1645 QVariantMap ScriptableProxy::selectedItemData(int selectedIndex)
1646 {
1647 INVOKE(selectedItemData, (selectedIndex));
1648
1649 auto c = currentBrowser();
1650 if (!c)
1651 return QVariantMap();
1652
1653 const auto index = selectedIndexes().value(selectedIndex);
1654 Q_ASSERT( !index.isValid() || index.model() == c->model() );
1655 return c->copyIndex(index);
1656 }
1657
setSelectedItemData(int selectedIndex,const QVariantMap & data)1658 bool ScriptableProxy::setSelectedItemData(int selectedIndex, const QVariantMap &data)
1659 {
1660 INVOKE(setSelectedItemData, (selectedIndex, data));
1661
1662 auto c = currentBrowser();
1663 if (!c)
1664 return false;
1665
1666 const auto index = selectedIndexes().value(selectedIndex);
1667 if ( !index.isValid() )
1668 return false;
1669
1670 Q_ASSERT( index.model() == c->model() );
1671 return c->model()->setData(index, data, contentType::data);
1672 }
1673
selectedItemsData()1674 QVector<QVariantMap> ScriptableProxy::selectedItemsData()
1675 {
1676 INVOKE(selectedItemsData, ());
1677
1678 auto c = currentBrowser();
1679 if (!c)
1680 return QVector<QVariantMap>();
1681
1682 const auto model = c->model();
1683
1684 QVector<QVariantMap> dataList;
1685 const auto selected = selectedIndexes();
1686 dataList.reserve(selected.size());
1687
1688 for (const auto &index : selected) {
1689 if ( index.isValid() ) {
1690 Q_ASSERT( index.model() == model );
1691 dataList.append( c->copyIndex(index) );
1692 }
1693 }
1694
1695 return dataList;
1696 }
1697
setSelectedItemsData(const QVector<QVariantMap> & dataList)1698 void ScriptableProxy::setSelectedItemsData(const QVector<QVariantMap> &dataList)
1699 {
1700 INVOKE2(setSelectedItemsData, (dataList));
1701
1702 auto c = currentBrowser();
1703 if (!c)
1704 return;
1705
1706 const auto model = c->model();
1707
1708 const auto indexes = selectedIndexes();
1709 const auto count = std::min( indexes.size(), dataList.size() );
1710 for ( int i = 0; i < count; ++i ) {
1711 const auto &index = indexes[i];
1712 if ( index.isValid() ) {
1713 Q_ASSERT( index.model() == model );
1714 model->setData(index, dataList[i], contentType::data);
1715 }
1716 }
1717 }
1718
createSelection(const QString & tabName)1719 int ScriptableProxy::createSelection(const QString &tabName)
1720 {
1721 INVOKE(createSelection, (tabName));
1722 const int newSelectionId = ++m_lastSelectionId;
1723 ClipboardBrowser *c = fetchBrowser(tabName);
1724 if (c)
1725 m_selections[newSelectionId] = {c, {}};
1726 return newSelectionId;
1727 }
1728
selectionCopy(int id)1729 int ScriptableProxy::selectionCopy(int id)
1730 {
1731 INVOKE(selectionCopy, (id));
1732 const int newSelectionId = ++m_lastSelectionId;
1733 auto selection = m_selections.value(id);
1734 if (selection.browser)
1735 m_selections[newSelectionId] = selection;
1736 return newSelectionId;
1737 }
1738
destroySelection(int id)1739 void ScriptableProxy::destroySelection(int id)
1740 {
1741 INVOKE2(destroySelection, (id));
1742 m_selections.remove(id);
1743 }
1744
selectionRemoveAll(int id)1745 void ScriptableProxy::selectionRemoveAll(int id)
1746 {
1747 INVOKE2(selectionRemoveAll, (id));
1748 auto selection = m_selections.take(id);
1749 if (!selection.browser)
1750 return;
1751 selectionRemoveInvalid(&selection.indexes);
1752
1753 QModelIndexList indexes;
1754 for (const auto &index : selection.indexes)
1755 indexes.append(index);
1756
1757 selection.browser->removeIndexes(indexes);
1758
1759 selectionRemoveInvalid(&selection.indexes);
1760 m_selections[id] = selection;
1761 }
1762
selectionSelectRemovable(int id)1763 void ScriptableProxy::selectionSelectRemovable(int id)
1764 {
1765 INVOKE2(selectionSelectRemovable, (id));
1766 auto selection = m_selections.take(id);
1767 if (!selection.browser)
1768 return;
1769
1770 QList<QPersistentModelIndex> indexes;
1771 for (int row = 0; row < selection.browser->length(); ++row) {
1772 const auto index = selection.browser->index(row);
1773 if ( !selection.indexes.contains(index) && selection.browser->canRemoveItems({index}) )
1774 indexes.append(index);
1775 }
1776 selection.indexes.append(indexes);
1777 m_selections[id] = selection;
1778 }
1779
selectionInvert(int id)1780 void ScriptableProxy::selectionInvert(int id)
1781 {
1782 INVOKE2(selectionInvert, (id));
1783 auto selection = m_selections.take(id);
1784 if (!selection.browser)
1785 return;
1786
1787 QList<QPersistentModelIndex> indexes;
1788 for (int row = 0; row < selection.browser->length(); ++row) {
1789 const auto index = selection.browser->index(row);
1790 if ( !selection.indexes.contains(index) )
1791 indexes.append(index);
1792 }
1793 selection.indexes = indexes;
1794 m_selections[id] = selection;
1795 }
1796
selectionSelectAll(int id)1797 void ScriptableProxy::selectionSelectAll(int id)
1798 {
1799 INVOKE2(selectionSelectAll, (id));
1800 auto selection = m_selections.take(id);
1801 if (!selection.browser)
1802 return;
1803
1804 selection.indexes.clear();
1805 for (int row = 0; row < selection.browser->length(); ++row)
1806 selection.indexes.append(selection.browser->index(row));
1807 m_selections[id] = selection;
1808 }
1809
selectionSelect(int id,const QVariant & maybeRe,const QString & mimeFormat)1810 void ScriptableProxy::selectionSelect(int id, const QVariant &maybeRe, const QString &mimeFormat)
1811 {
1812 INVOKE2(selectionSelect, (id, maybeRe, mimeFormat));
1813 auto selection = m_selections.take(id);
1814 if (!selection.browser)
1815 return;
1816
1817 const QRegularExpression re = maybeRe.toRegularExpression();
1818 QList<QPersistentModelIndex> indexes;
1819 for (int row = 0; row < selection.browser->length(); ++row) {
1820 const auto index = selection.browser->index(row);
1821 if ( selection.indexes.contains(index) )
1822 continue;
1823
1824 const QVariantMap dataMap = index.data(contentType::data).toMap();
1825 if ( mimeFormat.isEmpty() ) {
1826 if ( !maybeRe.isValid() )
1827 continue;
1828 const QString text = getTextData(dataMap);
1829 if ( text.contains(re) )
1830 indexes.append(index);
1831 } else if ( dataMap.contains(mimeFormat) == maybeRe.isValid() ) {
1832 const QString text = getTextData(dataMap, mimeFormat);
1833 if ( text.contains(re) )
1834 indexes.append(index);
1835 }
1836 }
1837 selection.indexes.append(indexes);
1838 m_selections[id] = selection;
1839 }
1840
selectionDeselectIndexes(int id,const QVector<int> & indexes)1841 void ScriptableProxy::selectionDeselectIndexes(int id, const QVector<int> &indexes)
1842 {
1843 INVOKE2(selectionDeselectIndexes, (id, indexes));
1844
1845 auto selection = m_selections.take(id);
1846 auto indexesSorted = indexes;
1847 std::sort(indexesSorted.begin(), indexesSorted.end(), std::greater<int>());
1848 for (int index : indexesSorted)
1849 selection.indexes.removeAt(index);
1850 m_selections[id] = selection;
1851 }
1852
selectionDeselectSelection(int id,int toDeselectId)1853 void ScriptableProxy::selectionDeselectSelection(int id, int toDeselectId)
1854 {
1855 INVOKE2(selectionDeselectSelection, (id, toDeselectId));
1856 auto selection = m_selections.take(id);
1857 const auto deselection = m_selections.value(toDeselectId);
1858
1859 selectionRemoveIf(
1860 &selection.indexes,
1861 [&](const QPersistentModelIndex &index){
1862 return !index.isValid() || deselection.indexes.contains(index);
1863 });
1864 m_selections[id] = selection;
1865 }
1866
selectionGetCurrent(int id)1867 void ScriptableProxy::selectionGetCurrent(int id)
1868 {
1869 INVOKE2(selectionGetCurrent, (id));
1870 auto selection = m_selections.take(id);
1871 if (!selection.browser)
1872 return;
1873
1874 auto selected = selectedIndexes();
1875 selectionRemoveInvalid(&selected);
1876 if ( selected.isEmpty() ) {
1877 m_selections[id] = {nullptr, {}};
1878 } else {
1879 ClipboardBrowser *c = m_wnd->browserForItem(selected.first());
1880 m_selections[id] = {c, selected};
1881 }
1882 }
1883
selectionGetSize(int id)1884 int ScriptableProxy::selectionGetSize(int id)
1885 {
1886 INVOKE(selectionGetSize, (id));
1887 return m_selections.value(id).indexes.size();
1888 }
1889
selectionGetTabName(int id)1890 QString ScriptableProxy::selectionGetTabName(int id)
1891 {
1892 INVOKE(selectionGetTabName, (id));
1893 const auto selection = m_selections.value(id);
1894 return selection.browser ? selection.browser->tabName() : QString();
1895 }
1896
selectionGetRows(int id)1897 QVector<int> ScriptableProxy::selectionGetRows(int id)
1898 {
1899 INVOKE(selectionGetRows, (id));
1900
1901 auto selection = m_selections.value(id);
1902 selectionRemoveInvalid(&selection.indexes);
1903 QVector<int> rows;
1904 rows.reserve(selection.indexes.size());
1905 for (const auto &index : selection.indexes)
1906 rows.append(index.row());
1907 return rows;
1908 }
1909
selectionGetItemIndex(int id,int index)1910 QVariantMap ScriptableProxy::selectionGetItemIndex(int id, int index)
1911 {
1912 INVOKE(selectionGetItemIndex, (id, index));
1913
1914 auto selection = m_selections.value(id);
1915 if ( selection.indexes.isEmpty() || index < 0 || index >= selection.indexes.size() )
1916 return {};
1917
1918 return selection.indexes[index].data(contentType::data).toMap();
1919 }
1920
selectionSetItemIndex(int id,int index,const QVariantMap & item)1921 void ScriptableProxy::selectionSetItemIndex(int id, int index, const QVariantMap &item)
1922 {
1923 INVOKE2(selectionSetItemIndex, (id, index, item));
1924
1925 const auto selection = m_selections.value(id);
1926 if ( !selection.browser || index < 0 || index >= selection.indexes.size() )
1927 return;
1928
1929 const QModelIndex ind = selection.indexes[index];
1930 selection.browser->model()->setData(ind, item, contentType::data);
1931 }
1932
selectionGetItemsData(int id)1933 QVariantList ScriptableProxy::selectionGetItemsData(int id)
1934 {
1935 INVOKE(selectionGetItemsData, (id));
1936
1937 QVariantList dataList;
1938 const auto selection = m_selections.value(id);
1939 for (const auto &index : selection.indexes) {
1940 const auto data = index.data(contentType::data).toMap();
1941 dataList.append(data);
1942 }
1943 return dataList;
1944 }
1945
selectionSetItemsData(int id,const QVariantList & dataList)1946 void ScriptableProxy::selectionSetItemsData(int id, const QVariantList &dataList)
1947 {
1948 INVOKE2(selectionSetItemsData, (id, dataList));
1949
1950 const auto selection = m_selections.value(id);
1951 const auto count = std::min( selection.indexes.size(), dataList.size() );
1952 for ( int i = 0; i < count; ++i ) {
1953 const auto &index = selection.indexes[i];
1954 if ( index.isValid() )
1955 selection.browser->model()->setData(index, dataList[i], contentType::data);
1956 }
1957 }
1958
selectionGetItemsFormat(int id,const QString & format)1959 QVariantList ScriptableProxy::selectionGetItemsFormat(int id, const QString &format)
1960 {
1961 INVOKE(selectionGetItemsFormat, (id, format));
1962
1963 QVariantList dataList;
1964 const auto selection = m_selections.value(id);
1965 for (const auto &index : selection.indexes) {
1966 const auto data = index.data(contentType::data).toMap();
1967 dataList.append( data.value(format) );
1968 }
1969 return dataList;
1970 }
1971
selectionSetItemsFormat(int id,const QString & mime,const QVariant & value)1972 void ScriptableProxy::selectionSetItemsFormat(int id, const QString &mime, const QVariant &value)
1973 {
1974 INVOKE2(selectionSetItemsFormat, (id, mime, value));
1975
1976 const auto selection = m_selections.value(id);
1977 for (const auto &index : selection.indexes) {
1978 if ( index.isValid() ) {
1979 QVariantMap data = index.data(contentType::data).toMap();
1980 if (value.isValid())
1981 data[mime] = value;
1982 else
1983 data.remove(mime);
1984 selection.browser->model()->setData(index, data, contentType::data);
1985 }
1986 }
1987 }
1988
selectionMove(int id,int row)1989 void ScriptableProxy::selectionMove(int id, int row)
1990 {
1991 INVOKE2(selectionMove, (id, row));
1992 auto selection = m_selections.value(id);
1993 selectionRemoveInvalid(&selection.indexes);
1994
1995 QModelIndexList indexes;
1996 for (const auto &index : selection.indexes)
1997 indexes.append(index);
1998
1999 if ( !indexes.isEmpty() )
2000 selection.browser->move(indexes, row);
2001 }
2002
2003 #ifdef HAS_TESTS
sendKeys(const QString & expectedWidgetName,const QString & keys,int delay)2004 void ScriptableProxy::sendKeys(const QString &expectedWidgetName, const QString &keys, int delay)
2005 {
2006 INVOKE2(sendKeys, (expectedWidgetName, keys, delay));
2007 Q_ASSERT( keyClicker()->succeeded() || keyClicker()->failed() );
2008 keyClicker()->sendKeyClicks(expectedWidgetName, keys, delay, 10);
2009 }
2010
sendKeysSucceeded()2011 bool ScriptableProxy::sendKeysSucceeded()
2012 {
2013 INVOKE(sendKeysSucceeded, ());
2014 return keyClicker()->succeeded();
2015 }
2016
sendKeysFailed()2017 bool ScriptableProxy::sendKeysFailed()
2018 {
2019 INVOKE(sendKeysFailed, ());
2020 return keyClicker()->failed();
2021 }
2022
testSelected()2023 QString ScriptableProxy::testSelected()
2024 {
2025 INVOKE(testSelected, ());
2026
2027 ClipboardBrowser *browser = m_wnd->browser();
2028 if (!browser)
2029 return QString();
2030
2031 if (browser->length() == 0)
2032 return browser->tabName();
2033
2034 QModelIndexList selectedIndexes = browser->selectionModel()->selectedIndexes();
2035
2036 QStringList result;
2037 result.reserve( selectedIndexes.size() + 1 );
2038
2039 const QModelIndex currentIndex = browser->currentIndex();
2040 result.append(currentIndex.isValid() ? QString::number(currentIndex.row()) : "_");
2041
2042 QList<int> selectedRows;
2043 selectedRows.reserve( selectedIndexes.size() );
2044 for (const auto &index : selectedIndexes)
2045 selectedRows.append(index.row());
2046 std::sort( selectedRows.begin(), selectedRows.end() );
2047
2048 for (int row : selectedRows)
2049 result.append(QString::number(row));
2050
2051 return browser->tabName() + " " + result.join(" ");
2052 }
2053 #endif // HAS_TESTS
2054
serverLog(const QString & text)2055 void ScriptableProxy::serverLog(const QString &text)
2056 {
2057 INVOKE2(serverLog, (text));
2058 log(text, LogAlways);
2059 }
2060
currentWindowTitle()2061 QString ScriptableProxy::currentWindowTitle()
2062 {
2063 INVOKE(currentWindowTitle, ());
2064 PlatformWindowPtr window = platformNativeInterface()->getCurrentWindow();
2065 return window ? window->getTitle() : QString();
2066 }
2067
inputDialog(const NamedValueList & values)2068 int ScriptableProxy::inputDialog(const NamedValueList &values)
2069 {
2070 INVOKE(inputDialog, (values));
2071
2072 InputDialog inputDialog;
2073 inputDialog.dialog = new QDialog(m_wnd);
2074 QDialog &dialog = *inputDialog.dialog;
2075
2076 QString dialogTitle;
2077 QIcon icon;
2078 QVBoxLayout layout(&dialog);
2079 QWidgetList widgets;
2080 widgets.reserve(values.size());
2081
2082 QString styleSheet;
2083 QRect geometry(-1, -1, 0, 0);
2084
2085 for (const auto &value : values) {
2086 if (value.name == ".title")
2087 dialogTitle = value.value.toString();
2088 else if (value.name == ".icon")
2089 icon = loadIcon(value.value.toString());
2090 else if (value.name == ".style")
2091 styleSheet = value.value.toString();
2092 else if (value.name == ".height")
2093 geometry.setHeight( pointsToPixels(value.value.toInt()) );
2094 else if (value.name == ".width")
2095 geometry.setWidth( pointsToPixels(value.value.toInt()) );
2096 else if (value.name == ".x")
2097 geometry.setX(value.value.toInt());
2098 else if (value.name == ".y")
2099 geometry.setY(value.value.toInt());
2100 else if (value.name == ".label")
2101 createAndSetWidget<QLabel>("text", value.value, &dialog);
2102 else if (value.name == ".defaultChoice")
2103 inputDialog.defaultChoice = value.value.toString();
2104 else
2105 widgets.append( createWidget(value.name, value.value, &inputDialog) );
2106 }
2107
2108 dialog.adjustSize();
2109
2110 if (geometry.height() == 0)
2111 geometry.setHeight(dialog.height());
2112 if (geometry.width() == 0)
2113 geometry.setWidth(dialog.width());
2114 if (geometry.isValid())
2115 dialog.resize(geometry.size());
2116 if (geometry.x() >= 0 && geometry.y() >= 0)
2117 dialog.move(geometry.topLeft());
2118
2119 if ( !styleSheet.isEmpty() )
2120 dialog.setStyleSheet(styleSheet);
2121
2122 auto buttons = new QDialogButtonBox(
2123 QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog);
2124 QObject::connect( buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept );
2125 QObject::connect( buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject );
2126 layout.addWidget(buttons);
2127
2128 installShortcutToCloseDialog(&dialog, &dialog, Qt::ControlModifier | Qt::Key_Enter);
2129 installShortcutToCloseDialog(&dialog, &dialog, Qt::ControlModifier | Qt::Key_Return);
2130
2131 if (icon.isNull())
2132 icon = appIcon();
2133 dialog.setWindowIcon(icon);
2134
2135 const int dialogId = ++m_lastInputDialogId;
2136 connect(&dialog, &QDialog::finished, this, [this, dialogId, inputDialog, widgets]() {
2137 if (inputDialog.dialog == nullptr)
2138 return;
2139
2140 NamedValueList result;
2141 result.reserve( widgets.size() );
2142
2143 if ( inputDialog.dialog->result() ) {
2144 for ( auto w : widgets ) {
2145 const QString propertyName = w->property(propertyWidgetProperty).toString();
2146 const QString name = w->property(propertyWidgetName).toString();
2147 const QVariant value = w->property(propertyName.toUtf8().constData());
2148 result.append( NamedValue(name, value) );
2149 }
2150 }
2151
2152 QByteArray bytes;
2153 {
2154 QDataStream stream(&bytes, QIODevice::WriteOnly);
2155 stream << dialogId << result;
2156 }
2157
2158 inputDialog.dialog->deleteLater();
2159 emit sendMessage(bytes, CommandInputDialogFinished);
2160 });
2161
2162 // Connecting this directly to QEventLoop::quit() doesn't seem to work always.
2163 connect(this, &ScriptableProxy::abortEvaluation, &dialog, &QDialog::reject);
2164
2165 if ( !dialogTitle.isNull() ) {
2166 dialog.setWindowTitle(dialogTitle);
2167 dialog.setObjectName(QLatin1String("dialog_") + dialogTitle);
2168 WindowGeometryGuard::create(&dialog);
2169 }
2170
2171 dialog.show();
2172
2173 // Skip raising dialog in tests.
2174 if ( !qApp->property("CopyQ_test_id").isValid() )
2175 raiseWindow(&dialog);
2176
2177 return dialogId;
2178 }
2179
setSelectedItemsData(const QString & mime,const QVariant & value)2180 void ScriptableProxy::setSelectedItemsData(const QString &mime, const QVariant &value)
2181 {
2182 INVOKE2(setSelectedItemsData, (mime, value));
2183 const QList<QPersistentModelIndex> selected = selectedIndexes();
2184 for (const auto &index : selected) {
2185 ClipboardBrowser *c = m_wnd->browserForItem(index);
2186 if (c) {
2187 QVariantMap data = c->model()->data(index, contentType::data).toMap();
2188 if (value.isValid())
2189 data[mime] = value;
2190 else
2191 data.remove(mime);
2192 c->model()->setData(index, data, contentType::data);
2193 }
2194 }
2195 }
2196
filter(const QString & text)2197 void ScriptableProxy::filter(const QString &text)
2198 {
2199 INVOKE2(filter, (text));
2200 m_wnd->setFilter(text);
2201 }
2202
filter()2203 QString ScriptableProxy::filter()
2204 {
2205 INVOKE(filter, ());
2206 return m_wnd->filter();
2207 }
2208
commands()2209 QVector<Command> ScriptableProxy::commands()
2210 {
2211 INVOKE(commands, ());
2212 return loadAllCommands();
2213 }
2214
setCommands(const QVector<Command> & commands)2215 void ScriptableProxy::setCommands(const QVector<Command> &commands)
2216 {
2217 INVOKE2(setCommands, (commands));
2218 m_wnd->setCommands(commands);
2219 }
2220
addCommands(const QVector<Command> & commands)2221 void ScriptableProxy::addCommands(const QVector<Command> &commands)
2222 {
2223 INVOKE2(addCommands, (commands));
2224 m_wnd->addCommands(commands);
2225 }
2226
screenshot(const QString & format,const QString & screenName,bool select)2227 QByteArray ScriptableProxy::screenshot(const QString &format, const QString &screenName, bool select)
2228 {
2229 INVOKE(screenshot, (format, screenName, select));
2230
2231 QScreen *selectedScreen = nullptr;
2232 if ( screenName.isEmpty() ) {
2233 const auto mousePosition = QCursor::pos();
2234 const int screenNumber = ::screenNumberAt(mousePosition);
2235 if (screenNumber != -1)
2236 selectedScreen = QGuiApplication::screens().value(screenNumber);
2237 } else {
2238 for ( const auto screen : QApplication::screens() ) {
2239 if (screen->name() == screenName) {
2240 selectedScreen = screen;
2241 break;
2242 }
2243 }
2244 }
2245
2246 if (!selectedScreen)
2247 return QByteArray();
2248
2249 auto pixmap = selectedScreen->grabWindow(0);
2250
2251 const auto geometry = selectedScreen->geometry();
2252
2253 if (select) {
2254 ScreenshotRectWidget rectWidget(pixmap);
2255 rectWidget.setGeometry(geometry);
2256 rectWidget.setWindowState(Qt::WindowFullScreen);
2257 rectWidget.setWindowModality(Qt::ApplicationModal);
2258 rectWidget.show();
2259 raiseWindow(&rectWidget);
2260
2261 while ( !rectWidget.isHidden() )
2262 QCoreApplication::processEvents();
2263 const auto rect = rectWidget.selectionRect;
2264 if ( rect.isValid() ) {
2265 const auto ratio = pixelRatio(&pixmap);
2266 const QRect rect2( rect.topLeft() * ratio, rect.size() * ratio );
2267 pixmap = pixmap.copy(rect2);
2268 }
2269 }
2270
2271 QByteArray bytes;
2272 {
2273 QBuffer buffer(&bytes);
2274 buffer.open(QIODevice::WriteOnly);
2275 if ( !pixmap.save(&buffer, format.toUtf8().constData()) )
2276 return QByteArray();
2277 }
2278
2279 return bytes;
2280 }
2281
screenNames()2282 QStringList ScriptableProxy::screenNames()
2283 {
2284 INVOKE(screenNames, ());
2285
2286 QStringList result;
2287 const auto screens = QApplication::screens();
2288 result.reserve( screens.size() );
2289
2290 for ( const auto screen : screens )
2291 result.append(screen->name());
2292
2293 return result;
2294 }
2295
queryKeyboardModifiers()2296 Qt::KeyboardModifiers ScriptableProxy::queryKeyboardModifiers()
2297 {
2298 INVOKE(queryKeyboardModifiers, ());
2299 return QApplication::queryKeyboardModifiers();
2300 }
2301
pointerPosition()2302 QPoint ScriptableProxy::pointerPosition()
2303 {
2304 INVOKE(pointerPosition, ());
2305 return QCursor::pos();
2306 }
2307
setPointerPosition(int x,int y)2308 void ScriptableProxy::setPointerPosition(int x, int y)
2309 {
2310 INVOKE2(setPointerPosition, (x, y));
2311 const QPoint pos(x, y);
2312 #if QT_VERSION < QT_VERSION_CHECK(5,10,0)
2313 const auto screens = QApplication::screens();
2314 const auto found = std::find_if(
2315 std::begin(screens), std::end(screens),
2316 [pos](QScreen *screen) { return screen->geometry().contains(pos); });
2317 if (found == std::end(screens))
2318 return;
2319 QScreen *screen = *found;
2320 #else
2321 QScreen *screen = QGuiApplication::screenAt(pos);
2322 #endif
2323
2324 if (screen)
2325 QCursor::setPos(screen, pos);
2326 }
2327
pluginsPath()2328 QString ScriptableProxy::pluginsPath()
2329 {
2330 INVOKE(pluginsPath, ());
2331 return ::pluginsPath();
2332 }
2333
themesPath()2334 QString ScriptableProxy::themesPath()
2335 {
2336 INVOKE(themesPath, ());
2337 return ::themesPath();
2338 }
2339
translationsPath()2340 QString ScriptableProxy::translationsPath()
2341 {
2342 INVOKE(translationsPath, ());
2343 return ::translationsPath();
2344 }
2345
iconColor()2346 QString ScriptableProxy::iconColor()
2347 {
2348 INVOKE(iconColor, ());
2349 const auto color = m_wnd->sessionIconColor();
2350 return color.isValid() ? color.name() : QString();
2351 }
2352
setIconColor(const QString & colorName)2353 bool ScriptableProxy::setIconColor(const QString &colorName)
2354 {
2355 INVOKE(setIconColor, (colorName));
2356
2357 QColor color(colorName);
2358 if ( !colorName.isEmpty() && !color.isValid() )
2359 return false;
2360
2361 m_wnd->setSessionIconColor(color);
2362 return true;
2363 }
2364
iconTag()2365 QString ScriptableProxy::iconTag()
2366 {
2367 INVOKE(iconTag, ());
2368 return m_wnd->sessionIconTag();
2369 }
2370
setIconTag(const QString & tag)2371 void ScriptableProxy::setIconTag(const QString &tag)
2372 {
2373 INVOKE2(setIconTag, (tag));
2374 m_wnd->setSessionIconTag(tag);
2375 }
2376
iconTagColor()2377 QString ScriptableProxy::iconTagColor()
2378 {
2379 INVOKE(iconTagColor, ());
2380 return m_wnd->sessionIconTagColor().name();
2381 }
2382
setIconTagColor(const QString & colorName)2383 bool ScriptableProxy::setIconTagColor(const QString &colorName)
2384 {
2385 INVOKE(setIconTagColor, (colorName));
2386 QColor color(colorName);
2387 if ( !color.isValid() )
2388 return false;
2389
2390 m_wnd->setSessionIconTagColor(color);
2391 return true;
2392 }
2393
setClipboardData(const QVariantMap & data)2394 void ScriptableProxy::setClipboardData(const QVariantMap &data)
2395 {
2396 INVOKE2(setClipboardData, (data));
2397 m_wnd->setClipboardData(data);
2398 }
2399
setTitle(const QString & title)2400 void ScriptableProxy::setTitle(const QString &title)
2401 {
2402 INVOKE2(setTitle, (title));
2403
2404 if (title.isEmpty()) {
2405 m_wnd->setWindowTitle(QString());
2406 m_wnd->setTrayTooltip(QGuiApplication::applicationDisplayName());
2407 } else {
2408 m_wnd->setWindowTitle(title);
2409 m_wnd->setTrayTooltip(title);
2410 }
2411 }
2412
setTitleForData(const QVariantMap & data)2413 void ScriptableProxy::setTitleForData(const QVariantMap &data)
2414 {
2415 INVOKE2(setTitleForData, (data));
2416
2417 const QString clipboardContent = textLabelForData(data);
2418 setTitle(clipboardContent);
2419 }
2420
saveData(const QString & tab,const QVariantMap & data,ClipboardMode mode)2421 void ScriptableProxy::saveData(const QString &tab, const QVariantMap &data, ClipboardMode mode)
2422 {
2423 INVOKE2(saveData, (tab, data, mode));
2424
2425 auto c = m_wnd->tab(tab);
2426 if (c)
2427 c->addUnique(data, mode);
2428 }
2429
showDataNotification(const QVariantMap & data)2430 void ScriptableProxy::showDataNotification(const QVariantMap &data)
2431 {
2432 INVOKE2(showDataNotification, (data));
2433
2434 const AppConfig appConfig;
2435 const auto maxLines = appConfig.option<Config::clipboard_notification_lines>();
2436 if (maxLines <= 0)
2437 return;
2438
2439 const auto intervalSeconds = appConfig.option<Config::item_popup_interval>();
2440 if (intervalSeconds == 0)
2441 return;
2442
2443 auto notification = m_wnd->createNotification("CopyQ_clipboard_notification");
2444 notification->setIcon(IconPaste);
2445 notification->setInterval(intervalSeconds * 1000);
2446
2447 const int maximumWidthPoints = appConfig.option<Config::notification_maximum_width>();
2448 const int width = pointsToPixels(maximumWidthPoints) - 16 - 8;
2449
2450 const QStringList formats = data.keys();
2451 const int imageIndex = formats.indexOf(QRegularExpression("^image/.*"));
2452 const QFont &font = notification->widget()
2453 ? notification->widget()->font()
2454 : qApp->font();
2455 const bool isHidden = data.contains(mimeHidden);
2456
2457 QString title;
2458
2459 if (data.isEmpty()) {
2460 notification->setInterval(0);
2461 } if ( !isHidden && data.contains(mimeText) ) {
2462 QString text = getTextData(data);
2463 const int n = text.count('\n') + 1;
2464
2465 if (n > 1) {
2466 title = QObject::tr("Text Copied (%n lines)",
2467 "Notification title for multi-line text in clipboard", n);
2468 } else {
2469 title = QObject::tr("Text Copied", "Notification title for single-line text in clipboard");
2470 }
2471
2472 text = elideText(text, font, QString(), false, width, maxLines);
2473 notification->setMessage(text);
2474 } else if (!isHidden && imageIndex != -1) {
2475 QPixmap pix;
2476 const QString &imageFormat = formats[imageIndex];
2477 pix.loadFromData( data[imageFormat].toByteArray(), imageFormat.toLatin1() );
2478
2479 const int height = maxLines * QFontMetrics(font).lineSpacing();
2480 if (pix.width() > width || pix.height() > height)
2481 pix = pix.scaled(QSize(width, height), Qt::KeepAspectRatio);
2482
2483 notification->setPixmap(pix);
2484 } else {
2485 title = QObject::tr("Data Copied", "Notification title for a copied data in clipboard");
2486 const QString text = textLabelForData(data, font, QString(), false, width, maxLines);
2487 notification->setMessage(text);
2488 }
2489
2490 notification->setTitle(title);
2491 }
2492
enableMenuItem(int actionId,int currentRun,int menuItemMatchCommandIndex,const QVariantMap & menuItem)2493 bool ScriptableProxy::enableMenuItem(int actionId, int currentRun, int menuItemMatchCommandIndex, const QVariantMap &menuItem)
2494 {
2495 INVOKE(enableMenuItem, (actionId, currentRun, menuItemMatchCommandIndex, menuItem));
2496 return m_wnd->setMenuItemEnabled(actionId, currentRun, menuItemMatchCommandIndex, menuItem);
2497 }
2498
setDisplayData(int actionId,const QVariantMap & displayData)2499 QVariantMap ScriptableProxy::setDisplayData(int actionId, const QVariantMap &displayData)
2500 {
2501 INVOKE(setDisplayData, (actionId, displayData));
2502 m_actionData = m_wnd->setDisplayData(actionId, displayData);
2503 return m_actionData;
2504 }
2505
automaticCommands()2506 QVector<Command> ScriptableProxy::automaticCommands()
2507 {
2508 INVOKE(automaticCommands, ());
2509 return m_wnd->automaticCommands();
2510 }
2511
displayCommands()2512 QVector<Command> ScriptableProxy::displayCommands()
2513 {
2514 INVOKE(displayCommands, ());
2515 return m_wnd->displayCommands();
2516 }
2517
scriptCommands()2518 QVector<Command> ScriptableProxy::scriptCommands()
2519 {
2520 INVOKE(scriptCommands, ());
2521 return m_wnd->scriptCommands();
2522 }
2523
openUrls(const QStringList & urls)2524 bool ScriptableProxy::openUrls(const QStringList &urls)
2525 {
2526 INVOKE(openUrls, (urls));
2527
2528 for (const auto &url : urls) {
2529 if ( !QDesktopServices::openUrl(QUrl(url)) )
2530 return false;
2531 }
2532
2533 return true;
2534 }
2535
loadTheme(const QString & path)2536 QString ScriptableProxy::loadTheme(const QString &path)
2537 {
2538 INVOKE(loadTheme, (path));
2539
2540 {
2541 const QFileInfo fileInfo(path);
2542 if ( !fileInfo.isFile() || !fileInfo.isReadable() )
2543 return "Failed to read theme";
2544 }
2545
2546 const QSettings settings(path, QSettings::IniFormat);
2547 if ( settings.status() != QSettings::NoError )
2548 return "Failed to load theme";
2549
2550 m_wnd->loadTheme(settings);
2551 if ( settings.status() != QSettings::NoError )
2552 return "Failed to parse theme";
2553
2554 return QString();
2555 }
2556
getClipboardData(const QString & mime,ClipboardMode mode)2557 QByteArray ScriptableProxy::getClipboardData(const QString &mime, ClipboardMode mode)
2558 {
2559 INVOKE(getClipboardData, (mime, mode));
2560
2561 const QMimeData *data = m_wnd->getClipboardData(mode);
2562 if (!data)
2563 return QByteArray();
2564
2565 if (mime == "?")
2566 return data->formats().join("\n").toUtf8() + '\n';
2567
2568 return cloneData(*data, QStringList(mime)).value(mime).toByteArray();
2569 }
2570
hasClipboardFormat(const QString & mime,ClipboardMode mode)2571 bool ScriptableProxy::hasClipboardFormat(const QString &mime, ClipboardMode mode)
2572 {
2573 INVOKE(hasClipboardFormat, (mime, mode));
2574
2575 const QMimeData *data = m_wnd->getClipboardData(mode);
2576 return data && data->hasFormat(mime);
2577 }
2578
styles()2579 QStringList ScriptableProxy::styles()
2580 {
2581 INVOKE(styles, ());
2582 return QStyleFactory::keys();
2583 }
2584
fetchBrowser(const QString & tabName)2585 ClipboardBrowser *ScriptableProxy::fetchBrowser(const QString &tabName)
2586 {
2587 if (tabName.isEmpty()) {
2588 const QString defaultTabName = m_actionData.value(mimeCurrentTab).toString();
2589 if (!defaultTabName.isEmpty())
2590 return fetchBrowser(defaultTabName);
2591 }
2592
2593 return tabName.isEmpty() ? m_wnd->browser(0) : m_wnd->tab(tabName);
2594 }
2595
itemData(const QString & tabName,int i)2596 QVariantMap ScriptableProxy::itemData(const QString &tabName, int i)
2597 {
2598 auto c = fetchBrowser(tabName);
2599 return c ? c->copyIndex( c->index(i) ) : QVariantMap();
2600 }
2601
itemData(const QString & tabName,int i,const QString & mime)2602 QByteArray ScriptableProxy::itemData(const QString &tabName, int i, const QString &mime)
2603 {
2604 const QVariantMap data = itemData(tabName, i);
2605 if ( data.isEmpty() )
2606 return QByteArray();
2607
2608 if (mime == "?")
2609 return QStringList(data.keys()).join("\n").toUtf8() + '\n';
2610
2611 if (mime == mimeItems)
2612 return serializeData(data);
2613
2614 return data.value(mime).toByteArray();
2615 }
2616
currentBrowser() const2617 ClipboardBrowser *ScriptableProxy::currentBrowser() const
2618 {
2619 const QString currentTabName = m_actionData.value(mimeCurrentTab).toString();
2620 if (currentTabName.isEmpty())
2621 return nullptr;
2622
2623 const int i = m_wnd->findTabIndex(currentTabName);
2624 if (i == -1)
2625 return nullptr;
2626
2627 auto c = m_wnd->browser(i);
2628 Q_ASSERT(c->tabName() == currentTabName);
2629 return c;
2630 }
2631
selectedIndexes() const2632 QList<QPersistentModelIndex> ScriptableProxy::selectedIndexes() const
2633 {
2634 return m_actionData.value(mimeSelectedItems)
2635 .value< QList<QPersistentModelIndex> >();
2636 }
2637
waitForFunctionCallFinished(int functionCallId)2638 QVariant ScriptableProxy::waitForFunctionCallFinished(int functionCallId)
2639 {
2640 if (m_disconnected)
2641 return QVariant();
2642
2643 QVariant result;
2644
2645 QEventLoop loop;
2646 connect(this, &ScriptableProxy::functionCallFinished, &loop,
2647 [&](int receivedFunctionCallId, const QVariant &returnValue) {
2648 if (receivedFunctionCallId != functionCallId)
2649 return;
2650 result = returnValue;
2651 loop.quit();
2652 });
2653 connect(this, &ScriptableProxy::abortEvaluation, &loop, &QEventLoop::quit);
2654
2655 connect(qApp, &QCoreApplication::aboutToQuit, &loop, &QEventLoop::quit);
2656 loop.exec();
2657
2658 return result;
2659 }
2660
2661 #ifdef HAS_TESTS
keyClicker()2662 KeyClicker *ScriptableProxy::keyClicker()
2663 {
2664 if (!m_keyClicker)
2665 m_keyClicker = new KeyClicker(m_wnd, this);
2666 return m_keyClicker;
2667 }
2668 #endif // HAS_TESTS
2669
pluginsPath()2670 QString pluginsPath()
2671 {
2672 QDir dir;
2673 if (platformNativeInterface()->findPluginDir(&dir))
2674 return dir.absolutePath();
2675
2676 return QString();
2677 }
2678
themesPath()2679 QString themesPath()
2680 {
2681 return platformNativeInterface()->themePrefix();
2682 }
2683
translationsPath()2684 QString translationsPath()
2685 {
2686 return platformNativeInterface()->translationPrefix();
2687 }
2688