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