Zack Rusin | 66ce10a | 2013-09-10 20:30:59 -0400 | [diff] [blame] | 1 | /** |
| 2 | * The source code is based on qpixeltool.cpp. |
| 3 | * Original license follows. |
| 4 | */ |
| 5 | |
| 6 | /**************************************************************************** |
| 7 | ** |
| 8 | ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). |
| 9 | ** Contact: http://www.qt-project.org/legal |
| 10 | ** |
| 11 | ** This file is part of the tools applications of the Qt Toolkit. |
| 12 | ** |
| 13 | ** $QT_BEGIN_LICENSE:LGPL$ |
| 14 | ** Commercial License Usage |
| 15 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 16 | ** accordance with the commercial license agreement provided with the |
| 17 | ** Software or, alternatively, in accordance with the terms contained in |
| 18 | ** a written agreement between you and Digia. For licensing terms and |
| 19 | ** conditions see http://qt.digia.com/licensing. For further information |
| 20 | ** use the contact form at http://qt.digia.com/contact-us. |
| 21 | ** |
| 22 | ** GNU Lesser General Public License Usage |
| 23 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
| 24 | ** General Public License version 2.1 as published by the Free Software |
| 25 | ** Foundation and appearing in the file LICENSE.LGPL included in the |
| 26 | ** packaging of this file. Please review the following information to |
| 27 | ** ensure the GNU Lesser General Public License version 2.1 requirements |
| 28 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| 29 | ** |
| 30 | ** In addition, as a special exception, Digia gives you certain additional |
| 31 | ** rights. These rights are described in the Digia Qt LGPL Exception |
| 32 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| 33 | ** |
| 34 | ** GNU General Public License Usage |
| 35 | ** Alternatively, this file may be used under the terms of the GNU |
| 36 | ** General Public License version 3.0 as published by the Free Software |
| 37 | ** Foundation and appearing in the file LICENSE.GPL included in the |
| 38 | ** packaging of this file. Please review the following information to |
| 39 | ** ensure the GNU General Public License version 3.0 requirements will be |
| 40 | ** met: http://www.gnu.org/copyleft/gpl.html. |
| 41 | ** |
| 42 | ** |
| 43 | ** $QT_END_LICENSE$ |
| 44 | ** |
| 45 | ****************************************************************************/ |
| 46 | |
| 47 | #include "pixelwidget.h" |
| 48 | |
| 49 | #include <qapplication.h> |
| 50 | #include <qdesktopwidget.h> |
| 51 | #include <qapplication.h> |
| 52 | #ifndef QT_NO_CLIPBOARD |
| 53 | #include <qclipboard.h> |
| 54 | #endif |
| 55 | #include <qpainter.h> |
| 56 | #include <qevent.h> |
| 57 | #include <qfiledialog.h> |
| 58 | #include <qmenu.h> |
| 59 | #include <qactiongroup.h> |
| 60 | |
| 61 | #include <qdebug.h> |
| 62 | |
| 63 | PixelWidget::PixelWidget(QWidget *parent) |
| 64 | : QWidget(parent) |
| 65 | { |
| 66 | setWindowTitle(QLatin1String("PixelTool")); |
| 67 | setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); |
velorums | 913565c | 2020-05-23 20:39:10 +0200 | [diff] [blame] | 68 | setFocusPolicy(Qt::StrongFocus); |
Zack Rusin | 66ce10a | 2013-09-10 20:30:59 -0400 | [diff] [blame] | 69 | |
| 70 | m_gridSize = 1; |
| 71 | m_gridActive = 0; |
| 72 | m_zoom = 1; |
| 73 | m_displayGridSize = false; |
| 74 | m_displayGridSizeId = 0; |
| 75 | |
| 76 | m_mouseDown = false; |
| 77 | |
| 78 | m_initialSize = QSize(250, 250); |
| 79 | |
| 80 | setMouseTracking(true); |
Zack Rusin | 8d6c36d | 2014-04-09 21:54:59 -0400 | [diff] [blame] | 81 | setAutoFillBackground(true); |
Zack Rusin | 66ce10a | 2013-09-10 20:30:59 -0400 | [diff] [blame] | 82 | } |
| 83 | |
| 84 | PixelWidget::~PixelWidget() |
| 85 | { |
| 86 | } |
| 87 | |
| 88 | void PixelWidget::setSurface(const QImage &image) |
| 89 | { |
| 90 | m_surface = image; |
| 91 | updateGeometry(); |
| 92 | update(); |
| 93 | } |
| 94 | |
| 95 | void PixelWidget::timerEvent(QTimerEvent *event) |
| 96 | { |
| 97 | if (event->timerId() == m_displayGridSizeId) { |
| 98 | killTimer(m_displayGridSizeId); |
| 99 | m_displayGridSize = false; |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | void render_string(QPainter *p, int w, int h, const QString &text, int flags) |
| 104 | { |
| 105 | p->setBrush(QColor(255, 255, 255, 191)); |
| 106 | p->setPen(Qt::black); |
| 107 | QRect bounds; |
| 108 | p->drawText(0, 0, w, h, Qt::TextDontPrint | flags, text, &bounds); |
| 109 | |
| 110 | if (bounds.x() == 0) bounds.adjust(0, 0, 10, 0); |
| 111 | else bounds.adjust(-10, 0, 0, 0); |
| 112 | |
| 113 | if (bounds.y() == 0) bounds.adjust(0, 0, 0, 10); |
| 114 | else bounds.adjust(0, -10, 0, 0); |
| 115 | |
| 116 | p->drawRect(bounds); |
| 117 | p->drawText(bounds, flags, text); |
| 118 | } |
| 119 | |
| 120 | void PixelWidget::paintEvent(QPaintEvent *) |
| 121 | { |
| 122 | QPainter p(this); |
| 123 | |
| 124 | int w = width(); |
| 125 | int h = height(); |
| 126 | |
| 127 | p.save(); |
| 128 | p.scale(zoomValue(), zoomValue()); |
Zack Rusin | 8d6c36d | 2014-04-09 21:54:59 -0400 | [diff] [blame] | 129 | p.setCompositionMode(QPainter::CompositionMode_SourceOver); |
Zack Rusin | 66ce10a | 2013-09-10 20:30:59 -0400 | [diff] [blame] | 130 | p.drawImage(0, 0, m_surface); |
| 131 | p.scale(1/zoomValue(), 1/zoomValue()); |
| 132 | p.restore(); |
| 133 | |
| 134 | // Draw the grid on top. |
| 135 | if (m_gridActive) { |
| 136 | p.setPen(m_gridActive == 1 ? Qt::black : Qt::white); |
| 137 | int incr = m_gridSize * zoomValue(); |
| 138 | for (int x=0; x<w; x+=incr) |
| 139 | p.drawLine(x, 0, x, h); |
| 140 | for (int y=0; y<h; y+=incr) |
| 141 | p.drawLine(0, y, w, y); |
| 142 | } |
| 143 | |
| 144 | QFont f(QLatin1String("courier")); |
| 145 | f.setBold(true); |
| 146 | p.setFont(f); |
| 147 | |
| 148 | if (m_displayGridSize) { |
| 149 | render_string(&p, w, h, |
| 150 | QString::fromLatin1("Grid size: %1").arg(m_gridSize), |
| 151 | Qt::AlignBottom | Qt::AlignLeft); |
| 152 | } |
| 153 | |
| 154 | if (m_mouseDown && m_dragStart != m_dragCurrent) { |
| 155 | int x1 = (m_dragStart.x() / zoomValue()) * zoomValue(); |
| 156 | int y1 = (m_dragStart.y() / zoomValue()) * zoomValue(); |
| 157 | int x2 = (m_dragCurrent.x() / zoomValue()) * zoomValue(); |
| 158 | int y2 = (m_dragCurrent.y() / zoomValue()) * zoomValue(); |
| 159 | QRect r = QRect(x1, y1, x2 - x1, y2 - y1).normalized(); |
| 160 | p.setBrush(Qt::NoBrush); |
| 161 | p.setPen(QPen(Qt::red, 3, Qt::SolidLine)); |
| 162 | p.drawRect(r); |
| 163 | p.setPen(QPen(Qt::black, 1, Qt::SolidLine)); |
| 164 | p.drawRect(r); |
| 165 | |
| 166 | QRect rect( |
| 167 | int(r.x() / zoomValue()), |
| 168 | int(r.y() / zoomValue()), |
| 169 | int(r.width() / zoomValue()), |
| 170 | int(r.height() / zoomValue())); |
| 171 | emit gridGeometry(rect); |
| 172 | } else { |
| 173 | QRect empty; |
| 174 | emit gridGeometry(empty); |
| 175 | } |
| 176 | |
| 177 | |
| 178 | } |
| 179 | |
| 180 | void PixelWidget::keyPressEvent(QKeyEvent *e) |
| 181 | { |
| 182 | switch (e->key()) { |
| 183 | case Qt::Key_Plus: |
| 184 | increaseZoom(); |
| 185 | break; |
| 186 | case Qt::Key_Minus: |
| 187 | decreaseZoom(); |
| 188 | break; |
| 189 | case Qt::Key_PageUp: |
| 190 | setGridSize(m_gridSize + 1); |
| 191 | break; |
| 192 | case Qt::Key_PageDown: |
| 193 | setGridSize(m_gridSize - 1); |
| 194 | break; |
| 195 | case Qt::Key_G: |
| 196 | toggleGrid(); |
| 197 | break; |
| 198 | case Qt::Key_C: |
| 199 | if (e->modifiers() & Qt::ControlModifier) |
| 200 | copyToClipboard(); |
| 201 | break; |
| 202 | case Qt::Key_S: |
| 203 | if (e->modifiers() & Qt::ControlModifier) { |
| 204 | releaseKeyboard(); |
| 205 | saveToFile(); |
| 206 | } |
| 207 | break; |
| 208 | case Qt::Key_Control: |
| 209 | grabKeyboard(); |
| 210 | break; |
velorums | 206996a | 2020-05-23 20:38:52 +0200 | [diff] [blame] | 211 | default: |
| 212 | QWidget::keyPressEvent(e); |
| 213 | break; |
Zack Rusin | 66ce10a | 2013-09-10 20:30:59 -0400 | [diff] [blame] | 214 | } |
| 215 | } |
| 216 | |
| 217 | void PixelWidget::keyReleaseEvent(QKeyEvent *e) |
| 218 | { |
| 219 | switch(e->key()) { |
| 220 | case Qt::Key_Control: |
| 221 | releaseKeyboard(); |
| 222 | break; |
| 223 | default: |
| 224 | break; |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | void PixelWidget::resizeEvent(QResizeEvent *e) |
| 229 | { |
| 230 | QWidget::resizeEvent(e); |
| 231 | } |
| 232 | |
| 233 | void PixelWidget::mouseMoveEvent(QMouseEvent *e) |
| 234 | { |
| 235 | if (m_mouseDown) |
| 236 | m_dragCurrent = e->pos(); |
| 237 | |
| 238 | int x = e->x() / zoomValue(); |
| 239 | int y = e->y() / zoomValue(); |
| 240 | |
| 241 | if (x < m_surface.width() && y < m_surface.height() && x >= 0 && y >= 0) { |
| 242 | m_currentColor = m_surface.pixel(x, y); |
| 243 | } else |
| 244 | m_currentColor = QColor(); |
| 245 | |
| 246 | emit mousePosition(x, y); |
| 247 | update(); |
| 248 | } |
| 249 | |
| 250 | void PixelWidget::mousePressEvent(QMouseEvent *e) |
| 251 | { |
| 252 | if (e->button() == Qt::LeftButton) |
| 253 | m_mouseDown = true; |
| 254 | m_dragStart = e->pos(); |
| 255 | } |
| 256 | |
| 257 | void PixelWidget::mouseReleaseEvent(QMouseEvent *) |
| 258 | { |
| 259 | m_mouseDown = false; |
| 260 | } |
| 261 | |
| 262 | void PixelWidget::contextMenuEvent(QContextMenuEvent *e) |
| 263 | { |
| 264 | QMenu menu; |
| 265 | |
| 266 | QAction title(QLatin1String("Surface Pixel Tool"), &menu); |
| 267 | title.setEnabled(false); |
| 268 | |
| 269 | // Grid color options... |
| 270 | QActionGroup gridGroup(this); |
| 271 | QAction whiteGrid(QLatin1String("White grid"), &gridGroup); |
| 272 | whiteGrid.setCheckable(true); |
| 273 | whiteGrid.setChecked(m_gridActive == 2); |
| 274 | whiteGrid.setShortcut(QKeySequence(Qt::Key_G)); |
| 275 | QAction blackGrid(QLatin1String("Black grid"), &gridGroup); |
| 276 | blackGrid.setCheckable(true); |
| 277 | blackGrid.setChecked(m_gridActive == 1); |
| 278 | blackGrid.setShortcut(QKeySequence(Qt::Key_G)); |
| 279 | QAction noGrid(QLatin1String("No grid"), &gridGroup); |
| 280 | noGrid.setCheckable(true); |
| 281 | noGrid.setChecked(m_gridActive == 0); |
| 282 | noGrid.setShortcut(QKeySequence(Qt::Key_G)); |
| 283 | |
| 284 | // Grid size options |
| 285 | QAction incrGrid(QLatin1String("Increase grid size"), &menu); |
| 286 | incrGrid.setShortcut(QKeySequence(Qt::Key_PageUp)); |
| 287 | connect(&incrGrid, SIGNAL(triggered()), this, SLOT(increaseGridSize())); |
| 288 | QAction decrGrid(QLatin1String("Decrease grid size"), &menu); |
| 289 | decrGrid.setShortcut(QKeySequence(Qt::Key_PageDown)); |
| 290 | connect(&decrGrid, SIGNAL(triggered()), this, SLOT(decreaseGridSize())); |
| 291 | |
| 292 | // Zoom options |
| 293 | QAction incrZoom(QLatin1String("Zoom in"), &menu); |
| 294 | incrZoom.setShortcut(QKeySequence(Qt::Key_Plus)); |
| 295 | connect(&incrZoom, SIGNAL(triggered()), this, SLOT(increaseZoom())); |
| 296 | QAction decrZoom(QLatin1String("Zoom out"), &menu); |
| 297 | decrZoom.setShortcut(QKeySequence(Qt::Key_Minus)); |
| 298 | connect(&decrZoom, SIGNAL(triggered()), this, SLOT(decreaseZoom())); |
| 299 | |
| 300 | // Copy to clipboard / save |
| 301 | QAction save(QLatin1String("Save as image"), &menu); |
| 302 | save.setShortcut(QKeySequence(QLatin1String("Ctrl+S"))); |
| 303 | connect(&save, SIGNAL(triggered()), this, SLOT(saveToFile())); |
| 304 | #ifndef QT_NO_CLIPBOARD |
| 305 | QAction copy(QLatin1String("Copy to clipboard"), &menu); |
| 306 | copy.setShortcut(QKeySequence(QLatin1String("Ctrl+C"))); |
| 307 | connect(©, SIGNAL(triggered()), this, SLOT(copyToClipboard())); |
| 308 | #endif |
| 309 | |
| 310 | menu.addAction(&title); |
| 311 | menu.addSeparator(); |
| 312 | menu.addAction(&whiteGrid); |
| 313 | menu.addAction(&blackGrid); |
| 314 | menu.addAction(&noGrid); |
| 315 | menu.addSeparator(); |
| 316 | menu.addAction(&incrGrid); |
| 317 | menu.addAction(&decrGrid); |
| 318 | menu.addSeparator(); |
| 319 | menu.addAction(&incrZoom); |
| 320 | menu.addAction(&decrZoom); |
| 321 | menu.addSeparator(); |
| 322 | menu.addAction(&save); |
| 323 | #ifndef QT_NO_CLIPBOARD |
| 324 | menu.addAction(©); |
| 325 | #endif |
| 326 | |
| 327 | menu.exec(mapToGlobal(e->pos())); |
| 328 | |
| 329 | if (noGrid.isChecked()) m_gridActive = 0; |
| 330 | else if (blackGrid.isChecked()) m_gridActive = 1; |
| 331 | else m_gridActive = 2; |
| 332 | } |
| 333 | |
| 334 | QSize PixelWidget::sizeHint() const |
| 335 | { |
| 336 | if (m_surface.isNull()) |
| 337 | return m_initialSize; |
| 338 | |
| 339 | QSize sz(m_surface.width() * zoomValue(), |
| 340 | m_surface.height() * zoomValue()); |
| 341 | return sz; |
| 342 | } |
| 343 | |
| 344 | void PixelWidget::startGridSizeVisibleTimer() |
| 345 | { |
| 346 | if (m_gridActive) { |
| 347 | if (m_displayGridSizeId > 0) |
| 348 | killTimer(m_displayGridSizeId); |
| 349 | m_displayGridSizeId = startTimer(5000); |
| 350 | m_displayGridSize = true; |
| 351 | update(); |
| 352 | } |
| 353 | } |
| 354 | |
| 355 | void PixelWidget::setZoom(int zoom) |
| 356 | { |
| 357 | if (zoom > 0 && zoom != m_zoom) { |
| 358 | QPoint pos = m_lastMousePos; |
| 359 | m_lastMousePos = QPoint(); |
| 360 | m_zoom = zoom; |
| 361 | m_lastMousePos = pos; |
| 362 | m_dragStart = m_dragCurrent = QPoint(); |
| 363 | |
| 364 | if (m_zoom == 1) |
| 365 | m_gridActive = 0; |
| 366 | else if (!m_gridActive) |
| 367 | m_gridActive = 1; |
| 368 | |
| 369 | zoomChanged(m_zoom); |
| 370 | updateGeometry(); |
| 371 | update(); |
| 372 | } |
| 373 | } |
| 374 | |
| 375 | void PixelWidget::toggleGrid() |
| 376 | { |
| 377 | if (++m_gridActive > 2) |
| 378 | m_gridActive = 0; |
| 379 | update(); |
| 380 | } |
| 381 | |
| 382 | void PixelWidget::setGridSize(int gridSize) |
| 383 | { |
| 384 | if (m_gridActive && gridSize > 0) { |
| 385 | m_gridSize = gridSize; |
| 386 | startGridSizeVisibleTimer(); |
| 387 | update(); |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | void PixelWidget::copyToClipboard() |
| 392 | { |
| 393 | QClipboard *cb = QApplication::clipboard(); |
| 394 | cb->setImage(m_surface); |
| 395 | } |
| 396 | |
| 397 | void PixelWidget::saveToFile() |
| 398 | { |
| 399 | QString name = QFileDialog::getSaveFileName(this, QLatin1String("Save as image"), QString(), QLatin1String("*.png")); |
| 400 | if (!name.isEmpty()) { |
| 401 | if (!name.endsWith(QLatin1String(".png"))) |
| 402 | name.append(QLatin1String(".png")); |
| 403 | m_surface.save(name, "PNG"); |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | QColor PixelWidget::colorAtCurrentPosition() const |
| 408 | { |
| 409 | return m_currentColor; |
| 410 | } |
| 411 | |
| 412 | #include "pixelwidget.moc" |