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