File indexing completed on 2024-04-28 08:51:04

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