File indexing completed on 2024-03-24 03:44:37
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"