File indexing completed on 2024-05-12 16:01:41
0001 /* 0002 * SPDX-FileCopyrightText: 2008 Sven Langkamp <sven.langkamp@gmail.com> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "kis_selection_decoration.h" 0008 0009 #include <QPainter> 0010 #include <QVarLengthArray> 0011 #include <QApplication> 0012 #include <QMainWindow> 0013 #include <QWindow> 0014 #include <QScreen> 0015 0016 #include <kis_debug.h> 0017 #include <klocalizedstring.h> 0018 0019 #include <KisMainWindow.h> 0020 #include "kis_types.h" 0021 #include "KisViewManager.h" 0022 #include "kis_selection.h" 0023 #include "kis_image.h" 0024 #include "flake/kis_shape_selection.h" 0025 #include "kis_pixel_selection.h" 0026 #include "kis_update_outline_job.h" 0027 #include "kis_selection_manager.h" 0028 #include "canvas/kis_canvas2.h" 0029 #include "kis_canvas_resource_provider.h" 0030 #include "kis_coordinates_converter.h" 0031 #include "kis_config.h" 0032 #include "kis_config_notifier.h" 0033 #include "kis_image_config.h" 0034 #include "KisImageConfigNotifier.h" 0035 #include "kis_painting_tweaks.h" 0036 #include "KisView.h" 0037 #include "kis_selection_mask.h" 0038 #include <KisPart.h> 0039 0040 static const unsigned int ANT_LENGTH = 4; 0041 static const unsigned int ANT_SPACE = 4; 0042 static const unsigned int ANT_ADVANCE_WIDTH = ANT_LENGTH + ANT_SPACE; 0043 0044 KisSelectionDecoration::KisSelectionDecoration(QPointer<KisView>_view) 0045 : KisCanvasDecoration("selection", _view), 0046 m_signalCompressor(50 /*ms*/, KisSignalCompressor::FIRST_ACTIVE), 0047 m_offset(0), 0048 m_mode(Ants) 0049 { 0050 Q_ASSERT(view()); 0051 Q_ASSERT(view()->mainWindow()); 0052 Q_ASSERT(view()->mainWindow()->windowHandle()); 0053 0054 m_window = view()->mainWindow()->windowHandle(); 0055 connect(m_window, SIGNAL(screenChanged(QScreen*)), this, SLOT(screenChanged(QScreen*))); 0056 0057 m_screen = m_window->screen(); 0058 connect(m_screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SLOT(initializePens())); 0059 0060 initializePens(); 0061 0062 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); 0063 connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); 0064 slotConfigChanged(); 0065 0066 m_antsTimer = new QTimer(this); 0067 m_antsTimer->setInterval(150); 0068 m_antsTimer->setSingleShot(false); 0069 connect(m_antsTimer, SIGNAL(timeout()), SLOT(antsAttackEvent())); 0070 0071 connect(&m_signalCompressor, SIGNAL(timeout()), SLOT(slotStartUpdateSelection())); 0072 0073 // selections should be at the top of the stack 0074 setPriority(100); 0075 } 0076 0077 KisSelectionDecoration::~KisSelectionDecoration() 0078 { 0079 } 0080 0081 KisSelectionDecoration::Mode KisSelectionDecoration::mode() const 0082 { 0083 return m_mode; 0084 } 0085 0086 void KisSelectionDecoration::setMode(Mode mode) 0087 { 0088 m_mode = mode; 0089 selectionChanged(); 0090 } 0091 0092 bool KisSelectionDecoration::selectionIsActive() 0093 { 0094 KisSelectionSP selection = view()->selection(); 0095 return visible() && selection && 0096 (selection->hasNonEmptyPixelSelection() || selection->hasNonEmptyShapeSelection()) && 0097 selection->isVisible(); 0098 } 0099 0100 void KisSelectionDecoration::initializePens() 0101 { 0102 /** 0103 * On MacOS in HiDPI mode devicePixelRatio is always fixed to 2.0, 0104 * so we should use physical DPI for better guess of the screen 0105 * detail level (which is divided by the pixel ratio in Qt). 0106 */ 0107 const qreal dotsPerInch = m_screen->physicalDotsPerInch() * m_screen->devicePixelRatio(); 0108 int screenScale = 1; 0109 0110 if (dotsPerInch < 220) { 0111 screenScale = 1; 0112 } 0113 else if (dotsPerInch < 300) { 0114 screenScale = 2; 0115 } 0116 else if (dotsPerInch < 500) { 0117 screenScale = 3; 0118 } 0119 else { 0120 screenScale = 4; 0121 } 0122 0123 KisPaintingTweaks::initAntsPen(&m_antsPen, &m_outlinePen, 0124 ANT_LENGTH, ANT_SPACE); 0125 0126 if (screenScale > 1) { 0127 m_antsPen.setWidth(screenScale); 0128 m_outlinePen.setWidth(screenScale); 0129 } 0130 else { 0131 m_antsPen.setCosmetic(true); 0132 m_outlinePen.setCosmetic(true); 0133 } 0134 } 0135 0136 void KisSelectionDecoration::selectionChanged() 0137 { 0138 KisSelectionMaskSP mask = qobject_cast<KisSelectionMask*>(view()->currentNode().data()); 0139 if (!mask || !mask->active() || !mask->visible(true)) { 0140 mask = 0; 0141 } 0142 0143 if (!view()->isCurrent() || 0144 view()->viewManager()->mainWindow() == KisPart::instance()->currentMainwindow()) { 0145 0146 view()->image()->setOverlaySelectionMask(mask); 0147 } 0148 0149 KisSelectionSP selection = view()->selection(); 0150 0151 if (!mask && selection && selectionIsActive()) { 0152 if ((m_mode == Ants && selection->outlineCacheValid()) || 0153 (m_mode == Mask && selection->thumbnailImageValid())) { 0154 0155 m_signalCompressor.stop(); 0156 0157 if (m_mode == Ants) { 0158 m_outlinePath = selection->outlineCache(); 0159 m_antsTimer->start(); 0160 } else { 0161 m_thumbnailImage = selection->thumbnailImage(); 0162 m_thumbnailImageTransform = selection->thumbnailImageTransform(); 0163 } 0164 if (view() && view()->canvasBase()) { 0165 view()->canvasBase()->updateCanvas(); 0166 } 0167 0168 } else { 0169 m_signalCompressor.start(); 0170 } 0171 } else { 0172 m_signalCompressor.stop(); 0173 m_outlinePath = QPainterPath(); 0174 m_thumbnailImage = QImage(); 0175 m_thumbnailImageTransform = QTransform(); 0176 view()->canvasBase()->updateCanvas(); 0177 m_antsTimer->stop(); 0178 } 0179 } 0180 0181 void KisSelectionDecoration::slotStartUpdateSelection() 0182 { 0183 KisSelectionSP selection = view()->selection(); 0184 if (!selection) return; 0185 0186 view()->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, m_mode == Mask, m_maskColor)); 0187 } 0188 0189 void KisSelectionDecoration::slotConfigChanged() 0190 { 0191 KisImageConfig imageConfig(true); 0192 KisConfig cfg(true); 0193 0194 m_maskColor = imageConfig.selectionOverlayMaskColor(); 0195 m_antialiasSelectionOutline = cfg.antialiasSelectionOutline(); 0196 } 0197 0198 void KisSelectionDecoration::screenChanged(QScreen *screen) 0199 { 0200 m_screen = screen; 0201 initializePens(); 0202 } 0203 0204 void KisSelectionDecoration::antsAttackEvent() 0205 { 0206 KisSelectionSP selection = view()->selection(); 0207 if (!selection) return; 0208 0209 if (selectionIsActive()) { 0210 m_offset = (m_offset + 1) % (ANT_ADVANCE_WIDTH); 0211 m_antsPen.setDashOffset(m_offset); 0212 view()->canvasBase()->updateCanvas(); 0213 } 0214 } 0215 0216 void KisSelectionDecoration::drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter, KisCanvas2 *canvas) 0217 { 0218 Q_UNUSED(updateRect); 0219 Q_UNUSED(canvas); 0220 0221 if (!selectionIsActive()) return; 0222 if ((m_mode == Ants && m_outlinePath.isEmpty()) || 0223 (m_mode == Mask && m_thumbnailImage.isNull())) return; 0224 0225 QTransform transform = converter->imageToWidgetTransform(); 0226 0227 gc.save(); 0228 gc.setTransform(transform, false); 0229 0230 if (m_mode == Mask) { 0231 gc.setRenderHints(QPainter::SmoothPixmapTransform | 0232 QPainter::HighQualityAntialiasing, false); 0233 0234 gc.setTransform(m_thumbnailImageTransform, true); 0235 gc.drawImage(QPoint(), m_thumbnailImage); 0236 0237 QRect r1 = m_thumbnailImageTransform.inverted().mapRect(view()->image()->bounds()); 0238 QRect r2 = m_thumbnailImage.rect(); 0239 0240 QPainterPath p1; 0241 p1.addRect(r1); 0242 0243 QPainterPath p2; 0244 p2.addRect(r2); 0245 0246 gc.setBrush(m_maskColor); 0247 gc.setPen(Qt::NoPen); 0248 gc.drawPath(p1 - p2); 0249 0250 } else /* if (m_mode == Ants) */ { 0251 gc.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing, m_antialiasSelectionOutline); 0252 0253 // render selection outline in white 0254 gc.setPen(m_outlinePen); 0255 gc.drawPath(m_outlinePath); 0256 0257 // render marching ants in black (above the white outline) 0258 gc.setPen(m_antsPen); 0259 gc.drawPath(m_outlinePath); 0260 } 0261 gc.restore(); 0262 } 0263 0264 void KisSelectionDecoration::setVisible(bool v) 0265 { 0266 KisCanvasDecoration::setVisible(v); 0267 selectionChanged(); 0268 } 0269 0270 void KisSelectionDecoration::notifyWindowMinimized(bool minimized) 0271 { 0272 if(minimized) { 0273 m_antsTimer->stop(); 0274 } else { 0275 m_antsTimer->start(); 0276 } 0277 } 0278