File indexing completed on 2024-05-19 04:29:07

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 #include <KisScreenMigrationTracker.h>
0040 
0041 static const unsigned int ANT_LENGTH = 4;
0042 static const unsigned int ANT_SPACE = 4;
0043 static const unsigned int ANT_ADVANCE_WIDTH = ANT_LENGTH + ANT_SPACE;
0044 
0045 KisSelectionDecoration::KisSelectionDecoration(QPointer<KisView>_view)
0046     : KisCanvasDecoration("selection", _view),
0047       m_signalCompressor(50 /*ms*/, KisSignalCompressor::FIRST_ACTIVE),
0048       m_offset(0),
0049       m_mode(Ants)
0050 {
0051     initializePens();
0052     connect(this->view()->canvasBase()->resourceManager(), SIGNAL(canvasResourceChanged(int, const QVariant&)), this, SLOT(slotCanvasResourcesChanged(int, const QVariant&)));
0053 
0054     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
0055     connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
0056     slotConfigChanged();
0057 
0058     m_antsTimer = new QTimer(this);
0059     m_antsTimer->setInterval(150);
0060     m_antsTimer->setSingleShot(false);
0061     connect(m_antsTimer, SIGNAL(timeout()), SLOT(antsAttackEvent()));
0062 
0063     connect(&m_signalCompressor, SIGNAL(timeout()), SLOT(slotStartUpdateSelection()));
0064 
0065     // selections should be at the top of the stack
0066     setPriority(100);
0067 }
0068 
0069 KisSelectionDecoration::~KisSelectionDecoration()
0070 {
0071 }
0072 
0073 KisSelectionDecoration::Mode KisSelectionDecoration::mode() const
0074 {
0075     return m_mode;
0076 }
0077 
0078 void KisSelectionDecoration::setMode(Mode mode)
0079 {
0080     m_mode = mode;
0081     selectionChanged();
0082 }
0083 
0084 bool KisSelectionDecoration::selectionIsActive()
0085 {
0086     KisSelectionSP selection = view()->selection();
0087     return visible() && selection &&
0088         (selection->hasNonEmptyPixelSelection() || selection->hasNonEmptyShapeSelection()) &&
0089             selection->isVisible();
0090 }
0091 
0092 void KisSelectionDecoration::initializePens()
0093 {
0094     KisPaintingTweaks::initAntsPen(&m_antsPen, &m_outlinePen,
0095                                    ANT_LENGTH, ANT_SPACE);
0096 
0097     m_antsPen.setWidth(decorationThickness());
0098     m_outlinePen.setWidth(decorationThickness());
0099 }
0100 
0101 void KisSelectionDecoration::selectionChanged()
0102 {
0103     KisSelectionMaskSP mask = qobject_cast<KisSelectionMask*>(view()->currentNode().data());
0104     if (!mask || !mask->active() || !mask->visible(true)) {
0105         mask = 0;
0106     }
0107 
0108     if (!view()->isCurrent() ||
0109         view()->viewManager()->mainWindow() == KisPart::instance()->currentMainwindow()) {
0110 
0111         view()->image()->setOverlaySelectionMask(mask);
0112     }
0113 
0114     KisSelectionSP selection = view()->selection();
0115 
0116     if (!mask && selection && selectionIsActive()) {
0117         if ((m_mode == Ants && selection->outlineCacheValid()) ||
0118             (m_mode == Mask && selection->thumbnailImageValid())) {
0119 
0120             m_signalCompressor.stop();
0121 
0122             if (m_mode == Ants) {
0123                 m_outlinePath = selection->outlineCache();
0124                 m_antsTimer->start();
0125             } else {
0126                 m_thumbnailImage = selection->thumbnailImage();
0127                 m_thumbnailImageTransform = selection->thumbnailImageTransform();
0128                 m_antsTimer->stop();
0129             }
0130             if (view() && view()->canvasBase()) {
0131                 view()->canvasBase()->updateCanvas();
0132             }
0133 
0134         } else {
0135             m_signalCompressor.start();
0136         }
0137     } else {
0138         m_signalCompressor.stop();
0139         m_outlinePath = QPainterPath();
0140         m_thumbnailImage = QImage();
0141         m_thumbnailImageTransform = QTransform();
0142         view()->canvasBase()->updateCanvas();
0143         m_antsTimer->stop();
0144     }
0145 }
0146 
0147 void KisSelectionDecoration::slotStartUpdateSelection()
0148 {
0149     KisSelectionSP selection = view()->selection();
0150     if (!selection) return;
0151 
0152     view()->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, m_mode == Mask, m_maskColor));
0153 }
0154 
0155 void KisSelectionDecoration::slotConfigChanged()
0156 {
0157     KisImageConfig imageConfig(true);
0158     KisConfig cfg(true);
0159 
0160     m_opacity = imageConfig.selectionOutlineOpacity();
0161     m_maskColor = imageConfig.selectionOverlayMaskColor();
0162     m_antialiasSelectionOutline = cfg.antialiasSelectionOutline();
0163 }
0164 
0165 void KisSelectionDecoration::slotCanvasResourcesChanged(int key, const QVariant &v)
0166 {
0167     Q_UNUSED(v);
0168     if (key == KoCanvasResource::DecorationThickness) {
0169         initializePens();
0170     }
0171 }
0172 
0173 void KisSelectionDecoration::antsAttackEvent()
0174 {
0175     KisSelectionSP selection = view()->selection();
0176     if (!selection) return;
0177 
0178     if (selectionIsActive()) {
0179         m_offset = (m_offset + 1) % (ANT_ADVANCE_WIDTH);
0180         m_antsPen.setDashOffset(m_offset);
0181         view()->canvasBase()->updateCanvas();
0182     }
0183 }
0184 
0185 void KisSelectionDecoration::drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter, KisCanvas2 *canvas)
0186 {
0187     Q_UNUSED(updateRect);
0188     Q_UNUSED(canvas);
0189 
0190     if (!selectionIsActive()) return;
0191     if ((m_mode == Ants && m_outlinePath.isEmpty()) ||
0192         (m_mode == Mask && m_thumbnailImage.isNull())) return;
0193 
0194     QTransform transform = converter->imageToWidgetTransform();
0195 
0196     gc.save();
0197     gc.setTransform(transform, false);
0198 
0199     if (m_mode == Mask) {
0200         gc.setRenderHints(QPainter::SmoothPixmapTransform |
0201                           QPainter::Antialiasing, false);
0202 
0203         gc.setTransform(m_thumbnailImageTransform, true);
0204         gc.drawImage(QPoint(), m_thumbnailImage);
0205 
0206         QRect r1 = m_thumbnailImageTransform.inverted().mapRect(view()->image()->bounds());
0207         QRect r2 = m_thumbnailImage.rect();
0208 
0209         QPainterPath p1;
0210         p1.addRect(r1);
0211 
0212         QPainterPath p2;
0213         p2.addRect(r2);
0214 
0215         gc.setBrush(m_maskColor);
0216         gc.setPen(Qt::NoPen);
0217         gc.drawPath(p1 - p2);
0218 
0219     } else /* if (m_mode == Ants) */ {
0220         gc.setRenderHints(QPainter::Antialiasing | QPainter::Antialiasing, m_antialiasSelectionOutline);
0221 
0222         gc.setOpacity(m_opacity);
0223 
0224         // render selection outline in white
0225         gc.setPen(m_outlinePen);
0226         gc.drawPath(m_outlinePath);
0227 
0228         // render marching ants in black (above the white outline)
0229         gc.setPen(m_antsPen);
0230         gc.drawPath(m_outlinePath);
0231     }
0232     gc.restore();
0233 }
0234 
0235 void KisSelectionDecoration::setVisible(bool v)
0236 {
0237     KisCanvasDecoration::setVisible(v);
0238     selectionChanged();
0239 }
0240 
0241 void KisSelectionDecoration::notifyWindowMinimized(bool minimized)
0242 {
0243     if(minimized) {
0244         m_antsTimer->stop();
0245     } else {
0246         selectionChanged();
0247     }
0248 }