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 #ifndef MAINWINDOW_H
21 #define MAINWINDOW_H
22
23 #include "common/clipboardmode.h"
24 #include "common/command.h"
25 #include "gui/clipboardbrowsershared.h"
26 #include "gui/menuitems.h"
27 #include "item/persistentdisplayitem.h"
28
29 #include "platform/platformnativeinterface.h"
30
31 #include <QMainWindow>
32 #include <QModelIndex>
33 #include <QPointer>
34 #include <QSystemTrayIcon>
35 #include <QTimer>
36 #include <QVector>
37
38 class Action;
39 class ActionDialog;
40 class AppConfig;
41 class ClipboardBrowser;
42 class ClipboardBrowserPlaceholder;
43 class CommandAction;
44 class CommandDialog;
45 class ConfigurationManager;
46 class Notification;
47 class QAction;
48 class QMimeData;
49 class Tabs;
50 class Theme;
51 class TrayMenu;
52 class ToolBar;
53 struct MainWindowOptions;
54 struct NotificationButton;
55
56 Q_DECLARE_METATYPE(QPersistentModelIndex)
Q_DECLARE_METATYPE(QList<QPersistentModelIndex>)57 Q_DECLARE_METATYPE(QList<QPersistentModelIndex>)
58
59 namespace Ui
60 {
61 class MainWindow;
62 }
63
64 enum ItemActivationCommand {
65 ActivateNoCommand = 0x0,
66 ActivateCloses = 0x1,
67 ActivateFocuses = 0x2,
68 ActivatePastes = 0x4
69 };
70
71 enum class ImportOptions {
72 /// Select what to import/export in dialog.
73 Select,
74 /// Import/export everything without asking.
75 All
76 };
77
78 struct MainWindowOptions {
activateClosesMainWindowOptions79 bool activateCloses() const { return itemActivationCommands & ActivateCloses; }
activateFocusesMainWindowOptions80 bool activateFocuses() const { return itemActivationCommands & ActivateFocuses; }
activatePastesMainWindowOptions81 bool activatePastes() const { return itemActivationCommands & ActivatePastes; }
82
83 bool confirmExit = true;
84 bool viMode = false;
85 bool trayCommands = false;
86 bool trayCurrentTab = false;
87 QString trayTabName;
88 int trayItems = 5;
89 bool trayImages = true;
90 bool trayMenuOpenOnLeftClick = false;
91 int transparency = 0;
92 int transparencyFocused = 0;
93
94 bool hideTabs = false;
95
96 bool hideMainWindow = false;
97 bool closeOnUnfocus = false;
98
99 int itemActivationCommands = ActivateCloses;
100
101 bool clearFirstTab = false;
102
103 bool trayItemPaste = true;
104
105 QString clipboardTab;
106 };
107
108 /**
109 * Application's main window.
110 *
111 * Contains search bar and tab widget.
112 * Each tab contains one clipboard browser widget.
113 *
114 * It operates in two modes:
115 * * browse mode with search bar hidden and empty (default) and
116 * * search mode with search bar shown and not empty.
117 *
118 * If user starts typing text the search mode will become active and
119 * the search bar focused.
120 * If the text is deleted or escape pressed the browse mode will become active.
121 */
122 class MainWindow final : public QMainWindow
123 {
124 Q_OBJECT
125
126 public:
127 explicit MainWindow(
128 const ClipboardBrowserSharedPtr &sharedData, QWidget *parent = nullptr);
129
130 ~MainWindow();
131
132 /** Return true if in browse mode (i.e. search field is hidden). */
133 bool browseMode() const;
134
135 /**
136 * Try to close command dialog and return true on success.
137 *
138 * Note that dialog won't be closed if it has unsaved changes
139 * and user cancels the closing.
140 */
141 bool maybeCloseCommandDialog();
142
143 /**
144 * Return browser widget in given tab @a index.
145 * Load items if not loaded yet.
146 */
147 ClipboardBrowser *browser(int index);
148
149 /**
150 * Return browser widget in current tab.
151 * Load items if not loaded yet.
152 */
153 ClipboardBrowser *browser();
154
155 /**
156 * Return browser widget in current tab or nullptr if not loaded.
157 */
158 ClipboardBrowser *browserOrNull();
159
160 /** Return browser containing item or nullptr. */
161 ClipboardBrowser *browserForItem(const QModelIndex &index);
162
163 /**
164 * Find tab with given @a name.
165 * @return found tab index or -1
166 */
167 int findTabIndex(const QString &name);
168
169 /**
170 * Tries to find tab with exact or similar name (ignores
171 * key hints '&') or creates new one.
172 * @return Existing or new tab with given @a name.
173 */
174 ClipboardBrowser *tab(
175 const QString &name //!< Name of the new tab.
176 );
177
178 /**
179 * Show/hide tray menu. Return true only if menu is shown.
180 */
181 bool toggleMenu();
182 bool toggleMenu(const QString &tabName, int itemCount, QPoint position);
183
184 /** Switch between browse and search mode. */
185 void enterBrowseMode();
186
187 void enterSearchMode();
188
189 void enterSearchMode(const QString &txt);
190
191 /** Show and focus main window. */
192 void showWindow();
193 /** Hide window to tray or minimize if tray is not available. */
194 void hideWindow();
195 /** Minimize window (hide if option is set). */
196 void minimizeWindow();
197 /** Set current tab. */
198 bool setCurrentTab(int index);
199
200 bool focusPrevious();
201
202 /** Open tab group renaming dialog. */
203 void openRenameTabGroupDialog(const QString &name);
204 /** Remove all tab in group. */
205 void removeTabGroup(const QString &name);
206 /** Remove tab. */
207 void removeTab(
208 bool ask, //!< Ask before removing.
209 int tabIndex //!< Tab index or current tab.
210 );
211 /** Set icon for tab or tab group. */
212 void setTabIcon(const QString &tabName);
213
214 void setTabIcon(const QString &tabName, const QString &icon);
215
216 bool unloadTab(const QString &tabName);
217 void forceUnloadTab(const QString &tabName);
218
219 /**
220 * Save all items in tab to file.
221 * @return True only if all items were successfully saved.
222 */
223 bool saveTab(
224 const QString &fileName,
225 int tabIndex = -1 //!< Tab index or current tab.
226 );
227
228 /** Save all unsaved tabs. */
229 Q_SLOT void saveTabs();
230
231 /**
232 * Load saved items to new tab.
233 * @return True only if all items were successfully loaded.
234 */
235 bool loadTab(const QString &fileName);
236
237 /**
238 * Import tabs, settings etc.
239 * @return True only if all data were successfully loaded.
240 */
241 bool importDataFrom(const QString &fileName, ImportOptions options);
242
243 /**
244 * Export tabs, settings etc.
245 * @return True only if all data were successfully saved.
246 */
247 bool exportAllData(const QString &fileName);
248
249 /** Temporarily disable monitoring (i.e. adding new clipboard content to the first tab). */
250 void disableClipboardStoring(bool disable);
251
252 /** Return true only if monitoring is enabled. */
253 bool isMonitoringEnabled() const;
254
255 QStringList tabs() const;
256
257 /// Used by config() command.
258 QVariant config(const QVariantList &nameValue);
259 QString configDescription();
260
261 QVariantMap actionData(int id) const;
262 void setActionData(int id, const QVariantMap &data);
263
264 void setCommands(const QVector<Command> &commands);
265
266 void setSessionIconColor(QColor color);
267
268 void setSessionIconTag(const QString &tag);
269
270 void setSessionIconTagColor(QColor color);
271
272 QColor sessionIconColor() const;
273
274 QString sessionIconTag() const;
275
276 QColor sessionIconTagColor() const;
277
278 void setTrayTooltip(const QString &tooltip);
279
280 bool setMenuItemEnabled(int actionId, int currentRun, int menuItemMatchCommandIndex, const QVariantMap &menuItem);
281
282 QVariantMap setDisplayData(int actionId, const QVariantMap &data);
283
automaticCommands()284 QVector<Command> automaticCommands() const { return m_automaticCommands; }
displayCommands()285 QVector<Command> displayCommands() const { return m_displayCommands; }
scriptCommands()286 QVector<Command> scriptCommands() const { return m_scriptCommands; }
287
288 /** Close main window and exit the application. */
289 void exit();
290
291 /** Load settings. */
292 void loadSettings(QSettings &settings, AppConfig *appConfig);
293
294 void loadTheme(const QSettings &themeSettings);
295
296 /** Open help. */
297 void openHelp();
298
299 /** Open log dialog. */
300 void openLogDialog();
301
302 /** Open about dialog. */
303 void openAboutDialog();
304
305 /** Open dialog with clipboard content. */
306 void showClipboardContent();
307
308 /** Open dialog with active commands. */
309 void showProcessManagerDialog();
310
311 /** Open action dialog with given input data. */
312 ActionDialog *openActionDialog(const QVariantMap &data);
313
314 /** Open action dialog with input data from selected items. */
315 void openActionDialog();
316
317 void showItemContent();
318
319 /** Open preferences dialog. */
320 void openPreferences();
321
322 /** Open commands dialog. */
323 void openCommands();
324
325 /** Sort selected items. */
326 void sortSelectedItems();
327 /** Reverse order of selected items. */
328 void reverseSelectedItems();
329
330 /**
331 * Import tabs, settings etc. (select file in dialog).
332 * @return True only if all data were successfully loaded.
333 */
334 bool importData();
335
336 /** Create new item in current tab. */
337 void editNewItem();
338 /** Paste items to current tab. */
339 void pasteItems();
340 /** Copy selected items in current tab. */
341 void copyItems();
342
343 /** Activate current item. */
344 void activateCurrentItem();
345
346 /** Show window and given tab and give focus to the tab. */
347 void showBrowser(const ClipboardBrowser *browser);
348
349 /** Show error popup message. */
350 void showError(const QString &msg);
351
352 Notification *createNotification(const QString &id = QString());
353
354 /** Open command dialog and add commands. */
355 void addCommands(const QVector<Command> &commands);
356
357 /** Execute command on given input data. */
358 Action *action(
359 const QVariantMap &data,
360 const Command &cmd,
361 const QModelIndex &outputIndex);
362
363 bool triggerMenuCommand(const Command &command, const QString &triggeredShortcut);
364
365 void runInternalAction(Action *action);
366 bool isInternalActionId(int id) const;
367
368 void setClipboard(const QVariantMap &data);
369 void setClipboard(const QVariantMap &data, ClipboardMode mode);
370 void setClipboardAndSelection(const QVariantMap &data);
371 void moveToClipboard(ClipboardBrowser *c, int row);
372
373 const QMimeData *getClipboardData(ClipboardMode mode);
374
375 /** Show/hide main window. Return true only if window is shown. */
376 bool toggleVisible();
377
378 /** Set icon for current tab or tab group. */
379 void setTabIcon();
380
381 /** Open tab creation dialog. */
382 void openNewTabDialog(const QString &name);
383 void openNewTabDialog();
384
385 /** Remove tab. */
386 void removeTab();
387
388 /** Rename current tab to given name (if possible). */
389 void renameTabGroup(const QString &newName, const QString &oldName);
390 /** Open tab renaming dialog (for given tab index or current tab). */
391 void openRenameTabDialog(int tabIndex);
392 void openRenameTabDialog();
393 /** Rename current tab to given name (if possible). */
394 void renameTab(const QString &name, int tabIndex);
395
396 void addAndFocusTab(const QString &name);
397
398 /** Toggle monitoring (i.e. adding new clipboard content to the first tab). */
399 void toggleClipboardStoring();
400
401 /**
402 * Export tabs, settings etc. (select in file dialog).
403 * @return True only if all data were successfully saved.
404 */
405 bool exportData();
406
407 /** Set next or first tab as current. */
408 void nextTab();
409 /** Set previous or last tab as current. */
410 void previousTab();
411
412 void setClipboardData(const QVariantMap &data);
413
414 /** Set text for filtering items. */
415 void setFilter(const QString &text);
416 QString filter() const;
417
418 void updateShortcuts();
419
420 void setItemPreviewVisible(bool visible);
421 bool isItemPreviewVisible() const;
422
423 signals:
424 /** Request clipboard change. */
425 void changeClipboard(const QVariantMap &data, ClipboardMode mode);
426
427 void tabGroupSelected(bool selected);
428
429 void requestExit();
430
431 void commandsSaved(const QVector<Command> &commands);
432
433 void configurationChanged(AppConfig *appConfig);
434
435 void disableClipboardStoringRequest(bool disable);
436
437 void sendActionData(int actionId, const QByteArray &bytes);
438
439 protected:
440 void keyPressEvent(QKeyEvent *event) override;
441 void keyReleaseEvent(QKeyEvent *event) override;
442 bool event(QEvent *event) override;
443
444 /** Hide (minimize to tray) window on close. */
445 void closeEvent(QCloseEvent *event) override;
446
447 bool focusNextPrevChild(bool next) override;
448
449 bool nativeEvent(const QByteArray &eventType, void *message, long *result) override;
450
451 private:
452 ClipboardBrowserPlaceholder *getPlaceholderForMenu();
453 ClipboardBrowserPlaceholder *getPlaceholderForTrayMenu();
454 void filterMenuItems(const QString &searchText);
455 void filterTrayMenuItems(const QString &searchText);
456 void trayActivated(QSystemTrayIcon::ActivationReason reason);
457 void onMenuActionTriggered(const QVariantMap &data, bool omitPaste);
458 void onTrayActionTriggered(const QVariantMap &data, bool omitPaste);
459 void findNextOrPrevious();
460 void tabChanged(int current, int previous);
461 void saveTabPositions();
462 void onSaveTabPositionsTimer();
463 void doSaveTabPositions(AppConfig *appConfig);
464 void tabsMoved(const QString &oldPrefix, const QString &newPrefix);
465 void tabBarMenuRequested(QPoint pos, int tab);
466 void tabTreeMenuRequested(QPoint pos, const QString &groupPath);
467 void tabCloseRequested(int tab);
468 void onFilterChanged();
469
470 void raiseLastWindowAfterMenuClosed();
471
472 /** Update WId for paste and last focused window if needed. */
473 void updateFocusWindows();
474
475 /** Update tray and window icon depending on current state. */
476 void updateIcon();
477
478 void updateContextMenuTimeout();
479
480 void updateTrayMenuItemsTimeout();
481
482 void updateItemPreviewAfterMs(int ms);
483
484 void updateItemPreviewTimeout();
485
486 void toggleItemPreviewVisible();
487
488 void onAboutToQuit();
489
490 void onSaveCommand(const Command &command);
491
492 void onItemCommandActionTriggered(CommandAction *commandAction, const QString &triggeredShortcut);
493 void onClipboardCommandActionTriggered(CommandAction *commandAction, const QString &triggeredShortcut);
494
495 void onTabWidgetDropItems(const QString &tabName, const QMimeData *data);
496
497 void showContextMenuAt(QPoint position);
498
499 void showContextMenu();
500
501 void moveUp();
502 void moveDown();
503 void moveToTop();
504 void moveToBottom();
505
506 void onBrowserCreated(ClipboardBrowser *browser);
507 void onBrowserDestroyed(ClipboardBrowserPlaceholder *placeholder);
508
509 void onItemSelectionChanged(const ClipboardBrowser *browser);
510 void onItemsChanged(const ClipboardBrowser *browser);
511 void onInternalEditorStateChanged(const ClipboardBrowser *self);
512
513 void onItemWidgetCreated(const PersistentDisplayItem &item);
514
515 void onActionDialogAccepted(const Command &command, const QStringList &arguments, const QVariantMap &data);
516
517 void onSearchShowRequest(const QString &text);
518
519 void updateEnabledCommands();
520
521 void updateCommands(QVector<Command> allCommands, bool forceSave);
522 bool addPluginCommands(QVector<Command> *allCommands);
523
524 void disableHideWindowOnUnfocus();
525 void enableHideWindowOnUnfocus();
526 void hideWindowIfNotActive();
527
528 template <typename SlotReturnType>
529 using MainWindowActionSlot = SlotReturnType (MainWindow::*)();
530
531 enum TabNameMatching {
532 MatchExactTabName,
533 MatchSimilarTabName
534 };
535
536 struct MenuMatchCommands {
537 int currentRun = 0;
538 int actionId = -1;
539 QStringList matchCommands;
540 QVector< QPointer<QAction> > actions;
541 QMenu *menu = nullptr;
542 };
543
544 void runDisplayCommands();
545
546 void clearHiddenDisplayData();
547
548 void reloadBrowsers();
549
550 ClipboardBrowserPlaceholder *createTab(const QString &name, TabNameMatching nameMatch, const Tabs &tabs);
551
552 int findTabIndexExactMatch(const QString &name);
553
554 /** Create menu bar and tray menu with items. Called once. */
555 void createMenu();
556
557 /** Create context menu for @a tab. It will be automatically deleted after closed. */
558 void popupTabBarMenu(QPoint pos, const QString &tab);
559
560 void updateContextMenu(int intervalMsec);
561
562 void updateTrayMenuItems();
563 void updateTrayMenuCommands();
564
565 void updateWindowTransparency(bool mouseOver = false);
566
567 /** Update name and icon of "disable/enable monitoring" menu actions. */
568 void updateMonitoringActions();
569
570 /** Return browser widget in given tab @a index. */
571 ClipboardBrowserPlaceholder *getPlaceholder(int index) const;
572
573 ClipboardBrowserPlaceholder *getPlaceholder(const QString &tabName) const;
574
575 /** Return browser widget in current tab. */
576 ClipboardBrowserPlaceholder *getPlaceholder() const;
577
578 /** Call updateFocusWindows() after a small delay if main window or menu is not active. */
579 void delayedUpdateForeignFocusWindows();
580
581 /** Show/hide tab bar. **/
582 void setHideTabs(bool hide);
583
584 /**
585 * Return true if window should be minimized instead of closed/hidden.
586 *
587 * If tray icon is not available, window should be minimized so that it can be opened with
588 * mouse.
589 */
590 bool closeMinimizes() const;
591
592 template <typename SlotReturnType>
593 QAction *createAction(int id, MainWindowActionSlot<SlotReturnType> slot, QMenu *menu, QWidget *parent = nullptr);
594
595 QAction *addTrayAction(int id);
596
597 void updateTabIcon(const QString &newName, const QString &oldName);
598
599 template <typename Receiver, typename ReturnType>
600 QAction *addItemAction(int id, Receiver *receiver, ReturnType (Receiver::* slot)());
601
602 QVector<Command> commandsForMenu(const QVariantMap &data, const QString &tabName, const QVector<Command> &allCommands);
603 void addCommandsToItemMenu(ClipboardBrowser *c);
604 void addCommandsToTrayMenu(const QVariantMap &clipboardData);
605 void addMenuMatchCommand(MenuMatchCommands *menuMatchCommands, const QString &matchCommand, QAction *act);
606 void runMenuCommandFilters(MenuMatchCommands *menuMatchCommands, QVariantMap &data);
607 void interruptMenuCommandFilters(MenuMatchCommands *menuMatchCommands);
608 void stopMenuCommandFilters(MenuMatchCommands *menuMatchCommands);
609
610 void terminateAction(int *actionId);
611
612 bool isItemMenuDefaultActionValid() const;
613
614 void updateToolBar();
615
616 void setTrayEnabled(bool enable = true);
617
618 void runDisplayCommand(const Command &command);
619
620 bool isWindowVisible() const;
621
622 void onEscape();
623
624 void updateActionShortcuts();
625
626 QAction *actionForMenuItem(int id, QWidget *parent, Qt::ShortcutContext context);
627
628 void addMenuItems(TrayMenu *menu, ClipboardBrowserPlaceholder *placeholder, int maxItemCount, const QString &searchText);
629 void activateMenuItem(ClipboardBrowserPlaceholder *placeholder, const QVariantMap &data, bool omitPaste);
630 bool toggleMenu(TrayMenu *menu, QPoint pos);
631 bool toggleMenu(TrayMenu *menu);
632
633 bool exportDataFrom(const QString &fileName, const QStringList &tabs, bool exportConfiguration, bool exportCommands);
634 bool exportDataV4(QDataStream *out, const QStringList &tabs, bool exportConfiguration, bool exportCommands);
635 bool importDataV3(QDataStream *in, ImportOptions options);
636 bool importDataV4(QDataStream *in, ImportOptions options);
637
638 const Theme &theme() const;
639
640 Action *runScript(const QString &script, const QVariantMap &data = QVariantMap());
641
642 void activateCurrentItemHelper();
643 void onItemClicked();
644 void onItemDoubleClicked();
645
646 ConfigurationManager *cm;
647 Ui::MainWindow *ui;
648
649 QMenu *m_menuItem;
650 TrayMenu *m_trayMenu;
651
652 QSystemTrayIcon *m_tray;
653
654 ToolBar *m_toolBar;
655
656 MainWindowOptions m_options;
657
658 bool m_clipboardStoringDisabled = false;
659 QPointer<QAction> m_actionToggleClipboardStoring;
660
661 ClipboardBrowserSharedPtr m_sharedData;
662
663 QVector<Command> m_automaticCommands;
664 QVector<Command> m_displayCommands;
665 QVector<Command> m_menuCommands;
666 QVector<Command> m_trayMenuCommands;
667 QVector<Command> m_scriptCommands;
668
669 PlatformWindowPtr m_lastWindow;
670
671 QTimer m_timerUpdateFocusWindows;
672 QTimer m_timerUpdateContextMenu;
673 QTimer m_timerUpdatePreview;
674 QTimer m_timerSaveTabPositions;
675 QTimer m_timerHideWindowIfNotActive;
676 QTimer m_timerRaiseLastWindowAfterMenuClosed;
677
678 bool m_trayMenuDirty = true;
679
680 QVariantMap m_clipboardData;
681
682 TrayMenu *m_menu;
683 QString m_menuTabName;
684 int m_menuMaxItemCount;
685
686 QPointer<CommandDialog> m_commandDialog;
687
688 bool m_wasMaximized = false;
689
690 bool m_showItemPreview = false;
691
692 bool m_activatingItem = false;
693
694 QVector< QPointer<QAction> > m_actions;
695 MenuItems m_menuItems;
696
697 QList<PersistentDisplayItem> m_displayItemList;
698 PersistentDisplayItem m_currentDisplayItem;
699 int m_displayActionId = -1;
700
701 MenuMatchCommands m_trayMenuMatchCommands;
702 MenuMatchCommands m_itemMenuMatchCommands;
703
704 PlatformClipboardPtr m_clipboard;
705
706 bool m_isActiveWindow = false;
707 bool m_singleClickActivate = 0;
708 };
709
710 #endif // MAINWINDOW_H
711