File indexing completed on 2024-06-23 04:26:27
0001 /* 0002 * SPDX-FileCopyrightText: 2009 Cyrille Berger <cberger@cberger.net> 0003 * SPDX-FileCopyrightText: 2021 Deif Lou <ginoba@gmail.com> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "overviewdocker_dock.h" 0009 #include "overviewwidget.h" 0010 0011 #include <QLabel> 0012 #include <QVBoxLayout> 0013 #include <QHBoxLayout> 0014 #include <QToolButton> 0015 #include <QStatusBar> 0016 #include <QApplication> 0017 0018 #include <KisAngleSelector.h> 0019 #include <klocalizedstring.h> 0020 #include "kis_canvas2.h" 0021 #include <KisViewManager.h> 0022 #include <kactioncollection.h> 0023 #include <kis_action.h> 0024 #include <kis_zoom_manager.h> 0025 #include "kis_image.h" 0026 #include "kis_paint_device.h" 0027 #include "kis_signal_compressor.h" 0028 #include "kis_canvas_controller.h" 0029 #include "kis_icon_utils.h" 0030 #include "kis_signals_blocker.h" 0031 #include <KoZoomWidget.h> 0032 #include <kis_icon_utils.h> 0033 0034 #include <kconfiggroup.h> 0035 #include <ksharedconfig.h> 0036 0037 OverviewDockerDock::OverviewDockerDock() 0038 : QDockWidget(i18n("Overview")) 0039 , m_lastOverviewMousePos(0.0, 0.0) 0040 { 0041 m_page = new QWidget(this); 0042 0043 m_overviewWidget = new OverviewWidget(m_page); 0044 m_overviewWidget->setMinimumHeight(50); 0045 m_overviewWidget->setBackgroundRole(QPalette::Base); 0046 // paints background role before paint() 0047 m_overviewWidget->setAutoFillBackground(true); 0048 m_overviewWidget->setAttribute(Qt::WA_AcceptTouchEvents, true); 0049 m_overviewWidget->installEventFilter(this); 0050 connect(m_overviewWidget, SIGNAL(signalDraggingStarted()), SLOT(on_overviewWidget_signalDraggingStarted())); 0051 connect(m_overviewWidget, SIGNAL(signalDraggingFinished()), SLOT(on_overviewWidget_signalDraggingFinished())); 0052 0053 m_controlsContainer = new QWidget(m_page); 0054 0055 m_controlsLayout = new QVBoxLayout; 0056 m_controlsLayout->setContentsMargins(2, 2, 2, 2); 0057 m_controlsLayout->setSpacing(2); 0058 m_controlsContainer->setLayout(m_controlsLayout); 0059 0060 setWidget(m_page); 0061 0062 m_showControlsTimer.setSingleShot(true); 0063 0064 m_showControlsAnimation.setEasingCurve(QEasingCurve(QEasingCurve::InOutCubic)); 0065 connect(&m_showControlsAnimation, &QVariantAnimation::valueChanged, this, &OverviewDockerDock::layoutMainWidgets); 0066 0067 KConfigGroup config = KSharedConfig::openConfig()->group("OverviewDocker"); 0068 m_pinControls = config.readEntry("pinControls", true); 0069 m_areControlsHidden = !m_pinControls; 0070 0071 setEnabled(false); 0072 } 0073 0074 OverviewDockerDock::~OverviewDockerDock() 0075 { 0076 KConfigGroup config = KSharedConfig::openConfig()->group("OverviewDocker"); 0077 config.writeEntry("pinControls", m_pinControls); 0078 } 0079 0080 void OverviewDockerDock::setCanvas(KoCanvasBase * canvas) 0081 { 0082 if(m_canvas == canvas) 0083 return; 0084 0085 setEnabled(canvas != nullptr); 0086 0087 if (m_canvas) { 0088 m_canvas->disconnectCanvasObserver(this); 0089 m_canvas->image()->disconnect(this); 0090 } 0091 0092 if (m_zoomSlider) { 0093 m_controlsSecondRowLayout->removeWidget(m_zoomSlider); 0094 delete m_zoomSlider; 0095 m_zoomSlider = nullptr; 0096 } 0097 0098 if (m_rotateAngleSelector) { 0099 m_controlsSecondRowLayout->removeWidget(m_rotateAngleSelector); 0100 delete m_rotateAngleSelector; 0101 m_rotateAngleSelector = nullptr; 0102 } 0103 0104 if (m_mirrorCanvas) { 0105 m_controlsSecondRowLayout->removeWidget(m_mirrorCanvas); 0106 delete m_mirrorCanvas; 0107 m_mirrorCanvas = nullptr; 0108 } 0109 0110 if (m_pinControlsButton) { 0111 m_controlsSecondRowLayout->removeWidget(m_pinControlsButton); 0112 delete m_pinControlsButton; 0113 m_pinControlsButton = nullptr; 0114 } 0115 0116 // Delete the stretch 0117 while (m_controlsSecondRowLayout && m_controlsSecondRowLayout->count() && m_controlsSecondRowLayout->itemAt(0)->spacerItem()) { 0118 delete m_controlsSecondRowLayout->takeAt(0); 0119 } 0120 0121 m_controlsLayout->removeItem(m_controlsSecondRowLayout); 0122 0123 delete m_controlsSecondRowLayout; 0124 0125 m_canvas = dynamic_cast<KisCanvas2*>(canvas); 0126 0127 m_overviewWidget->setCanvas(m_canvas); 0128 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->zoomController() && m_canvas->viewManager()->zoomController()->zoomAction()) { 0129 m_zoomSlider = m_canvas->viewManager()->zoomController()->zoomAction()->createWidget(m_canvas->imageView()->KisView::statusBar()); 0130 static_cast<KoZoomWidget*>(m_zoomSlider)->setZoomInputFlat(false); 0131 m_controlsLayout->addWidget(m_zoomSlider); 0132 0133 m_rotateAngleSelector = new KisAngleSelector(); 0134 m_rotateAngleSelector->setRange(-360.00, 360.0); 0135 m_rotateAngleSelector->setAngle(m_canvas->rotationAngle()); 0136 m_rotateAngleSelector->setIncreasingDirection(KisAngleGauge::IncreasingDirection_Clockwise); 0137 m_rotateAngleSelector->setFlipOptionsMode(KisAngleSelector::FlipOptionsMode_ContextMenu); 0138 connect(m_rotateAngleSelector, SIGNAL(angleChanged(qreal)), this, SLOT(rotateCanvasView(qreal)), Qt::UniqueConnection); 0139 connect(m_canvas->canvasController()->proxyObject, SIGNAL(canvasOffsetXChanged(int)), this, SLOT(updateSlider())); 0140 0141 m_mirrorCanvas = new QToolButton(); 0142 QList<QAction *> actions = m_canvas->viewManager()->actionCollection()->actions(); 0143 Q_FOREACH(QAction* action, actions) { 0144 if (action->objectName()=="mirror_canvas") { 0145 m_mirrorCanvas->setDefaultAction(action); 0146 } 0147 } 0148 m_mirrorCanvas->setIcon(KisIconUtils::loadIcon("mirror-view-16")); 0149 m_mirrorCanvas->setAutoRaise(true); 0150 connect(m_mirrorCanvas, SIGNAL(toggled(bool)), this, SLOT(mirrorUpdateIcon())); 0151 0152 m_pinControlsButton = new QToolButton; 0153 m_pinControlsButton->setCheckable(true); 0154 m_pinControlsButton->setChecked(m_pinControls); 0155 m_pinControlsButton->setToolTip( 0156 i18nc("Make the controls in the overview docker auto-hide or always visible", "Pin navigation controls") 0157 ); 0158 m_pinControlsButton->setIcon(KisIconUtils::loadIcon("krita_tool_reference_images")); 0159 m_pinControlsButton->setAutoRaise(true); 0160 connect(m_pinControlsButton, SIGNAL(toggled(bool)), SLOT(setPinControls(bool))); 0161 0162 m_controlsSecondRowLayout = new QHBoxLayout(); 0163 0164 m_controlsSecondRowLayout->addWidget(m_rotateAngleSelector); 0165 m_controlsSecondRowLayout->addStretch(); 0166 m_controlsSecondRowLayout->addWidget(m_mirrorCanvas); 0167 m_controlsSecondRowLayout->addStretch(); 0168 m_controlsSecondRowLayout->addWidget(m_pinControlsButton); 0169 m_controlsLayout->addLayout(m_controlsSecondRowLayout); 0170 0171 m_zoomSlider->setVisible(true); 0172 m_rotateAngleSelector->setVisible(true); 0173 0174 // Show/hide the controls 0175 if (m_pinControls) { 0176 showControls(0); 0177 } else { 0178 if (m_cursorIsHover) { 0179 showControls(0); 0180 } else { 0181 hideControls(0); 0182 } 0183 } 0184 } 0185 } 0186 0187 void OverviewDockerDock::unsetCanvas() 0188 { 0189 setEnabled(false); 0190 m_canvas = nullptr; 0191 m_overviewWidget->setCanvas(0); 0192 } 0193 0194 void OverviewDockerDock::mirrorUpdateIcon() 0195 { 0196 if(!m_mirrorCanvas) return; 0197 m_mirrorCanvas->setIcon(KisIconUtils::loadIcon("mirror-view-16")); 0198 } 0199 0200 void OverviewDockerDock::rotateCanvasView(qreal rotation) 0201 { 0202 if (!m_canvas) return; 0203 KisCanvasController *canvasController = 0204 dynamic_cast<KisCanvasController*>(m_canvas->viewManager()->canvasBase()->canvasController()); 0205 if (canvasController) { 0206 canvasController->rotateCanvas(rotation-m_canvas->rotationAngle()); 0207 } 0208 } 0209 0210 void OverviewDockerDock::updateSlider() 0211 { 0212 if (!m_canvas) return; 0213 KisSignalsBlocker l(m_rotateAngleSelector); 0214 0215 m_rotateAngleSelector->setAngle(m_canvas->rotationAngle()); 0216 } 0217 0218 void OverviewDockerDock::setPinControls(bool pin) 0219 { 0220 m_pinControls = pin; 0221 } 0222 0223 void OverviewDockerDock::resizeEvent(QResizeEvent*) 0224 { 0225 layoutMainWidgets(); 0226 } 0227 0228 void OverviewDockerDock::leaveEvent(QEvent*) 0229 { 0230 m_cursorIsHover = false; 0231 if (isEnabled() && !m_pinControls) { 0232 hideControls(0); 0233 m_cumulatedMouseDistanceSquared = 0.0; 0234 } 0235 } 0236 0237 void OverviewDockerDock::enterEvent(QEvent*) 0238 { 0239 m_cursorIsHover = true; 0240 if (isEnabled() && !m_pinControls) { 0241 showControls(showControlsTimerDuration); 0242 } 0243 } 0244 0245 bool OverviewDockerDock::event(QEvent *e) 0246 { 0247 if (e->type() == QEvent::StyleChange || e->type() == QEvent::FontChange) { 0248 resizeEvent(nullptr); 0249 } 0250 return QDockWidget::event(e); 0251 } 0252 0253 bool OverviewDockerDock::eventFilter(QObject *o, QEvent *e) 0254 { 0255 if (!isEnabled()) { 0256 return false; 0257 } 0258 0259 if (o == m_overviewWidget) { 0260 // Filter out the mouse events if we are touching the overview widget 0261 // and the event was not synthesized in this function from the touch events 0262 if (e->type() == QEvent::MouseButtonPress) { 0263 if (m_isTouching) { 0264 return static_cast<QMouseEvent*>(e)->source() != Qt::MouseEventSynthesizedByApplication; 0265 } 0266 0267 } else if (e->type() == QEvent::MouseButtonRelease) { 0268 if (m_isTouching) { 0269 return static_cast<QMouseEvent*>(e)->source() != Qt::MouseEventSynthesizedByApplication; 0270 } 0271 0272 } else if (e->type() == QEvent::MouseMove) { 0273 if (m_isTouching) { 0274 return static_cast<QMouseEvent*>(e)->source() != Qt::MouseEventSynthesizedByApplication; 0275 } 0276 if (!m_overviewWidget->isDragging() && m_areControlsHidden && !m_pinControls) { 0277 QMouseEvent *me = static_cast<QMouseEvent*>(e); 0278 constexpr double showControlsAreaRadiusSquared = showControlsAreaRadius * showControlsAreaRadius; 0279 const QPointF d = me->localPos() - m_lastOverviewMousePos; 0280 const double distanceSquared = d.x() * d.x() + d.y() * d.y(); 0281 if (distanceSquared > m_cumulatedMouseDistanceSquared) { 0282 if (distanceSquared >= showControlsAreaRadiusSquared) { 0283 showControls(showControlsTimerDuration); 0284 m_lastOverviewMousePos = me->localPos(); 0285 m_cumulatedMouseDistanceSquared = 0.0; 0286 } else { 0287 m_cumulatedMouseDistanceSquared = distanceSquared; 0288 } 0289 } 0290 } 0291 0292 } else if (e->type() == QEvent::TouchBegin) { 0293 if (!m_isTouching) { 0294 QTouchEvent *te = static_cast<QTouchEvent*>(e); 0295 m_isTouching = true; 0296 // Store the first touch point. We will only track this one 0297 m_touchPointId = te->touchPoints().first().id(); 0298 m_lastTouchPos = te->touchPoints().first().pos(); 0299 } 0300 // Accept the event so that other touch events keep coming 0301 e->accept(); 0302 return true; 0303 0304 } else if (e->type() == QEvent::TouchUpdate) { 0305 if (!m_isTouching) { 0306 return true; 0307 } 0308 QTouchEvent *te = static_cast<QTouchEvent*>(e); 0309 // Get the touch point position 0310 QPointF currentPosition; 0311 for (const QTouchEvent::TouchPoint &touchPoint : te->touchPoints()) { 0312 if (touchPoint.id() == m_touchPointId) { 0313 // If the touch point wasn't moved, this event wasn't 0314 // generated from our touch point 0315 if (touchPoint.state() == Qt::TouchPointStationary) { 0316 return true; 0317 } 0318 currentPosition = touchPoint.pos(); 0319 break; 0320 } 0321 } 0322 if (!m_isDraggingWithTouch) { 0323 // Compute distance 0324 const QPointF delta = currentPosition - m_lastTouchPos; 0325 const qreal distanceSquared = delta.x() * delta.x() + delta.y() * delta.y(); 0326 if (distanceSquared >= touchDragDistanceSquared) { 0327 m_isDraggingWithTouch = true; 0328 // synthesize mouse press event 0329 QMouseEvent *se = new QMouseEvent(QEvent::MouseButtonPress, 0330 m_lastTouchPos, QPointF(), QPointF(), 0331 Qt::LeftButton, Qt::LeftButton, Qt::NoModifier, 0332 Qt::MouseEventSynthesizedByApplication); 0333 qApp->sendEvent(m_overviewWidget, se); 0334 } 0335 } 0336 if (m_isDraggingWithTouch) { 0337 // Synthesize mouse move event 0338 QMouseEvent *se = new QMouseEvent(QEvent::MouseMove, 0339 currentPosition, QPointF(), QPointF(), 0340 Qt::LeftButton, Qt::LeftButton, Qt::NoModifier, 0341 Qt::MouseEventSynthesizedByApplication); 0342 qApp->sendEvent(m_overviewWidget, se); 0343 // Update 0344 m_lastTouchPos = currentPosition; 0345 } 0346 return true; 0347 0348 } else if (e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { 0349 if (!m_isTouching) { 0350 return true; 0351 } 0352 QTouchEvent *te = static_cast<QTouchEvent*>(e); 0353 if (e->type() == QEvent::TouchEnd) { 0354 // If the touch point is not in the released state 0355 // then this event wasn't generated from our touch point 0356 for (const QTouchEvent::TouchPoint &touchPoint : te->touchPoints()) { 0357 if (touchPoint.id() == m_touchPointId) { 0358 if (touchPoint.state() != Qt::TouchPointReleased) { 0359 return true; 0360 } 0361 break; 0362 } 0363 } 0364 } 0365 // If we are dragging then synthesize mouse release event. 0366 // Show/hide the controls otherwise 0367 if (m_isDraggingWithTouch) { 0368 QMouseEvent *se = new QMouseEvent(QEvent::MouseButtonRelease, 0369 m_lastTouchPos, QPointF(), QPointF(), 0370 Qt::LeftButton, Qt::LeftButton, Qt::NoModifier, 0371 Qt::MouseEventSynthesizedByApplication); 0372 qApp->sendEvent(m_overviewWidget, se); 0373 } else if (e->type() == QEvent::TouchEnd) { 0374 m_pinControls = m_areControlsHidden; 0375 KisSignalsBlocker blocker(m_pinControlsButton); 0376 m_pinControlsButton->setChecked(m_pinControls); 0377 if (m_areControlsHidden) { 0378 showControls(0); 0379 } else { 0380 hideControls(0); 0381 } 0382 } 0383 // Reset 0384 m_isTouching = false; 0385 m_isDraggingWithTouch = false; 0386 return true; 0387 } 0388 } 0389 return false; 0390 } 0391 0392 void OverviewDockerDock::layoutMainWidgets() 0393 { 0394 m_page->setMinimumHeight(m_overviewWidget->minimumHeight() + 0395 m_controlsContainer->minimumSizeHint().height()); 0396 0397 if (m_showControlsAnimation.state() == QVariantAnimation::Running) { 0398 const qreal pageHeight = static_cast<qreal>(m_page->height()); 0399 const qreal controlsContainerHeight = static_cast<qreal>(m_controlsContainer->sizeHint().height()); 0400 const qreal animationProgress = m_showControlsAnimation.currentValue().toReal(); 0401 const int widgetLimitPosition = static_cast<int>(std::round(pageHeight - animationProgress * controlsContainerHeight)); 0402 m_overviewWidget->setGeometry(0, 0, m_page->width(), widgetLimitPosition); 0403 m_controlsContainer->setGeometry(0, widgetLimitPosition, m_page->width(), static_cast<int>(controlsContainerHeight)); 0404 } else { 0405 const int controlsContainerHeight = m_controlsContainer->sizeHint().height(); 0406 if (m_pinControls || !m_areControlsHidden) { 0407 const int widgetLimitPosition = m_page->height() - controlsContainerHeight; 0408 m_overviewWidget->setGeometry(0, 0, m_page->width(), widgetLimitPosition); 0409 m_controlsContainer->setGeometry(0, widgetLimitPosition, m_page->width(), controlsContainerHeight); 0410 } else { 0411 m_overviewWidget->setGeometry(0, 0, m_page->width(), m_page->height()); 0412 m_controlsContainer->setGeometry(0, m_page->height(), m_page->width(), controlsContainerHeight); 0413 } 0414 } 0415 } 0416 0417 void OverviewDockerDock::showControls(int delay) const 0418 { 0419 auto animFunction = 0420 [this]() -> void 0421 { 0422 int animationDuration; 0423 qreal animationStartValue; 0424 0425 if (m_areControlsHidden) { 0426 if (m_showControlsAnimation.state() == QVariantAnimation::Running) { 0427 m_showControlsAnimation.stop(); 0428 animationDuration = 0429 static_cast<int>(std::round((1.0 - m_showControlsAnimation.currentValue().toReal()) * showControlsAnimationDuration)); 0430 animationStartValue = m_showControlsAnimation.currentValue().toReal(); 0431 } else { 0432 animationDuration = showControlsAnimationDuration; 0433 animationStartValue = 0.0; 0434 } 0435 } else { 0436 animationDuration = 1; 0437 animationStartValue = 1.0; 0438 } 0439 0440 m_areControlsHidden = false; 0441 m_showControlsAnimation.setStartValue(animationStartValue); 0442 m_showControlsAnimation.setEndValue(1.0); 0443 m_showControlsAnimation.setDuration(animationDuration); 0444 m_showControlsAnimation.start(); 0445 }; 0446 0447 delay = qMax(delay, 0); 0448 0449 m_showControlsTimer.disconnect(); 0450 connect(&m_showControlsTimer, &QTimer::timeout, animFunction); 0451 m_showControlsTimer.start(delay); 0452 } 0453 0454 void OverviewDockerDock::hideControls(int delay) const 0455 { 0456 auto animFunction = 0457 [this]() -> void 0458 { 0459 int animationDuration; 0460 qreal animationStartValue; 0461 0462 if (!m_areControlsHidden) { 0463 if (m_showControlsAnimation.state() == QVariantAnimation::Running) { 0464 m_showControlsAnimation.stop(); 0465 animationDuration = 0466 static_cast<int>(std::round(m_showControlsAnimation.currentValue().toReal() * showControlsAnimationDuration)); 0467 animationStartValue = m_showControlsAnimation.currentValue().toReal(); 0468 } else { 0469 animationDuration = showControlsAnimationDuration; 0470 animationStartValue = 1.0; 0471 } 0472 } else { 0473 animationDuration = 1; 0474 animationStartValue = 0.0; 0475 } 0476 0477 m_areControlsHidden = true; 0478 m_showControlsAnimation.setStartValue(animationStartValue); 0479 m_showControlsAnimation.setEndValue(0.0); 0480 m_showControlsAnimation.setDuration(animationDuration); 0481 m_showControlsAnimation.start(); 0482 }; 0483 0484 delay = qMax(delay, 0); 0485 0486 m_showControlsTimer.disconnect(); 0487 connect(&m_showControlsTimer, &QTimer::timeout, animFunction); 0488 m_showControlsTimer.start(delay); 0489 } 0490 0491 void OverviewDockerDock::on_overviewWidget_signalDraggingStarted() 0492 { 0493 if (!m_pinControls && m_areControlsHidden && m_showControlsTimer.isActive()) { 0494 m_showControlsTimer.stop(); 0495 } 0496 } 0497 0498 void OverviewDockerDock::on_overviewWidget_signalDraggingFinished() 0499 { 0500 if (!m_pinControls && m_areControlsHidden && !m_isTouching) { 0501 showControls(showControlsTimerDuration); 0502 } 0503 }