File indexing completed on 2024-04-21 07:27:51

0001 /***************************************************************************
0002  *   Copyright (C) 2004-2007 by Albert Astals Cid                          *
0003  *   aacid@kde.org                                                         *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  ***************************************************************************/
0010 
0011 #include "placemapwidget.h"
0012 
0013 #include <QColormap>
0014 #include <QCursor>
0015 #include <QGraphicsPixmapItem>
0016 #include <QGraphicsRectItem>
0017 #include <QGraphicsScene>
0018 #include <QMouseEvent>
0019 #include <QScrollBar>
0020 
0021 #include <KLocalizedString>
0022 #include <math.h>
0023 
0024 #include "division.h"
0025 
0026 placeMapWidget::placeMapWidget(QWidget *parent) : QGraphicsView(parent)
0027     , p_mode(None)
0028     , p_mapImage(nullptr)
0029     , p_gameImage(nullptr)
0030     , p_currentCursor(nullptr)
0031     , p_zoomRect(nullptr)
0032     , p_automaticZoom(false)
0033     , p_currentDivisionItem(nullptr)
0034     , lastMouseEvent(QPoint(0,0))
0035 {
0036     setCacheMode( CacheBackground );
0037     p_scene = new QGraphicsScene( this );
0038     p_scene->setBackgroundBrush(Qt::white);
0039     setScene(p_scene);
0040     p_scene->setItemIndexMethod(QGraphicsScene::NoIndex);
0041     setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
0042 }
0043 
0044 placeMapWidget::~placeMapWidget()
0045 {
0046     delete p_currentCursor;
0047     delete p_gameImage;
0048 }
0049 
0050 void placeMapWidget::init(KGmap *map, QImage *mapImage)
0051 {
0052     p_map = map;
0053     p_mapImage = mapImage;
0054     createGameMapImage();
0055     p_scene->clear();
0056     p_scene->addPixmap(QPixmap::fromImage(*p_gameImage));
0057     p_scene->setSceneRect( p_gameImage->rect() );
0058     setMouseTracking(true);
0059 
0060     // work around bug in QGraphicsView?
0061     QMetaObject::invokeMethod(this, "setAutomaticZoom", Qt::QueuedConnection, Q_ARG(bool, p_automaticZoom));
0062 }
0063 
0064 size_t placeMapWidget::nbPixels(int pixelIndex) const
0065 {
0066     return p_pixelsStats[pixelIndex];
0067 }
0068 
0069 static int indexOfPair(uchar pixelIndexMin, uchar pixelIndexMax)
0070 {
0071     const int pairIndex = pixelIndexMin + pixelIndexMax * (pixelIndexMax - 1) / 2;
0072     return pairIndex;
0073 }
0074 
0075 
0076 size_t placeMapWidget::nbBorderPixels(int pixelIndex1, int pixelIndex2) const
0077 {
0078     int pairIndex = indexOfPair(qMin(pixelIndex1, pixelIndex2), qMax(pixelIndex1, pixelIndex2));
0079     return p_bordersStats[pairIndex];
0080 }
0081 
0082 static void addOrderedPixel(QVector<uchar> *pixels, uchar pixelIndex)
0083 {
0084     int i = pixels->size();
0085     do { --i;} while ( i >= 0 && pixels->at(i) > pixelIndex );;
0086     if ( i < 0 || pixels->at(i) < pixelIndex )
0087             pixels->insert(i+1, pixelIndex);
0088 }
0089 
0090 QString writeUpBorderStats(const QVector<size_t> &stats, const QVector<size_t> &histo, const QVector<QRgb> &cmap)
0091 {
0092     int nbCells = stats.size(); // = n * (n*1) / 2 => 2p == n2 + n => n2 + n -2p == 0
0093     // delta = 1 + 8p -> n = (-1 +/- sqrt(1 + 8p))/2
0094     int nbRows = (sqrt((double)1 + 8 * nbCells) -1) /2;
0095     QString ret;
0096     ret += QLatin1Char('\n');
0097     ret.reserve(17 * nbCells + nbRows * 20);
0098     for ( int ic = 0 ; ic < nbRows ; ic++ )
0099         ret += QStringLiteral("%1(%2,%3,%4):%5\n").arg(ic).arg(qRed(cmap[ic])).arg(qGreen(cmap[ic])).arg(qBlue(cmap[ic])).arg(histo[ic]);
0100     ret += QLatin1Char('\n');
0101     for ( int m = 0 ; m < nbRows ; m++ ) {
0102         for ( int p = 0 ; p < m ; p++ )
0103             ret += QStringLiteral("(%1, %2):%3 ").arg(m).arg(p).arg(stats[indexOfPair(p, m)]);
0104         ret += QLatin1Char('\n');
0105     }
0106     return ret;
0107 }
0108 
0109 void placeMapWidget::createGameMapImage()
0110 {
0111     QVector<uchar> indexesToCopy;
0112     const QVector<QRgb> colormap = p_mapImage->colorTable();
0113     p_gameImage = new QImage(p_mapImage->size(), QImage::Format_RGB32);
0114     // So far, nobody has dedicated this color to a division :)
0115     // I, for one, reserve grays for non-division pixels.
0116     p_gameImage->fill(QColor(224,224,224).rgb());
0117 
0118     const QList<const division*> ignoredDivisions = p_map->getIgnoredDivisions(division::eClick);
0119     for(const division *id : ignoredDivisions)
0120     {
0121         const QRgb rgb = id->getRGB();
0122         const int colorIdx = colormap.indexOf(rgb);
0123         indexesToCopy << colorIdx;
0124     }
0125 
0126     const int nbBytesPerLine = p_mapImage->bytesPerLine();
0127     const uchar *bits = p_mapImage->bits();
0128 
0129     const int width = p_mapImage->width();
0130     const int height = p_mapImage->height();
0131     const int deltaX[] = {-1,  0,  1,  1,  1,  0, -1, -1};
0132     const int deltaY[] = {-1, -1, -1,  0,  1,  1,  1,  0};
0133 
0134     const size_t nbColors = p_mapImage->colorTable().size();
0135     p_pixelsStats.resize(nbColors);
0136     p_bordersStats.resize(nbColors * (nbColors +1) / 2);
0137     
0138     for (int x = 1; x < width -1; x++)
0139     {
0140         for (int y = 1; y < height -1; y++)
0141         {
0142             const uchar pixelIndex = bits[y * nbBytesPerLine + x];
0143             p_pixelsStats[pixelIndex] += 1;
0144 
0145             if(indexesToCopy.contains(pixelIndex) )
0146             {
0147                 QVector<uchar> orderedNeighbours;
0148                 bool outerFound = false;
0149                 bool divisionColorFound = false;
0150                 for ( int neighbourIdx = 0 ;
0151                       neighbourIdx < 8 ;
0152                       neighbourIdx ++ )
0153                 {
0154                     const int ox = x + deltaX[neighbourIdx];
0155                     const int oy = y + deltaY[neighbourIdx];
0156                     const uchar oPixelIndex = bits[oy * nbBytesPerLine + ox];
0157                     if (oPixelIndex != pixelIndex)
0158                     {
0159                         addOrderedPixel(&orderedNeighbours, oPixelIndex);
0160                         if ( indexesToCopy.contains(oPixelIndex) )
0161                             outerFound = true;
0162                         else
0163                             divisionColorFound = true;
0164                     }
0165                 }
0166 
0167                 if ( outerFound || ! divisionColorFound )
0168                     p_gameImage->setPixel(x,y,p_mapImage->pixel(x,y));
0169 
0170                 if ( ! divisionColorFound )
0171                     continue;
0172                 for ( int maxIdx = orderedNeighbours.size() - 1 ; --maxIdx >= 0 ; )
0173                 {
0174                     for ( int minIdx = maxIdx ; minIdx >= 0 ; minIdx-- )
0175                     {
0176                         const uchar pixelIndexMin = orderedNeighbours[minIdx];
0177                         const uchar pixelIndexMax = orderedNeighbours[maxIdx + 1];
0178                         const int pairIndex = indexOfPair(pixelIndexMin, pixelIndexMax);
0179                         p_bordersStats[pairIndex] += 1;
0180                     }
0181                 }
0182             }
0183         }
0184     }
0185     p_outerPixelIndices = indexesToCopy;
0186 }
0187 
0188 void placeMapWidget::setMapMove(bool b)
0189 {
0190     if (b)
0191     {
0192         p_mode = WantMove;
0193         setCursor(QCursor(Qt::OpenHandCursor));
0194     }
0195     else if ( p_mode == WantMove || p_mode == Moving )
0196     {
0197         p_mode = None;
0198         setCursor(QCursor(Qt::BlankCursor));
0199     }
0200     updateActions();
0201 }
0202 
0203 void placeMapWidget::setMapZoom(bool b)
0204 {
0205     if (b)
0206     {
0207         p_mode = WantZoom;
0208     }
0209     else if ( p_mode == WantZoom || p_mode == Zooming )
0210     {
0211         p_mode = None;
0212     }
0213     updateActions();
0214 }
0215 
0216 void placeMapWidget::setCurrentDivisionImage(QImage *divisionImage)
0217 {
0218     p_currentDivisionImage = divisionImage;
0219     // add the pixmap and set position to the middle of the pixmap under the mouse
0220     p_currentDivisionItem = p_scene->addPixmap(QPixmap::fromImage(*p_currentDivisionImage));
0221     p_currentDivisionItem->setFlag(QGraphicsItem::ItemIsMovable, true);
0222     QPoint p(lastMouseEvent.x()-p_currentDivisionImage->width()/2,lastMouseEvent.y()-p_currentDivisionImage->height()/2);
0223     p_currentDivisionItem->setPos(mapToScene(p));
0224     updateCursor();
0225 }
0226 
0227 void placeMapWidget::updateCursor()
0228 {
0229     // enable the normal cursor over the scrollbars
0230     viewport()->setCursor(QCursor(Qt::BlankCursor));
0231 }
0232 
0233 void placeMapWidget::placeDivision(QRect& position)
0234 {
0235     // TODO: An animation to position
0236     p_currentDivisionItem->setPos(position.topLeft());
0237 }
0238 
0239 void placeMapWidget::mousePressEvent(QMouseEvent *e)
0240 {
0241     p_initial = mapToScene( e->pos() );
0242     
0243     if (e -> button() == Qt::LeftButton)
0244     {
0245         if ( p_mode == WantZoom )
0246         {
0247             p_zoomRect = p_scene->addRect( QRectF( p_initial, QSize( 0, 0 ) ) );
0248             p_mode = Zooming;
0249             updateActions();
0250         }
0251         else if ( p_mode == WantMove )
0252         {
0253             p_prev = e->pos();
0254             setCursor(Qt::SizeAllCursor);
0255             p_mode = Moving;
0256             updateActions();
0257         }
0258         else
0259         {
0260             if ( QRectF(p_gameImage->rect()).contains( p_initial ) )
0261             {
0262                 QRgb rgb = p_mapImage->pixel( int(p_initial.x()), int(p_initial.y()) );
0263                 // check against the topleft corner, because the image is 1x1 size smaller than the image rectangle
0264                 QPoint p(e->pos().x()-p_currentDivisionImage->width()/2,e->pos().y()-p_currentDivisionImage->height()/2);
0265                 Q_EMIT clicked( rgb, e->pos(), mapToScene(p));
0266             }
0267         }
0268     }
0269     else if (e -> button() == Qt::MiddleButton)
0270     {
0271         p_modeBeforeMidClick = p_mode;
0272         p_mode = WantMove;
0273         updateActions();
0274         p_prev = e->pos();
0275         p_mode = Moving;
0276         updateActions();
0277     }
0278     else if ( p_mode == WantZoom )
0279     {
0280         setGameImage();
0281         updateActions();
0282     }
0283     else e->ignore(); // that makes the event go to mapasker and clear the popup
0284 }
0285 
0286 void placeMapWidget::mouseMoveEvent(QMouseEvent *e)
0287 {
0288     lastMouseEvent = e->pos();
0289     if ( p_mode == Zooming )
0290     {
0291         QPointF current = mapToScene( e->pos() );
0292 
0293         QRectF r;
0294         r.setTopLeft( p_initial );
0295         r.setBottomRight( current );
0296         p_zoomRect->setRect( r.normalized() );
0297     }
0298     else if ( p_mode == Moving )
0299     {
0300         QPoint diff = p_prev - e->pos();
0301 
0302         horizontalScrollBar()->setValue( horizontalScrollBar()->value() + diff.x() );
0303         verticalScrollBar()->setValue( verticalScrollBar()->value() + diff.y() );
0304 
0305         p_prev = e->pos();
0306     }
0307     if (p_currentDivisionItem)
0308     {
0309         QPoint p(e->pos().x()-p_currentDivisionImage->width()/2,e->pos().y()-p_currentDivisionImage->height()/2);
0310         p_currentDivisionItem->setPos(mapToScene(p));
0311     }
0312 }
0313 
0314 void placeMapWidget::mouseReleaseEvent(QMouseEvent *)
0315 {
0316     if ( p_mode == Zooming )
0317     {
0318         p_automaticZoom = false;
0319         fitInView( p_zoomRect, Qt::KeepAspectRatio );
0320         delete p_zoomRect;
0321         p_zoomRect = nullptr;
0322 
0323         p_mode = WantZoom;
0324     }
0325     else if ( p_mode == Moving )
0326     {
0327         p_mode = p_modeBeforeMidClick;
0328     }
0329 }
0330 
0331 void placeMapWidget::resizeEvent(QResizeEvent *)
0332 {
0333     updateZoom();
0334     updateActions();
0335 
0336     // Another hack to work around buginess in QGraphicsView
0337     if ( transform().isIdentity() )
0338         QMetaObject::invokeMethod(this, "setAutomaticZoom", Qt::QueuedConnection, Q_ARG(bool, p_automaticZoom));
0339 }
0340 
0341 void placeMapWidget::wheelEvent(QWheelEvent *e)
0342 {
0343     if ( e->modifiers() == Qt::NoModifier ) QGraphicsView::wheelEvent(e);
0344     else if ( e->modifiers()  == Qt::ShiftModifier )
0345     {
0346         // Scroll horizontally by swapping x and y for the delta
0347 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0348         QWheelEvent reorientedEvent(e->position(), e->globalPosition(), e->pixelDelta().transposed(), -e->angleDelta().transposed(), e->buttons(), Qt::NoModifier, e->phase(), e->inverted(), e->source(), e->pointingDevice());
0349 #else
0350         QWheelEvent reorientedEvent(e->position(), e->globalPosition(), e->pixelDelta().transposed(), e->angleDelta().transposed(), e->buttons(), Qt::NoModifier, e->phase(), e->inverted(), e->source());
0351 #endif
0352         QGraphicsView::wheelEvent(&reorientedEvent);
0353     }
0354     else if ( e->modifiers()  == Qt::ControlModifier )
0355     {
0356         int delta = e->angleDelta().y();
0357         if ( delta != 0 )
0358         {
0359             const qreal rescale = pow(2, qreal(delta/120)/2.0);
0360             scale(rescale, rescale);
0361         }
0362     }
0363 }
0364 
0365 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0366 void placeMapWidget::enterEvent(QEnterEvent*)
0367 #else
0368 void placeMapWidget::enterEvent(QEvent*)
0369 #endif
0370 {
0371     if (p_currentDivisionItem)
0372     {
0373         p_currentDivisionItem->show();
0374     }
0375 }
0376 
0377 void placeMapWidget::leaveEvent(QEvent*)
0378 {
0379     if (p_currentDivisionItem)
0380     {
0381         p_currentDivisionItem->hide();
0382     }
0383 }
0384 
0385 
0386 void placeMapWidget::setAutomaticZoom(bool automaticZoom)
0387 {
0388     if (!automaticZoom)
0389     {
0390         setGameImage();
0391     }
0392     else
0393     {
0394         p_automaticZoom = true;
0395         updateZoom();
0396         updateActions();
0397     }
0398 }
0399 
0400 void placeMapWidget::setGameImage()
0401 {
0402     p_automaticZoom = false;
0403     // Possibly bug in QGraphicsView? The view isn't updated properly
0404     // if the matrix isn't set to something non-identity first
0405     setTransform( QTransform( 2, 0, 0, 2, 0, 0 ) );
0406     resetTransform();
0407     updateActions();
0408 }
0409 
0410 void placeMapWidget::updateZoom()
0411 {
0412     if ( !p_automaticZoom || !p_gameImage )
0413         return;
0414     fitInView( p_gameImage->rect(), Qt::KeepAspectRatio );
0415 }
0416 
0417 QSize placeMapWidget::mapSize() const
0418 {
0419     return p_gameImage->size();
0420 }
0421 
0422 void placeMapWidget::updateActions()
0423 {
0424     if(p_gameImage)
0425     {
0426         if ( p_mode != Zooming && p_mode != WantZoom )
0427         {
0428           p_currentDivisionItem->show();
0429         }
0430         else
0431         {
0432           p_currentDivisionItem->hide();
0433         }
0434         // Whether the image is bigger than that viewable
0435         bool biggerThanView = (p_gameImage->width() * transform().m11() >= width()) || (p_gameImage->height() * transform().m22() >= height());
0436 
0437         Q_EMIT setMoveActionEnabled( !p_automaticZoom && biggerThanView );
0438         Q_EMIT setMoveActionChecked( !p_automaticZoom && (p_mode == Moving || p_mode == WantMove) && biggerThanView );
0439     }
0440 
0441     Q_EMIT setZoomActionChecked( p_mode == Zooming || p_mode == WantZoom );
0442 }
0443 
0444 #include "moc_placemapwidget.cpp"