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 "mainwindow.h"
21 #include "ui_mainwindow.h"
22
23 #include "common/action.h"
24 #include "common/actionoutput.h"
25 #include "common/appconfig.h"
26 #include "common/common.h"
27 #include "common/command.h"
28 #include "common/commandstore.h"
29 #include "common/config.h"
30 #include "common/contenttype.h"
31 #include "common/display.h"
32 #include "common/log.h"
33 #include "common/mimetypes.h"
34 #include "common/shortcuts.h"
35 #include "common/tabs.h"
36 #include "common/textdata.h"
37 #include "common/timer.h"
38 #include "gui/aboutdialog.h"
39 #include "gui/actiondialog.h"
40 #include "gui/actionhandler.h"
41 #include "gui/clipboardbrowser.h"
42 #include "gui/clipboardbrowserplaceholder.h"
43 #include "gui/clipboardbrowsershared.h"
44 #include "gui/clipboarddialog.h"
45 #include "gui/commandaction.h"
46 #include "gui/commanddialog.h"
47 #include "gui/configurationmanager.h"
48 #include "gui/importexportdialog.h"
49 #include "gui/iconfactory.h"
50 #include "gui/iconfactory.h"
51 #include "gui/iconselectdialog.h"
52 #include "gui/icons.h"
53 #include "gui/logdialog.h"
54 #include "gui/notification.h"
55 #include "gui/notificationbutton.h"
56 #include "gui/notificationdaemon.h"
57 #include "gui/tabdialog.h"
58 #include "gui/tabicons.h"
59 #include "gui/tabwidget.h"
60 #include "gui/theme.h"
61 #include "gui/traymenu.h"
62 #include "gui/windowgeometryguard.h"
63 #include "item/itemfactory.h"
64 #include "item/serialize.h"
65 #include "platform/platformclipboard.h"
66 #include "platform/platformnativeinterface.h"
67 #include "platform/platformwindow.h"
68
69 #ifdef Q_OS_MAC
70 # include "platform/mac/foregroundbackgroundfilter.h"
71 #endif
72
73 #include <QAction>
74 #include <QCloseEvent>
75 #include <QDesktopServices>
76 #include <QFile>
77 #include <QFileDialog>
78 #include <QFlags>
79 #include <QMenu>
80 #include <QMenuBar>
81 #include <QMessageBox>
82 #include <QMimeData>
83 #include <QModelIndex>
84 #include <QPushButton>
85 #include <QTemporaryFile>
86 #include <QTimer>
87 #include <QToolBar>
88 #include <QUrl>
89
90 #include <algorithm>
91 #include <memory>
92
93 namespace {
94
95 const int contextMenuUpdateIntervalMsec = 100;
96 const int itemPreviewUpdateIntervalMsec = 100;
97
98 const QLatin1String menuItemKeyColor("color");
99 const QLatin1String menuItemKeyIcon("icon");
100 const QLatin1String menuItemKeyTag("tag");
101
iconClipboard()102 const QIcon iconClipboard() { return getIcon("clipboard", IconPaste); }
iconTabIcon()103 const QIcon iconTabIcon() { return getIconFromResources("tab_icon"); }
iconTabNew()104 const QIcon iconTabNew() { return getIconFromResources("tab_new"); }
iconTabRemove()105 const QIcon iconTabRemove() { return getIconFromResources("tab_remove"); }
iconTabRename()106 const QIcon iconTabRename() { return getIconFromResources("tab_rename"); }
107
108 const char propertyWidgetSizeGuarded[] = "CopyQ_widget_size_guarded";
109
110 const char propertyActionFilterCommandFailed[] = "CopyQ_action_filter_command_failed";
111
112 /// Omit size changes of a widget.
113 class WidgetSizeGuard final : public QObject {
114 public:
WidgetSizeGuard(QWidget * guardedObject)115 explicit WidgetSizeGuard(QWidget *guardedObject)
116 : m_guarded(guardedObject)
117 {
118 if ( m_guarded->property(propertyWidgetSizeGuarded).toBool() ) {
119 m_guarded = nullptr;
120 } else {
121 m_guarded->setProperty(propertyWidgetSizeGuarded, true);
122 m_guarded->setFixedSize( m_guarded->size() );
123 }
124 }
125
~WidgetSizeGuard()126 ~WidgetSizeGuard()
127 {
128 if (m_guarded) {
129 m_guarded->setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
130 m_guarded->adjustSize();
131 m_guarded->resize( m_guarded->sizeHint() );
132 m_guarded->setProperty(propertyWidgetSizeGuarded, false);
133 }
134 }
135
136 private:
137 QWidget *m_guarded;
138 };
139
canPaste()140 bool canPaste()
141 {
142 return !QApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier);
143 }
144
matchData(const QRegularExpression & re,const QVariantMap & data,const QString & format)145 bool matchData(const QRegularExpression &re, const QVariantMap &data, const QString &format)
146 {
147 if ( re.pattern().isEmpty() )
148 return true;
149
150 const QString text = getTextData(data, format);
151 return text.contains(re);
152 }
153
canExecuteCommand(const Command & command,const QVariantMap & data,const QString & sourceTabName)154 bool canExecuteCommand(const Command &command, const QVariantMap &data, const QString &sourceTabName)
155 {
156 // Verify that an action is provided.
157 if ( command.cmd.isEmpty() && !command.remove
158 && (command.tab.isEmpty() || command.tab == sourceTabName) )
159 {
160 return false;
161 }
162
163 // Verify that data for given MIME is available.
164 if ( !command.input.isEmpty() ) {
165 if (command.input == mimeItems || command.input == "!OUTPUT") {
166 // Disallow applying action that takes serialized item more times.
167 if ( data.contains(command.output) )
168 return false;
169 } else if ( !data.contains(command.input) ) {
170 return false;
171 }
172 }
173
174 // Verify that and text matches given regexp.
175 if ( !matchData(command.re, data, mimeText) )
176 return false;
177
178 // Verify that window title matches given regexp.
179 if ( !matchData(command.wndre, data, mimeWindowTitle) )
180 return false;
181
182 return true;
183 }
184
stealFocus(const QWidget & window)185 void stealFocus(const QWidget &window)
186 {
187 WId wid = window.winId();
188 PlatformWindowPtr platformWindow = platformNativeInterface()->getWindow(wid);
189 if (platformWindow)
190 platformWindow->raise();
191 }
192
193 template <typename WidgetOrAction>
disableActionWhenTabGroupSelected(WidgetOrAction * action,MainWindow * window)194 void disableActionWhenTabGroupSelected(WidgetOrAction *action, MainWindow *window)
195 {
196 QObject::connect( window, &MainWindow::tabGroupSelected,
197 action, &WidgetOrAction::setDisabled );
198 }
199
200 /// Adds information about current tab and selection if command is triggered by user.
addSelectionData(const ClipboardBrowser & c,const QModelIndex & currentIndex,const QModelIndexList & selectedIndexes)201 QVariantMap addSelectionData(
202 const ClipboardBrowser &c,
203 const QModelIndex ¤tIndex,
204 const QModelIndexList &selectedIndexes)
205 {
206 auto result = c.copyIndexes(selectedIndexes);
207
208 result.insert(mimeCurrentTab, c.tabName());
209
210 if ( currentIndex.isValid() ) {
211 const QPersistentModelIndex current = currentIndex;
212 result.insert(mimeCurrentItem, QVariant::fromValue(current));
213 }
214
215 if ( !selectedIndexes.isEmpty() ) {
216 QList<QPersistentModelIndex> selected;
217 selected.reserve(selectedIndexes.size());
218 for (const auto &index : selectedIndexes)
219 selected.append(index);
220 std::sort(selected.begin(), selected.end());
221 result.insert(mimeSelectedItems, QVariant::fromValue(selected));
222 }
223
224 return result;
225 }
226
addSelectionData(const ClipboardBrowser & c)227 QVariantMap addSelectionData(const ClipboardBrowser &c)
228 {
229 const QModelIndexList selectedIndexes = c.selectionModel()->selectedIndexes();
230 const auto current = c.selectionModel()->currentIndex();
231 return addSelectionData(c, current, selectedIndexes);
232 }
233
findSubMenu(const QString & name,const QMenu & menu)234 QMenu *findSubMenu(const QString &name, const QMenu &menu)
235 {
236 for (auto action : menu.actions()) {
237 QMenu *subMenu = action->menu();
238 if (subMenu && subMenu->title() == name)
239 return subMenu;
240 }
241
242 return nullptr;
243 }
244
createSubMenus(QString * name,QMenu * menu)245 QMenu *createSubMenus(QString *name, QMenu *menu)
246 {
247 QStringList path = name->split('|');
248 if (path.size() == 1)
249 return menu;
250
251 *name = path.takeLast();
252
253 QMenu *parentMenu = menu;
254
255 for (const auto &subMenuName : path) {
256 QMenu *subMenu = findSubMenu(subMenuName, *parentMenu);
257
258 if (!subMenu) {
259 subMenu = new QMenu(subMenuName, parentMenu);
260 parentMenu->addMenu(subMenu);
261 }
262
263 parentMenu = subMenu;
264 }
265
266 return parentMenu;
267 }
268
269 // WORKAROUND: setWindowFlags() hides the window.
270 // See: https://doc.qt.io/qt-5/qwidget.html#windowFlags-prop
setWindowFlag(QWidget * window,Qt::WindowType flag,bool enable)271 bool setWindowFlag(QWidget *window, Qt::WindowType flag, bool enable)
272 {
273 if (!window)
274 return false;
275
276 const Qt::WindowFlags flags = window->windowFlags();
277 const bool wasEnabled = flags.testFlag(flag);
278 if (wasEnabled == enable)
279 return false;
280
281 const bool wasVisible = window->isVisible();
282 const bool wasActive = window->isActiveWindow();
283 window->setWindowFlags(flags ^ flag);
284 if (wasVisible) {
285 if (wasActive) {
286 window->show();
287 } else {
288 const bool showWithoutActivating = window->testAttribute(Qt::WA_ShowWithoutActivating);
289 window->setAttribute(Qt::WA_ShowWithoutActivating);
290 window->show();
291 window->setAttribute(Qt::WA_ShowWithoutActivating, showWithoutActivating);
292 }
293
294 if (wasActive) {
295 window->raise();
296 window->activateWindow();
297 QApplication::setActiveWindow(window);
298 stealFocus(*window);
299 }
300 }
301
302 return true;
303 }
304
setAlwaysOnTop(QWidget * window,bool alwaysOnTop)305 void setAlwaysOnTop(QWidget *window, bool alwaysOnTop)
306 {
307 if ( setWindowFlag(window, Qt::WindowStaysOnTopHint, alwaysOnTop) ) {
308 // Workaround for QTBUG-28601.
309 window->setAcceptDrops(true);
310 }
311 }
312
setHideInTaskBar(QWidget * window,bool hideInTaskBar)313 void setHideInTaskBar(QWidget *window, bool hideInTaskBar)
314 {
315 setWindowFlag(window, Qt::Tool, hideInTaskBar);
316 }
317
318 template<typename Dialog, typename ...Ts>
319 Dialog *openDialog(Ts... arguments)
320 {
321 std::unique_ptr<Dialog> dialog( new Dialog(arguments...) );
322 WindowGeometryGuard::create( dialog.get() );
323 dialog->setAttribute(Qt::WA_DeleteOnClose, true);
324 dialog->setWindowIcon(appIcon());
325 dialog->activateWindow();
326 dialog->show();
327 return dialog.release();
328 }
329
isItemActivationShortcut(const QKeySequence & shortcut)330 bool isItemActivationShortcut(const QKeySequence &shortcut)
331 {
332 return (shortcut[0] == Qt::Key_Return || shortcut[0] == Qt::Key_Enter)
333 && shortcut[1] == 0
334 && shortcut[2] == 0
335 && shortcut[3] == 0;
336 }
337
importExportFileDialogFilter()338 QString importExportFileDialogFilter()
339 {
340 return MainWindow::tr("CopyQ Items (*.cpq)");
341 }
342
343 /**
344 * Returns QVariant value that can be serialized and deserialized with QDataStream.
345 *
346 * WORKAROUND: Invalid QVariant() can be serialized but deserialization fails.
347 */
serializableValue(const QSettings & settings,const QString & key)348 QVariant serializableValue(const QSettings &settings, const QString &key)
349 {
350 const auto value = settings.value(key);
351 if ( value.isValid() )
352 return value;
353
354 return QString();
355 }
356
isAnyApplicationWindowActive()357 bool isAnyApplicationWindowActive()
358 {
359 if ( qApp->activeWindow() )
360 return true;
361
362 const auto platform = platformNativeInterface();
363 const auto currentWindow = platform->getCurrentWindow();
364 if (!currentWindow)
365 return false;
366
367 const auto currentWindowTitle = currentWindow->getTitle();
368 if ( currentWindowTitle.isEmpty() )
369 return false;
370
371 for ( auto window : qApp->topLevelWidgets() ) {
372 const auto ownWindow = platform->getWindow( window->winId() );
373 if ( ownWindow && currentWindowTitle == ownWindow->getTitle() )
374 return true;
375 }
376
377 return false;
378 }
379
hasDialogOpen(QWidget * parent)380 bool hasDialogOpen(QWidget *parent)
381 {
382 for ( auto window : qApp->topLevelWidgets() ) {
383 if ( window->isVisible() && window->parentWidget() == parent )
384 return true;
385 }
386
387 return false;
388 }
389
deleteSubMenus(QObject * parent)390 void deleteSubMenus(QObject *parent)
391 {
392 for (auto subMenu : parent->findChildren<QMenu*>()) {
393 if (subMenu->parent() == parent)
394 delete subMenu;
395 }
396 }
397
clearActions(QMenu * menu)398 void clearActions(QMenu *menu)
399 {
400 for (QAction *action : menu->actions()) {
401 action->setVisible(false);
402 action->setDisabled(true);
403 action->setShortcuts({});
404 action->deleteLater();
405 menu->removeAction(action);
406 }
407
408 deleteSubMenus(menu);
409 menu->clear();
410 }
411
clearActions(QToolBar * toolBar)412 void clearActions(QToolBar *toolBar)
413 {
414 for (QAction *action : toolBar->actions()) {
415 // Omit removing action from other menus.
416 if (action->parent() == toolBar)
417 delete action;
418 }
419
420 deleteSubMenus(toolBar);
421 toolBar->clear();
422 }
423
hasCommandFuzzy(const QVector<Command> & commands,const Command & command)424 bool hasCommandFuzzy(const QVector<Command> &commands, const Command &command)
425 {
426 return std::any_of(std::begin(commands), std::end(commands), [&command](const Command &cmd){
427 return command.name == cmd.name || command.cmd == cmd.cmd;
428 });
429 }
430
431 } // namespace
432
433 class ToolBar final : public QToolBar {
434 public:
ToolBar(QWidget * parent)435 explicit ToolBar(QWidget *parent)
436 : QToolBar(parent)
437 {
438 setObjectName("toolBar");
439 setContextMenuPolicy(Qt::PreventContextMenu);
440 initSingleShotTimer( &m_timerUnfreeze, 50, this, &ToolBar::unfreeze );
441 }
442
setFrozen(bool frozen)443 void setFrozen(bool frozen) {
444 if (frozen) {
445 m_frozen = true;
446 setUpdatesEnabled(false);
447 setEnabled(false);
448 m_timerUnfreeze.stop();
449 } else {
450 m_timerUnfreeze.start();
451 }
452 }
453
454 protected:
paintEvent(QPaintEvent * ev)455 void paintEvent(QPaintEvent *ev) override
456 {
457 if (m_frozen)
458 return;
459
460 QToolBar::paintEvent(ev);
461 }
462
463 private:
unfreeze()464 void unfreeze()
465 {
466 m_frozen = false;
467 setEnabled(true);
468 setUpdatesEnabled(true);
469 update();
470 }
471
472 bool m_frozen = false;
473 QTimer m_timerUnfreeze;
474 };
475
MainWindow(const ClipboardBrowserSharedPtr & sharedData,QWidget * parent)476 MainWindow::MainWindow(const ClipboardBrowserSharedPtr &sharedData, QWidget *parent)
477 : QMainWindow(parent)
478 , cm(nullptr)
479 , ui(new Ui::MainWindow)
480 , m_menuItem(nullptr)
481 , m_trayMenu( new TrayMenu(this) )
482 , m_tray(nullptr)
483 , m_toolBar(new ToolBar(this))
484 , m_actionToggleClipboardStoring()
485 , m_sharedData(sharedData)
486 , m_lastWindow()
487 , m_menu( new TrayMenu(this) )
488 , m_menuMaxItemCount(-1)
489 , m_commandDialog(nullptr)
490 , m_menuItems(menuItems())
491 , m_clipboard(platformNativeInterface()->clipboard())
492 {
493 ui->setupUi(this);
494
495 #ifdef Q_OS_MAC
496 // Open above fullscreen windows on OS X.
497 setWindowModality(Qt::WindowModal);
498 setWindowFlag(Qt::Sheet);
499 #endif
500
501 menuBar()->setObjectName("menu_bar");
502 createMenu();
503
504 ui->tabWidget->addToolBars(this);
505 addToolBar(Qt::RightToolBarArea, m_toolBar);
506
507 ui->dockWidgetItemPreview->setFocusProxy(ui->scrollAreaItemPreview);
508 ui->dockWidgetItemPreview->hide();
509
510 WindowGeometryGuard::create(this);
511 restoreState( mainWindowState(objectName()) );
512 // NOTE: QWidget::isVisible() returns false if parent is not visible.
513 m_showItemPreview = !ui->dockWidgetItemPreview->isHidden();
514
515 // Disable the show-preview option when the preview dock is closed.
516 connect( ui->dockWidgetItemPreview, &QDockWidget::visibilityChanged,
517 this, [this]() {
518 if ( ui->dockWidgetItemPreview->isHidden() )
519 setItemPreviewVisible(false);
520 } );
521
522 updateIcon();
523
524 updateFocusWindows();
525
526 connect( m_trayMenu, &QMenu::aboutToShow,
527 this, &MainWindow::updateFocusWindows );
528 connect( m_trayMenu, &QMenu::aboutToShow,
529 this, &MainWindow::updateTrayMenuItemsTimeout );
530 connect( m_trayMenu, &QMenu::aboutToHide,
531 this, [this](){ m_timerRaiseLastWindowAfterMenuClosed.start(); } );
532 connect( m_trayMenu, &TrayMenu::searchRequest,
533 this, &MainWindow::filterTrayMenuItems );
534 connect( m_trayMenu, &TrayMenu::clipboardItemActionTriggered,
535 this, &MainWindow::onTrayActionTriggered );
536
537 connect( m_menu, &QMenu::aboutToShow,
538 this, &MainWindow::updateFocusWindows );
539 connect( m_menu, &QMenu::aboutToHide,
540 this, [this](){ m_timerRaiseLastWindowAfterMenuClosed.start(); } );
541 connect( m_menu, &TrayMenu::searchRequest,
542 this, &MainWindow::filterMenuItems );
543 connect( m_menu, &TrayMenu::clipboardItemActionTriggered,
544 this, &MainWindow::onMenuActionTriggered );
545
546 connect( ui->tabWidget, &TabWidget::currentChanged,
547 this, &MainWindow::tabChanged );
548 connect( ui->tabWidget, &TabWidget::tabMoved,
549 this, &MainWindow::saveTabPositions );
550 connect( ui->tabWidget, &TabWidget::tabsMoved,
551 this, &MainWindow::tabsMoved );
552 connect( ui->tabWidget, &TabWidget::tabBarMenuRequested,
553 this, &MainWindow::tabBarMenuRequested );
554 connect( ui->tabWidget, &TabWidget::tabTreeMenuRequested,
555 this, &MainWindow::tabTreeMenuRequested );
556 connect( ui->tabWidget, &TabWidget::tabRenamed,
557 this, &MainWindow::renameTab );
558 connect( ui->tabWidget, &TabWidget::tabCloseRequested,
559 this, &MainWindow::tabCloseRequested );
560 connect( ui->tabWidget, &TabWidget::dropItems,
561 this, &MainWindow::onTabWidgetDropItems);
562 connect( ui->searchBar, &Utils::FilterLineEdit::filterChanged,
563 this, &MainWindow::onFilterChanged );
564 connect( qApp, &QCoreApplication::aboutToQuit,
565 this, &MainWindow::onAboutToQuit );
566
567 connect(m_sharedData->itemFactory, &ItemFactory::error,
568 this, &MainWindow::showError);
569 connect(m_sharedData->itemFactory, &ItemFactory::addCommands,
570 this, &MainWindow::addCommands);
571
572 initSingleShotTimer( &m_timerUpdateFocusWindows, 100, this, &MainWindow::updateFocusWindows );
573 initSingleShotTimer( &m_timerUpdateContextMenu, 0, this, &MainWindow::updateContextMenuTimeout );
574 initSingleShotTimer( &m_timerUpdatePreview, 0, this, &MainWindow::updateItemPreviewTimeout );
575 initSingleShotTimer( &m_timerSaveTabPositions, 1000, this, &MainWindow::onSaveTabPositionsTimer );
576 initSingleShotTimer( &m_timerRaiseLastWindowAfterMenuClosed, 50, this, &MainWindow::raiseLastWindowAfterMenuClosed);
577 enableHideWindowOnUnfocus();
578
579 m_trayMenu->setObjectName("TrayMenu");
580 m_menu->setObjectName("Menu");
581
582 auto act = m_trayMenu->addAction( appIcon(), tr("&Show/Hide") );
583 connect(act, &QAction::triggered, this, &MainWindow::toggleVisible);
584 m_trayMenu->setDefaultAction(act);
585 addTrayAction(Actions::File_Preferences);
586 addTrayAction(Actions::File_ToggleClipboardStoring);
587 m_trayMenu->addSeparator();
588 addTrayAction(Actions::File_Exit);
589 }
590
browseMode() const591 bool MainWindow::browseMode() const
592 {
593 return ui->searchBar->isHidden();
594 }
595
exit()596 void MainWindow::exit()
597 {
598 // Check if not editing in any tab (show and try to close editors).
599 for ( int i = 0; i < ui->tabWidget->count(); ++i ) {
600 auto c = getPlaceholder(i)->browser();
601 if ( c && (c->isInternalEditorOpen() || c->isExternalEditorOpen()) ) {
602 setCurrentTab(i);
603 if ( !c->maybeCloseEditors() )
604 return;
605 }
606 }
607
608 int answer = QMessageBox::Yes;
609 if (m_options.confirmExit) {
610 showWindow();
611 answer = QMessageBox::question(
612 this,
613 tr("Exit?"),
614 tr("Do you want to <strong>exit</strong> CopyQ?"),
615 QMessageBox::Yes | QMessageBox::No,
616 QMessageBox::Yes);
617 }
618
619 if (answer == QMessageBox::Yes)
620 emit requestExit();
621 }
622
closeEvent(QCloseEvent * event)623 void MainWindow::closeEvent(QCloseEvent *event)
624 {
625 event->ignore();
626 hideWindow();
627 COPYQ_LOG("Got main window close event.");
628 }
629
focusNextPrevChild(bool next)630 bool MainWindow::focusNextPrevChild(bool next)
631 {
632 auto c = browser();
633 if (!c)
634 return false;
635
636 // Fix tab order while searching in editor.
637 if (c->isInternalEditorOpen() && !browseMode()) {
638 if ( next && ui->searchBar->hasFocus() ) {
639 c->setFocus();
640 return true;
641 }
642
643 if ( !next && c->hasFocus() ) {
644 ui->searchBar->setFocus();
645 return true;
646 }
647 }
648
649 return QMainWindow::focusNextPrevChild(next);
650 }
651
createMenu()652 void MainWindow::createMenu()
653 {
654 QMenuBar *menubar = menuBar();
655 QMenu *menu;
656 QAction *act;
657
658 // Some action should not be triggered from tab widget or preview dock.
659 QWidget *actionParent = ui->tabWidget;
660
661 menubar->clear();
662
663 // File
664 menu = menubar->addMenu( tr("&File") );
665
666 // - new
667 act = createAction( Actions::File_New, &MainWindow::editNewItem, menu );
668 disableActionWhenTabGroupSelected(act, this);
669
670 // - import
671 createAction( Actions::File_Import, &MainWindow::importData, menu );
672
673 // - export
674 createAction( Actions::File_Export, &MainWindow::exportData, menu );
675
676 // - separator
677 menu->addSeparator();
678
679 // - preferences
680 createAction( Actions::File_Preferences, &MainWindow::openPreferences, menu );
681
682 // - commands
683 createAction( Actions::File_Commands, &MainWindow::openCommands, menu );
684
685 // - separator
686 menu->addSeparator();
687
688 // - show clipboard content
689 createAction( Actions::File_ShowClipboardContent, &MainWindow::showClipboardContent, menu );
690
691 // - show preview
692 QAction *togglePreviewAction =
693 createAction( Actions::File_ShowPreview, &MainWindow::toggleItemPreviewVisible, menu );
694 togglePreviewAction->setCheckable(true);
695 togglePreviewAction->setChecked(m_showItemPreview);
696
697 // - active commands
698 createAction( Actions::File_ProcessManager, &MainWindow::showProcessManagerDialog, menu );
699
700 // - enable/disable
701 m_actionToggleClipboardStoring = createAction( Actions::File_ToggleClipboardStoring,
702 &MainWindow::toggleClipboardStoring, menu );
703 updateMonitoringActions();
704
705 // - separator
706 menu->addSeparator();
707
708 // - exit
709 createAction( Actions::File_Exit, &MainWindow::exit, menu );
710
711 // Edit
712 menu = menubar->addMenu( tr("&Edit") );
713
714 // - find
715 createAction( Actions::Edit_FindItems, &MainWindow::findNextOrPrevious, menu, actionParent );
716
717 // - separator
718 menu->addSeparator();
719
720 // - sort
721 createAction( Actions::Edit_SortSelectedItems, &MainWindow::sortSelectedItems, menu, actionParent );
722
723 // - reverse order
724 createAction( Actions::Edit_ReverseSelectedItems, &MainWindow::reverseSelectedItems, menu, actionParent );
725
726 // - separator
727 menu->addSeparator();
728
729 // - paste items
730 createAction( Actions::Edit_PasteItems, &MainWindow::pasteItems, menu, actionParent );
731
732 // - copy items
733 createAction( Actions::Edit_CopySelectedItems, &MainWindow::copyItems, menu, actionParent );
734
735 // Items
736 m_menuItem = menubar->addMenu( tr("&Item") );
737 disableActionWhenTabGroupSelected(m_menuItem, this);
738
739 // Tabs
740 menu = menubar->addMenu(tr("&Tabs"));
741
742 // - new tab
743 createAction( Actions::Tabs_NewTab, &MainWindow::openNewTabDialog, menu );
744
745 // - rename tab
746 act = createAction( Actions::Tabs_RenameTab, &MainWindow::openRenameTabDialog, menu );
747 disableActionWhenTabGroupSelected(act, this);
748
749 // - remove tab
750 act = createAction( Actions::Tabs_RemoveTab, &MainWindow::removeTab, menu );
751 disableActionWhenTabGroupSelected(act, this);
752
753 createAction( Actions::Tabs_ChangeTabIcon, &MainWindow::setTabIcon, menu );
754
755 // - separator
756 menu->addSeparator();
757
758 // - next tab
759 createAction( Actions::Tabs_NextTab, &MainWindow::nextTab, menu );
760
761 // - previous tab
762 createAction( Actions::Tabs_PreviousTab, &MainWindow::previousTab, menu );
763
764 // Help
765 menu = menubar->addMenu(tr("&Help"));
766 createAction( Actions::Help_Help, &MainWindow::openHelp, menu );
767 createAction( Actions::Help_ShowLog, &MainWindow::openLogDialog, menu );
768 createAction( Actions::Help_About, &MainWindow::openAboutDialog, menu );
769
770 // Open Item Menu
771 createAction( Actions::ItemMenu, &MainWindow::showContextMenu, nullptr );
772
773 for (auto subMenu : menuBar()->findChildren<QMenu*>()) {
774 connect( subMenu, &QMenu::aboutToShow,
775 this, &MainWindow::disableHideWindowOnUnfocus );
776 connect( subMenu, &QMenu::aboutToHide,
777 this, &MainWindow::enableHideWindowOnUnfocus );
778 }
779 }
780
popupTabBarMenu(QPoint pos,const QString & tab)781 void MainWindow::popupTabBarMenu(QPoint pos, const QString &tab)
782 {
783 QMenu menu(ui->tabWidget);
784
785 const int tabIndex = ui->tabWidget->tabs().indexOf(tab);
786 bool hasTab = tabIndex != -1;
787 bool isGroup = ui->tabWidget->isTabGroup(tab);
788
789 const QString quotedTab = quoteString(tab);
790 QAction *actNew = menu.addAction( iconTabNew(), tr("&New Tab") );
791 QAction *actRenameGroup =
792 isGroup ? menu.addAction( iconTabRename(), tr("Rename &Group %1").arg(quotedTab) ) : nullptr;
793 QAction *actRename =
794 hasTab ? menu.addAction( iconTabRename(), tr("Re&name Tab %1").arg(quotedTab) ) : nullptr;
795 QAction *actRemove =
796 hasTab ? menu.addAction( iconTabRemove(), tr("Re&move Tab %1").arg(quotedTab) ) : nullptr;
797 QAction *actRemoveGroup =
798 isGroup ? menu.addAction( iconTabRemove(), tr("Remove Group %1").arg(quotedTab) ) : nullptr;
799
800 QAction *actIcon = menu.addAction( iconTabIcon(), tr("&Change Tab Icon") );
801
802 QAction *act = menu.exec(pos);
803 if (act != nullptr) {
804 if (act == actNew)
805 openNewTabDialog(tab);
806 else if (act == actRenameGroup)
807 openRenameTabGroupDialog(tab);
808 else if (act == actRename)
809 openRenameTabDialog(tabIndex);
810 else if (act == actRemove)
811 removeTab(true, tabIndex);
812 else if (act == actRemoveGroup)
813 removeTabGroup(tab);
814 else if (act == actIcon)
815 setTabIcon(tab);
816 }
817 }
818
updateContextMenu(int intervalMsec)819 void MainWindow::updateContextMenu(int intervalMsec)
820 {
821 interruptMenuCommandFilters(&m_itemMenuMatchCommands);
822
823 // Omit tool bar flickering.
824 m_toolBar->setFrozen(true);
825
826 clearActions(m_menuItem);
827
828 m_timerUpdateContextMenu.start(intervalMsec);
829 }
830
updateTrayMenuItems()831 void MainWindow::updateTrayMenuItems()
832 {
833 m_trayMenuDirty = true;
834 }
835
updateTrayMenuCommands()836 void MainWindow::updateTrayMenuCommands()
837 {
838 m_trayMenu->clearCustomActions();
839
840 if (!m_options.trayCommands)
841 return;
842
843 const QString format = tr("&Clipboard: %1", "Tray menu clipboard item format");
844 const auto font = m_trayMenu->font();
845 const auto clipboardLabel = textLabelForData(m_clipboardData, font, format, true);
846
847 QAction *clipboardAction = m_trayMenu->addAction( iconClipboard(), clipboardLabel );
848 connect(clipboardAction, &QAction::triggered,
849 this, &MainWindow::showClipboardContent);
850 m_trayMenu->addCustomAction(clipboardAction);
851
852 m_trayMenu->markItemInClipboard(m_clipboardData);
853
854 int i = m_trayMenu->actions().size();
855 addCommandsToTrayMenu(m_clipboardData);
856 QList<QAction *> actions = m_trayMenu->actions();
857 for ( ; i < actions.size(); ++i )
858 m_trayMenu->addCustomAction(actions[i]);
859 }
860
updateIcon()861 void MainWindow::updateIcon()
862 {
863 const QIcon icon = appIcon();
864 setWindowIcon(icon);
865 if (m_tray)
866 m_tray->setIcon(icon);
867 }
868
updateContextMenuTimeout()869 void MainWindow::updateContextMenuTimeout()
870 {
871 auto c = browserOrNull();
872 if ( ui->tabWidget->isTabGroupSelected() || !c || c->isInternalEditorOpen()) {
873 clearActions(m_toolBar);
874 m_toolBar->setFrozen(false);
875 return;
876 }
877
878 addCommandsToItemMenu(c);
879
880 m_menuItem->addSeparator();
881
882 addItemAction( Actions::Item_MoveToClipboard, c, &ClipboardBrowser::moveToClipboard );
883 addItemAction( Actions::Item_ShowContent, this, &MainWindow::showItemContent );
884 addItemAction( Actions::Item_Remove, c, &ClipboardBrowser::remove );
885 addItemAction( Actions::Item_Edit, c, &ClipboardBrowser::editSelected );
886 addItemAction( Actions::Item_EditNotes, c, &ClipboardBrowser::editNotes );
887 addItemAction( Actions::Item_EditWithEditor, c, &ClipboardBrowser::openEditor );
888 addItemAction( Actions::Item_Action, this, &MainWindow::openActionDialog );
889
890 m_menuItem->addSeparator();
891
892 addItemAction( Actions::Item_MoveUp, this, &MainWindow::moveUp );
893 addItemAction( Actions::Item_MoveDown, this, &MainWindow::moveDown );
894 addItemAction( Actions::Item_MoveToTop, this, &MainWindow::moveToTop );
895 addItemAction( Actions::Item_MoveToBottom, this, &MainWindow::moveToBottom );
896
897 updateToolBar();
898 updateActionShortcuts();
899 }
900
updateItemPreviewAfterMs(int ms)901 void MainWindow::updateItemPreviewAfterMs(int ms)
902 {
903 m_timerUpdatePreview.start(ms);
904 }
905
updateItemPreviewTimeout()906 void MainWindow::updateItemPreviewTimeout()
907 {
908 const bool showPreview = m_showItemPreview;
909
910 auto c = browserOrNull();
911 if (c && c->length() > 0) {
912 ui->dockWidgetItemPreview->setVisible(m_showItemPreview && !c->isInternalEditorOpen());
913
914 QWidget *w = ui->dockWidgetItemPreview->isVisible() && !ui->tabWidget->isTabGroupSelected()
915 ? c->currentItemPreview(ui->scrollAreaItemPreview)
916 : nullptr;
917
918 ui->scrollAreaItemPreview->setVisible(w != nullptr);
919 ui->scrollAreaItemPreview->setWidget(w);
920 if (w) {
921 ui->dockWidgetItemPreview->setStyleSheet( c->styleSheet() );
922 w->show();
923 }
924 } else {
925 ui->dockWidgetItemPreview->hide();
926 }
927
928 m_showItemPreview = showPreview;
929 m_timerUpdatePreview.stop();
930 }
931
toggleItemPreviewVisible()932 void MainWindow::toggleItemPreviewVisible()
933 {
934 setItemPreviewVisible(!m_showItemPreview);
935 }
936
setItemPreviewVisible(bool visible)937 void MainWindow::setItemPreviewVisible(bool visible)
938 {
939 if (m_showItemPreview == visible)
940 return;
941
942 m_showItemPreview = visible;
943 updateItemPreviewAfterMs(0);
944 }
945
isItemPreviewVisible() const946 bool MainWindow::isItemPreviewVisible() const
947 {
948 return m_showItemPreview;
949 }
950
onAboutToQuit()951 void MainWindow::onAboutToQuit()
952 {
953 if (cm)
954 cm->disconnect();
955
956 saveMainWindowState( objectName(), saveState() );
957 hideWindow();
958 if (m_tray)
959 m_tray->hide();
960
961 stopMenuCommandFilters(&m_itemMenuMatchCommands);
962 stopMenuCommandFilters(&m_trayMenuMatchCommands);
963 terminateAction(&m_displayActionId);
964 }
965
onSaveCommand(const Command & command)966 void MainWindow::onSaveCommand(const Command &command)
967 {
968 auto commands = loadAllCommands();
969 commands.append(command);
970 setCommands(commands);
971 }
972
onItemCommandActionTriggered(CommandAction * commandAction,const QString & triggeredShortcut)973 void MainWindow::onItemCommandActionTriggered(CommandAction *commandAction, const QString &triggeredShortcut)
974 {
975 COPYQ_LOG( QString("Trigger: %1").arg(commandAction->text()) );
976 auto c = getPlaceholder()->createBrowser();
977 if (!c)
978 return;
979
980 const QModelIndexList selected = c->selectionModel()->selectedIndexes();
981
982 const auto command = commandAction->command();
983
984 if ( !command.cmd.isEmpty() ) {
985 if (command.transform) {
986 for (const auto &index : selected) {
987 const auto selection = QModelIndexList() << index;
988 auto actionData = addSelectionData(*c, index, selection);
989 if ( !triggeredShortcut.isEmpty() )
990 actionData.insert(mimeShortcut, triggeredShortcut);
991 action(actionData, command, index);
992 }
993 } else {
994 auto actionData = addSelectionData(*c);
995 if ( !triggeredShortcut.isEmpty() )
996 actionData.insert(mimeShortcut, triggeredShortcut);
997 action(actionData, command, QModelIndex());
998 }
999 }
1000
1001 if ( !command.tab.isEmpty() && command.tab != c->tabName() ) {
1002 auto c2 = tab(command.tab);
1003 if (c2) {
1004 for (int i = selected.size() - 1; i >= 0; --i) {
1005 const auto data = c->copyIndex(selected[i]);
1006 if ( !data.isEmpty() )
1007 c2->addUnique(data, ClipboardMode::Clipboard);
1008 }
1009 }
1010 }
1011
1012 if (command.remove) {
1013 const int lastRow = c->removeIndexes(selected);
1014 if (lastRow != -1)
1015 c->setCurrent(lastRow);
1016 }
1017
1018 if (command.hideWindow)
1019 hideWindow();
1020 }
1021
onClipboardCommandActionTriggered(CommandAction * commandAction,const QString & triggeredShortcut)1022 void MainWindow::onClipboardCommandActionTriggered(CommandAction *commandAction, const QString &triggeredShortcut)
1023 {
1024 const QMimeData *data = m_clipboard->mimeData(ClipboardMode::Clipboard);
1025 if (data == nullptr)
1026 return;
1027
1028 auto actionData = cloneData(*data);
1029 if ( !triggeredShortcut.isEmpty() )
1030 actionData.insert(mimeShortcut, triggeredShortcut);
1031
1032 auto command = commandAction->command();
1033
1034 action( actionData, command, QModelIndex() );
1035 }
1036
onTabWidgetDropItems(const QString & tabName,const QMimeData * data)1037 void MainWindow::onTabWidgetDropItems(const QString &tabName, const QMimeData *data)
1038 {
1039 auto browser = tab(tabName);
1040
1041 if (browser) {
1042 const QVariantMap dataMap = data->hasFormat(mimeItems)
1043 ? cloneData(*data, QStringList() << mimeItems) : cloneData(*data);
1044 browser->addAndSelect(dataMap, 0);
1045 }
1046 }
1047
showContextMenuAt(QPoint position)1048 void MainWindow::showContextMenuAt(QPoint position)
1049 {
1050 // Restrict menu position to central widget.
1051 const auto localRect = centralWidget()->rect();
1052 const auto rect = QRect(
1053 centralWidget()->mapToGlobal(localRect.topLeft()),
1054 centralWidget()->mapToGlobal(localRect.bottomRight())
1055 );
1056 const QPoint positionInCentralWidget(
1057 qBound(rect.left(), position.x(), rect.right()),
1058 qBound(rect.top(), position.y(), rect.bottom())
1059 );
1060
1061 m_menuItem->exec(positionInCentralWidget);
1062 }
1063
showContextMenu()1064 void MainWindow::showContextMenu()
1065 {
1066 auto c = browser();
1067 if (!c)
1068 return;
1069
1070 const auto index = c->currentIndex();
1071 if ( !index.isValid() )
1072 return;
1073
1074 const auto itemRect = c->visualRect(index);
1075 const auto viewportPosition = itemRect.center();
1076 const auto position = c->mapToGlobal(viewportPosition);
1077 showContextMenuAt(position);
1078 }
1079
moveUp()1080 void MainWindow::moveUp()
1081 {
1082 auto c = browser();
1083 if (c)
1084 c->move(Qt::Key_Up);
1085 }
1086
moveDown()1087 void MainWindow::moveDown()
1088 {
1089 auto c = browser();
1090 if (c)
1091 c->move(Qt::Key_Down);
1092 }
1093
moveToTop()1094 void MainWindow::moveToTop()
1095 {
1096 auto c = browser();
1097 if (c)
1098 c->move(Qt::Key_Home);
1099 }
1100
moveToBottom()1101 void MainWindow::moveToBottom()
1102 {
1103 auto c = browser();
1104 if (c)
1105 c->move(Qt::Key_End);
1106 }
1107
onBrowserCreated(ClipboardBrowser * browser)1108 void MainWindow::onBrowserCreated(ClipboardBrowser *browser)
1109 {
1110 connect( browser, &ClipboardBrowser::changeClipboard,
1111 this, &MainWindow::setClipboardAndSelection );
1112 connect( browser, &ClipboardBrowser::requestShow,
1113 this, &MainWindow::showBrowser );
1114 connect( browser, &ClipboardBrowser::error,
1115 this, &MainWindow::showError );
1116 connect( browser, &QAbstractItemView::clicked,
1117 this, &MainWindow::onItemClicked );
1118 connect( browser, &QAbstractItemView::doubleClicked,
1119 this, &MainWindow::onItemDoubleClicked );
1120 connect( browser, &ClipboardBrowser::itemCountChanged,
1121 ui->tabWidget, &TabWidget::setTabItemCount );
1122 connect( browser, &ClipboardBrowser::showContextMenu,
1123 this, &MainWindow::showContextMenuAt );
1124 connect( browser, &ClipboardBrowser::itemSelectionChanged,
1125 this, &MainWindow::onItemSelectionChanged );
1126 connect( browser, &ClipboardBrowser::itemsChanged,
1127 this, &MainWindow::onItemsChanged );
1128 connect( browser, &ClipboardBrowser::internalEditorStateChanged,
1129 this, &MainWindow::onInternalEditorStateChanged );
1130 connect( browser, &ClipboardBrowser::searchRequest,
1131 this, &MainWindow::findNextOrPrevious );
1132 connect( browser, &ClipboardBrowser::searchHideRequest,
1133 ui->searchBar, &Utils::FilterLineEdit::hide );
1134 connect( browser, &ClipboardBrowser::searchShowRequest,
1135 this, &MainWindow::onSearchShowRequest );
1136 connect( browser, &ClipboardBrowser::itemWidgetCreated,
1137 this, &MainWindow::onItemWidgetCreated );
1138
1139 if (browserOrNull() == browser) {
1140 const int index = ui->tabWidget->currentIndex();
1141 tabChanged(index, index);
1142 }
1143 }
1144
onBrowserDestroyed(ClipboardBrowserPlaceholder * placeholder)1145 void MainWindow::onBrowserDestroyed(ClipboardBrowserPlaceholder *placeholder)
1146 {
1147 if (placeholder == getPlaceholder()) {
1148 updateContextMenu(0);
1149 updateItemPreviewAfterMs(0);
1150 }
1151 }
1152
onItemSelectionChanged(const ClipboardBrowser * browser)1153 void MainWindow::onItemSelectionChanged(const ClipboardBrowser *browser)
1154 {
1155 if (browser == browserOrNull()) {
1156 updateContextMenu(0);
1157 updateItemPreviewAfterMs(0);
1158 }
1159 }
1160
onItemsChanged(const ClipboardBrowser * browser)1161 void MainWindow::onItemsChanged(const ClipboardBrowser *browser)
1162 {
1163 if (browser == browserOrNull()) {
1164 updateContextMenu(contextMenuUpdateIntervalMsec);
1165 updateItemPreviewAfterMs(itemPreviewUpdateIntervalMsec);
1166 }
1167
1168 const ClipboardBrowserPlaceholder *placeholder = getPlaceholderForTrayMenu();
1169 if (placeholder && placeholder->browser() == browser)
1170 updateTrayMenuItems();
1171 }
1172
onInternalEditorStateChanged(const ClipboardBrowser * browser)1173 void MainWindow::onInternalEditorStateChanged(const ClipboardBrowser *browser)
1174 {
1175 if (browser == browserOrNull()) {
1176 updateContextMenu(0);
1177 updateItemPreviewAfterMs(0);
1178 }
1179 }
1180
onItemWidgetCreated(const PersistentDisplayItem & item)1181 void MainWindow::onItemWidgetCreated(const PersistentDisplayItem &item)
1182 {
1183 if ( m_displayCommands.isEmpty() )
1184 return;
1185
1186 m_displayItemList.append(item);
1187 runDisplayCommands();
1188 }
1189
onActionDialogAccepted(const Command & command,const QStringList & arguments,const QVariantMap & data)1190 void MainWindow::onActionDialogAccepted(const Command &command, const QStringList &arguments, const QVariantMap &data)
1191 {
1192 auto act = new Action();
1193 act->setCommand(command.cmd, arguments);
1194 act->setInputWithFormat(data, command.input);
1195 act->setName(command.name);
1196 act->setData(data);
1197
1198 if ( !command.output.isEmpty() ) {
1199 if ( !command.sep.isEmpty() )
1200 actionOutput(this, act, command.output, command.outputTab, QRegularExpression(command.sep));
1201 else
1202 actionOutput(this, act, command.output, command.outputTab);
1203 }
1204
1205 m_sharedData->actions->action(act);
1206 }
1207
onSearchShowRequest(const QString & text)1208 void MainWindow::onSearchShowRequest(const QString &text)
1209 {
1210 enterSearchMode();
1211 if (m_options.viMode && text == "/")
1212 return;
1213
1214 ui->searchBar->setText(text);
1215 ui->searchBar->end(false);
1216 }
1217
runDisplayCommands()1218 void MainWindow::runDisplayCommands()
1219 {
1220 if ( m_displayItemList.isEmpty() )
1221 return;
1222
1223 if ( !isInternalActionId(m_displayActionId) ) {
1224 m_currentDisplayItem = m_displayItemList.takeFirst();
1225 const auto action = runScript("runDisplayCommands()", m_currentDisplayItem.data());
1226 m_displayActionId = action->id();
1227 }
1228
1229 emit sendActionData(m_displayActionId, QByteArray());
1230 }
1231
clearHiddenDisplayData()1232 void MainWindow::clearHiddenDisplayData()
1233 {
1234 for (int i = m_displayItemList.size() - 1; i >= 0; --i) {
1235 auto &item = m_displayItemList[i];
1236 if ( !item.isValid() )
1237 m_displayItemList.removeAt(i);
1238 }
1239 }
1240
reloadBrowsers()1241 void MainWindow::reloadBrowsers()
1242 {
1243 for( int i = 0; i < ui->tabWidget->count(); ++i )
1244 getPlaceholder(i)->reloadBrowser();
1245 }
1246
findTabIndexExactMatch(const QString & name)1247 int MainWindow::findTabIndexExactMatch(const QString &name)
1248 {
1249 TabWidget *w = ui->tabWidget;
1250
1251 for( int i = 0; i < w->count(); ++i ) {
1252 if ( name == w->tabName(i) )
1253 return i;
1254 }
1255
1256 return -1;
1257 }
1258
setClipboardData(const QVariantMap & data)1259 void MainWindow::setClipboardData(const QVariantMap &data)
1260 {
1261 m_clipboardData = data;
1262 updateContextMenu(contextMenuUpdateIntervalMsec);
1263 updateTrayMenuCommands();
1264 }
1265
setFilter(const QString & text)1266 void MainWindow::setFilter(const QString &text)
1267 {
1268 if ( text.isEmpty() ) {
1269 enterBrowseMode();
1270 } else {
1271 enterSearchMode(text);
1272 getPlaceholder()->setFocus();
1273 }
1274 }
1275
filter() const1276 QString MainWindow::filter() const
1277 {
1278 return ui->searchBar->isVisible() ? ui->searchBar->text() : QString();
1279 }
1280
updateWindowTransparency(bool mouseOver)1281 void MainWindow::updateWindowTransparency(bool mouseOver)
1282 {
1283 int opacity = 100 - (mouseOver || isActiveWindow() ? m_options.transparencyFocused : m_options.transparency);
1284 setWindowOpacity(opacity / 100.0);
1285 }
1286
updateMonitoringActions()1287 void MainWindow::updateMonitoringActions()
1288 {
1289 if ( !m_actionToggleClipboardStoring.isNull() ) {
1290 m_actionToggleClipboardStoring->setIcon(
1291 getIcon("", m_clipboardStoringDisabled ? IconCheck : IconBan));
1292 m_actionToggleClipboardStoring->setText( m_clipboardStoringDisabled
1293 ? tr("&Enable Clipboard Storing")
1294 : tr("&Disable Clipboard Storing") );
1295 }
1296 }
1297
getPlaceholder(int index) const1298 ClipboardBrowserPlaceholder *MainWindow::getPlaceholder(int index) const
1299 {
1300 return qobject_cast<ClipboardBrowserPlaceholder*>( ui->tabWidget->widget(index) );
1301 }
1302
getPlaceholder(const QString & tabName) const1303 ClipboardBrowserPlaceholder *MainWindow::getPlaceholder(const QString &tabName) const
1304 {
1305 for (auto placeholder : findChildren<ClipboardBrowserPlaceholder*>()) {
1306 if ( placeholder->tabName() == tabName )
1307 return placeholder;
1308 }
1309 return nullptr;
1310 }
1311
getPlaceholder() const1312 ClipboardBrowserPlaceholder *MainWindow::getPlaceholder() const
1313 {
1314 return qobject_cast<ClipboardBrowserPlaceholder*>( ui->tabWidget->currentWidget() );
1315 }
1316
delayedUpdateForeignFocusWindows()1317 void MainWindow::delayedUpdateForeignFocusWindows()
1318 {
1319 if ( isActiveWindow() || m_trayMenu->isActiveWindow() || m_menu->isActiveWindow() )
1320 m_timerUpdateFocusWindows.stop();
1321 else
1322 m_timerUpdateFocusWindows.start();
1323 }
1324
setHideTabs(bool hide)1325 void MainWindow::setHideTabs(bool hide)
1326 {
1327 ui->tabWidget->setTabBarHidden(hide);
1328 }
1329
closeMinimizes() const1330 bool MainWindow::closeMinimizes() const
1331 {
1332 return !m_options.hideMainWindow
1333 && (!m_tray || !QSystemTrayIcon::isSystemTrayAvailable());
1334 }
1335
createTab(const QString & name,TabNameMatching nameMatch,const Tabs & tabs)1336 ClipboardBrowserPlaceholder *MainWindow::createTab(const QString &name, TabNameMatching nameMatch, const Tabs &tabs)
1337 {
1338 if ( name.isEmpty() )
1339 return nullptr;
1340
1341 const int i = nameMatch == MatchExactTabName
1342 ? findTabIndexExactMatch(name)
1343 : findTabIndex(name);
1344
1345 ClipboardBrowserPlaceholder *placeholder = nullptr;
1346 if (i != -1) {
1347 placeholder = getPlaceholder(i);
1348 } else {
1349 placeholder = new ClipboardBrowserPlaceholder(name, m_sharedData, this);
1350 connect( placeholder, &ClipboardBrowserPlaceholder::browserCreated,
1351 this, &MainWindow::onBrowserCreated );
1352 connect( placeholder, &ClipboardBrowserPlaceholder::browserDestroyed,
1353 this, [this, placeholder]() { onBrowserDestroyed(placeholder); } );
1354
1355 ui->tabWidget->addTab(placeholder, name);
1356 saveTabPositions();
1357 }
1358
1359 const TabProperties tab = tabs.tabProperties(name);
1360 placeholder->setStoreItems(tab.storeItems);
1361
1362 int maxItemCount = tab.maxItemCount;
1363 if (maxItemCount <= 0)
1364 maxItemCount = m_sharedData->maxItems;
1365 else if (maxItemCount > Config::maxItems)
1366 maxItemCount = Config::maxItems;
1367
1368 placeholder->setMaxItemCount(maxItemCount);
1369
1370 return placeholder;
1371 }
1372
1373 template <typename SlotReturnType>
createAction(int id,MainWindowActionSlot<SlotReturnType> slot,QMenu * menu,QWidget * parent)1374 QAction *MainWindow::createAction(int id, MainWindowActionSlot<SlotReturnType> slot, QMenu *menu, QWidget *parent)
1375 {
1376 QAction *act = parent
1377 ? actionForMenuItem(id, parent, Qt::WidgetWithChildrenShortcut)
1378 : actionForMenuItem(id, this, Qt::WindowShortcut);
1379 connect(act, &QAction::triggered, this, slot, Qt::UniqueConnection);
1380 if (menu)
1381 menu->addAction(act);
1382 return act;
1383 }
1384
addTrayAction(int id)1385 QAction *MainWindow::addTrayAction(int id)
1386 {
1387 QAction *act = actionForMenuItem(id, m_trayMenu, Qt::WindowShortcut);
1388 m_trayMenu->addAction(act);
1389 return act;
1390 }
1391
updateTabIcon(const QString & newName,const QString & oldName)1392 void MainWindow::updateTabIcon(const QString &newName, const QString &oldName)
1393 {
1394 const QString icon = getIconNameForTabName(oldName);
1395 if ( !icon.isEmpty() )
1396 setIconNameForTabName(newName, icon);
1397 }
1398
1399 template <typename Receiver, typename ReturnType>
addItemAction(int id,Receiver * receiver,ReturnType (Receiver::* slot)())1400 QAction *MainWindow::addItemAction(int id, Receiver *receiver, ReturnType (Receiver::* slot)())
1401 {
1402 QAction *act = actionForMenuItem(id, getPlaceholder(), Qt::WidgetWithChildrenShortcut);
1403 connect( act, &QAction::triggered, receiver, slot, Qt::UniqueConnection );
1404 m_menuItem->addAction(act);
1405 return act;
1406 }
1407
commandsForMenu(const QVariantMap & data,const QString & tabName,const QVector<Command> & allCommands)1408 QVector<Command> MainWindow::commandsForMenu(const QVariantMap &data, const QString &tabName, const QVector<Command> &allCommands)
1409 {
1410 QVector<Command> commands;
1411 for (const auto &command : allCommands) {
1412 if ( canExecuteCommand(command, data, tabName) ) {
1413 Command cmd = command;
1414 if ( cmd.outputTab.isEmpty() )
1415 cmd.outputTab = tabName;
1416 commands.append(cmd);
1417 }
1418 }
1419
1420 return commands;
1421 }
1422
addCommandsToItemMenu(ClipboardBrowser * c)1423 void MainWindow::addCommandsToItemMenu(ClipboardBrowser *c)
1424 {
1425 if ( m_menuCommands.isEmpty() ) {
1426 interruptMenuCommandFilters(&m_itemMenuMatchCommands);
1427 return;
1428 }
1429
1430 auto data = addSelectionData(*c);
1431 const auto commands = commandsForMenu(data, c->tabName(), m_menuCommands);
1432
1433 for (const auto &command : commands) {
1434 QString name = command.name;
1435 QMenu *currentMenu = createSubMenus(&name, m_menuItem);
1436 auto act = new CommandAction(command, name, currentMenu);
1437 c->addAction(act);
1438
1439 addMenuMatchCommand(&m_itemMenuMatchCommands, command.matchCmd, act);
1440
1441 connect(act, &CommandAction::triggerCommand,
1442 this, &MainWindow::onItemCommandActionTriggered);
1443 }
1444
1445 runMenuCommandFilters(&m_itemMenuMatchCommands, data);
1446 }
1447
addCommandsToTrayMenu(const QVariantMap & clipboardData)1448 void MainWindow::addCommandsToTrayMenu(const QVariantMap &clipboardData)
1449 {
1450 if ( m_trayMenuCommands.isEmpty() ) {
1451 interruptMenuCommandFilters(&m_trayMenuMatchCommands);
1452 return;
1453 }
1454
1455 ClipboardBrowserPlaceholder *placeholder = getPlaceholderForTrayMenu();
1456 if (!placeholder)
1457 return;
1458
1459 // Pass current window title to commands in tray menu.
1460 auto data = clipboardData;
1461 if (m_lastWindow)
1462 data.insert( mimeWindowTitle, m_lastWindow->getTitle() );
1463
1464 const auto commands = commandsForMenu(data, placeholder->tabName(), m_trayMenuCommands);
1465
1466 for (const auto &command : commands) {
1467 QString name = command.name;
1468 QMenu *currentMenu = createSubMenus(&name, m_trayMenu);
1469 auto act = new CommandAction(command, name, currentMenu);
1470
1471 addMenuMatchCommand(&m_trayMenuMatchCommands, command.matchCmd, act);
1472
1473 connect(act, &CommandAction::triggerCommand,
1474 this, &MainWindow::onClipboardCommandActionTriggered);
1475 }
1476
1477 runMenuCommandFilters(&m_trayMenuMatchCommands, data);
1478 }
1479
addMenuMatchCommand(MenuMatchCommands * menuMatchCommands,const QString & matchCommand,QAction * act)1480 void MainWindow::addMenuMatchCommand(MenuMatchCommands *menuMatchCommands, const QString &matchCommand, QAction *act)
1481 {
1482 if ( !matchCommand.isEmpty() ) {
1483 act->setDisabled(true);
1484 menuMatchCommands->matchCommands.append(matchCommand);
1485 menuMatchCommands->actions.append(act);
1486 }
1487 }
1488
runMenuCommandFilters(MenuMatchCommands * menuMatchCommands,QVariantMap & data)1489 void MainWindow::runMenuCommandFilters(MenuMatchCommands *menuMatchCommands, QVariantMap &data)
1490 {
1491 if ( menuMatchCommands->actions.isEmpty() ) {
1492 interruptMenuCommandFilters(menuMatchCommands);
1493 return;
1494 }
1495
1496 data[COPYQ_MIME_PREFIX "match-commands"] = menuMatchCommands->matchCommands;
1497
1498 const bool isRunning = isInternalActionId(menuMatchCommands->actionId);
1499 if (isRunning) {
1500 m_sharedData->actions->setActionData(menuMatchCommands->actionId, data);
1501 } else {
1502 const auto act = runScript("runMenuCommandFilters()", data);
1503 menuMatchCommands->actionId = act->id();
1504 }
1505
1506 const int currentRun = ++menuMatchCommands->currentRun;
1507 emit sendActionData(menuMatchCommands->actionId, QByteArray::number(currentRun));
1508 }
1509
interruptMenuCommandFilters(MainWindow::MenuMatchCommands * menuMatchCommands)1510 void MainWindow::interruptMenuCommandFilters(MainWindow::MenuMatchCommands *menuMatchCommands)
1511 {
1512 ++menuMatchCommands->currentRun;
1513 menuMatchCommands->matchCommands.clear();
1514 menuMatchCommands->actions.clear();
1515
1516 const bool isRunning = isInternalActionId(menuMatchCommands->actionId);
1517 if (isRunning)
1518 emit sendActionData(menuMatchCommands->actionId, QByteArray());
1519 }
1520
stopMenuCommandFilters(MainWindow::MenuMatchCommands * menuMatchCommands)1521 void MainWindow::stopMenuCommandFilters(MainWindow::MenuMatchCommands *menuMatchCommands)
1522 {
1523 ++menuMatchCommands->currentRun;
1524 menuMatchCommands->matchCommands.clear();
1525 menuMatchCommands->actions.clear();
1526 terminateAction(&menuMatchCommands->actionId);
1527 }
1528
terminateAction(int * actionId)1529 void MainWindow::terminateAction(int *actionId)
1530 {
1531 if (*actionId == -1)
1532 return;
1533
1534 const int id = *actionId;
1535 *actionId = -1;
1536 emit sendActionData(id, "ABORT");
1537 }
1538
isItemMenuDefaultActionValid() const1539 bool MainWindow::isItemMenuDefaultActionValid() const
1540 {
1541 const auto defaultAction = m_menuItem->defaultAction();
1542 return defaultAction != nullptr && defaultAction->isEnabled();
1543 }
1544
updateToolBar()1545 void MainWindow::updateToolBar()
1546 {
1547 clearActions(m_toolBar);
1548
1549 if ( m_toolBar->isHidden() )
1550 return;
1551
1552 QAction *act = actionForMenuItem(Actions::File_New, this, Qt::WindowShortcut);
1553 m_toolBar->addAction(act);
1554
1555 for ( auto action : m_menuItem->actions() ) {
1556 if ( action->isSeparator() ) {
1557 m_toolBar->addSeparator();
1558 } else if ( !action->icon().isNull() ) {
1559 act = m_toolBar->addAction(QString());
1560
1561 const auto update = [=]() {
1562 const QIcon icon = action->icon();
1563 act->setIcon(icon);
1564
1565 const QString text = action->text().remove("&");
1566 const QString shortcut = action->shortcut().toString(QKeySequence::NativeText);
1567 const QString label = text + (shortcut.isEmpty() ? QString() : "\n[" + shortcut + "]");
1568 act->setText(label);
1569
1570 const QString tooltip = "<center>" + escapeHtml(text)
1571 + (shortcut.isEmpty() ? QString() : "<br /><b>" + escapeHtml(shortcut) + "</b>") + "</center>";
1572 act->setToolTip(tooltip);
1573 act->setEnabled(action->isEnabled());
1574
1575 if ( action->isCheckable() ) {
1576 act->setCheckable(true);
1577 act->setChecked(action->isChecked());
1578 }
1579 };
1580
1581 connect(act, &QAction::triggered, action, &QAction::triggered);
1582 connect(action, &QAction::changed, act, update);
1583 update();
1584 }
1585 }
1586
1587 m_toolBar->setFrozen(false);
1588 }
1589
setTrayEnabled(bool enable)1590 void MainWindow::setTrayEnabled(bool enable)
1591 {
1592 const bool trayAlreadyEnabled = m_tray != nullptr;
1593 if (enable == trayAlreadyEnabled)
1594 return;
1595
1596 if (enable) {
1597 m_tray = new QSystemTrayIcon(this);
1598 connect( m_tray, &QSystemTrayIcon::activated,
1599 this, &MainWindow::trayActivated );
1600 updateIcon();
1601
1602 // On macOS, avoid showing tray menu with the main window.
1603 #ifndef Q_OS_MAC
1604 m_tray->setContextMenu(m_trayMenu);
1605 #endif
1606
1607 m_tray->show();
1608
1609 if ( isMinimized() )
1610 hideWindow();
1611 } else {
1612 // Hide tray on Ubuntu (buggy sni-qt) before disabling.
1613 m_tray->hide();
1614
1615 delete m_tray;
1616 m_tray = nullptr;
1617
1618 if ( isHidden() && !isMinimized() )
1619 minimizeWindow();
1620 }
1621 }
1622
isWindowVisible() const1623 bool MainWindow::isWindowVisible() const
1624 {
1625 return !isMinimized() && isVisible() && m_isActiveWindow;
1626 }
1627
onEscape()1628 void MainWindow::onEscape()
1629 {
1630 if ( browseMode() ) {
1631 auto c = browser();
1632 if (c && !c->hasFocus()) {
1633 enterBrowseMode();
1634 return;
1635 }
1636
1637 hideWindow();
1638 if (c)
1639 c->setCurrent(0);
1640 } else {
1641 enterBrowseMode();
1642 }
1643 }
1644
updateActionShortcuts()1645 void MainWindow::updateActionShortcuts()
1646 {
1647 QList<QKeySequence> usedShortcuts;
1648
1649 for (auto act : m_menuItem->findChildren<CommandAction*>()) {
1650 if (!act->isEnabled() && !act->isVisible())
1651 continue;
1652
1653 if ( act->property(propertyActionFilterCommandFailed).toBool() )
1654 continue;
1655
1656 const Command &command = act->command();
1657 QList<QKeySequence> uniqueShortcuts;
1658
1659 for (const auto &shortcutText : command.shortcuts) {
1660 const QKeySequence shortcut(shortcutText, QKeySequence::PortableText);
1661 if ( !shortcut.isEmpty() && !usedShortcuts.contains(shortcut) ) {
1662 usedShortcuts.append(shortcut);
1663 uniqueShortcuts.append(shortcut);
1664
1665 if ( !isItemMenuDefaultActionValid() && isItemActivationShortcut(shortcut) )
1666 m_menuItem->setDefaultAction(act);
1667 }
1668 }
1669
1670 if (!uniqueShortcuts.isEmpty())
1671 act->setShortcuts(uniqueShortcuts);
1672 }
1673
1674 for (int id = 0; id < m_actions.size(); ++id) {
1675 QAction *action = m_actions[id];
1676 if (!action)
1677 continue;
1678
1679 QList<QKeySequence> shortcuts = m_menuItems[id].shortcuts;
1680 for (const auto &shortcut : usedShortcuts)
1681 shortcuts.removeAll(shortcut);
1682
1683 action->setShortcuts(shortcuts);
1684 }
1685 }
1686
actionForMenuItem(int id,QWidget * parent,Qt::ShortcutContext context)1687 QAction *MainWindow::actionForMenuItem(int id, QWidget *parent, Qt::ShortcutContext context)
1688 {
1689 Q_ASSERT(id < m_menuItems.size());
1690
1691 m_actions.resize(m_menuItems.size());
1692
1693 QPointer<QAction> &action = m_actions[id];
1694 if (action && !action->isEnabled() && !action->isVisible()) {
1695 action->deleteLater();
1696 action = nullptr;
1697 }
1698
1699 const MenuItem &item = m_menuItems[id];
1700
1701 if (!action) {
1702 action = new QAction(item.text, parent);
1703 action->setShortcutContext(context);
1704 parent->addAction(action);
1705 }
1706
1707 action->setIcon( getIcon(item.iconName, item.iconId) );
1708
1709 return action;
1710 }
1711
addMenuItems(TrayMenu * menu,ClipboardBrowserPlaceholder * placeholder,int maxItemCount,const QString & searchText)1712 void MainWindow::addMenuItems(TrayMenu *menu, ClipboardBrowserPlaceholder *placeholder, int maxItemCount, const QString &searchText)
1713 {
1714 WidgetSizeGuard sizeGuard(menu);
1715 menu->clearClipboardItems();
1716
1717 if (maxItemCount <= 0)
1718 return;
1719
1720 if (!placeholder)
1721 return;
1722
1723 const ClipboardBrowser *c = placeholder->createBrowser();
1724 if (!c)
1725 return;
1726
1727 int itemCount = 0;
1728 for ( int i = 0; i < c->length() && itemCount < maxItemCount; ++i ) {
1729 const QModelIndex index = c->model()->index(i, 0);
1730 if ( !searchText.isEmpty() ) {
1731 const QString itemText = index.data(contentType::text).toString().toLower();
1732 if ( !itemText.contains(searchText.toLower()) )
1733 continue;
1734 }
1735 const QVariantMap data = index.data(contentType::data).toMap();
1736 menu->addClipboardItemAction(data, m_options.trayImages);
1737 ++itemCount;
1738 }
1739 }
1740
activateMenuItem(ClipboardBrowserPlaceholder * placeholder,const QVariantMap & data,bool omitPaste)1741 void MainWindow::activateMenuItem(ClipboardBrowserPlaceholder *placeholder, const QVariantMap &data, bool omitPaste)
1742 {
1743 if ( m_sharedData->moveItemOnReturnKey ) {
1744 const auto itemHash = ::hash(data);
1745 if (placeholder) {
1746 ClipboardBrowser *c = placeholder->createBrowser();
1747 if (c)
1748 c->moveToTop(itemHash);
1749 }
1750 }
1751
1752 if ( QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier) )
1753 setClipboard( createDataMap(mimeText, data.value(mimeText) ) );
1754 else
1755 setClipboard(data);
1756
1757 if (!m_lastWindow)
1758 updateFocusWindows();
1759
1760 PlatformWindowPtr lastWindow = m_lastWindow;
1761
1762 if ( m_options.trayItemPaste && lastWindow && !omitPaste && canPaste() ) {
1763 COPYQ_LOG( QString("Pasting item from tray menu to \"%1\".")
1764 .arg(lastWindow->getTitle()) );
1765 lastWindow->pasteClipboard();
1766 }
1767 }
1768
toggleMenu(TrayMenu * menu,QPoint pos)1769 bool MainWindow::toggleMenu(TrayMenu *menu, QPoint pos)
1770 {
1771 if ( menu->isVisible() ) {
1772 menu->close();
1773 return false;
1774 }
1775
1776 menu->popup( toScreen(pos, menu) );
1777
1778 menu->raise();
1779 menu->activateWindow();
1780 QApplication::setActiveWindow(menu);
1781 QApplication::processEvents();
1782 stealFocus(*menu);
1783
1784 return true;
1785 }
1786
toggleMenu(TrayMenu * menu)1787 bool MainWindow::toggleMenu(TrayMenu *menu)
1788 {
1789 return toggleMenu(menu, QCursor::pos());
1790 }
1791
exportDataFrom(const QString & fileName,const QStringList & tabs,bool exportConfiguration,bool exportCommands)1792 bool MainWindow::exportDataFrom(const QString &fileName, const QStringList &tabs, bool exportConfiguration, bool exportCommands)
1793 {
1794 QTemporaryFile file(fileName + ".XXXXXX.part");
1795 if ( !file.open() ) {
1796 log( QString("Failed to open temporary file: %1")
1797 .arg(file.errorString()), LogError );
1798 return false;
1799 }
1800
1801 QDataStream out(&file);
1802 if ( !exportDataV4(&out, tabs, exportConfiguration, exportCommands) )
1803 return false;
1804
1805 if ( !file.flush() ) {
1806 log( QString("Failed to flush temporary file %1: %2")
1807 .arg(file.fileName(), file.errorString()), LogError );
1808 return false;
1809 }
1810
1811 QFile originalFile(fileName);
1812 if ( originalFile.exists() && !originalFile.remove(fileName) ) {
1813 log( QString("Failed to remove original file %1: %2")
1814 .arg(fileName, originalFile.errorString()), LogError );
1815 return false;
1816 }
1817
1818 file.setAutoRemove(false);
1819 if ( !file.rename(fileName) ) {
1820 log( QString("Failed to move temporary file %1: %2")
1821 .arg(file.fileName(), file.errorString()), LogError );
1822 return false;
1823 }
1824
1825 return true;
1826 }
1827
exportDataV4(QDataStream * out,const QStringList & tabs,bool exportConfiguration,bool exportCommands)1828 bool MainWindow::exportDataV4(QDataStream *out, const QStringList &tabs, bool exportConfiguration, bool exportCommands)
1829 {
1830 out->setVersion(QDataStream::Qt_4_7);
1831 (*out) << QByteArray("CopyQ v4");
1832
1833 QVariantMap settingsMap;
1834 if (exportConfiguration) {
1835 const QSettings settings;
1836
1837 for (const auto &key : settings.allKeys()) {
1838 if ( !key.startsWith("Commands/") )
1839 settingsMap[key] = serializableValue(settings, key);
1840 }
1841 }
1842
1843 QVariantList commandsList;
1844 if (exportCommands) {
1845 Settings settings(getConfigurationFilePath("-commands.ini"));
1846
1847 const int commandCount = settings.beginReadArray("Commands");
1848 commandsList.reserve(commandCount);
1849 for (int i = 0; i < commandCount; ++i) {
1850 settings.setArrayIndex(i);
1851
1852 QVariantMap commandMap;
1853 for ( const auto &key : settings.allKeys() )
1854 commandMap[key] = serializableValue(settings.constSettingsData(), key);
1855
1856 commandsList.append(commandMap);
1857 }
1858
1859 settings.endArray();
1860 }
1861
1862 QVariantMap data;
1863 if ( !tabs.isEmpty() )
1864 data["tabs"] = tabs;
1865 if ( !settingsMap.isEmpty() )
1866 data["settings"] = settingsMap;
1867 if ( !commandsList.isEmpty() )
1868 data["commands"] = commandsList;
1869
1870 (*out) << data;
1871
1872 for (const auto &tab : tabs) {
1873 const auto i = findTabIndex(tab);
1874 if (i == -1)
1875 continue;
1876
1877 auto placeholder = getPlaceholder(i);
1878 const bool wasLoaded = placeholder->isDataLoaded();
1879 auto c = placeholder->createBrowserAgain();
1880 if (!c) {
1881 log(QString("Failed to open tab \"%s\" for export").arg(tab), LogError);
1882 return false;
1883 }
1884
1885 const auto &tabName = c->tabName();
1886
1887 bool saved = false;
1888 QByteArray tabBytes;
1889 {
1890 QDataStream tabOut(&tabBytes, QIODevice::WriteOnly);
1891 tabOut.setVersion(QDataStream::Qt_4_7);
1892 saved = serializeData(*c->model(), &tabOut);
1893 }
1894
1895 if (!wasLoaded)
1896 placeholder->expire();
1897
1898 if (!saved) {
1899 log(QString("Failed to export tab \"%s\"").arg(tab), LogError);
1900 return false;
1901 }
1902
1903 const auto iconName = getIconNameForTabName(tabName);
1904
1905 QVariantMap tabMap;
1906 tabMap["name"] = tabName;
1907 tabMap["data"] = tabBytes;
1908 if ( !iconName.isEmpty() )
1909 tabMap["icon"] = iconName;
1910
1911 (*out) << tabMap;
1912 }
1913
1914 return out->status() == QDataStream::Ok;
1915 }
1916
importDataV3(QDataStream * in,ImportOptions options)1917 bool MainWindow::importDataV3(QDataStream *in, ImportOptions options)
1918 {
1919 QByteArray header;
1920 (*in) >> header;
1921 if ( !header.startsWith("CopyQ v3") )
1922 return false;
1923
1924 QVariantMap data;
1925 (*in) >> data;
1926 if ( in->status() != QDataStream::Ok )
1927 return false;
1928
1929 const auto tabsList = data.value("tabs").toList();
1930
1931 QStringList tabs;
1932 tabs.reserve( tabsList.size() );
1933 for (const auto &tabMapValue : tabsList) {
1934 const auto tabMap = tabMapValue.toMap();
1935 const auto oldTabName = tabMap["name"].toString();
1936 tabs.append(oldTabName);
1937 }
1938
1939 const auto settingsMap = data.value("settings").toMap();
1940 const auto commandsList = data.value("commands").toList();
1941
1942 bool importConfiguration = true;
1943 bool importCommands = true;
1944
1945 if (options == ImportOptions::Select) {
1946 ImportExportDialog importDialog(this);
1947 importDialog.setWindowTitle( tr("Options for Import") );
1948 importDialog.setTabs(tabs);
1949 importDialog.setHasConfiguration( !settingsMap.isEmpty() );
1950 importDialog.setHasCommands( !commandsList.isEmpty() );
1951 importDialog.setConfigurationEnabled(true);
1952 importDialog.setCommandsEnabled(true);
1953 if ( importDialog.exec() != QDialog::Accepted )
1954 return true;
1955
1956 tabs = importDialog.selectedTabs();
1957 importConfiguration = importDialog.isConfigurationEnabled();
1958 importCommands = importDialog.isCommandsEnabled();
1959 }
1960
1961 const Tabs tabProps;
1962 for (const auto &tabMapValue : tabsList) {
1963 const auto tabMap = tabMapValue.toMap();
1964 const auto oldTabName = tabMap["name"].toString();
1965 if ( !tabs.contains(oldTabName) )
1966 continue;
1967
1968 auto tabName = oldTabName;
1969 renameToUnique( &tabName, ui->tabWidget->tabs() );
1970
1971 const auto iconName = tabMap.value("icon").toString();
1972 if ( !iconName.isEmpty() )
1973 setIconNameForTabName(tabName, iconName);
1974
1975 auto c = createTab(tabName, MatchExactTabName, tabProps)->createBrowser();
1976 if (!c) {
1977 log(QString("Failed to create tab \"%s\" for import").arg(tabName), LogError);
1978 return false;
1979 }
1980
1981 const auto tabBytes = tabMap.value("data").toByteArray();
1982 QDataStream tabIn(tabBytes);
1983 tabIn.setVersion(QDataStream::Qt_4_7);
1984
1985 // Don't read items based on current value of "maxitems" option since
1986 // the option can be later also imported.
1987 if ( !deserializeData( c->model(), &tabIn, Config::maxItems ) ) {
1988 log(QString("Failed to import tab \"%s\"").arg(tabName), LogError);
1989 return false;
1990 }
1991
1992 const auto i = findTabIndex(tabName);
1993 if (i != -1)
1994 getPlaceholder(i)->expire();
1995 }
1996
1997 if (importConfiguration) {
1998 // Configuration dialog shouldn't be open.
1999 if (cm) {
2000 log("Failed to import configuration while configuration dialog is open", LogError);
2001 return false;
2002 }
2003
2004 Settings settings;
2005
2006 for (auto it = settingsMap.constBegin(); it != settingsMap.constEnd(); ++it)
2007 settings.setValue( it.key(), it.value() );
2008
2009 AppConfig appConfig;
2010 emit configurationChanged(&appConfig);
2011 }
2012
2013 if (importCommands) {
2014 // Close command dialog.
2015 if ( !maybeCloseCommandDialog() ) {
2016 log("Failed to import command while command dialog is open", LogError);
2017 return false;
2018 }
2019
2020 // Re-create command dialog again later.
2021 if (m_commandDialog) {
2022 m_commandDialog->deleteLater();
2023 m_commandDialog = nullptr;
2024 }
2025
2026 Settings settings;
2027
2028 int i = settings.beginReadArray("Commands");
2029 settings.endArray();
2030
2031 settings.beginWriteArray("Commands");
2032
2033 for ( const auto &commandDataValue : commandsList ) {
2034 settings.setArrayIndex(i++);
2035 const auto commandMap = commandDataValue.toMap();
2036 for (auto it = commandMap.constBegin(); it != commandMap.constEnd(); ++it)
2037 settings.setValue( it.key(), it.value() );
2038 }
2039
2040 settings.endArray();
2041
2042 updateEnabledCommands();
2043 }
2044
2045 return in->status() == QDataStream::Ok;
2046 }
2047
importDataV4(QDataStream * in,ImportOptions options)2048 bool MainWindow::importDataV4(QDataStream *in, ImportOptions options)
2049 {
2050 QByteArray header;
2051 (*in) >> header;
2052 if ( !header.startsWith("CopyQ v4") )
2053 return false;
2054
2055 QVariantMap data;
2056 (*in) >> data;
2057 if ( in->status() != QDataStream::Ok )
2058 return false;
2059
2060 QStringList tabs = data.value("tabs").toStringList();
2061 const auto settingsMap = data.value("settings").toMap();
2062 const auto commandsList = data.value("commands").toList();
2063
2064 bool importConfiguration = true;
2065 bool importCommands = true;
2066
2067 if (options == ImportOptions::Select) {
2068 ImportExportDialog importDialog(this);
2069 importDialog.setWindowTitle( tr("Options for Import") );
2070 importDialog.setTabs(tabs);
2071 importDialog.setHasConfiguration( !settingsMap.isEmpty() );
2072 importDialog.setHasCommands( !commandsList.isEmpty() );
2073 importDialog.setConfigurationEnabled(true);
2074 importDialog.setCommandsEnabled(true);
2075 if ( importDialog.exec() != QDialog::Accepted )
2076 return true;
2077
2078 tabs = importDialog.selectedTabs();
2079 importConfiguration = importDialog.isConfigurationEnabled();
2080 importCommands = importDialog.isCommandsEnabled();
2081 }
2082
2083 const Tabs tabProps;
2084 while ( !in->atEnd() ) {
2085 QVariantMap tabMap;
2086 (*in) >> tabMap;
2087 if ( in->status() != QDataStream::Ok )
2088 return false;
2089
2090 const auto oldTabName = tabMap["name"].toString();
2091 if ( !tabs.contains(oldTabName) )
2092 continue;
2093
2094 auto tabName = oldTabName;
2095 renameToUnique( &tabName, ui->tabWidget->tabs() );
2096
2097 const auto iconName = tabMap.value("icon").toString();
2098 if ( !iconName.isEmpty() )
2099 setIconNameForTabName(tabName, iconName);
2100
2101 auto c = createTab(tabName, MatchExactTabName, tabProps)->createBrowser();
2102 if (!c) {
2103 log(QString("Failed to create tab \"%s\" for import").arg(tabName), LogError);
2104 return false;
2105 }
2106
2107 const auto tabBytes = tabMap.value("data").toByteArray();
2108 QDataStream tabIn(tabBytes);
2109 tabIn.setVersion(QDataStream::Qt_4_7);
2110
2111 // Don't read items based on current value of "maxitems" option since
2112 // the option can be later also imported.
2113 const int maxItems = importConfiguration ? Config::maxItems : m_sharedData->maxItems;
2114 if ( !deserializeData( c->model(), &tabIn, maxItems ) ) {
2115 log(QString("Failed to import tab \"%s\"").arg(tabName), LogError);
2116 return false;
2117 }
2118
2119 const auto i = findTabIndex(tabName);
2120 if (i != -1)
2121 getPlaceholder(i)->expire();
2122 }
2123
2124 if (importConfiguration) {
2125 // Configuration dialog shouldn't be open.
2126 if (cm) {
2127 log("Failed to import configuration while configuration dialog is open", LogError);
2128 return false;
2129 }
2130
2131 Settings settings;
2132
2133 for (auto it = settingsMap.constBegin(); it != settingsMap.constEnd(); ++it)
2134 settings.setValue( it.key(), it.value() );
2135
2136 AppConfig appConfig;
2137 emit configurationChanged(&appConfig);
2138 }
2139
2140 if (importCommands) {
2141 // Close command dialog.
2142 if ( !maybeCloseCommandDialog() ) {
2143 log("Failed to import command while command dialog is open", LogError);
2144 return false;
2145 }
2146
2147 // Re-create command dialog again later.
2148 if (m_commandDialog) {
2149 m_commandDialog->deleteLater();
2150 m_commandDialog = nullptr;
2151 }
2152
2153 Settings settings(getConfigurationFilePath("-commands.ini"));
2154
2155 int i = settings.beginReadArray("Commands");
2156 settings.endArray();
2157
2158 settings.beginWriteArray("Commands");
2159
2160 for ( const auto &commandDataValue : commandsList ) {
2161 settings.setArrayIndex(i++);
2162 const auto commandMap = commandDataValue.toMap();
2163 for (auto it = commandMap.constBegin(); it != commandMap.constEnd(); ++it)
2164 settings.setValue( it.key(), it.value() );
2165 }
2166
2167 settings.endArray();
2168
2169 updateEnabledCommands();
2170 }
2171
2172 return in->status() == QDataStream::Ok;
2173 }
2174
updateEnabledCommands()2175 void MainWindow::updateEnabledCommands()
2176 {
2177 updateCommands(loadAllCommands(), false);
2178 }
2179
updateCommands(QVector<Command> allCommands,bool forceSave)2180 void MainWindow::updateCommands(QVector<Command> allCommands, bool forceSave)
2181 {
2182 m_automaticCommands.clear();
2183 m_menuCommands.clear();
2184 m_scriptCommands.clear();
2185 m_trayMenuCommands.clear();
2186
2187 QVector<Command> displayCommands;
2188
2189 if ( addPluginCommands(&allCommands) || forceSave )
2190 saveCommands(allCommands);
2191
2192 const auto disabledPluginCommands = m_sharedData->itemFactory->commands(false);
2193 Commands commands;
2194 for (const auto &command : allCommands) {
2195 if ( command.enable && !hasCommandFuzzy(disabledPluginCommands, command) )
2196 commands.append(command);
2197 }
2198
2199 for (const auto &command : commands) {
2200 const auto type = command.type();
2201
2202 if (type & CommandType::Automatic)
2203 m_automaticCommands.append(command);
2204
2205 if (type & CommandType::Display)
2206 displayCommands.append(command);
2207
2208 if (type & CommandType::Menu)
2209 m_menuCommands.append(command);
2210
2211 if (m_options.trayCommands && type & CommandType::GlobalShortcut)
2212 m_trayMenuCommands.append(command);
2213
2214 if (type & CommandType::Script)
2215 m_scriptCommands.append(command);
2216 }
2217
2218 if (m_displayCommands != displayCommands) {
2219 m_displayItemList.clear();
2220 m_displayCommands = displayCommands;
2221 reloadBrowsers();
2222 }
2223
2224 updateContextMenu(contextMenuUpdateIntervalMsec);
2225 updateTrayMenuCommands();
2226 emit commandsSaved(commands);
2227 }
2228
addPluginCommands(QVector<Command> * allCommands)2229 bool MainWindow::addPluginCommands(QVector<Command> *allCommands)
2230 {
2231 const auto oldSize = allCommands->size();
2232
2233 for ( const auto &command : m_sharedData->itemFactory->commands() ) {
2234 if ( !hasCommandFuzzy(*allCommands, command) )
2235 allCommands->append(command);
2236 }
2237
2238 return oldSize != allCommands->size();
2239 }
2240
disableHideWindowOnUnfocus()2241 void MainWindow::disableHideWindowOnUnfocus()
2242 {
2243 m_timerHideWindowIfNotActive.disconnect();
2244 }
2245
enableHideWindowOnUnfocus()2246 void MainWindow::enableHideWindowOnUnfocus()
2247 {
2248 initSingleShotTimer( &m_timerHideWindowIfNotActive, 100, this, &MainWindow::hideWindowIfNotActive );
2249 }
2250
hideWindowIfNotActive()2251 void MainWindow::hideWindowIfNotActive()
2252 {
2253 if ( isVisible() && !hasDialogOpen(this) && !isAnyApplicationWindowActive() ) {
2254 COPYQ_LOG("Auto-hiding unfocused main window");
2255 hideWindow();
2256 }
2257 }
2258
theme() const2259 const Theme &MainWindow::theme() const
2260 {
2261 return m_sharedData->theme;
2262 }
2263
runScript(const QString & script,const QVariantMap & data)2264 Action *MainWindow::runScript(const QString &script, const QVariantMap &data)
2265 {
2266 auto act = new Action();
2267 act->setCommand(QStringList() << "copyq" << "eval" << "--" << script);
2268 act->setData(data);
2269 runInternalAction(act);
2270 return act;
2271 }
2272
findTabIndex(const QString & name)2273 int MainWindow::findTabIndex(const QString &name)
2274 {
2275 TabWidget *w = ui->tabWidget;
2276
2277 const int found = findTabIndexExactMatch(name);
2278 if (found != -1)
2279 return found;
2280
2281 // Ignore key hints ('&').
2282 if ( !hasKeyHint(name) ) {
2283 for( int i = 0; i < w->count(); ++i ) {
2284 QString tabName = w->tabName(i);
2285 if ( name == removeKeyHint(&tabName) )
2286 return i;
2287 }
2288 }
2289
2290 return -1;
2291 }
2292
tab(const QString & name)2293 ClipboardBrowser *MainWindow::tab(const QString &name)
2294 {
2295 return createTab(name, MatchSimilarTabName, Tabs())->createBrowser();
2296 }
2297
maybeCloseCommandDialog()2298 bool MainWindow::maybeCloseCommandDialog()
2299 {
2300 return !m_commandDialog || m_commandDialog->maybeClose(this);
2301 }
2302
showError(const QString & msg)2303 void MainWindow::showError(const QString &msg)
2304 {
2305 const auto notificationId = qHash(msg);
2306 auto notification = createNotification( QString::number(notificationId) );
2307 notification->setTitle( tr("CopyQ Error", "Notification error message title") );
2308 notification->setMessage(msg);
2309 notification->setIcon(IconTimesCircle);
2310 }
2311
createNotification(const QString & id)2312 Notification *MainWindow::createNotification(const QString &id)
2313 {
2314 return m_sharedData->notifications->createNotification(id);
2315 }
2316
addCommands(const QVector<Command> & commands)2317 void MainWindow::addCommands(const QVector<Command> &commands)
2318 {
2319 openCommands();
2320 if (m_commandDialog)
2321 m_commandDialog->addCommands(commands);
2322 }
2323
keyPressEvent(QKeyEvent * event)2324 void MainWindow::keyPressEvent(QKeyEvent *event)
2325 {
2326 const int key = event->key();
2327 const Qt::KeyboardModifiers modifiers = event->modifiers();
2328
2329 // Search items or text in editor (search previous when Shift is pressed).
2330 if ( key == Qt::Key_F3
2331 || (modifiers.testFlag(Qt::ControlModifier) && (key == Qt::Key_F || key == Qt::Key_G)) )
2332 {
2333 findNextOrPrevious();
2334 return;
2335 }
2336
2337 auto c = browser();
2338 if (c && c->isInternalEditorOpen())
2339 return;
2340
2341 if (m_options.hideTabs && key == Qt::Key_Alt)
2342 setHideTabs(false);
2343
2344 if (m_options.viMode) {
2345 if (modifiers == Qt::ControlModifier && key == Qt::Key_BracketLeft) {
2346 onEscape();
2347 return;
2348 }
2349
2350 if (browseMode()) {
2351 if (c && handleViKey(event, c))
2352 return;
2353
2354 switch(key) {
2355 case Qt::Key_Slash:
2356 enterSearchMode();
2357 event->accept();
2358 return;
2359 case Qt::Key_H:
2360 previousTab();
2361 event->accept();
2362 return;
2363 case Qt::Key_L:
2364 nextTab();
2365 event->accept();
2366 return;
2367 }
2368 }
2369 }
2370
2371 if ( event->matches(QKeySequence::NextChild) ) {
2372 nextTab();
2373 return;
2374 }
2375
2376 if ( event->matches(QKeySequence::PreviousChild) ) {
2377 previousTab();
2378 return;
2379 }
2380
2381 // Ctrl/Alt+0 to Ctrl/Alt+9 to focus tabs (0 to focus the last, 1 to focus the first and so on).
2382 if (modifiers == Qt::ControlModifier || modifiers == Qt::AltModifier) {
2383 if (key >= Qt::Key_0 && key <= Qt::Key_9) {
2384 const int index = (key == Qt::Key_0) ? ui->tabWidget->count() - 1
2385 : key - Qt::Key_1;
2386 ui->tabWidget->setCurrentIndex(index);
2387 return;
2388 }
2389 }
2390
2391 if (modifiers == Qt::ControlModifier) {
2392 switch(key) {
2393 case Qt::Key_Return:
2394 case Qt::Key_Enter:
2395 if (c)
2396 activateCurrentItem();
2397 return;
2398 default:
2399 QMainWindow::keyPressEvent(event);
2400 break;
2401 }
2402 return;
2403 }
2404
2405 if (modifiers == Qt::AltModifier)
2406 return;
2407
2408 switch(key) {
2409 case Qt::Key_Down:
2410 case Qt::Key_Up:
2411 case Qt::Key_PageDown:
2412 case Qt::Key_PageUp:
2413 if ( c && !c->hasFocus() )
2414 c->setFocus();
2415 break;
2416
2417 case Qt::Key_Return:
2418 case Qt::Key_Enter:
2419 if (c)
2420 activateCurrentItem();
2421 else
2422 getPlaceholder()->createBrowser();
2423 break;
2424
2425 case Qt::Key_Tab:
2426 QMainWindow::keyPressEvent(event);
2427 break;
2428
2429 #ifndef Q_OS_MAC
2430 case Qt::Key_Backspace:
2431 // fallthrough
2432 #endif // Q_OS_MAC
2433 case Qt::Key_Escape:
2434 onEscape();
2435 break;
2436
2437 default:
2438 QMainWindow::keyPressEvent(event);
2439 break;
2440 }
2441 }
2442
keyReleaseEvent(QKeyEvent * event)2443 void MainWindow::keyReleaseEvent(QKeyEvent *event)
2444 {
2445 if (m_options.hideTabs && event->key() == Qt::Key_Alt)
2446 setHideTabs(true);
2447
2448 QMainWindow::keyReleaseEvent(event);
2449 }
2450
event(QEvent * event)2451 bool MainWindow::event(QEvent *event)
2452 {
2453 QEvent::Type type = event->type();
2454
2455 if (type == QEvent::Enter) {
2456 if ( !isActiveWindow() )
2457 updateFocusWindows();
2458 updateWindowTransparency(true);
2459 } else if (type == QEvent::Leave) {
2460 updateWindowTransparency(false);
2461 setHideTabs(m_options.hideTabs);
2462 } else if (type == QEvent::WindowActivate) {
2463 m_isActiveWindow = true;
2464 if ( !isActiveWindow() )
2465 updateFocusWindows();
2466 updateWindowTransparency();
2467 enableHideWindowOnUnfocus();
2468 } else if (type == QEvent::WindowDeactivate) {
2469 updateWindowTransparency();
2470 setHideTabs(m_options.hideTabs);
2471 m_timerUpdateFocusWindows.start();
2472 } else if (type == QEvent::Hide) {
2473 m_wasMaximized = isMaximized();
2474 }
2475
2476 return QMainWindow::event(event);
2477 }
2478
nativeEvent(const QByteArray & eventType,void * message,long * result)2479 bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
2480 {
2481 delayedUpdateForeignFocusWindows();
2482 return QMainWindow::nativeEvent(eventType, message, result);
2483 }
2484
loadSettings(QSettings & settings,AppConfig * appConfig)2485 void MainWindow::loadSettings(QSettings &settings, AppConfig *appConfig)
2486 {
2487 stopMenuCommandFilters(&m_itemMenuMatchCommands);
2488 stopMenuCommandFilters(&m_trayMenuMatchCommands);
2489 terminateAction(&m_displayActionId);
2490
2491 theme().decorateMainWindow(this);
2492 ui->scrollAreaItemPreview->setObjectName("ClipboardBrowser");
2493 theme().decorateItemPreview(ui->scrollAreaItemPreview);
2494
2495 setUseSystemIcons( theme().useSystemIcons() );
2496
2497 m_options.confirmExit = appConfig->option<Config::confirm_exit>();
2498
2499 // always on top window hint
2500 bool alwaysOnTop = appConfig->option<Config::always_on_top>();
2501 setAlwaysOnTop(this, alwaysOnTop);
2502 setAlwaysOnTop(m_commandDialog.data(), alwaysOnTop);
2503
2504 // Vi mode
2505 m_options.viMode = appConfig->option<Config::vi>();
2506 m_trayMenu->setViModeEnabled(m_options.viMode);
2507 m_menu->setViModeEnabled(m_options.viMode);
2508
2509 // Number search
2510 m_trayMenu->setNumberSearchEnabled(m_sharedData->numberSearch);
2511 m_menu->setNumberSearchEnabled(m_sharedData->numberSearch);
2512
2513 m_trayMenu->setRowIndexFromOne(m_sharedData->rowIndexFromOne);
2514 m_menu->setRowIndexFromOne(m_sharedData->rowIndexFromOne);
2515
2516 m_options.transparency = appConfig->option<Config::transparency>();
2517 m_options.transparencyFocused = appConfig->option<Config::transparency_focused>();
2518 updateWindowTransparency();
2519
2520 // save unsaved tab data
2521 if ( ui->tabWidget->count() != 0 ) {
2522 if ( m_timerSaveTabPositions.isActive() )
2523 doSaveTabPositions(appConfig);
2524 ui->tabWidget->saveTabInfo();
2525 }
2526
2527 const QStringList tabNames = savedTabs();
2528
2529 // tab bar position
2530 const bool tabTreeEnabled = appConfig->option<Config::tab_tree>();
2531 ui->tabWidget->setTreeModeEnabled(tabTreeEnabled);
2532 ui->tabWidget->setTabItemCountVisible(appConfig->option<Config::show_tab_item_count>());
2533 for ( auto scrollArea : ui->tabWidget->toolBar()->findChildren<QAbstractScrollArea*>() )
2534 theme().decorateScrollArea(scrollArea);
2535
2536 // create tabs
2537 const Tabs tabs;
2538 for (const auto &name : tabNames)
2539 createTab(name, MatchExactTabName, tabs);
2540
2541 ui->tabWidget->setTabsOrder(tabNames);
2542
2543 m_options.hideTabs = appConfig->option<Config::hide_tabs>();
2544 setHideTabs(m_options.hideTabs);
2545
2546 bool hideToolbar = appConfig->option<Config::hide_toolbar>();
2547 clearActions(m_toolBar);
2548 m_toolBar->setHidden(hideToolbar);
2549 bool hideToolBarLabels = appConfig->option<Config::hide_toolbar_labels>();
2550 m_toolBar->setToolButtonStyle(hideToolBarLabels ? Qt::ToolButtonIconOnly
2551 : Qt::ToolButtonTextUnderIcon);
2552
2553 m_options.hideMainWindow = appConfig->option<Config::hide_main_window>();
2554 m_options.closeOnUnfocus = appConfig->option<Config::close_on_unfocus>();
2555
2556 const bool hideInTaskBar = appConfig->option<Config::hide_main_window_in_task_bar>();
2557 setHideInTaskBar(this, hideInTaskBar);
2558
2559 Q_ASSERT( ui->tabWidget->count() > 0 );
2560
2561 // Save any tabs loaded from new tab files.
2562 appConfig->setOption("tabs", tabNames);
2563
2564 reloadBrowsers();
2565
2566 ui->tabWidget->updateTabs();
2567
2568 m_timerSaveTabPositions.stop();
2569
2570 updateContextMenu(contextMenuUpdateIntervalMsec);
2571 updateItemPreviewAfterMs(itemPreviewUpdateIntervalMsec);
2572
2573 m_options.itemActivationCommands = ActivateNoCommand;
2574 if ( appConfig->option<Config::activate_closes>() )
2575 m_options.itemActivationCommands |= ActivateCloses;
2576 if ( appConfig->option<Config::activate_focuses>() )
2577 m_options.itemActivationCommands |= ActivateFocuses;
2578 if ( appConfig->option<Config::activate_pastes>() )
2579 m_options.itemActivationCommands |= ActivatePastes;
2580
2581 m_options.trayItems = appConfig->option<Config::tray_items>();
2582 m_options.trayItemPaste = appConfig->option<Config::tray_item_paste>();
2583 m_options.trayCommands = appConfig->option<Config::tray_commands>();
2584 m_options.trayCurrentTab = appConfig->option<Config::tray_tab_is_current>();
2585 m_options.trayTabName = appConfig->option<Config::tray_tab>();
2586 m_options.trayImages = appConfig->option<Config::tray_images>();
2587 m_options.trayMenuOpenOnLeftClick = appConfig->option<Config::tray_menu_open_on_left_click>();
2588 m_options.clipboardTab = appConfig->option<Config::clipboard_tab>();
2589
2590 m_singleClickActivate = appConfig->option<Config::activate_item_with_single_click>();
2591
2592 const auto toolTipStyleSheet = theme().getToolTipStyleSheet();
2593 m_trayMenu->setStyleSheet(toolTipStyleSheet);
2594 m_menu->setStyleSheet(toolTipStyleSheet);
2595
2596 setTrayEnabled( !appConfig->option<Config::disable_tray>() );
2597 updateTrayMenuItems();
2598
2599 updateIcon();
2600
2601 menuBar()->setNativeMenuBar( appConfig->option<Config::native_menu_bar>() );
2602
2603 ui->searchBar->loadSettings();
2604
2605 settings.beginGroup("Shortcuts");
2606 loadShortcuts(&m_menuItems, settings);
2607 updateActionShortcuts();
2608 settings.endGroup();
2609
2610 enterBrowseMode();
2611
2612 updateEnabledCommands();
2613
2614 m_sharedData->notifications->setIconColor( theme().color("notification_fg") );
2615 }
2616
loadTheme(const QSettings & themeSettings)2617 void MainWindow::loadTheme(const QSettings &themeSettings)
2618 {
2619 m_sharedData->theme.loadTheme(themeSettings);
2620 if (themeSettings.status() != QSettings::NoError)
2621 return;
2622
2623 {
2624 Settings settings;
2625 settings.beginGroup("Theme");
2626 m_sharedData->theme.saveTheme(settings.settingsData());
2627 settings.endGroup();
2628 }
2629
2630 AppConfig appConfig;
2631 emit configurationChanged(&appConfig);
2632 }
2633
openHelp()2634 void MainWindow::openHelp()
2635 {
2636 QDesktopServices::openUrl( QUrl("https://copyq.readthedocs.io") );
2637 }
2638
showWindow()2639 void MainWindow::showWindow()
2640 {
2641 if ( isWindowVisible() )
2642 return;
2643
2644 m_trayMenu->close();
2645 m_menu->close();
2646
2647 updateFocusWindows();
2648
2649 moveToCurrentWorkspace(this);
2650
2651 if (m_wasMaximized || isMaximized())
2652 showMaximized();
2653 else
2654 showNormal();
2655 raise();
2656 activateWindow();
2657
2658 auto c = browser();
2659 if (c) {
2660 if ( !c->isInternalEditorOpen() )
2661 c->scrollTo( c->currentIndex() );
2662 c->setFocus();
2663 }
2664
2665 QApplication::setActiveWindow(this);
2666
2667 stealFocus(*this);
2668 }
2669
hideWindow()2670 void MainWindow::hideWindow()
2671 {
2672 if ( closeMinimizes() )
2673 minimizeWindow();
2674 else
2675 hide();
2676
2677 // It can be unexpected to have search active or random items selected when
2678 // reopening main window. This resets search and selection after the window
2679 // is closed.
2680 if ( !browseMode() ) {
2681 enterBrowseMode();
2682 auto c = browser();
2683 if (c)
2684 c->setCurrent(0);
2685 }
2686 }
2687
minimizeWindow()2688 void MainWindow::minimizeWindow()
2689 {
2690 if (m_options.hideMainWindow)
2691 hide();
2692 else
2693 showMinimized();
2694 }
2695
toggleVisible()2696 bool MainWindow::toggleVisible()
2697 {
2698 if ( isWindowVisible() ) {
2699 hideWindow();
2700 return false;
2701 }
2702
2703 showWindow();
2704 return true;
2705 }
2706
showBrowser(const ClipboardBrowser * browser)2707 void MainWindow::showBrowser(const ClipboardBrowser *browser)
2708 {
2709 int i = 0;
2710 for( ; i < ui->tabWidget->count() && getPlaceholder(i)->browser() != browser; ++i ) {}
2711 setCurrentTab(i);
2712 showWindow();
2713 }
2714
setCurrentTab(int index)2715 bool MainWindow::setCurrentTab(int index)
2716 {
2717 if ( index < 0 || ui->tabWidget->count() <= index )
2718 return false;
2719
2720 ui->tabWidget->setCurrentIndex(index);
2721 return true;
2722 }
2723
focusPrevious()2724 bool MainWindow::focusPrevious()
2725 {
2726 if ( !m_lastWindow )
2727 return false;
2728
2729 m_lastWindow->raise();
2730 return true;
2731 }
2732
onMenuActionTriggered(const QVariantMap & data,bool omitPaste)2733 void MainWindow::onMenuActionTriggered(const QVariantMap &data, bool omitPaste)
2734 {
2735 m_menu->close();
2736 activateMenuItem( getPlaceholderForMenu(), data, omitPaste );
2737 }
2738
onTrayActionTriggered(const QVariantMap & data,bool omitPaste)2739 void MainWindow::onTrayActionTriggered(const QVariantMap &data, bool omitPaste)
2740 {
2741 m_trayMenu->close();
2742 activateMenuItem( getPlaceholderForTrayMenu(), data, omitPaste );
2743 }
2744
trayActivated(QSystemTrayIcon::ActivationReason reason)2745 void MainWindow::trayActivated(QSystemTrayIcon::ActivationReason reason)
2746 {
2747 if ( reason == QSystemTrayIcon::MiddleClick
2748 || (m_options.trayMenuOpenOnLeftClick && reason == QSystemTrayIcon::Trigger) )
2749 {
2750 toggleMenu();
2751 } else if ( reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::DoubleClick ) {
2752 // Like toggleVisible() but hide window if visible and not focused
2753 // (this seems better behavior when using mouse).
2754 if (!isMinimized() && isVisible())
2755 hideWindow();
2756 else
2757 showWindow();
2758
2759 }
2760 }
2761
toggleMenu()2762 bool MainWindow::toggleMenu()
2763 {
2764 m_trayMenu->search(QString());
2765
2766 if ( !m_trayMenu->isVisible() )
2767 updateTrayMenuItemsTimeout();
2768
2769 return toggleMenu(m_trayMenu);
2770 }
2771
toggleMenu(const QString & tabName,int itemCount,QPoint position)2772 bool MainWindow::toggleMenu(const QString &tabName, int itemCount, QPoint position)
2773 {
2774 // Just close the previously opened menu if parameters are the same.
2775 if ( m_menu->isVisible()
2776 && (m_menuTabName == tabName && m_menuMaxItemCount == itemCount) )
2777 {
2778 m_menu->close();
2779 return false;
2780 }
2781
2782 WidgetSizeGuard sizeGuard(m_menu);
2783
2784 m_menuTabName = tabName;
2785 m_menuMaxItemCount = itemCount;
2786 if (m_menuMaxItemCount < 0)
2787 m_menuMaxItemCount = m_options.trayItems > 0 ? m_options.trayItems : 10;
2788
2789 m_menu->clearAllActions();
2790 filterMenuItems(QString());
2791
2792 if ( m_menu->isVisible() )
2793 m_menu->close();
2794
2795 if ( m_menu->isEmpty() )
2796 return false;
2797
2798 if (position.x() >= 0 && position.y() >= 0)
2799 return toggleMenu(m_menu, position);
2800
2801 return toggleMenu(m_menu);
2802 }
2803
tabChanged(int current,int)2804 void MainWindow::tabChanged(int current, int)
2805 {
2806 bool currentIsTabGroup = current == -1;
2807
2808 emit tabGroupSelected(currentIsTabGroup);
2809
2810 if (!currentIsTabGroup) {
2811 // update item menu (necessary for keyboard shortcuts to work)
2812 auto c = browserOrNull();
2813 if (c) {
2814 c->filterItems( browseMode() ? nullptr : ui->searchBar->filter() );
2815
2816 if ( current >= 0 ) {
2817 if( !c->currentIndex().isValid() && isVisible() ) {
2818 c->setCurrent(0);
2819 }
2820 }
2821
2822 setTabOrder(ui->searchBar, c);
2823 }
2824 }
2825
2826 updateContextMenu(0);
2827 updateItemPreviewAfterMs(0);
2828
2829 if (m_options.trayCurrentTab)
2830 updateTrayMenuItems();
2831 }
2832
saveTabPositions()2833 void MainWindow::saveTabPositions()
2834 {
2835 m_timerSaveTabPositions.start();
2836 }
2837
onSaveTabPositionsTimer()2838 void MainWindow::onSaveTabPositionsTimer()
2839 {
2840 AppConfig appConfig;
2841 doSaveTabPositions(&appConfig);
2842 }
2843
doSaveTabPositions(AppConfig * appConfig)2844 void MainWindow::doSaveTabPositions(AppConfig *appConfig)
2845 {
2846 m_timerSaveTabPositions.stop();
2847 const QStringList tabs = ui->tabWidget->tabs();
2848 appConfig->setOption("tabs", tabs);
2849 }
2850
tabsMoved(const QString & oldPrefix,const QString & newPrefix)2851 void MainWindow::tabsMoved(const QString &oldPrefix, const QString &newPrefix)
2852 {
2853 const QStringList tabs = ui->tabWidget->tabs();
2854 Q_ASSERT( oldPrefix == newPrefix || !tabs.contains(oldPrefix) );
2855 Q_ASSERT( !tabs.contains(QString()) );
2856
2857 const QString prefix = oldPrefix + '/';
2858
2859 // Rename tabs if needed.
2860 for (int i = 0 ; i < tabs.size(); ++i) {
2861 auto placeholder = getPlaceholder(i);
2862 const QString oldTabName = placeholder->tabName();
2863
2864 if ( (oldTabName == oldPrefix || oldTabName.startsWith(prefix)) && newPrefix != oldPrefix) {
2865 const QString newName = newPrefix + oldTabName.mid(oldPrefix.size());
2866 updateTabIcon(newName, placeholder->tabName());
2867 if ( placeholder->setTabName(newName) ) {
2868 auto c = placeholder->browser();
2869 if (c)
2870 ui->tabWidget->setTabItemCount( newName, c->length() );
2871 }
2872 }
2873 }
2874
2875 saveTabPositions();
2876 }
2877
tabBarMenuRequested(QPoint pos,int tab)2878 void MainWindow::tabBarMenuRequested(QPoint pos, int tab)
2879 {
2880 auto placeholder = getPlaceholder(tab);
2881 if (placeholder == nullptr)
2882 return;
2883 const QString tabName = placeholder->tabName();
2884 popupTabBarMenu(pos, tabName);
2885 }
2886
tabTreeMenuRequested(QPoint pos,const QString & groupPath)2887 void MainWindow::tabTreeMenuRequested(QPoint pos, const QString &groupPath)
2888 {
2889 popupTabBarMenu(pos, groupPath);
2890 }
2891
tabCloseRequested(int tab)2892 void MainWindow::tabCloseRequested(int tab)
2893 {
2894 removeTab(true, tab);
2895 }
2896
config(const QVariantList & nameValue)2897 QVariant MainWindow::config(const QVariantList &nameValue)
2898 {
2899 AppConfig appConfig;
2900
2901 if ( m_timerSaveTabPositions.isActive() )
2902 doSaveTabPositions(&appConfig);
2903
2904 ConfigurationManager configurationManager;
2905
2906 QStringList unknownOptions;
2907 const auto validOptions = configurationManager.options();
2908
2909 // Check if option names are valid.
2910 for (int i = 0; i < nameValue.size(); i += 2) {
2911 const QString name = nameValue[i].toString();
2912 if ( !validOptions.contains(name) )
2913 unknownOptions.append(name);
2914 }
2915
2916 if ( !unknownOptions.isEmpty() )
2917 return unknownOptions;
2918
2919 configurationManager.loadSettings(&appConfig);
2920
2921 QVariantMap result;
2922 bool emitConfigurationChanged = false;
2923 for (int i = 0; i < nameValue.size(); i += 2) {
2924 const QString name = nameValue[i].toString();
2925 const QVariant value = nameValue.value(i + 1);
2926 if ( i + 1 < nameValue.size() && configurationManager.setOptionValue(name, value, &appConfig) )
2927 emitConfigurationChanged = true;
2928
2929 result.insert( name, configurationManager.optionValue(name) );
2930 }
2931
2932 if (emitConfigurationChanged) {
2933 configurationManager.setAutostartEnable(&appConfig);
2934 emit configurationChanged(&appConfig);
2935 }
2936
2937 return result;
2938 }
2939
configDescription()2940 QString MainWindow::configDescription()
2941 {
2942 ConfigurationManager configurationManager;
2943 QStringList options = configurationManager.options();
2944 options.sort();
2945 QString opts;
2946 for (const auto &option : options)
2947 opts.append( option + "\n " + configurationManager.optionToolTip(option).replace('\n', "\n ") + '\n' );
2948 return opts;
2949 }
2950
actionData(int id) const2951 QVariantMap MainWindow::actionData(int id) const
2952 {
2953 return m_sharedData->actions->actionData(id);
2954 }
2955
setActionData(int id,const QVariantMap & data)2956 void MainWindow::setActionData(int id, const QVariantMap &data)
2957 {
2958 m_sharedData->actions->setActionData(id, data);
2959 }
2960
setCommands(const QVector<Command> & commands)2961 void MainWindow::setCommands(const QVector<Command> &commands)
2962 {
2963 if ( !maybeCloseCommandDialog() )
2964 return;
2965
2966 updateCommands(commands, true);
2967 }
2968
setSessionIconColor(QColor color)2969 void MainWindow::setSessionIconColor(QColor color)
2970 {
2971 ::setSessionIconColor(color);
2972 updateIcon();
2973 }
2974
setSessionIconTag(const QString & tag)2975 void MainWindow::setSessionIconTag(const QString &tag)
2976 {
2977 ::setSessionIconTag(tag);
2978 updateIcon();
2979 }
2980
setSessionIconTagColor(QColor color)2981 void MainWindow::setSessionIconTagColor(QColor color)
2982 {
2983 ::setSessionIconTagColor(color);
2984 updateIcon();
2985 }
2986
sessionIconColor() const2987 QColor MainWindow::sessionIconColor() const
2988 {
2989 return ::sessionIconColor();
2990 }
2991
sessionIconTag() const2992 QString MainWindow::sessionIconTag() const
2993 {
2994 return ::sessionIconTag();
2995 }
2996
sessionIconTagColor() const2997 QColor MainWindow::sessionIconTagColor() const
2998 {
2999 return ::sessionIconTagColor();
3000 }
3001
setTrayTooltip(const QString & tooltip)3002 void MainWindow::setTrayTooltip(const QString &tooltip)
3003 {
3004 if (m_tray)
3005 m_tray->setToolTip(tooltip);
3006 }
3007
setMenuItemEnabled(int actionId,int currentRun,int menuItemMatchCommandIndex,const QVariantMap & menuItem)3008 bool MainWindow::setMenuItemEnabled(int actionId, int currentRun, int menuItemMatchCommandIndex, const QVariantMap &menuItem)
3009 {
3010 if (actionId != m_trayMenuMatchCommands.actionId && actionId != m_itemMenuMatchCommands.actionId)
3011 return false;
3012
3013 const auto &menuMatchCommands = actionId == m_trayMenuMatchCommands.actionId
3014 ? m_trayMenuMatchCommands
3015 : m_itemMenuMatchCommands;
3016
3017 if (currentRun != menuMatchCommands.currentRun)
3018 return false;
3019
3020 if (menuMatchCommands.actions.size() <= menuItemMatchCommandIndex)
3021 return false;
3022
3023 auto action = menuMatchCommands.actions[menuItemMatchCommandIndex];
3024 if (!action)
3025 return true;
3026
3027 for (auto it = menuItem.constBegin(); it != menuItem.constEnd(); ++it) {
3028 const auto &key = it.key();
3029 if (key == menuItemKeyColor || key == menuItemKeyIcon || key == menuItemKeyTag)
3030 continue;
3031
3032 const auto value = it.value();
3033 action->setProperty(key.toLatin1(), value);
3034 }
3035
3036 if ( menuItem.contains(menuItemKeyTag) || menuItem.contains(menuItemKeyIcon) ) {
3037 QString icon = menuItem.value(menuItemKeyIcon).toString();
3038 if (icon.isEmpty()) {
3039 const auto commandAction = qobject_cast<CommandAction*>(action);
3040 if (commandAction)
3041 icon = commandAction->command().icon;
3042 }
3043 const QString colorName = menuItem.value(menuItemKeyColor).toString();
3044 const QColor color = colorName.isEmpty() ? getDefaultIconColor(*this) : deserializeColor(colorName);
3045 const QString tag = menuItem.value(menuItemKeyTag).toString();
3046 action->setIcon( iconFromFile(icon, tag, color) );
3047 }
3048
3049 const bool enabled = action->isEnabled();
3050 action->setProperty(propertyActionFilterCommandFailed, !enabled);
3051
3052 const auto shortcuts = action->shortcuts();
3053
3054 if ( !enabled && (actionId == m_trayMenuMatchCommands.actionId || !m_menuItem->isVisible()) )
3055 action->deleteLater();
3056
3057 if ( !shortcuts.isEmpty() )
3058 updateActionShortcuts();
3059
3060 return true;
3061 }
3062
setDisplayData(int actionId,const QVariantMap & data)3063 QVariantMap MainWindow::setDisplayData(int actionId, const QVariantMap &data)
3064 {
3065 if (m_displayActionId != actionId)
3066 return QVariantMap();
3067
3068 m_currentDisplayItem.setData(data);
3069
3070 clearHiddenDisplayData();
3071
3072 if ( m_displayItemList.isEmpty() )
3073 return QVariantMap();
3074
3075 m_currentDisplayItem = m_displayItemList.takeFirst();
3076 m_sharedData->actions->setActionData(actionId, m_currentDisplayItem.data());
3077 return m_currentDisplayItem.data();
3078 }
3079
nextTab()3080 void MainWindow::nextTab()
3081 {
3082 ui->tabWidget->nextTab();
3083 }
3084
previousTab()3085 void MainWindow::previousTab()
3086 {
3087 ui->tabWidget->previousTab();
3088 }
3089
setClipboard(const QVariantMap & data)3090 void MainWindow::setClipboard(const QVariantMap &data)
3091 {
3092 setClipboard(data, ClipboardMode::Clipboard);
3093 #ifdef HAS_MOUSE_SELECTIONS
3094 setClipboard(data, ClipboardMode::Selection);
3095 #endif
3096 }
3097
setClipboard(const QVariantMap & data,ClipboardMode mode)3098 void MainWindow::setClipboard(const QVariantMap &data, ClipboardMode mode)
3099 {
3100 m_clipboard->setData(mode, data);
3101 }
3102
setClipboardAndSelection(const QVariantMap & data)3103 void MainWindow::setClipboardAndSelection(const QVariantMap &data)
3104 {
3105 setClipboard(data);
3106 }
3107
moveToClipboard(ClipboardBrowser * c,int row)3108 void MainWindow::moveToClipboard(ClipboardBrowser *c, int row)
3109 {
3110 const auto index = c ? c->index(row) : QModelIndex();
3111 if ( index.isValid() )
3112 c->moveToClipboard(index);
3113 else
3114 setClipboard(QVariantMap());
3115 }
3116
getClipboardData(ClipboardMode mode)3117 const QMimeData *MainWindow::getClipboardData(ClipboardMode mode)
3118 {
3119 return m_clipboard->mimeData(mode);
3120 }
3121
activateCurrentItem()3122 void MainWindow::activateCurrentItem()
3123 {
3124 // Omit activating item multiple times in quick succession.
3125 if (m_activatingItem)
3126 return;
3127
3128 m_activatingItem = true;
3129 activateCurrentItemHelper();
3130 m_activatingItem = false;
3131 }
3132
activateCurrentItemHelper()3133 void MainWindow::activateCurrentItemHelper()
3134 {
3135 if ( QApplication::queryKeyboardModifiers() == Qt::NoModifier
3136 && isItemMenuDefaultActionValid() )
3137 {
3138 m_menuItem->defaultAction()->trigger();
3139 return;
3140 }
3141
3142 auto c = browser();
3143 if (!c)
3144 return;
3145
3146 // Perform custom actions on item activation.
3147 PlatformWindowPtr lastWindow = m_lastWindow;
3148 const bool paste = lastWindow && m_options.activatePastes() && canPaste();
3149 const bool activateWindow = paste || (lastWindow && m_options.activateFocuses());
3150
3151 // Copy current item or selection to clipboard.
3152 // While clipboard is being set (in separate process)
3153 // activate target window for pasting.
3154 c->moveToClipboard();
3155
3156 if ( m_options.activateCloses() )
3157 hideWindow();
3158
3159 if (activateWindow)
3160 lastWindow->raise();
3161
3162 enterBrowseMode();
3163
3164 if (paste) {
3165 COPYQ_LOG( QString("Pasting item from main window to \"%1\".")
3166 .arg(lastWindow->getTitle()) );
3167 lastWindow->pasteClipboard();
3168 }
3169 }
3170
onItemClicked()3171 void MainWindow::onItemClicked()
3172 {
3173 if (m_singleClickActivate)
3174 activateCurrentItem();
3175 }
3176
onItemDoubleClicked()3177 void MainWindow::onItemDoubleClicked()
3178 {
3179 if (!m_singleClickActivate)
3180 activateCurrentItem();
3181 }
3182
disableClipboardStoring(bool disable)3183 void MainWindow::disableClipboardStoring(bool disable)
3184 {
3185 if (m_clipboardStoringDisabled == disable)
3186 return;
3187
3188 m_clipboardStoringDisabled = disable;
3189 emit disableClipboardStoringRequest(disable);
3190
3191 updateMonitoringActions();
3192
3193 ::setSessionIconEnabled(!disable);
3194
3195 updateIcon();
3196
3197 if (m_clipboardStoringDisabled)
3198 runScript("setTitle(); showDataNotification()");
3199
3200 COPYQ_LOG( QString("Clipboard monitoring %1.")
3201 .arg(m_clipboardStoringDisabled ? "disabled" : "enabled") );
3202 }
3203
isMonitoringEnabled() const3204 bool MainWindow::isMonitoringEnabled() const
3205 {
3206 return !m_clipboardStoringDisabled;
3207 }
3208
toggleClipboardStoring()3209 void MainWindow::toggleClipboardStoring()
3210 {
3211 disableClipboardStoring(!m_clipboardStoringDisabled);
3212 }
3213
tabs() const3214 QStringList MainWindow::tabs() const
3215 {
3216 return ui->tabWidget->tabs();
3217 }
3218
getPlaceholderForMenu()3219 ClipboardBrowserPlaceholder *MainWindow::getPlaceholderForMenu()
3220 {
3221 const auto i = findTabIndex(m_menuTabName);
3222 return i != -1 ? getPlaceholder(i) : nullptr;
3223 }
3224
getPlaceholderForTrayMenu()3225 ClipboardBrowserPlaceholder *MainWindow::getPlaceholderForTrayMenu()
3226 {
3227 if (m_options.trayCurrentTab)
3228 return getPlaceholder();
3229
3230 if ( m_options.trayTabName.isEmpty() )
3231 return m_options.clipboardTab.isEmpty() ? nullptr : getPlaceholder(m_options.clipboardTab);
3232
3233 int i = findTabIndex(m_options.trayTabName);
3234 return i != -1 ? getPlaceholder(i) : nullptr;
3235 }
3236
onFilterChanged()3237 void MainWindow::onFilterChanged()
3238 {
3239 ItemFilterPtr filter = ui->searchBar->filter();
3240 if ( filter->matchesAll() )
3241 enterBrowseMode();
3242 else if ( browseMode() )
3243 enterSearchMode();
3244
3245 auto c = browser();
3246 if (c)
3247 c->filterItems(filter);
3248 updateItemPreviewAfterMs(2 * itemPreviewUpdateIntervalMsec);
3249 }
3250
raiseLastWindowAfterMenuClosed()3251 void MainWindow::raiseLastWindowAfterMenuClosed()
3252 {
3253 if ( m_lastWindow && !isAnyApplicationWindowActive() )
3254 m_lastWindow->raise();
3255 }
3256
updateFocusWindows()3257 void MainWindow::updateFocusWindows()
3258 {
3259 m_isActiveWindow = isActiveWindow();
3260
3261 if ( QApplication::activePopupWidget() )
3262 return;
3263
3264 auto platform = platformNativeInterface();
3265 PlatformWindowPtr lastWindow = platform->getCurrentWindow();
3266 if (lastWindow) {
3267 const QString title = lastWindow->getTitle();
3268 const QWidget *activeWindow = qApp->activeWindow();
3269 if (activeWindow) {
3270 if (activeWindow == m_trayMenu || activeWindow == m_menu) {
3271 COPYQ_LOG(QString("Focus window is \"%1\" - tray menu - ignoring").arg(title) );
3272 } else {
3273 COPYQ_LOG(QString("Focus window is \"%1\": [%2] %3").arg(
3274 title,
3275 QLatin1String(activeWindow->metaObject()->className()),
3276 activeWindow->windowTitle()
3277 ));
3278 m_lastWindow = lastWindow;
3279 }
3280 } else {
3281 COPYQ_LOG( QString("Focus window is \"%1\"").arg(title) );
3282 m_lastWindow = lastWindow;
3283 }
3284 }
3285
3286 if (m_options.closeOnUnfocus)
3287 m_timerHideWindowIfNotActive.start();
3288 }
3289
updateShortcuts()3290 void MainWindow::updateShortcuts()
3291 {
3292 if ( m_timerUpdateContextMenu.isActive() ) {
3293 m_timerUpdateContextMenu.stop();
3294 updateContextMenuTimeout();
3295 }
3296 }
3297
findNextOrPrevious()3298 void MainWindow::findNextOrPrevious()
3299 {
3300 if (browseMode()) {
3301 enterSearchMode();
3302 } else {
3303 auto c = browser();
3304 if (!c)
3305 return;
3306
3307 const bool next = !QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
3308 if ( c->isInternalEditorOpen() ) {
3309 ui->searchBar->setFocus(Qt::ShortcutFocusReason);
3310 if (next)
3311 c->findNext();
3312 else
3313 c->findPrevious();
3314 } else {
3315 c->setFocus();
3316 c->setCurrent( c->currentIndex().row() + (next ? 1 : -1) );
3317 }
3318 }
3319 }
3320
enterBrowseMode()3321 void MainWindow::enterBrowseMode()
3322 {
3323 getPlaceholder()->setFocus();
3324 ui->searchBar->hide();
3325
3326 auto c = browserOrNull();
3327 if (c)
3328 c->filterItems(nullptr);
3329 }
3330
enterSearchMode()3331 void MainWindow::enterSearchMode()
3332 {
3333 ui->searchBar->show();
3334 ui->searchBar->setFocus(Qt::ShortcutFocusReason);
3335
3336 if ( !ui->searchBar->text().isEmpty() ) {
3337 auto c = browserOrNull();
3338 if (c) {
3339 const int currentRow = c->currentIndex().row();
3340 c->filterItems( ui->searchBar->filter() );
3341 c->setCurrent(currentRow);
3342 }
3343 }
3344 }
3345
enterSearchMode(const QString & txt)3346 void MainWindow::enterSearchMode(const QString &txt)
3347 {
3348 const bool searchModeActivated = !ui->searchBar->isVisible();
3349
3350 ui->searchBar->show();
3351 ui->searchBar->setFocus(Qt::ShortcutFocusReason);
3352
3353 if (searchModeActivated)
3354 ui->searchBar->setText(txt);
3355 else
3356 ui->searchBar->setText( ui->searchBar->text() + txt );
3357
3358 auto c = browser();
3359 if (c)
3360 c->filterItems( ui->searchBar->filter() );
3361 }
3362
updateTrayMenuItemsTimeout()3363 void MainWindow::updateTrayMenuItemsTimeout()
3364 {
3365 if (!m_trayMenuDirty)
3366 return;
3367
3368 // Update tray only if not currently visible.
3369 if ( m_trayMenu->isVisible() ) {
3370 updateTrayMenuItems();
3371 return;
3372 }
3373
3374 m_trayMenuDirty = false;
3375
3376 COPYQ_LOG("Updating tray menu");
3377
3378 WidgetSizeGuard sizeGuard(m_trayMenu);
3379
3380 interruptMenuCommandFilters(&m_trayMenuMatchCommands);
3381 m_trayMenu->clearClipboardItems();
3382
3383 filterTrayMenuItems(QString());
3384 }
3385
filterMenuItems(const QString & searchText)3386 void MainWindow::filterMenuItems(const QString &searchText)
3387 {
3388 addMenuItems(m_menu, getPlaceholderForMenu(), m_menuMaxItemCount, searchText);
3389 }
3390
filterTrayMenuItems(const QString & searchText)3391 void MainWindow::filterTrayMenuItems(const QString &searchText)
3392 {
3393 addMenuItems(m_trayMenu, getPlaceholderForTrayMenu(), m_options.trayItems, searchText);
3394 m_trayMenu->markItemInClipboard(m_clipboardData);
3395 }
3396
openLogDialog()3397 void MainWindow::openLogDialog()
3398 {
3399 openDialog<LogDialog>(this);
3400 }
3401
openAboutDialog()3402 void MainWindow::openAboutDialog()
3403 {
3404 openDialog<AboutDialog>(this);
3405 }
3406
showClipboardContent()3407 void MainWindow::showClipboardContent()
3408 {
3409 ClipboardDialog *clipboardDialog = openDialog<ClipboardDialog>(this);
3410 connect( clipboardDialog, &ClipboardDialog::changeClipboard,
3411 this, &MainWindow::setClipboardAndSelection );
3412 }
3413
showProcessManagerDialog()3414 void MainWindow::showProcessManagerDialog()
3415 {
3416 m_sharedData->actions->showProcessManagerDialog(this);
3417 }
3418
openActionDialog(const QVariantMap & data)3419 ActionDialog *MainWindow::openActionDialog(const QVariantMap &data)
3420 {
3421 auto actionDialog = openDialog<ActionDialog>(this);
3422 actionDialog->setInputData(data);
3423
3424 const auto tabs = ui->tabWidget->tabs();
3425 actionDialog->setOutputTabs(tabs);
3426
3427 const int currentTabIndex = ui->tabWidget->currentIndex();
3428 if (currentTabIndex >= 0) {
3429 const auto currentTab = ui->tabWidget->tabName(currentTabIndex);
3430 actionDialog->setCurrentTab(currentTab);
3431 }
3432
3433 connect( actionDialog, &ActionDialog::commandAccepted,
3434 this, &MainWindow::onActionDialogAccepted );
3435
3436 connect( actionDialog, &ActionDialog::saveCommand,
3437 this, &MainWindow::onSaveCommand );
3438
3439 stealFocus(*actionDialog);
3440
3441 return actionDialog;
3442 }
3443
openActionDialog()3444 void MainWindow::openActionDialog()
3445 {
3446 auto c = browser();
3447 const auto data = c ? addSelectionData(*c) : QVariantMap();
3448 openActionDialog(data);
3449 }
3450
showItemContent()3451 void MainWindow::showItemContent()
3452 {
3453 auto c = browser( ui->tabWidget->currentIndex() );
3454 if (!c)
3455 return;
3456
3457 const QModelIndex current = c->currentIndex();
3458 if ( current.isValid() )
3459 openDialog<ClipboardDialog>(current, c->model(), this);
3460 }
3461
openPreferences()3462 void MainWindow::openPreferences()
3463 {
3464 if ( !isEnabled() )
3465 return;
3466
3467 if (cm) {
3468 cm->activateWindow();
3469 return;
3470 }
3471
3472 ConfigurationManager configurationManager(m_sharedData->itemFactory, this);
3473 WindowGeometryGuard::create(&configurationManager);
3474
3475 // notify window if configuration changes
3476 connect( &configurationManager, &ConfigurationManager::configurationChanged,
3477 this, &MainWindow::configurationChanged );
3478 connect( &configurationManager, &ConfigurationManager::error,
3479 this, &MainWindow::showError );
3480
3481 // WORKAROUND: Fix drag'n'drop in list in modal dialog for Qt 5.9.2 (QTBUG-63846).
3482 configurationManager.setWindowModality(Qt::WindowModal);
3483
3484 cm = &configurationManager;
3485 configurationManager.exec();
3486 cm = nullptr;
3487 }
3488
openCommands()3489 void MainWindow::openCommands()
3490 {
3491 if ( !isEnabled() )
3492 return;
3493
3494 if (m_commandDialog) {
3495 m_commandDialog->show();
3496 m_commandDialog->activateWindow();
3497 } else {
3498 const QVector<Command> pluginCommands = m_sharedData->itemFactory->commands();
3499 QStringList formats = m_sharedData->itemFactory->formatsToSave();
3500 formats.prepend(mimeText);
3501 formats.removeDuplicates();
3502
3503 QWidget *parent = this;
3504 if (cm)
3505 parent = cm;
3506
3507 m_commandDialog = openDialog<CommandDialog>(pluginCommands, formats, parent);
3508 connect(this, &QObject::destroyed, m_commandDialog.data(), &QWidget::close);
3509 connect(m_commandDialog.data(), &CommandDialog::commandsSaved, this, &MainWindow::updateEnabledCommands);
3510 }
3511
3512 if (cm && cm->isVisible())
3513 m_commandDialog->setWindowModality(Qt::ApplicationModal);
3514 }
3515
browser(int index)3516 ClipboardBrowser *MainWindow::browser(int index)
3517 {
3518 return getPlaceholder(index)->createBrowser();
3519 }
3520
browser()3521 ClipboardBrowser *MainWindow::browser()
3522 {
3523 return browser( ui->tabWidget->currentIndex() );
3524 }
3525
browserOrNull()3526 ClipboardBrowser *MainWindow::browserOrNull()
3527 {
3528 const ClipboardBrowserPlaceholder *currentPlaceholder = getPlaceholder();
3529 return currentPlaceholder ? currentPlaceholder->browser() : nullptr;
3530 }
3531
browserForItem(const QModelIndex & index)3532 ClipboardBrowser *MainWindow::browserForItem(const QModelIndex &index)
3533 {
3534 if ( index.isValid() ) {
3535 auto c = qobject_cast<ClipboardBrowser*>(index.model()->parent());
3536 Q_ASSERT(c);
3537 return c;
3538 }
3539
3540 return nullptr;
3541 }
3542
addAndFocusTab(const QString & name)3543 void MainWindow::addAndFocusTab(const QString &name)
3544 {
3545 auto placeholder = createTab(name, MatchExactTabName, Tabs());
3546 if (!placeholder)
3547 return;
3548
3549 int i = 0;
3550 for( ; i < ui->tabWidget->count() && getPlaceholder(i) != placeholder; ++i ) {}
3551 setCurrentTab(i);
3552
3553 saveTabPositions();
3554 }
3555
editNewItem()3556 void MainWindow::editNewItem()
3557 {
3558 auto c = browser( ui->tabWidget->currentIndex() );
3559 if (!c)
3560 return;
3561
3562 showWindow();
3563 if ( !c->isInternalEditorOpen() ) {
3564 c->setFocus();
3565 c->editNew();
3566 }
3567 }
3568
pasteItems()3569 void MainWindow::pasteItems()
3570 {
3571 const QMimeData *data = m_clipboard->mimeData(ClipboardMode::Clipboard);
3572 if (data == nullptr)
3573 return;
3574
3575 auto c = browser();
3576 if (!c)
3577 return;
3578
3579 QModelIndexList list = c->selectionModel()->selectedIndexes();
3580 std::sort( list.begin(), list.end() );
3581 const int row = list.isEmpty() ? 0 : list.first().row();
3582 c->addAndSelect( cloneData(*data), row );
3583 }
3584
copyItems()3585 void MainWindow::copyItems()
3586 {
3587 auto c = browser();
3588 if (!c)
3589 return;
3590
3591 const QModelIndexList indexes = c->selectionModel()->selectedRows();
3592 if ( indexes.isEmpty() )
3593 return;
3594
3595 const auto data = c->copyIndexes(indexes);
3596 setClipboard(data);
3597 }
3598
saveTab(const QString & fileName,int tabIndex)3599 bool MainWindow::saveTab(const QString &fileName, int tabIndex)
3600 {
3601 QFile file(fileName);
3602 if ( !file.open(QIODevice::WriteOnly | QIODevice::Truncate) )
3603 return false;
3604
3605 QDataStream out(&file);
3606 out.setVersion(QDataStream::Qt_4_7);
3607
3608 int i = tabIndex >= 0 ? tabIndex : ui->tabWidget->currentIndex();
3609 auto c = browser(i);
3610 if (!c)
3611 return false;
3612
3613 out << QByteArray("CopyQ v2") << c->tabName();
3614 serializeData(*c->model(), &out);
3615
3616 file.close();
3617
3618 return true;
3619 }
3620
exportData()3621 bool MainWindow::exportData()
3622 {
3623 ImportExportDialog exportDialog(this);
3624 exportDialog.setWindowTitle( tr("Options for Export") );
3625 exportDialog.setTabs( ui->tabWidget->tabs() );
3626 if ( !ui->tabWidget->isTabGroupSelected() )
3627 exportDialog.setCurrentTab( getPlaceholder()->tabName() );
3628 if ( exportDialog.exec() != QDialog::Accepted )
3629 return false;
3630
3631 auto fileName = QFileDialog::getSaveFileName(
3632 this, QString(), QString(), importExportFileDialogFilter() );
3633 if ( fileName.isNull() )
3634 return false;
3635
3636 if ( !fileName.endsWith(".cpq") )
3637 fileName.append(".cpq");
3638
3639 const auto tabs = exportDialog.selectedTabs();
3640 const bool exportConfiguration = exportDialog.isConfigurationEnabled();
3641 const bool exportCommands = exportDialog.isCommandsEnabled();
3642
3643 if ( !exportDataFrom(fileName, tabs, exportConfiguration, exportCommands) ) {
3644 QMessageBox::critical(
3645 this, tr("Export Error"),
3646 tr("Failed to export file %1!")
3647 .arg(quoteString(fileName)) );
3648 return false;
3649 }
3650
3651 return true;
3652 }
3653
saveTabs()3654 void MainWindow::saveTabs()
3655 {
3656 for( int i = 0; i < ui->tabWidget->count(); ++i ) {
3657 auto c = getPlaceholder(i)->browser();
3658 if (c)
3659 c->saveUnsavedItems();
3660 }
3661 ui->tabWidget->saveTabInfo();
3662 }
3663
loadTab(const QString & fileName)3664 bool MainWindow::loadTab(const QString &fileName)
3665 {
3666 QFile file(fileName);
3667 if ( !file.open(QIODevice::ReadOnly) )
3668 return false;
3669
3670 QDataStream in(&file);
3671 in.setVersion(QDataStream::Qt_4_7);
3672
3673 QByteArray header;
3674 QString tabName;
3675 in >> header >> tabName;
3676 if ( !(header.startsWith("CopyQ v1") || header.startsWith("CopyQ v2")) || tabName.isEmpty() ) {
3677 file.close();
3678 return false;
3679 }
3680
3681 // Find unique tab name.
3682 renameToUnique(&tabName, ui->tabWidget->tabs());
3683
3684 auto c = createTab(tabName, MatchExactTabName, Tabs())->createBrowser();
3685 if (!c)
3686 return false;
3687
3688 deserializeData(c->model(), &in, m_sharedData->maxItems);
3689
3690 c->loadItems();
3691 c->saveItems();
3692
3693 file.close();
3694
3695 ui->tabWidget->setCurrentIndex( ui->tabWidget->count() - 1 );
3696
3697 return true;
3698 }
3699
importDataFrom(const QString & fileName,ImportOptions options)3700 bool MainWindow::importDataFrom(const QString &fileName, ImportOptions options)
3701 {
3702 // Compatibility with v2.9.0 and earlier.
3703 if ( loadTab(fileName) )
3704 return true;
3705
3706 QFile file(fileName);
3707 if ( !file.open(QIODevice::ReadOnly) )
3708 return false;
3709
3710 QDataStream in(&file);
3711 in.setVersion(QDataStream::Qt_4_7);
3712
3713 if ( importDataV4(&in, options) )
3714 return true;
3715
3716 file.seek(0);
3717 return importDataV3(&in, options);
3718 }
3719
exportAllData(const QString & fileName)3720 bool MainWindow::exportAllData(const QString &fileName)
3721 {
3722 const auto tabs = ui->tabWidget->tabs();
3723 const bool exportConfiguration = true;
3724 const bool exportCommands = true;
3725
3726 return exportDataFrom(fileName, tabs, exportConfiguration, exportCommands);
3727 }
3728
importData()3729 bool MainWindow::importData()
3730 {
3731 const auto fileName = QFileDialog::getOpenFileName(
3732 this, QString(), QString(), importExportFileDialogFilter() );
3733 if ( fileName.isNull() )
3734 return false;
3735
3736 if ( !importDataFrom(fileName, ImportOptions::Select) ) {
3737 QMessageBox::critical(
3738 this, tr("Import Error"),
3739 tr("Failed to import file %1!")
3740 .arg(quoteString(fileName)) );
3741 return false;
3742 }
3743
3744 return true;
3745 }
3746
sortSelectedItems()3747 void MainWindow::sortSelectedItems()
3748 {
3749 auto c = browser();
3750 if (c)
3751 c->sortItems( c->selectionModel()->selectedRows() );
3752 }
3753
reverseSelectedItems()3754 void MainWindow::reverseSelectedItems()
3755 {
3756 auto c = browser();
3757 if (c)
3758 c->reverseItems( c->selectionModel()->selectedRows() );
3759 }
3760
action(const QVariantMap & data,const Command & cmd,const QModelIndex & outputIndex)3761 Action *MainWindow::action(const QVariantMap &data, const Command &cmd, const QModelIndex &outputIndex)
3762 {
3763 if (cmd.wait) {
3764 auto actionDialog = openActionDialog(data);
3765 if ( !cmd.outputTab.isEmpty() )
3766 actionDialog->setCurrentTab(cmd.outputTab);
3767 actionDialog->setCommand(cmd);
3768 } else if ( cmd.cmd.isEmpty() ) {
3769 m_sharedData->actions->addFinishedAction(cmd.name);
3770 } else {
3771 auto act = new Action();
3772 act->setCommand( cmd.cmd, QStringList(getTextData(data)) );
3773 act->setInputWithFormat(data, cmd.input);
3774 act->setName(cmd.name);
3775 act->setData(data);
3776
3777 if ( !cmd.output.isEmpty() ) {
3778 if ( outputIndex.isValid() )
3779 actionOutput(this, act, cmd.output, outputIndex);
3780 else if ( !cmd.sep.isEmpty() )
3781 actionOutput(this, act, cmd.output, cmd.outputTab, QRegularExpression(cmd.sep));
3782 else
3783 actionOutput(this, act, cmd.output, cmd.outputTab);
3784 }
3785
3786 m_sharedData->actions->action(act);
3787 return act;
3788 }
3789
3790 return nullptr;
3791 }
3792
triggerMenuCommand(const Command & command,const QString & triggeredShortcut)3793 bool MainWindow::triggerMenuCommand(const Command &command, const QString &triggeredShortcut)
3794 {
3795 updateShortcuts();
3796
3797 Command cmd = command;
3798 for (auto act : m_menuItem->findChildren<CommandAction*>()) {
3799 if ( !act->isEnabled() || !act->isVisible() )
3800 continue;
3801
3802 const Command &menuCommand = act->command();
3803 // Ignore outputTab value overridden in the action.
3804 cmd.outputTab = menuCommand.outputTab;
3805 if (cmd == menuCommand) {
3806 onItemCommandActionTriggered(act, triggeredShortcut);
3807 return true;
3808 }
3809 }
3810 return false;
3811 }
3812
runInternalAction(Action * action)3813 void MainWindow::runInternalAction(Action *action)
3814 {
3815 m_sharedData->actions->internalAction(action);
3816 }
3817
isInternalActionId(int id) const3818 bool MainWindow::isInternalActionId(int id) const
3819 {
3820 return id != -1 && m_sharedData->actions->isInternalActionId(id);
3821 }
3822
openNewTabDialog(const QString & name)3823 void MainWindow::openNewTabDialog(const QString &name)
3824 {
3825 auto d = new TabDialog(TabDialog::TabNew, this);
3826 d->setAttribute(Qt::WA_DeleteOnClose, true);
3827 d->setTabs(ui->tabWidget->tabs());
3828
3829 d->setTabName(name);
3830
3831 connect( d, &TabDialog::newTabNameAccepted,
3832 this, &MainWindow::addAndFocusTab );
3833
3834 d->open();
3835 }
3836
openNewTabDialog()3837 void MainWindow::openNewTabDialog()
3838 {
3839 QString tabPath = ui->tabWidget->getCurrentTabPath();
3840 if ( ui->tabWidget->isTabGroup(tabPath) )
3841 tabPath.append('/');
3842
3843 openNewTabDialog(tabPath);
3844 }
3845
openRenameTabGroupDialog(const QString & name)3846 void MainWindow::openRenameTabGroupDialog(const QString &name)
3847 {
3848 auto d = new TabDialog(TabDialog::TabGroupRename, this);
3849 d->setAttribute(Qt::WA_DeleteOnClose, true);
3850 d->setTabs(ui->tabWidget->tabs());
3851 d->setTabGroupName(name);
3852
3853 connect( d, &TabDialog::treeTabNameAccepted,
3854 this, &MainWindow::renameTabGroup );
3855
3856 d->open();
3857 }
3858
renameTabGroup(const QString & newName,const QString & oldName)3859 void MainWindow::renameTabGroup(const QString &newName, const QString &oldName)
3860 {
3861 const QStringList tabs = ui->tabWidget->tabs();
3862 const QString tabPrefix = oldName + '/';
3863
3864 for ( int i = 0; i < tabs.size(); ++i ) {
3865 const QString &tab = tabs[i];
3866 if ( tab == oldName || tab.startsWith(tabPrefix) )
3867 renameTab( newName + tab.mid(oldName.size()), i );
3868 }
3869 }
3870
openRenameTabDialog(int tabIndex)3871 void MainWindow::openRenameTabDialog(int tabIndex)
3872 {
3873 auto d = new TabDialog(TabDialog::TabRename, this);
3874 d->setAttribute(Qt::WA_DeleteOnClose, true);
3875 d->setTabIndex(tabIndex);
3876 d->setTabs(ui->tabWidget->tabs());
3877 d->setTabName( getPlaceholder(tabIndex)->tabName() );
3878
3879 connect( d, &TabDialog::barTabNameAccepted,
3880 this, &MainWindow::renameTab );
3881
3882 d->open();
3883 }
3884
openRenameTabDialog()3885 void MainWindow::openRenameTabDialog()
3886 {
3887 const int tabIndex = ui->tabWidget->currentIndex();
3888 if (tabIndex >= 0)
3889 openRenameTabDialog(tabIndex);
3890 }
3891
renameTab(const QString & name,int tabIndex)3892 void MainWindow::renameTab(const QString &name, int tabIndex)
3893 {
3894 if ( name.isEmpty() || ui->tabWidget->tabs().contains(name) )
3895 return;
3896
3897 auto placeholder = getPlaceholder(tabIndex);
3898 if (placeholder) {
3899 const QString oldName = placeholder->tabName();
3900 if ( placeholder->setTabName(name) ) {
3901 updateTabIcon(name, oldName);
3902 ui->tabWidget->setTabName(tabIndex, name);
3903 saveTabPositions();
3904
3905 QVariantList optionsAndValues;
3906 if (oldName == m_options.clipboardTab) {
3907 optionsAndValues.append(QStringLiteral("clipboard_tab"));
3908 optionsAndValues.append(name);
3909 }
3910 if (oldName == m_options.trayTabName) {
3911 optionsAndValues.append(QStringLiteral("tray_tab"));
3912 optionsAndValues.append(name);
3913 }
3914 if ( !optionsAndValues.isEmpty() )
3915 config(optionsAndValues);
3916 }
3917 }
3918 }
3919
removeTabGroup(const QString & name)3920 void MainWindow::removeTabGroup(const QString &name)
3921 {
3922 int answer = QMessageBox::question(
3923 this,
3924 tr("Remove All Tabs in Group?"),
3925 tr("Do you want to remove <strong>all tabs</strong> in group <strong>%1</strong>?"
3926 ).arg(name),
3927 QMessageBox::Yes | QMessageBox::No,
3928 QMessageBox::Yes);
3929 if (answer == QMessageBox::Yes) {
3930 ui->tabWidget->setCurrentIndex(0);
3931 const QStringList tabs = ui->tabWidget->tabs();
3932 const QString tabPrefix = name + '/';
3933 const int currentTabIndex = ui->tabWidget->currentIndex();
3934
3935 for ( int i = tabs.size() - 1; i >= 0; --i ) {
3936 const QString &tab = tabs[i];
3937 if ( tab == name || tab.startsWith(tabPrefix) ) {
3938 if (currentTabIndex == i)
3939 ui->tabWidget->setCurrentIndex(0);
3940
3941 auto placeholder = getPlaceholder(i);
3942 placeholder->removeItems();
3943 placeholder->deleteLater();
3944
3945 ui->tabWidget->removeTab(i);
3946 }
3947 }
3948
3949 saveTabPositions();
3950 }
3951 }
3952
removeTab()3953 void MainWindow::removeTab()
3954 {
3955 removeTab(true, ui->tabWidget->currentIndex());
3956 }
3957
removeTab(bool ask,int tabIndex)3958 void MainWindow::removeTab(bool ask, int tabIndex)
3959 {
3960 if (tabIndex < 0)
3961 return;
3962
3963 TabWidget *w = ui->tabWidget;
3964
3965 auto placeholder = getPlaceholder(tabIndex);
3966
3967 if ( placeholder != nullptr && w->count() > 1 ) {
3968 int answer = QMessageBox::Yes;
3969 if (ask) {
3970 answer = QMessageBox::question(
3971 this,
3972 tr("Remove Tab?"),
3973 tr("Do you want to remove tab <strong>%1</strong>?"
3974 ).arg( w->tabName(tabIndex).remove('&') ),
3975 QMessageBox::Yes | QMessageBox::No,
3976 QMessageBox::Yes);
3977 }
3978 if (answer == QMessageBox::Yes) {
3979 placeholder->removeItems();
3980 placeholder->deleteLater();
3981 w->removeTab(tabIndex);
3982 saveTabPositions();
3983 }
3984 }
3985 }
3986
setTabIcon()3987 void MainWindow::setTabIcon()
3988 {
3989 if ( ui->tabWidget->isTabGroupSelected() )
3990 setTabIcon( ui->tabWidget->getCurrentTabPath() );
3991 else
3992 setTabIcon( getPlaceholder()->tabName() );
3993 }
3994
setTabIcon(const QString & tabName)3995 void MainWindow::setTabIcon(const QString &tabName)
3996 {
3997 IconSelectDialog dialog( getIconNameForTabName(tabName), this );
3998
3999 if ( dialog.exec() == QDialog::Accepted )
4000 setTabIcon(tabName, dialog.selectedIcon());
4001 }
4002
setTabIcon(const QString & tabName,const QString & icon)4003 void MainWindow::setTabIcon(const QString &tabName, const QString &icon)
4004 {
4005 if ( tabs().contains(tabName) || ui->tabWidget->isTabGroup(tabName) ) {
4006 setIconNameForTabName(tabName, icon);
4007 ui->tabWidget->updateTabIcon(tabName);
4008 }
4009 }
4010
unloadTab(const QString & tabName)4011 bool MainWindow::unloadTab(const QString &tabName)
4012 {
4013 ClipboardBrowserPlaceholder *placeholder = getPlaceholder(tabName);
4014 return !placeholder || placeholder->expire();
4015 }
4016
forceUnloadTab(const QString & tabName)4017 void MainWindow::forceUnloadTab(const QString &tabName)
4018 {
4019 ClipboardBrowserPlaceholder *placeholder = getPlaceholder(tabName);
4020 if (!placeholder)
4021 return;
4022
4023 placeholder->unloadBrowser();
4024
4025 placeholder->createLoadButton();
4026 }
4027
~MainWindow()4028 MainWindow::~MainWindow()
4029 {
4030 delete ui;
4031 }
4032