Warning, file /network/krdc/mainwindow.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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