File indexing completed on 2024-04-14 04:53:31

0001 /*
0002     SPDX-FileCopyrightText: 2007-2013 Urs Wolfer <uwolfer@kde.org>
0003     SPDX-FileCopyrightText: 2009-2010 Tony Murray <murraytony@gmail.com>
0004     SPDX-FileCopyrightText: 2021 RafaƂ Lalik <rafallalik@gmail.com>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "config-kactivities.h"
0010 
0011 #include "bookmarkmanager.h"
0012 #include "config/preferencesdialog.h"
0013 #include "connectiondelegate.h"
0014 #include "factorwidget.h"
0015 #include "floatingtoolbar.h"
0016 #include "hostpreferences.h"
0017 #include "krdc_debug.h"
0018 #include "mainwindow.h"
0019 #include "remotedesktopsmodel.h"
0020 #include "settings.h"
0021 #include "systemtrayicon.h"
0022 #include "tabbedviewwidget.h"
0023 
0024 #include <KActionCollection>
0025 #include <KActionMenu>
0026 #include <KComboBox>
0027 #include <KLineEdit>
0028 #include <KLocalizedString>
0029 #include <KMessageBox>
0030 #include <KNotifyConfigWidget>
0031 #include <KPluginMetaData>
0032 #include <KToggleAction>
0033 #include <KToggleFullScreenAction>
0034 #include <KToolBar>
0035 
0036 #if HAVE_KACTIVITIES
0037 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0038 #include <KActivities/ResourceInstance>
0039 #else
0040 #include <PlasmaActivities/ResourceInstance>
0041 #endif
0042 #endif
0043 
0044 #include <QClipboard>
0045 #include <QDockWidget>
0046 #include <QFontMetrics>
0047 #include <QGroupBox>
0048 #include <QGuiApplication>
0049 #include <QHBoxLayout>
0050 #include <QHeaderView>
0051 #include <QIcon>
0052 #include <QInputDialog>
0053 #include <QLabel>
0054 #include <QLayout>
0055 #include <QMenu>
0056 #include <QMenuBar>
0057 #include <QPushButton>
0058 #include <QRegularExpression>
0059 #include <QScrollBar>
0060 #include <QSortFilterProxyModel>
0061 #include <QStatusBar>
0062 #include <QTabWidget>
0063 #include <QTableView>
0064 #include <QTimer>
0065 #include <QToolBar>
0066 #include <QVBoxLayout>
0067 
0068 MainWindow::MainWindow(QWidget *parent)
0069     : KXmlGuiWindow(parent)
0070     , m_fullscreenWindow(nullptr)
0071     , m_protocolInput(nullptr)
0072     , m_addressInput(nullptr)
0073     , m_toolBar(nullptr)
0074     , m_currentRemoteView(-1)
0075     , m_systemTrayIcon(nullptr)
0076     , m_dockWidgetTableView(nullptr)
0077     , m_newConnectionTableView(nullptr)
0078     , m_newConnectionWidget(nullptr)
0079 {
0080     loadAllPlugins();
0081 
0082     setupActions();
0083 
0084     setStandardToolBarMenuEnabled(true);
0085 
0086     m_tabWidget = new TabbedViewWidget(this);
0087     m_tabWidget->setAutoFillBackground(true);
0088     m_tabWidget->setMovable(true);
0089     m_tabWidget->setTabPosition((QTabWidget::TabPosition)Settings::tabPosition());
0090 
0091     m_tabWidget->setTabsClosable(Settings::tabCloseButton());
0092 
0093     connect(m_tabWidget, SIGNAL(tabCloseRequested(int)), SLOT(closeTab(int)));
0094 
0095     if (Settings::tabMiddleClick())
0096         connect(m_tabWidget, SIGNAL(mouseMiddleClick(int)), SLOT(closeTab(int)));
0097 
0098     connect(m_tabWidget, SIGNAL(tabBarDoubleClicked(int)), SLOT(openTabSettings(int)));
0099 
0100     m_tabWidget->tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);
0101     connect(m_tabWidget->tabBar(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(tabContextMenu(QPoint)));
0102 
0103     m_tabWidget->setMinimumSize(600, 400);
0104     setCentralWidget(m_tabWidget);
0105 
0106     createDockWidget();
0107 
0108     setupGUI(ToolBar | Keys | Save | Create);
0109 
0110     if (Settings::systemTrayIcon()) {
0111         m_systemTrayIcon = new SystemTrayIcon(this);
0112         if (m_fullscreenWindow) {
0113 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0114             m_systemTrayIcon->setAssociatedWidget(m_fullscreenWindow);
0115 #else
0116             m_systemTrayIcon->setAssociatedWindow(m_fullscreenWindow->windowHandle());
0117 #endif
0118         }
0119     }
0120 
0121     connect(m_tabWidget, SIGNAL(currentChanged(int)), SLOT(tabChanged(int)));
0122 
0123     if (Settings::showStatusBar())
0124         statusBar()->showMessage(i18n("KDE Remote Desktop Client started"));
0125 
0126     updateActionStatus(); // disable remote view actions
0127 
0128     if (Settings::openSessions().count() == 0) // just create a new connection tab if there are no open sessions
0129         m_tabWidget->addTab(newConnectionWidget(), i18n("New Connection"));
0130 
0131     if (Settings::rememberSessions()) // give some time to create and show the window first
0132         QTimer::singleShot(100, this, SLOT(restoreOpenSessions()));
0133 }
0134 
0135 MainWindow::~MainWindow()
0136 {
0137 }
0138 
0139 void MainWindow::setupActions()
0140 {
0141     QAction *connectionAction = actionCollection()->addAction(QStringLiteral("new_connection"));
0142     connectionAction->setText(i18n("New Connection"));
0143     connectionAction->setIcon(QIcon::fromTheme(QStringLiteral("network-connect")));
0144     actionCollection()->setDefaultShortcuts(connectionAction, KStandardShortcut::openNew());
0145     connect(connectionAction, SIGNAL(triggered()), SLOT(newConnectionPage()));
0146 
0147     QAction *screenshotAction = actionCollection()->addAction(QStringLiteral("take_screenshot"));
0148     screenshotAction->setText(i18n("Copy Screenshot to Clipboard"));
0149     screenshotAction->setIconText(i18n("Screenshot"));
0150     screenshotAction->setIcon(QIcon::fromTheme(QStringLiteral("ksnapshot")));
0151     connect(screenshotAction, SIGNAL(triggered()), SLOT(takeScreenshot()));
0152 
0153     QAction *fullscreenAction = actionCollection()->addAction(
0154         QStringLiteral("switch_fullscreen")); // note: please do not switch to KStandardShortcut unless you know what you are doing (see history of this file)
0155     fullscreenAction->setText(i18n("Switch to Full Screen Mode"));
0156     fullscreenAction->setIconText(i18n("Full Screen"));
0157     fullscreenAction->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen")));
0158     actionCollection()->setDefaultShortcuts(fullscreenAction, KStandardShortcut::fullScreen());
0159     connect(fullscreenAction, SIGNAL(triggered()), SLOT(switchFullscreen()));
0160 
0161     QAction *viewOnlyAction = actionCollection()->addAction(QStringLiteral("view_only"));
0162     viewOnlyAction->setCheckable(true);
0163     viewOnlyAction->setText(i18n("View Only"));
0164     viewOnlyAction->setIcon(QIcon::fromTheme(QStringLiteral("document-preview")));
0165     connect(viewOnlyAction, SIGNAL(triggered(bool)), SLOT(viewOnly(bool)));
0166 
0167     QAction *disconnectAction = actionCollection()->addAction(QStringLiteral("disconnect"));
0168     disconnectAction->setText(i18n("Disconnect"));
0169     disconnectAction->setIcon(QIcon::fromTheme(QStringLiteral("network-disconnect")));
0170     actionCollection()->setDefaultShortcuts(disconnectAction, KStandardShortcut::close());
0171     connect(disconnectAction, SIGNAL(triggered()), SLOT(disconnectHost()));
0172 
0173     QAction *showLocalCursorAction = actionCollection()->addAction(QStringLiteral("show_local_cursor"));
0174     showLocalCursorAction->setCheckable(true);
0175     showLocalCursorAction->setIcon(QIcon::fromTheme(QStringLiteral("input-mouse")));
0176     showLocalCursorAction->setText(i18n("Show Local Cursor"));
0177     showLocalCursorAction->setIconText(i18n("Local Cursor"));
0178     connect(showLocalCursorAction, SIGNAL(triggered(bool)), SLOT(showLocalCursor(bool)));
0179 
0180     QAction *grabAllKeysAction = actionCollection()->addAction(QStringLiteral("grab_all_keys"));
0181     grabAllKeysAction->setCheckable(true);
0182     grabAllKeysAction->setIcon(QIcon::fromTheme(QStringLiteral("configure-shortcuts")));
0183     grabAllKeysAction->setText(i18n("Grab All Possible Keys"));
0184     grabAllKeysAction->setIconText(i18n("Grab Keys"));
0185     connect(grabAllKeysAction, SIGNAL(triggered(bool)), SLOT(grabAllKeys(bool)));
0186 
0187     QAction *scaleAction = actionCollection()->addAction(QStringLiteral("scale"));
0188     scaleAction->setCheckable(true);
0189     scaleAction->setIcon(QIcon::fromTheme(QStringLiteral("zoom-fit-best")));
0190     scaleAction->setText(i18n("Scale Remote Screen to Fit Window Size"));
0191     scaleAction->setIconText(i18n("Scale"));
0192     connect(scaleAction, SIGNAL(triggered(bool)), SLOT(scale(bool)));
0193 
0194     FactorWidget *m_scaleSlider = new FactorWidget(i18n("Scaling Factor"), this, actionCollection());
0195     QAction *scaleFactorAction = actionCollection()->addAction(QStringLiteral("scale_factor"), m_scaleSlider);
0196     scaleFactorAction->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
0197 
0198     KStandardAction::quit(this, SLOT(quit()), actionCollection());
0199     KStandardAction::preferences(this, SLOT(preferences()), actionCollection());
0200     QAction *configNotifyAction = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection());
0201     configNotifyAction->setVisible(false);
0202     m_menubarAction = KStandardAction::showMenubar(this, SLOT(showMenubar()), actionCollection());
0203     m_menubarAction->setChecked(!menuBar()->isHidden());
0204 
0205     KActionMenu *bookmarkMenu = new KActionMenu(i18n("Bookmarks"), actionCollection());
0206     m_bookmarkManager = new BookmarkManager(actionCollection(), bookmarkMenu->menu(), this);
0207     actionCollection()->addAction(QStringLiteral("bookmark"), bookmarkMenu);
0208     connect(m_bookmarkManager, SIGNAL(openUrl(QUrl)), SLOT(newConnection(QUrl)));
0209 }
0210 
0211 void MainWindow::loadAllPlugins()
0212 {
0213     const QVector<KPluginMetaData> offers = KPluginMetaData::findPlugins(QStringLiteral("krdc"));
0214     const KConfigGroup conf = KSharedConfig::openConfig()->group(QStringLiteral("Plugins"));
0215 
0216     for (const KPluginMetaData &plugin : offers) {
0217         const bool enabled = plugin.isEnabled(conf);
0218 
0219         if (enabled) {
0220             const auto result = KPluginFactory::instantiatePlugin<RemoteViewFactory>(plugin);
0221 
0222             if (result) {
0223                 RemoteViewFactory *component = result.plugin;
0224                 const int sorting = plugin.value(QStringLiteral("X-KDE-KRDC-Sorting"), 0);
0225                 m_remoteViewFactories.insert(sorting, component);
0226             }
0227         }
0228     }
0229 }
0230 
0231 void MainWindow::restoreOpenSessions()
0232 {
0233     const QStringList list = Settings::openSessions();
0234     QListIterator<QString> it(list);
0235     while (it.hasNext()) {
0236         newConnection(QUrl(it.next()));
0237     }
0238 }
0239 
0240 QUrl MainWindow::getInputUrl()
0241 {
0242     QString userInput = m_addressInput->text();
0243     qCDebug(KRDC) << "input url " << userInput;
0244     // percent encode usernames so QUrl can parse it
0245     static QRegularExpression reg(QStringLiteral("@[^@]+$"));
0246     int lastAtIndex = userInput.indexOf(reg);
0247     if (lastAtIndex > 0) {
0248         userInput = QString::fromLatin1(QUrl::toPercentEncoding(userInput.left(lastAtIndex))) + userInput.mid(lastAtIndex);
0249         qCDebug(KRDC) << "input url " << userInput;
0250     }
0251 
0252     return QUrl(m_protocolInput->currentText() + QStringLiteral("://") + userInput);
0253 }
0254 
0255 void MainWindow::newConnection(const QUrl &newUrl, bool switchFullscreenWhenConnected, const QString &tabName)
0256 {
0257     m_switchFullscreenWhenConnected = switchFullscreenWhenConnected;
0258 
0259     const QUrl url = newUrl.isEmpty() ? getInputUrl() : newUrl;
0260 
0261     if (!url.isValid() || (url.host().isEmpty() && url.port() < 0) || (!url.path().isEmpty() && url.path() != QStringLiteral("/"))) {
0262         KMessageBox::error(this, i18n("The entered address does not have the required form.\n Syntax: [username@]host[:port]"), i18n("Malformed URL"));
0263         return;
0264     }
0265 
0266     if (m_protocolInput && m_addressInput) {
0267         m_protocolInput->setCurrentText(url.scheme());
0268         m_addressInput->setText(url.authority());
0269     }
0270 
0271     RemoteView *view = nullptr;
0272     KConfigGroup configGroup = Settings::self()->config()->group(QStringLiteral("hostpreferences")).group(url.toDisplayString(QUrl::StripTrailingSlash));
0273 
0274     for (RemoteViewFactory *factory : qAsConst(m_remoteViewFactories)) {
0275         if (factory->supportsUrl(url)) {
0276             view = factory->createView(this, url, configGroup);
0277             qCDebug(KRDC) << "Found plugin to handle url (" << url.url() << "): " << view->metaObject()->className();
0278             break;
0279         }
0280     }
0281 
0282     if (!view) {
0283         KMessageBox::error(this, i18n("The entered address cannot be handled."), i18n("Unusable URL"));
0284         return;
0285     }
0286 
0287     // Configure the view
0288     HostPreferences *prefs = view->hostPreferences();
0289     // if the user press cancel
0290     if (!prefs->showDialogIfNeeded(this))
0291         return;
0292 
0293     view->showLocalCursor(prefs->showLocalCursor() ? RemoteView::CursorOn : RemoteView::CursorOff);
0294     view->setViewOnly(prefs->viewOnly());
0295     bool scale_state = false;
0296     if (switchFullscreenWhenConnected)
0297         scale_state = prefs->fullscreenScale();
0298     else
0299         scale_state = prefs->windowedScale();
0300 
0301     view->enableScaling(scale_state);
0302 
0303     connect(view, SIGNAL(framebufferSizeChanged(int, int)), this, SLOT(resizeTabWidget(int, int)));
0304     connect(view, SIGNAL(statusChanged(RemoteView::RemoteStatus)), this, SLOT(statusChanged(RemoteView::RemoteStatus)));
0305     connect(view, SIGNAL(disconnected()), this, SLOT(disconnectHost()));
0306 
0307     QScrollArea *scrollArea = createScrollArea(m_tabWidget, view);
0308 
0309     const int indexOfNewConnectionWidget = m_tabWidget->indexOf(m_newConnectionWidget);
0310     if (indexOfNewConnectionWidget >= 0)
0311         m_tabWidget->removeTab(indexOfNewConnectionWidget);
0312 
0313     const int newIndex =
0314         m_tabWidget->addTab(scrollArea, QIcon::fromTheme(QStringLiteral("krdc")), tabName.isEmpty() ? url.toDisplayString(QUrl::StripTrailingSlash) : tabName);
0315     m_tabWidget->setCurrentIndex(newIndex);
0316     m_remoteViewMap.insert(m_tabWidget->widget(newIndex), view);
0317     tabChanged(newIndex); // force to update m_currentRemoteView (tabChanged is not emitted when start page has been disabled)
0318 
0319     view->start();
0320     setFactor(view->hostPreferences()->scaleFactor());
0321 
0322 #if HAVE_KACTIVITIES
0323     KActivities::ResourceInstance::notifyAccessed(url, QGuiApplication::desktopFileName());
0324 #endif
0325 
0326     Q_EMIT factorUpdated(view->hostPreferences()->scaleFactor());
0327     Q_EMIT scaleUpdated(scale_state);
0328 }
0329 
0330 void MainWindow::openFromRemoteDesktopsModel(const QModelIndex &index)
0331 {
0332     const QString urlString = index.data(10001).toString();
0333     const QString nameString = index.data(10003).toString();
0334     if (!urlString.isEmpty()) {
0335         const QUrl url(urlString);
0336         // first check if url has already been opened; in case show the tab
0337         for (auto it = m_remoteViewMap.constBegin(), end = m_remoteViewMap.constEnd(); it != end; ++it) {
0338             RemoteView *view = it.value();
0339             if (view->url() == url) {
0340                 QWidget *widget = it.key();
0341                 m_tabWidget->setCurrentWidget(widget);
0342                 return;
0343             }
0344         }
0345 
0346         newConnection(url, false, nameString);
0347     }
0348 }
0349 
0350 void MainWindow::selectFromRemoteDesktopsModel(const QModelIndex &index)
0351 {
0352     const QString urlString = index.data(10001).toString();
0353 
0354     if (!urlString.isEmpty() && m_protocolInput && m_addressInput) {
0355         const QUrl url(urlString);
0356         m_addressInput->blockSignals(true); // block signals so we don't filter the address list on click
0357         m_addressInput->setText(url.authority());
0358         m_addressInput->blockSignals(false);
0359         m_protocolInput->setCurrentText(url.scheme());
0360     }
0361 }
0362 
0363 void MainWindow::resizeTabWidget(int w, int h)
0364 {
0365     qCDebug(KRDC) << "tabwidget resize, view size: w: " << w << ", h: " << h;
0366     if (m_fullscreenWindow) {
0367         qCDebug(KRDC) << "in fullscreen mode, refusing to resize";
0368         return;
0369     }
0370 
0371     const QSize viewSize = QSize(w, h);
0372     QScreen *currentScreen = QGuiApplication::screenAt(geometry().center());
0373 
0374     if (Settings::fullscreenOnConnect()) {
0375         const QSize screenSize = currentScreen->availableGeometry().size();
0376 
0377         if (screenSize == viewSize) {
0378             qCDebug(KRDC) << "screen size equal to target view size -> switch to fullscreen mode";
0379             switchFullscreen();
0380             return;
0381         }
0382     }
0383 
0384     if (Settings::resizeOnConnect()) {
0385         QWidget *currentWidget = m_tabWidget->currentWidget();
0386         const QSize newWindowSize = size() - currentWidget->frameSize() + viewSize;
0387 
0388         const QSize desktopSize = currentScreen->availableGeometry().size();
0389         qCDebug(KRDC) << "new window size: " << newWindowSize << " available space:" << desktopSize;
0390 
0391         if ((newWindowSize.width() >= desktopSize.width()) || (newWindowSize.height() >= desktopSize.height())) {
0392             qCDebug(KRDC) << "remote desktop needs more space than available -> show window maximized";
0393             setWindowState(windowState() | Qt::WindowMaximized);
0394             return;
0395         }
0396         setWindowState(windowState() & ~Qt::WindowMaximized);
0397         resize(newWindowSize);
0398     }
0399 }
0400 
0401 void MainWindow::statusChanged(RemoteView::RemoteStatus status)
0402 {
0403     qCDebug(KRDC) << status;
0404 
0405     // the remoteview is already deleted, so don't show it; otherwise it would crash
0406     if (status == RemoteView::Disconnecting || status == RemoteView::Disconnected)
0407         return;
0408 
0409     RemoteView *view = qobject_cast<RemoteView *>(QObject::sender());
0410     const QString host = view->host();
0411 
0412     QString iconName = QStringLiteral("krdc");
0413     QString message;
0414 
0415     switch (status) {
0416     case RemoteView::Connecting:
0417         iconName = QStringLiteral("network-connect");
0418         message = i18n("Connecting to %1", host);
0419         break;
0420     case RemoteView::Authenticating:
0421         iconName = QStringLiteral("dialog-password");
0422         message = i18n("Authenticating at %1", host);
0423         break;
0424     case RemoteView::Preparing:
0425         iconName = QStringLiteral("view-history");
0426         message = i18n("Preparing connection to %1", host);
0427         break;
0428     case RemoteView::Connected:
0429         iconName = QStringLiteral("krdc");
0430         message = i18n("Connected to %1", host);
0431 
0432         if (view->grabAllKeys() != view->hostPreferences()->grabAllKeys()) {
0433             view->setGrabAllKeys(view->hostPreferences()->grabAllKeys());
0434             updateActionStatus();
0435         }
0436 
0437         // when started with command line fullscreen argument
0438         if (m_switchFullscreenWhenConnected) {
0439             m_switchFullscreenWhenConnected = false;
0440             switchFullscreen();
0441         }
0442 
0443         if (Settings::rememberHistory()) {
0444             m_bookmarkManager->addHistoryBookmark(view);
0445         }
0446 
0447         break;
0448     default:
0449         break;
0450     }
0451 
0452     m_tabWidget->setTabIcon(m_tabWidget->indexOf(view), QIcon::fromTheme(iconName));
0453     if (Settings::showStatusBar())
0454         statusBar()->showMessage(message);
0455 }
0456 
0457 void MainWindow::takeScreenshot()
0458 {
0459     const QPixmap snapshot = currentRemoteView()->takeScreenshot();
0460 
0461     QApplication::clipboard()->setPixmap(snapshot);
0462 }
0463 
0464 void MainWindow::switchFullscreen()
0465 {
0466     qCDebug(KRDC);
0467 
0468     RemoteView *view = currentRemoteView();
0469     bool scale_state = false;
0470 
0471     if (m_fullscreenWindow) {
0472         // Leaving full screen mode
0473         m_fullscreenWindow->setWindowState(Qt::WindowNoState);
0474         m_fullscreenWindow->hide();
0475 
0476         m_tabWidget->tabBar()->setHidden(m_tabWidget->count() <= 1 && !Settings::showTabBar());
0477         m_tabWidget->setDocumentMode(false);
0478         setCentralWidget(m_tabWidget);
0479 
0480         show();
0481         restoreGeometry(m_mainWindowGeometry);
0482         if (m_systemTrayIcon) {
0483 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0484             m_systemTrayIcon->setAssociatedWidget(this);
0485 #else
0486             m_systemTrayIcon->setAssociatedWindow(windowHandle());
0487 #endif
0488         }
0489 
0490         for (RemoteView *view : qAsConst(m_remoteViewMap)) {
0491             view->enableScaling(view->hostPreferences()->windowedScale());
0492         }
0493 
0494         if (m_toolBar) {
0495             m_toolBar->hideAndDestroy();
0496             m_toolBar->deleteLater();
0497             m_toolBar = nullptr;
0498         }
0499 
0500         actionCollection()->action(QStringLiteral("switch_fullscreen"))->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen")));
0501         actionCollection()->action(QStringLiteral("switch_fullscreen"))->setText(i18n("Switch to Full Screen Mode"));
0502         actionCollection()->action(QStringLiteral("switch_fullscreen"))->setIconText(i18n("Full Screen"));
0503         if (view)
0504             scale_state = view->hostPreferences()->windowedScale();
0505 
0506         m_fullscreenWindow->deleteLater();
0507         m_fullscreenWindow = nullptr;
0508     } else {
0509         // Entering full screen mode
0510         m_fullscreenWindow = new QWidget(this, Qt::Window);
0511         m_fullscreenWindow->setWindowTitle(
0512             i18nc("window title when in full screen mode (for example displayed in tasklist)", "KDE Remote Desktop Client (Full Screen)"));
0513 
0514         m_mainWindowGeometry = saveGeometry();
0515 
0516         m_tabWidget->tabBar()->hide();
0517         m_tabWidget->setDocumentMode(true);
0518 
0519         for (RemoteView *currentView : qAsConst(m_remoteViewMap)) {
0520             currentView->enableScaling(currentView->hostPreferences()->fullscreenScale());
0521         }
0522 
0523         QVBoxLayout *fullscreenLayout = new QVBoxLayout(m_fullscreenWindow);
0524         fullscreenLayout->setContentsMargins(QMargins(0, 0, 0, 0));
0525         fullscreenLayout->addWidget(m_tabWidget);
0526 
0527         KToggleFullScreenAction::setFullScreen(m_fullscreenWindow, true);
0528 
0529         MinimizePixel *minimizePixel = new MinimizePixel(m_fullscreenWindow);
0530         connect(minimizePixel, SIGNAL(rightClicked()), m_fullscreenWindow, SLOT(showMinimized()));
0531         m_fullscreenWindow->installEventFilter(this);
0532 
0533         m_fullscreenWindow->show();
0534         hide(); // hide after showing the new window so it stays on the same screen
0535 
0536         if (m_systemTrayIcon) {
0537 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0538             m_systemTrayIcon->setAssociatedWidget(m_fullscreenWindow);
0539 #else
0540             m_systemTrayIcon->setAssociatedWindow(m_fullscreenWindow->windowHandle());
0541 #endif
0542         }
0543 
0544         actionCollection()->action(QStringLiteral("switch_fullscreen"))->setIcon(QIcon::fromTheme(QStringLiteral("view-restore")));
0545         actionCollection()->action(QStringLiteral("switch_fullscreen"))->setText(i18n("Switch to Window Mode"));
0546         actionCollection()->action(QStringLiteral("switch_fullscreen"))->setIconText(i18n("Window Mode"));
0547         showRemoteViewToolbar();
0548         if (view)
0549             scale_state = view->hostPreferences()->fullscreenScale();
0550     }
0551     if (m_tabWidget->currentWidget() == m_newConnectionWidget && m_addressInput) {
0552         m_addressInput->setFocus();
0553     }
0554 
0555     if (view) {
0556         Q_EMIT factorUpdated(view->hostPreferences()->scaleFactor());
0557         Q_EMIT scaleUpdated(scale_state);
0558     }
0559     actionCollection()->action(QStringLiteral("scale"))->setChecked(scale_state);
0560 }
0561 
0562 QScrollArea *MainWindow::createScrollArea(QWidget *parent, RemoteView *remoteView)
0563 {
0564     RemoteViewScrollArea *scrollArea = new RemoteViewScrollArea(parent);
0565     scrollArea->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
0566 
0567     connect(scrollArea, SIGNAL(resized(int, int)), remoteView, SLOT(scaleResize(int, int)));
0568 
0569     QPalette palette = scrollArea->palette();
0570     palette.setColor(QPalette::Window, Settings::backgroundColor());
0571     scrollArea->setPalette(palette);
0572 
0573     scrollArea->setFrameStyle(QFrame::NoFrame);
0574     scrollArea->setAutoFillBackground(true);
0575     scrollArea->setWidget(remoteView);
0576 
0577     return scrollArea;
0578 }
0579 
0580 void MainWindow::disconnectHost()
0581 {
0582     qCDebug(KRDC);
0583 
0584     RemoteView *view = qobject_cast<RemoteView *>(QObject::sender());
0585 
0586     QWidget *widgetToDelete;
0587     if (view) {
0588         widgetToDelete = (QWidget *)view->parent()->parent();
0589         m_remoteViewMap.remove(m_remoteViewMap.key(view));
0590     } else {
0591         widgetToDelete = m_tabWidget->currentWidget();
0592         view = currentRemoteView();
0593         m_remoteViewMap.remove(m_remoteViewMap.key(view));
0594     }
0595 
0596     saveHostPrefs(view);
0597     view->startQuitting(); // some deconstructors can't properly quit, so quit early
0598     m_tabWidget->removePage(widgetToDelete);
0599     widgetToDelete->deleteLater();
0600 
0601     // if closing the last connection, create new connection tab
0602     if (m_tabWidget->count() == 0) {
0603         newConnectionPage(false);
0604     }
0605 
0606     // if the newConnectionWidget is the only tab and we are fullscreen, switch to window mode
0607     if (m_fullscreenWindow && m_tabWidget->count() == 1 && m_tabWidget->currentWidget() == m_newConnectionWidget) {
0608         switchFullscreen();
0609     }
0610 }
0611 
0612 void MainWindow::closeTab(int index)
0613 {
0614     if (index == -1) {
0615         return;
0616     }
0617     QWidget *widget = m_tabWidget->widget(index);
0618     bool isNewConnectionPage = widget == m_newConnectionWidget;
0619 
0620     if (!isNewConnectionPage) {
0621         RemoteView *view = m_remoteViewMap.take(widget);
0622         view->startQuitting();
0623         widget->deleteLater();
0624     }
0625 
0626     m_tabWidget->removePage(widget);
0627 
0628     // if closing the last connection, create new connection tab
0629     if (m_tabWidget->count() == 0) {
0630         newConnectionPage(false);
0631     }
0632 
0633     // if the newConnectionWidget is the only tab and we are fullscreen, switch to window mode
0634     if (m_fullscreenWindow && m_tabWidget->count() == 1 && m_tabWidget->currentWidget() == m_newConnectionWidget) {
0635         switchFullscreen();
0636     }
0637 }
0638 
0639 void MainWindow::openTabSettings(int index)
0640 {
0641     if (index == -1) {
0642         newConnectionPage();
0643         return;
0644     }
0645     QWidget *widget = m_tabWidget->widget(index);
0646     RemoteViewScrollArea *scrollArea = qobject_cast<RemoteViewScrollArea *>(widget);
0647     if (!scrollArea)
0648         return;
0649     RemoteView *view = qobject_cast<RemoteView *>(scrollArea->widget());
0650     if (!view)
0651         return;
0652 
0653     const QString url = view->url().url();
0654     qCDebug(KRDC) << url;
0655 
0656     showSettingsDialog(url);
0657 }
0658 
0659 void MainWindow::showSettingsDialog(const QString &url)
0660 {
0661     HostPreferences *prefs = nullptr;
0662 
0663     for (RemoteViewFactory *factory : qAsConst(m_remoteViewFactories)) {
0664         if (factory->supportsUrl(QUrl(url))) {
0665             prefs = factory->createHostPreferences(Settings::self()->config()->group(QStringLiteral("hostpreferences")).group(url), this);
0666             if (prefs) {
0667                 qCDebug(KRDC) << "Found plugin to handle url (" << url << "): " << prefs->metaObject()->className();
0668             } else {
0669                 qCDebug(KRDC) << "Found plugin to handle url (" << url << "), but plugin does not provide preferences";
0670             }
0671         }
0672     }
0673 
0674     if (prefs) {
0675         prefs->setShownWhileConnected(true);
0676         prefs->showDialog(this);
0677     } else {
0678         KMessageBox::error(this, i18n("The selected host cannot be handled."), i18n("Unusable URL"));
0679     }
0680 }
0681 
0682 void MainWindow::showConnectionContextMenu(const QPoint &pos)
0683 {
0684     // QTableView does not take headers into account when it does mapToGlobal(), so calculate the offset
0685     QPoint offset = QPoint(m_newConnectionTableView->verticalHeader()->size().width(), m_newConnectionTableView->horizontalHeader()->size().height());
0686     QModelIndex index = m_newConnectionTableView->indexAt(pos);
0687 
0688     if (!index.isValid())
0689         return;
0690 
0691     const QString url = index.data(10001).toString();
0692     const QString title = index.model()->index(index.row(), RemoteDesktopsModel::Title).data(Qt::DisplayRole).toString();
0693     const QString source = index.model()->index(index.row(), RemoteDesktopsModel::Source).data(Qt::DisplayRole).toString();
0694 
0695     QMenu *menu = new QMenu(url, m_newConnectionTableView);
0696 
0697     QAction *connectAction = menu->addAction(QIcon::fromTheme(QStringLiteral("network-connect")), i18n("Connect"));
0698     QAction *renameAction = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename"));
0699     QAction *settingsAction = menu->addAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Settings"));
0700     QAction *deleteAction = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete"));
0701 
0702     // not very clean, but it works,
0703     if (!(source == i18nc("Where each displayed link comes from", "Bookmarks") || source == i18nc("Where each displayed link comes from", "History"))) {
0704         renameAction->setEnabled(false);
0705         deleteAction->setEnabled(false);
0706     }
0707 
0708     QAction *selectedAction = menu->exec(m_newConnectionTableView->mapToGlobal(pos + offset));
0709 
0710     if (selectedAction == connectAction) {
0711         openFromRemoteDesktopsModel(index);
0712     } else if (selectedAction == renameAction) {
0713         // TODO: use inline editor if possible
0714         bool ok = false;
0715         const QString newTitle = QInputDialog::getText(this, i18n("Rename %1", title), i18n("Rename %1 to", title), QLineEdit::EchoMode::Normal, title, &ok);
0716         if (ok && !newTitle.isEmpty()) {
0717             BookmarkManager::updateTitle(m_bookmarkManager->getManager(), url, newTitle);
0718         }
0719     } else if (selectedAction == settingsAction) {
0720         showSettingsDialog(url);
0721     } else if (selectedAction == deleteAction) {
0722         if (KMessageBox::warningContinueCancel(this, i18n("Are you sure you want to delete %1?", url), i18n("Delete %1", title), KStandardGuiItem::del())
0723             == KMessageBox::Continue) {
0724             BookmarkManager::removeByUrl(m_bookmarkManager->getManager(), url);
0725         }
0726     }
0727 
0728     menu->deleteLater();
0729 }
0730 
0731 void MainWindow::tabContextMenu(const QPoint &point)
0732 {
0733     int index = m_tabWidget->tabBar()->tabAt(point);
0734     QWidget *widget = m_tabWidget->widget(index);
0735     RemoteViewScrollArea *scrollArea = qobject_cast<RemoteViewScrollArea *>(widget);
0736     if (!scrollArea)
0737         return;
0738     RemoteView *view = qobject_cast<RemoteView *>(scrollArea->widget());
0739     if (!view)
0740         return;
0741 
0742     const QString url = view->url().toDisplayString(QUrl::StripTrailingSlash);
0743     qCDebug(KRDC) << url;
0744 
0745     QMenu *menu = new QMenu(url, this);
0746     QAction *bookmarkAction = menu->addAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("Add Bookmark"));
0747     QAction *closeAction = menu->addAction(QIcon::fromTheme(QStringLiteral("tab-close")), i18n("Close Tab"));
0748     QAction *selectedAction = menu->exec(QCursor::pos());
0749     if (selectedAction) {
0750         if (selectedAction == closeAction) {
0751             closeTab(m_tabWidget->indexOf(widget));
0752         } else if (selectedAction == bookmarkAction) {
0753             m_bookmarkManager->addManualBookmark(view->url(), url);
0754         }
0755     }
0756     menu->deleteLater();
0757 }
0758 
0759 void MainWindow::showLocalCursor(bool showLocalCursor)
0760 {
0761     qCDebug(KRDC) << showLocalCursor;
0762 
0763     RemoteView *view = currentRemoteView();
0764     view->showLocalCursor(showLocalCursor ? RemoteView::CursorOn : RemoteView::CursorOff);
0765     view->hostPreferences()->setShowLocalCursor(showLocalCursor);
0766     saveHostPrefs(view);
0767 }
0768 
0769 void MainWindow::viewOnly(bool viewOnly)
0770 {
0771     qCDebug(KRDC) << viewOnly;
0772 
0773     RemoteView *view = currentRemoteView();
0774     view->setViewOnly(viewOnly);
0775     view->hostPreferences()->setViewOnly(viewOnly);
0776     saveHostPrefs(view);
0777 }
0778 
0779 void MainWindow::grabAllKeys(bool grabAllKeys)
0780 {
0781     qCDebug(KRDC);
0782 
0783     RemoteView *view = currentRemoteView();
0784     view->setGrabAllKeys(grabAllKeys);
0785     view->hostPreferences()->setGrabAllKeys(grabAllKeys);
0786     saveHostPrefs(view);
0787 }
0788 
0789 void setActionStatus(QAction *action, bool enabled, bool visible, bool checked)
0790 {
0791     action->setEnabled(enabled);
0792     action->setVisible(visible);
0793     action->setChecked(checked);
0794 }
0795 
0796 void MainWindow::scale(bool scale)
0797 {
0798     qCDebug(KRDC);
0799 
0800     RemoteView *view = currentRemoteView();
0801     view->enableScaling(scale);
0802     if (m_fullscreenWindow)
0803         view->hostPreferences()->setFullscreenScale(scale);
0804     else
0805         view->hostPreferences()->setWindowedScale(scale);
0806 
0807     saveHostPrefs(view);
0808 
0809     Q_EMIT scaleUpdated(scale);
0810 }
0811 
0812 void MainWindow::setFactor(int scale)
0813 {
0814     float s = float(scale) / 100.;
0815 
0816     RemoteView *view = currentRemoteView();
0817     if (view) {
0818         view->setScaleFactor(s);
0819 
0820         view->enableScaling(view->scaling());
0821         view->hostPreferences()->setScaleFactor(scale);
0822 
0823         saveHostPrefs(view);
0824     }
0825 }
0826 
0827 void MainWindow::showRemoteViewToolbar()
0828 {
0829     qCDebug(KRDC);
0830 
0831     if (!m_toolBar) {
0832         m_toolBar = new FloatingToolBar(m_fullscreenWindow, m_fullscreenWindow);
0833         m_toolBar->setSide(FloatingToolBar::Top);
0834 
0835         KComboBox *sessionComboBox = new KComboBox(m_toolBar);
0836         sessionComboBox->setStyleSheet(QStringLiteral("QComboBox:!editable{background:transparent;}"));
0837         sessionComboBox->setModel(m_tabWidget->getModel());
0838         sessionComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
0839         sessionComboBox->setCurrentIndex(m_tabWidget->currentIndex());
0840         connect(sessionComboBox, SIGNAL(activated(int)), m_tabWidget, SLOT(setCurrentIndex(int)));
0841         connect(m_tabWidget, SIGNAL(currentChanged(int)), sessionComboBox, SLOT(setCurrentIndex(int)));
0842         m_toolBar->addWidget(sessionComboBox);
0843 
0844         QToolBar *buttonBox = new QToolBar(m_toolBar);
0845 
0846         buttonBox->addAction(actionCollection()->action(QStringLiteral("new_connection")));
0847         buttonBox->addAction(actionCollection()->action(QStringLiteral("switch_fullscreen")));
0848 
0849         QAction *minimizeAction = new QAction(m_toolBar);
0850         minimizeAction->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
0851         minimizeAction->setText(i18n("Minimize Full Screen Window"));
0852         connect(minimizeAction, SIGNAL(triggered()), m_fullscreenWindow, SLOT(showMinimized()));
0853         buttonBox->addAction(minimizeAction);
0854 
0855         buttonBox->addAction(actionCollection()->action(QStringLiteral("take_screenshot")));
0856         buttonBox->addAction(actionCollection()->action(QStringLiteral("view_only")));
0857         buttonBox->addAction(actionCollection()->action(QStringLiteral("show_local_cursor")));
0858         buttonBox->addAction(actionCollection()->action(QStringLiteral("grab_all_keys")));
0859         buttonBox->addAction(actionCollection()->action(QStringLiteral("scale")));
0860         buttonBox->addAction(actionCollection()->action(QStringLiteral("scale_factor")));
0861         buttonBox->addAction(actionCollection()->action(QStringLiteral("disconnect")));
0862         buttonBox->addAction(actionCollection()->action(QStringLiteral("file_quit")));
0863 
0864         QAction *stickToolBarAction = new QAction(m_toolBar);
0865         stickToolBarAction->setCheckable(true);
0866         stickToolBarAction->setIcon(QIcon::fromTheme(QStringLiteral("object-locked")));
0867         stickToolBarAction->setText(i18n("Stick Toolbar"));
0868         connect(stickToolBarAction, SIGNAL(triggered(bool)), m_toolBar, SLOT(setSticky(bool)));
0869         buttonBox->addAction(stickToolBarAction);
0870 
0871         m_toolBar->addWidget(buttonBox);
0872     }
0873 }
0874 
0875 void MainWindow::updateActionStatus()
0876 {
0877     qCDebug(KRDC) << m_tabWidget->currentIndex();
0878 
0879     bool enabled = true;
0880 
0881     if (m_tabWidget->currentWidget() == m_newConnectionWidget)
0882         enabled = false;
0883 
0884     RemoteView *view = (m_currentRemoteView >= 0 && enabled) ? currentRemoteView() : nullptr;
0885 
0886     actionCollection()->action(QStringLiteral("take_screenshot"))->setEnabled(enabled);
0887     actionCollection()->action(QStringLiteral("disconnect"))->setEnabled(enabled);
0888 
0889     setActionStatus(actionCollection()->action(QStringLiteral("view_only")), enabled, view ? view->supportsViewOnly() : false, view ? view->viewOnly() : false);
0890 
0891     setActionStatus(actionCollection()->action(QStringLiteral("show_local_cursor")),
0892                     enabled,
0893                     view ? view->supportsLocalCursor() : false,
0894                     view ? view->localCursorState() == RemoteView::CursorOn : false);
0895 
0896     setActionStatus(actionCollection()->action(QStringLiteral("scale")), enabled, view ? view->supportsScaling() : false, view ? view->scaling() : false);
0897 
0898     actionCollection()->action(QStringLiteral("scale_factor"))->setVisible(view ? view->supportsScaling() : false);
0899     setFactor(view ? view->hostPreferences()->scaleFactor() : 0);
0900     Q_EMIT factorUpdated(view ? view->hostPreferences()->scaleFactor() : 0);
0901     Q_EMIT scaleUpdated(view ? view->scaling() : false);
0902 
0903     setActionStatus(actionCollection()->action(QStringLiteral("grab_all_keys")), enabled, enabled, view ? view->grabAllKeys() : false);
0904 }
0905 
0906 void MainWindow::preferences()
0907 {
0908     // An instance of your dialog could be already created and could be
0909     // cached, in which case you want to display the cached dialog
0910     // instead of creating another one
0911     if (PreferencesDialog::showDialog(QStringLiteral("preferences")))
0912         return;
0913 
0914     // KConfigDialog didn't find an instance of this dialog, so lets
0915     // create it:
0916     PreferencesDialog *dialog = new PreferencesDialog(this, Settings::self());
0917 
0918     // User edited the configuration - update your local copies of the
0919     // configuration data
0920     connect(dialog, SIGNAL(settingsChanged(QString)), this, SLOT(updateConfiguration()));
0921 
0922     dialog->show();
0923 }
0924 
0925 void MainWindow::updateConfiguration()
0926 {
0927     if (!Settings::showStatusBar())
0928         statusBar()->deleteLater();
0929     else
0930         statusBar()->showMessage({}); // force creation of statusbar
0931 
0932     m_tabWidget->tabBar()->setHidden((m_tabWidget->count() <= 1 && !Settings::showTabBar()) || m_fullscreenWindow);
0933     m_tabWidget->setTabPosition((QTabWidget::TabPosition)Settings::tabPosition());
0934     m_tabWidget->setTabsClosable(Settings::tabCloseButton());
0935 
0936     disconnect(m_tabWidget, SIGNAL(mouseMiddleClick(int)), this, SLOT(closeTab(int))); // just be sure it is not connected twice
0937     if (Settings::tabMiddleClick())
0938         connect(m_tabWidget, SIGNAL(mouseMiddleClick(int)), SLOT(closeTab(int)));
0939 
0940     if (Settings::systemTrayIcon() && !m_systemTrayIcon) {
0941         m_systemTrayIcon = new SystemTrayIcon(this);
0942         if (m_systemTrayIcon) {
0943 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0944             m_systemTrayIcon->setAssociatedWidget(m_fullscreenWindow);
0945 #else
0946             m_systemTrayIcon->setAssociatedWindow(m_fullscreenWindow->windowHandle());
0947 #endif
0948         }
0949     } else if (m_systemTrayIcon) {
0950         delete m_systemTrayIcon;
0951         m_systemTrayIcon = nullptr;
0952     }
0953 
0954     // update the scroll areas background color
0955     for (int i = 0; i < m_tabWidget->count(); ++i) {
0956         QPalette palette = m_tabWidget->widget(i)->palette();
0957         palette.setColor(QPalette::Dark, Settings::backgroundColor());
0958         m_tabWidget->widget(i)->setPalette(palette);
0959     }
0960 
0961     if (m_protocolInput) {
0962         m_protocolInput->setCurrentText(Settings::defaultProtocol());
0963     }
0964 
0965     // Send update configuration message to all views
0966     for (RemoteView *view : qAsConst(m_remoteViewMap)) {
0967         view->updateConfiguration();
0968     }
0969 }
0970 
0971 void MainWindow::quit(bool systemEvent)
0972 {
0973     const bool haveRemoteConnections = !m_remoteViewMap.isEmpty();
0974     if (systemEvent || !haveRemoteConnections
0975         || KMessageBox::warningContinueCancel(this,
0976                                               i18n("Are you sure you want to quit the KDE Remote Desktop Client?"),
0977                                               i18n("Confirm Quit"),
0978                                               KStandardGuiItem::quit(),
0979                                               KStandardGuiItem::cancel(),
0980                                               QStringLiteral("DoNotAskBeforeExit"))
0981             == KMessageBox::Continue) {
0982         if (Settings::rememberSessions()) { // remember open remote views for next startup
0983             QStringList list;
0984             for (RemoteView *view : qAsConst(m_remoteViewMap)) {
0985                 qCDebug(KRDC) << view->url();
0986                 list.append(view->url().toDisplayString(QUrl::StripTrailingSlash));
0987             }
0988             Settings::setOpenSessions(list);
0989         }
0990 
0991         saveHostPrefs();
0992 
0993         const QMap<QWidget *, RemoteView *> currentViews = m_remoteViewMap;
0994         for (RemoteView *view : currentViews) {
0995             view->startQuitting();
0996         }
0997 
0998         Settings::self()->save();
0999 
1000         qApp->quit();
1001     }
1002 }
1003 
1004 void MainWindow::configureNotifications()
1005 {
1006     KNotifyConfigWidget::configure(this);
1007 }
1008 
1009 void MainWindow::showMenubar()
1010 {
1011     if (m_menubarAction->isChecked())
1012         menuBar()->show();
1013     else
1014         menuBar()->hide();
1015 }
1016 
1017 bool MainWindow::eventFilter(QObject *obj, QEvent *event)
1018 {
1019     // check for close events from the fullscreen window.
1020     if (obj == m_fullscreenWindow && event->type() == QEvent::Close) {
1021         quit(true);
1022     }
1023     // allow other events to pass through.
1024     return QObject::eventFilter(obj, event);
1025 }
1026 
1027 void MainWindow::closeEvent(QCloseEvent *event)
1028 {
1029     if (event->spontaneous()) { // Returns true if the event originated outside the application (a system event); otherwise returns false.
1030         event->ignore();
1031         if (Settings::systemTrayIcon()) {
1032             hide(); // just hide the mainwindow, keep it in systemtray
1033         } else {
1034             quit();
1035         }
1036     } else {
1037         quit(true);
1038     }
1039 }
1040 
1041 void MainWindow::saveProperties(KConfigGroup &group)
1042 {
1043     qCDebug(KRDC);
1044     KMainWindow::saveProperties(group);
1045     saveHostPrefs();
1046 }
1047 
1048 void MainWindow::saveHostPrefs()
1049 {
1050     for (RemoteView *view : qAsConst(m_remoteViewMap)) {
1051         saveHostPrefs(view);
1052     }
1053 }
1054 
1055 void MainWindow::saveHostPrefs(RemoteView *view)
1056 {
1057     // should saving this be a user option?
1058     if (view && view->scaling()) {
1059         QSize viewSize = m_tabWidget->currentWidget()->size();
1060         qCDebug(KRDC) << "saving window size:" << viewSize;
1061         view->hostPreferences()->setWidth(viewSize.width());
1062         view->hostPreferences()->setHeight(viewSize.height());
1063     }
1064 
1065     Settings::self()->config()->sync();
1066 }
1067 
1068 void MainWindow::tabChanged(int index)
1069 {
1070     qCDebug(KRDC) << index;
1071 
1072     m_tabWidget->tabBar()->setHidden((m_tabWidget->count() <= 1 && !Settings::showTabBar()) || m_fullscreenWindow);
1073 
1074     m_currentRemoteView = index;
1075 
1076     if (m_tabWidget->currentWidget() == m_newConnectionWidget) {
1077         m_currentRemoteView = -1;
1078         if (m_addressInput)
1079             m_addressInput->setFocus();
1080     }
1081 
1082     const QString tabTitle = m_tabWidget->tabText(index).remove(QLatin1Char('&'));
1083     setCaption(tabTitle == i18n("New Connection") ? QString() : tabTitle);
1084 
1085     updateActionStatus();
1086 }
1087 
1088 QWidget *MainWindow::newConnectionWidget()
1089 {
1090     if (m_newConnectionWidget)
1091         return m_newConnectionWidget;
1092 
1093     m_newConnectionWidget = new QWidget(this);
1094 
1095     QVBoxLayout *startLayout = new QVBoxLayout(m_newConnectionWidget);
1096     startLayout->setContentsMargins(QMargins(8, 4, 8, 4));
1097 
1098     QSortFilterProxyModel *remoteDesktopsModelProxy = new QSortFilterProxyModel(this);
1099     remoteDesktopsModelProxy->setSourceModel(m_remoteDesktopsModel);
1100     remoteDesktopsModelProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
1101     remoteDesktopsModelProxy->setFilterRole(10002);
1102 
1103     {
1104         QHBoxLayout *connectLayout = new QHBoxLayout;
1105 
1106         QLabel *addressLabel = new QLabel(i18n("Connect to:"), m_newConnectionWidget);
1107         m_protocolInput = new KComboBox(m_newConnectionWidget);
1108         m_addressInput = new KLineEdit(m_newConnectionWidget);
1109         m_addressInput->setClearButtonEnabled(true);
1110         m_addressInput->setPlaceholderText(i18n("Type here to connect to an address and filter the list."));
1111         connect(m_addressInput, SIGNAL(textChanged(QString)), remoteDesktopsModelProxy, SLOT(setFilterFixedString(QString)));
1112 
1113         for (RemoteViewFactory *factory : qAsConst(m_remoteViewFactories)) {
1114             m_protocolInput->addItem(factory->scheme());
1115         }
1116         m_protocolInput->setCurrentText(Settings::defaultProtocol());
1117 
1118         connect(m_addressInput, SIGNAL(returnPressed()), SLOT(newConnection()));
1119         m_addressInput->setToolTip(i18n("Type an IP or DNS Name here. Clear the line to get a list of connection methods."));
1120 
1121         QPushButton *connectButton = new QPushButton(m_newConnectionWidget);
1122         connectButton->setToolTip(i18n("Goto Address"));
1123         connectButton->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-locationbar")));
1124         connect(connectButton, SIGNAL(clicked()), SLOT(newConnection()));
1125 
1126         connectLayout->addWidget(addressLabel);
1127         connectLayout->addWidget(m_protocolInput);
1128         connectLayout->addWidget(m_addressInput, 1);
1129         connectLayout->addWidget(connectButton);
1130         connectLayout->setContentsMargins(QMargins(0, 6, 0, 10));
1131         startLayout->addLayout(connectLayout);
1132     }
1133 
1134     {
1135         m_newConnectionTableView = new QTableView(m_newConnectionWidget);
1136         m_newConnectionTableView->setModel(remoteDesktopsModelProxy);
1137 
1138         // set up the view so it looks nice
1139         m_newConnectionTableView->setItemDelegate(new ConnectionDelegate(m_newConnectionTableView));
1140         m_newConnectionTableView->setShowGrid(false);
1141         m_newConnectionTableView->setSelectionMode(QAbstractItemView::NoSelection);
1142         m_newConnectionTableView->verticalHeader()->hide();
1143         m_newConnectionTableView->verticalHeader()->setDefaultSectionSize(m_newConnectionTableView->fontMetrics().height() + 3);
1144         m_newConnectionTableView->horizontalHeader()->setStretchLastSection(true);
1145         m_newConnectionTableView->setAlternatingRowColors(true);
1146         // set up sorting and actions (double click open, right click custom menu)
1147         m_newConnectionTableView->setSortingEnabled(true);
1148         m_newConnectionTableView->sortByColumn(Settings::connectionListSortColumn(), Qt::SortOrder(Settings::connectionListSortOrder()));
1149         m_newConnectionTableView->resizeColumnsToContents();
1150         connect(m_newConnectionTableView->horizontalHeader(),
1151                 SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)),
1152                 SLOT(saveConnectionListSort(int, Qt::SortOrder)));
1153         connect(m_newConnectionTableView, SIGNAL(doubleClicked(QModelIndex)), SLOT(openFromRemoteDesktopsModel(QModelIndex)));
1154         // useful to edit similar address
1155         connect(m_newConnectionTableView, SIGNAL(clicked(QModelIndex)), SLOT(selectFromRemoteDesktopsModel(QModelIndex)));
1156         m_newConnectionTableView->setContextMenuPolicy(Qt::CustomContextMenu);
1157         connect(m_newConnectionTableView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showConnectionContextMenu(QPoint)));
1158 
1159         startLayout->addWidget(m_newConnectionTableView);
1160     }
1161 
1162     return m_newConnectionWidget;
1163 }
1164 
1165 void MainWindow::saveConnectionListSort(const int logicalindex, const Qt::SortOrder order)
1166 {
1167     Settings::setConnectionListSortColumn(logicalindex);
1168     Settings::setConnectionListSortOrder(order);
1169     Settings::self()->save();
1170 }
1171 
1172 void MainWindow::newConnectionPage(bool clearInput)
1173 {
1174     const int indexOfNewConnectionWidget = m_tabWidget->indexOf(m_newConnectionWidget);
1175     if (indexOfNewConnectionWidget >= 0)
1176         m_tabWidget->setCurrentIndex(indexOfNewConnectionWidget);
1177     else {
1178         const int index = m_tabWidget->addTab(newConnectionWidget(), i18n("New Connection"));
1179         m_tabWidget->setCurrentIndex(index);
1180     }
1181     if (clearInput) {
1182         m_addressInput->clear();
1183     } else {
1184         m_addressInput->selectAll();
1185     }
1186     m_addressInput->setFocus();
1187 }
1188 
1189 QMap<QWidget *, RemoteView *> MainWindow::remoteViewList() const
1190 {
1191     return m_remoteViewMap;
1192 }
1193 
1194 QList<RemoteViewFactory *> MainWindow::remoteViewFactoriesList() const
1195 {
1196     return m_remoteViewFactories.values();
1197 }
1198 
1199 RemoteView *MainWindow::currentRemoteView() const
1200 {
1201     if (m_currentRemoteView >= 0) {
1202         return m_remoteViewMap.value(m_tabWidget->widget(m_currentRemoteView));
1203     } else {
1204         return nullptr;
1205     }
1206 }
1207 
1208 void MainWindow::createDockWidget()
1209 {
1210     QDockWidget *remoteDesktopsDockWidget = new QDockWidget(this);
1211     QWidget *remoteDesktopsDockLayoutWidget = new QWidget(remoteDesktopsDockWidget);
1212     QVBoxLayout *remoteDesktopsDockLayout = new QVBoxLayout(remoteDesktopsDockLayoutWidget);
1213     remoteDesktopsDockWidget->setObjectName(QStringLiteral("remoteDesktopsDockWidget")); // required for saving position / state
1214     remoteDesktopsDockWidget->setWindowTitle(i18n("Remote Desktops"));
1215     QFontMetrics fontMetrics(remoteDesktopsDockWidget->font());
1216     remoteDesktopsDockWidget->setMinimumWidth(fontMetrics.horizontalAdvance(QStringLiteral("vnc://192.168.100.100:6000")));
1217     QAction *dockAction = actionCollection()->addAction(QStringLiteral("remote_desktop_dockwidget"), remoteDesktopsDockWidget->toggleViewAction());
1218     dockAction->setIcon(QIcon::fromTheme(QStringLiteral("view-sidetree")));
1219 
1220     m_dockWidgetTableView = new QTableView(remoteDesktopsDockLayoutWidget);
1221     m_remoteDesktopsModel = new RemoteDesktopsModel(this, m_bookmarkManager->getManager());
1222     QSortFilterProxyModel *remoteDesktopsModelProxy = new QSortFilterProxyModel(this);
1223     remoteDesktopsModelProxy->setSourceModel(m_remoteDesktopsModel);
1224     remoteDesktopsModelProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
1225     remoteDesktopsModelProxy->setFilterRole(10002);
1226     m_dockWidgetTableView->setModel(remoteDesktopsModelProxy);
1227 
1228     m_dockWidgetTableView->setShowGrid(false);
1229     m_dockWidgetTableView->verticalHeader()->hide();
1230     m_dockWidgetTableView->verticalHeader()->setDefaultSectionSize(m_dockWidgetTableView->fontMetrics().height() + 2);
1231     m_dockWidgetTableView->horizontalHeader()->hide();
1232     m_dockWidgetTableView->horizontalHeader()->setStretchLastSection(true);
1233     // hide all columns, then show the one we want
1234     for (int i = 0; i < remoteDesktopsModelProxy->columnCount(); i++) {
1235         m_dockWidgetTableView->hideColumn(i);
1236     }
1237     m_dockWidgetTableView->showColumn(RemoteDesktopsModel::Title);
1238     m_dockWidgetTableView->sortByColumn(RemoteDesktopsModel::Title, Qt::AscendingOrder);
1239 
1240     connect(m_dockWidgetTableView, SIGNAL(doubleClicked(QModelIndex)), SLOT(openFromRemoteDesktopsModel(QModelIndex)));
1241 
1242     KLineEdit *filterLineEdit = new KLineEdit(remoteDesktopsDockLayoutWidget);
1243     filterLineEdit->setPlaceholderText(i18n("Filter"));
1244     filterLineEdit->setClearButtonEnabled(true);
1245     connect(filterLineEdit, SIGNAL(textChanged(QString)), remoteDesktopsModelProxy, SLOT(setFilterFixedString(QString)));
1246     remoteDesktopsDockLayout->addWidget(filterLineEdit);
1247     remoteDesktopsDockLayout->addWidget(m_dockWidgetTableView);
1248     remoteDesktopsDockWidget->setWidget(remoteDesktopsDockLayoutWidget);
1249     addDockWidget(Qt::LeftDockWidgetArea, remoteDesktopsDockWidget);
1250 }