File indexing completed on 2024-06-16 04:16:24
0001 /* 0002 * SPDX-FileCopyrightText: 2017 Boudewijn Rempt <boud@valdyas.org> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "TouchDockerDock.h" 0008 0009 #include <QtQuickWidgets/QQuickWidget> 0010 #include <QQmlEngine> 0011 #include <QQmlContext> 0012 #include <QAction> 0013 #include <QUrl> 0014 #include <QKeyEvent> 0015 #include <QApplication> 0016 #include <QQuickWindow> 0017 0018 #include <klocalizedstring.h> 0019 #include <kactioncollection.h> 0020 #include <ksharedconfig.h> 0021 #include <kconfiggroup.h> 0022 0023 #include <kis_action_registry.h> 0024 #include <KoDialog.h> 0025 #include <KoResourcePaths.h> 0026 #include <kis_icon.h> 0027 #include <KoCanvasBase.h> 0028 #include <KoCanvasController.h> 0029 #include <KisViewManager.h> 0030 #include <kis_canvas2.h> 0031 #include <KisMainWindow.h> 0032 #include <kis_config.h> 0033 #include <KisPart.h> 0034 #include <KisDocument.h> 0035 #include <KisMimeDatabase.h> 0036 #include <kis_action_manager.h> 0037 #include <kis_action.h> 0038 0039 #include <Theme.h> 0040 #include <Settings.h> 0041 #include <DocumentManager.h> 0042 #include <KisSketchView.h> 0043 0044 #include <QVersionNumber> 0045 0046 #ifdef Q_OS_WIN 0047 #include "TouchDockerQQuickWidget.h" 0048 #endif 0049 0050 namespace 0051 { 0052 0053 bool shouldSetAcceptTouchEvents() 0054 { 0055 // See https://bugreports.qt.io/browse/QTBUG-66718 0056 static QVersionNumber qtVersion = QVersionNumber::fromString(qVersion()); 0057 static bool retval = qtVersion > QVersionNumber(5, 9, 3) && qtVersion.normalized() != QVersionNumber(5, 10); 0058 return retval; 0059 } 0060 0061 } // namespace 0062 0063 class TouchDockerDock::Private 0064 { 0065 public: 0066 Private() 0067 { 0068 } 0069 0070 TouchDockerDock *q {nullptr}; 0071 bool allowClose {true}; 0072 KisSketchView *sketchView {0}; 0073 QString currentSketchPage; 0074 KoDialog *openDialog {0}; 0075 KoDialog *saveAsDialog {0}; 0076 0077 QMap<QString, QString> buttonMapping; 0078 0079 bool shiftOn {false}; 0080 bool ctrlOn {false}; 0081 bool altOn {false}; 0082 }; 0083 0084 0085 TouchDockerDock::TouchDockerDock() 0086 : QDockWidget(i18n("Touch Docker")) 0087 , d(new Private()) 0088 { 0089 KisConfig cfg(true); 0090 0091 QStringList defaultMapping = QStringList() << "decrease_opacity" 0092 << "increase_opacity" 0093 << "make_brush_color_lighter" 0094 << "make_brush_color_darker" 0095 << "decrease_brush_size" 0096 << "increase_brush_size" 0097 << "previous_preset" 0098 << "clear"; 0099 0100 QStringList mapping = cfg.readEntry<QString>("touchdockermapping", defaultMapping.join(',')).split(','); 0101 for (int i = 0; i < 8; ++i) { 0102 if (i < mapping.size()) { 0103 d->buttonMapping[QString("%1").arg(i + 1)] = mapping[i]; 0104 } 0105 else if (i < defaultMapping.size()) { 0106 d->buttonMapping[QString("%1").arg(i + 1)] = defaultMapping[i]; 0107 } 0108 } 0109 0110 if (!cfg.useOpenGL()) { 0111 QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software); 0112 } 0113 0114 #ifdef Q_OS_WIN 0115 m_quickWidget = new TouchDockerQQuickWidget(this); 0116 #else 0117 m_quickWidget = new QQuickWidget(this); 0118 #endif 0119 if (shouldSetAcceptTouchEvents()) { 0120 m_quickWidget->setAttribute(Qt::WA_AcceptTouchEvents); 0121 } 0122 setWidget(m_quickWidget); 0123 setEnabled(true); 0124 m_quickWidget->engine()->rootContext()->setContextProperty("mainWindow", this); 0125 0126 m_quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/"); 0127 m_quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/"); 0128 0129 m_quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/"); 0130 m_quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/"); 0131 0132 Settings *settings = new Settings(this); 0133 DocumentManager::instance()->setSettingsManager(settings); 0134 m_quickWidget->engine()->rootContext()->setContextProperty("Settings", settings); 0135 0136 Theme *theme = Theme::load(KSharedConfig::openConfig()->group("General").readEntry<QString>("theme", "default"), 0137 m_quickWidget->engine()); 0138 if (theme) { 0139 settings->setTheme(theme); 0140 } 0141 0142 0143 m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); 0144 m_quickWidget->setSource(QUrl("qrc:/touchstrip.qml")); 0145 0146 0147 } 0148 0149 TouchDockerDock::~TouchDockerDock() 0150 { 0151 // Prevent double free 0152 m_quickWidget->setParent(nullptr); 0153 0154 // Work around child widget being refreshed *after* its parent is gone 0155 // https://bugreports.qt.io/browse/QTBUG-81247 0156 delete m_quickWidget; 0157 } 0158 0159 bool TouchDockerDock::allowClose() const 0160 { 0161 return d->allowClose; 0162 } 0163 0164 void TouchDockerDock::setAllowClose(bool allow) 0165 { 0166 d->allowClose = allow; 0167 } 0168 0169 QString TouchDockerDock::currentSketchPage() const 0170 { 0171 return d->currentSketchPage; 0172 } 0173 0174 void TouchDockerDock::setCurrentSketchPage(QString newPage) 0175 { 0176 d->currentSketchPage = newPage; 0177 emit currentSketchPageChanged(); 0178 } 0179 0180 void TouchDockerDock::closeEvent(QCloseEvent* event) 0181 { 0182 if (!d->allowClose) { 0183 event->ignore(); 0184 emit closeRequested(); 0185 } else { 0186 event->accept(); 0187 } 0188 } 0189 0190 void TouchDockerDock::slotButtonPressed(const QString &id) 0191 { 0192 if (id == "fileOpenButton") { 0193 showFileOpenDialog(); 0194 } 0195 else if (id == "fileSaveButton" && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->document()) { 0196 if(m_canvas->viewManager()->document()->path().isEmpty()) { 0197 showFileSaveAsDialog(); 0198 } else { 0199 bool batchMode = m_canvas->viewManager()->document()->fileBatchMode(); 0200 m_canvas->viewManager()->document()->setFileBatchMode(true); 0201 m_canvas->viewManager()->document()->save(true, 0); 0202 m_canvas->viewManager()->document()->setFileBatchMode(batchMode); 0203 } 0204 } 0205 else if (id == "fileSaveAsButton" && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->document()) { 0206 showFileSaveAsDialog(); 0207 } 0208 else { 0209 QAction *a = action(id); 0210 if (a) { 0211 if (a->isCheckable()) { 0212 a->toggle(); 0213 } 0214 else { 0215 a->trigger(); 0216 } 0217 } 0218 0219 else if (id == "shift") { 0220 // set shift state for the next pointer event, somehow 0221 QKeyEvent event(d->shiftOn ? QEvent::KeyRelease : QEvent::KeyPress, 0222 0, 0223 Qt::ShiftModifier); 0224 QApplication::sendEvent(KisPart::instance()->currentMainwindow(), &event); 0225 d->shiftOn = !d->shiftOn; 0226 } 0227 else if (id == "ctrl") { 0228 // set ctrl state for the next pointer event, somehow 0229 QKeyEvent event(d->ctrlOn ? QEvent::KeyRelease : QEvent::KeyPress, 0230 0, 0231 Qt::ControlModifier); 0232 QApplication::sendEvent(KisPart::instance()->currentMainwindow(), &event); 0233 d->ctrlOn = !d->ctrlOn; 0234 } 0235 else if (id == "alt") { 0236 // set alt state for the next pointer event, somehow 0237 QKeyEvent event(d->altOn ? QEvent::KeyRelease : QEvent::KeyPress, 0238 0, 0239 Qt::AltModifier); 0240 QApplication::sendEvent(KisPart::instance()->currentMainwindow(), &event); 0241 d->altOn = !d->altOn; 0242 0243 } 0244 } 0245 } 0246 0247 void TouchDockerDock::slotOpenImage(QString path) 0248 { 0249 if (d->openDialog) { 0250 d->openDialog->accept(); 0251 } 0252 KisPart::instance()->currentMainwindow()->openDocument(path, KisMainWindow::None); 0253 } 0254 0255 void TouchDockerDock::slotSaveAs(QString path, QString mime) 0256 { 0257 if (d->saveAsDialog) { 0258 d->saveAsDialog->accept(); 0259 } 0260 m_canvas->viewManager()->document()->saveAs(path, mime.toLatin1(), true); 0261 m_canvas->viewManager()->document()->waitForSavingToComplete(); 0262 } 0263 0264 void TouchDockerDock::hideFileOpenDialog() 0265 { 0266 if (d->openDialog) { 0267 d->openDialog->accept(); 0268 } 0269 } 0270 0271 void TouchDockerDock::hideFileSaveAsDialog() 0272 { 0273 if (d->saveAsDialog) { 0274 d->saveAsDialog->accept(); 0275 } 0276 } 0277 0278 QString TouchDockerDock::imageForButton(QString id) 0279 { 0280 if (d->buttonMapping.contains(id)) { 0281 id = d->buttonMapping[id]; 0282 } 0283 if (KisActionRegistry::instance()->hasAction(id)) { 0284 QString a = KisActionRegistry::instance()->getActionProperty(id, "icon"); 0285 if (!a.isEmpty()) { 0286 return "image://icon/" + a; 0287 } 0288 } 0289 return QString(); 0290 } 0291 0292 QString TouchDockerDock::iconForButton(QString id, bool useDarkIcons) 0293 { 0294 // adapted from kis_icon_utils, as here we need a path to give to qml, not a QIcon 0295 const char * const prefix = useDarkIcons ? "dark_" : "light_"; 0296 0297 QString name = ""; 0298 0299 if (d->buttonMapping.contains(id)) { 0300 name = d->buttonMapping[id]; 0301 } 0302 else { 0303 name = id; 0304 } 0305 QString realName = QLatin1String(prefix) + name; 0306 0307 // Dark and light, no size specified 0308 const QStringList names = { "pics/" + realName + ".png", 0309 "pics/" + realName + ".svg", 0310 "pics/" + realName + ".svgz", 0311 "pics/" + name + ".png", 0312 "pics/" + name + ".svg", 0313 "pics/" + name + ".svgz", 0314 realName + ".png", 0315 realName + ".svg", 0316 realName + ".svgz", 0317 name, 0318 name + ".png", 0319 name + ".svg", 0320 name + ".svgz"}; 0321 0322 for (const QString &resname : names) { 0323 if (QFile(":/" + resname).exists()) { 0324 return resname; 0325 } 0326 } 0327 return QString(); 0328 } 0329 0330 QString TouchDockerDock::textForButton(QString id) 0331 { 0332 if (d->buttonMapping.contains(id)) { 0333 id = d->buttonMapping[id]; 0334 } 0335 if (KisActionRegistry::instance()->hasAction(id)) { 0336 QString a = KisActionRegistry::instance()->getActionProperty(id, "iconText"); 0337 if (a.isEmpty()) { 0338 a = KisActionRegistry::instance()->getActionProperty(id, "text"); 0339 } 0340 return a; 0341 } 0342 0343 return id; 0344 } 0345 0346 QAction *TouchDockerDock::action(QString id) const 0347 { 0348 if (m_canvas && m_canvas->viewManager()) { 0349 if (d->buttonMapping.contains(id)) { 0350 id = d->buttonMapping[id]; 0351 } 0352 0353 QAction *action = m_canvas->viewManager()->actionManager()->actionByName(id); 0354 if (!action) { 0355 return m_canvas->canvasController()->actionCollection()->action(id); 0356 } 0357 0358 return action; 0359 } 0360 return 0; 0361 } 0362 0363 void TouchDockerDock::showFileOpenDialog() 0364 { 0365 if (!d->openDialog) { 0366 d->openDialog = createDialog("qrc:/opendialog.qml"); 0367 } 0368 0369 d->openDialog->exec(); 0370 } 0371 0372 void TouchDockerDock::showFileSaveAsDialog() 0373 { 0374 if (!d->saveAsDialog) { 0375 d->saveAsDialog = createDialog("qrc:/saveasdialog.qml"); 0376 } 0377 d->saveAsDialog->exec(); 0378 } 0379 0380 void TouchDockerDock::changeEvent(QEvent *event) 0381 { 0382 if (event->type() == QEvent::PaletteChange) { 0383 m_quickWidget->setSource(QUrl("qrc:/touchstrip.qml")); 0384 event->accept(); 0385 } else { 0386 event->ignore(); 0387 } 0388 } 0389 0390 void TouchDockerDock::tabletEvent(QTabletEvent *event) 0391 { 0392 #ifdef Q_OS_WIN 0393 /** 0394 * On Windows (only in WinInk mode), unless we accept the tablet event, 0395 * OS will start windows gestures, like click+hold for right click. 0396 * It will block any mouse events generation. 0397 * 0398 * In our own (hacky) implementation, if we accept the event, we block 0399 * the gesture, but still generate a fake mouse event. 0400 */ 0401 event->accept(); 0402 #else 0403 QDockWidget::tabletEvent(event); 0404 #endif 0405 } 0406 0407 KoDialog *TouchDockerDock::createDialog(const QString qml) 0408 { 0409 KoDialog *dlg = new KoDialog(this); 0410 dlg->setButtons(KoDialog::None); 0411 0412 QQuickWidget *quickWidget = new QQuickWidget(this); 0413 if (shouldSetAcceptTouchEvents()) { 0414 quickWidget->setAttribute(Qt::WA_AcceptTouchEvents); 0415 } 0416 dlg->setMainWidget(quickWidget); 0417 0418 setEnabled(true); 0419 quickWidget->engine()->rootContext()->setContextProperty("mainWindow", this); 0420 0421 quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/"); 0422 quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/"); 0423 0424 quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/"); 0425 quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/"); 0426 0427 0428 Settings *settings = new Settings(this); 0429 DocumentManager::instance()->setSettingsManager(settings); 0430 quickWidget->engine()->rootContext()->setContextProperty("Settings", settings); 0431 0432 Theme *theme = Theme::load(KSharedConfig::openConfig()->group("General").readEntry<QString>("theme", "default"), 0433 quickWidget->engine()); 0434 settings->setTheme(theme); 0435 0436 quickWidget->setSource(QUrl(qml)); 0437 quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); 0438 0439 dlg->setMinimumSize(1280, 768); 0440 0441 return dlg; 0442 } 0443 0444 QObject *TouchDockerDock::sketchKisView() const 0445 { 0446 return d->sketchView; 0447 } 0448 0449 void TouchDockerDock::setSketchKisView(QObject* newView) 0450 { 0451 if (d->sketchView) { 0452 d->sketchView->disconnect(this); 0453 } 0454 0455 if (d->sketchView != newView) { 0456 d->sketchView = qobject_cast<KisSketchView*>(newView); 0457 emit sketchKisViewChanged(); 0458 } 0459 } 0460 0461 void TouchDockerDock::setCanvas(KoCanvasBase *canvas) 0462 { 0463 setEnabled(true); 0464 0465 if (m_canvas == canvas) { 0466 return; 0467 } 0468 0469 if (m_canvas) { 0470 m_canvas->disconnectCanvasObserver(this); 0471 } 0472 0473 if (!canvas) { 0474 m_canvas = 0; 0475 return; 0476 } 0477 0478 m_canvas = dynamic_cast<KisCanvas2*>(canvas); 0479 } 0480 0481 0482 void TouchDockerDock::unsetCanvas() 0483 { 0484 setEnabled(true); 0485 m_canvas = 0; 0486 }