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); |
| 68 | |
| 69 | m_gridSize = 1; |
| 70 | m_gridActive = 0; |
| 71 | m_zoom = 1; |
| 72 | m_displayGridSize = false; |
| 73 | m_displayGridSizeId = 0; |
| 74 | |
| 75 | m_mouseDown = false; |
| 76 | |
| 77 | m_initialSize = QSize(250, 250); |
| 78 | |
| 79 | setMouseTracking(true); |
Zack Rusin | 8d6c36d | 2014-04-09 21:54:59 -0400 | [diff] [blame] | 80 | setAutoFillBackground(true); |
Zack Rusin | 66ce10a | 2013-09-10 20:30:59 -0400 | [diff] [blame] | 81 | } |
| 82 | |
| 83 | PixelWidget::~PixelWidget() |
| 84 | { |
| 85 | } |
| 86 | |
| 87 | void PixelWidget::setSurface(const QImage &image) |
| 88 | { |
| 89 | m_surface = image; |
| 90 | updateGeometry(); |
| 91 | update(); |
| 92 | } |
| 93 | |
| 94 | void PixelWidget::timerEvent(QTimerEvent *event) |
| 95 | { |
| 96 | if (event->timerId() == m_displayGridSizeId) { |
| 97 | killTimer(m_displayGridSizeId); |
| 98 | m_displayGridSize = false; |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | void render_string(QPainter *p, int w, int h, const QString &text, int flags) |
| 103 | { |
| 104 | p->setBrush(QColor(255, 255, 255, 191)); |
| 105 | p->setPen(Qt::black); |
| 106 | QRect bounds; |
| 107 | p->drawText(0, 0, w, h, Qt::TextDontPrint | flags, text, &bounds); |
| 108 | |
| 109 | if (bounds.x() == 0) bounds.adjust(0, 0, 10, 0); |
| 110 | else bounds.adjust(-10, 0, 0, 0); |
| 111 | |
| 112 | if (bounds.y() == 0) bounds.adjust(0, 0, 0, 10); |
| 113 | else bounds.adjust(0, -10, 0, 0); |
| 114 | |
| 115 | p->drawRect(bounds); |
| 116 | p->drawText(bounds, flags, text); |
| 117 | } |
| 118 | |
| 119 | void PixelWidget::paintEvent(QPaintEvent *) |
| 120 | { |
| 121 | QPainter p(this); |
| 122 | |
| 123 | int w = width(); |
| 124 | int h = height(); |
| 125 | |
| 126 | p.save(); |
| 127 | p.scale(zoomValue(), zoomValue()); |
Zack Rusin | 8d6c36d | 2014-04-09 21:54:59 -0400 | [diff] [blame] | 128 | p.setCompositionMode(QPainter::CompositionMode_SourceOver); |
Zack Rusin | 66ce10a | 2013-09-10 20:30:59 -0400 | [diff] [blame] | 129 | p.drawImage(0, 0, m_surface); |
| 130 | p.scale(1/zoomValue(), 1/zoomValue()); |
| 131 | p.restore(); |
| 132 | |
| 133 | // Draw the grid on top. |
| 134 | if (m_gridActive) { |
| 135 | p.setPen(m_gridActive == 1 ? Qt::black : Qt::white); |
| 136 | int incr = m_gridSize * zoomValue(); |
| 137 | for (int x=0; x<w; x+=incr) |
| 138 | p.drawLine(x, 0, x, h); |
| 139 | for (int y=0; y<h; y+=incr) |
| 140 | p.drawLine(0, y, w, y); |
| 141 | } |
| 142 | |
| 143 | QFont f(QLatin1String("courier")); |
| 144 | f.setBold(true); |
| 145 | p.setFont(f); |
| 146 | |
| 147 | if (m_displayGridSize) { |
| 148 | render_string(&p, w, h, |
| 149 | QString::fromLatin1("Grid size: %1").arg(m_gridSize), |
| 150 | Qt::AlignBottom | Qt::AlignLeft); |
| 151 | } |
| 152 | |
| 153 | if (m_mouseDown && m_dragStart != m_dragCurrent) { |
| 154 | int x1 = (m_dragStart.x() / zoomValue()) * zoomValue(); |
| 155 | int y1 = (m_dragStart.y() / zoomValue()) * zoomValue(); |
| 156 | int x2 = (m_dragCurrent.x() / zoomValue()) * zoomValue(); |
| 157 | int y2 = (m_dragCurrent.y() / zoomValue()) * zoomValue(); |
| 158 | QRect r = QRect(x1, y1, x2 - x1, y2 - y1).normalized(); |
| 159 | p.setBrush(Qt::NoBrush); |
| 160 | p.setPen(QPen(Qt::red, 3, Qt::SolidLine)); |
| 161 | p.drawRect(r); |
| 162 | p.setPen(QPen(Qt::black, 1, Qt::SolidLine)); |
| 163 | p.drawRect(r); |
| 164 | |
| 165 | QRect rect( |
| 166 | int(r.x() / zoomValue()), |
| 167 | int(r.y() / zoomValue()), |
| 168 | int(r.width() / zoomValue()), |
| 169 | int(r.height() / zoomValue())); |
| 170 | emit gridGeometry(rect); |
| 171 | } else { |
| 172 | QRect empty; |
| 173 | emit gridGeometry(empty); |
| 174 | } |
| 175 | |
| 176 | |
| 177 | } |
| 178 | |
| 179 | void PixelWidget::keyPressEvent(QKeyEvent *e) |
| 180 | { |
| 181 | switch (e->key()) { |
| 182 | case Qt::Key_Plus: |
| 183 | increaseZoom(); |
| 184 | break; |
| 185 | case Qt::Key_Minus: |
| 186 | decreaseZoom(); |
| 187 | break; |
| 188 | case Qt::Key_PageUp: |
| 189 | setGridSize(m_gridSize + 1); |
| 190 | break; |
| 191 | case Qt::Key_PageDown: |
| 192 | setGridSize(m_gridSize - 1); |
| 193 | break; |
| 194 | case Qt::Key_G: |
| 195 | toggleGrid(); |
| 196 | break; |
| 197 | case Qt::Key_C: |
| 198 | if (e->modifiers() & Qt::ControlModifier) |
| 199 | copyToClipboard(); |
| 200 | break; |
| 201 | case Qt::Key_S: |
| 202 | if (e->modifiers() & Qt::ControlModifier) { |
| 203 | releaseKeyboard(); |
| 204 | saveToFile(); |
| 205 | } |
| 206 | break; |
| 207 | case Qt::Key_Control: |
| 208 | grabKeyboard(); |
| 209 | break; |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | void PixelWidget::keyReleaseEvent(QKeyEvent *e) |
| 214 | { |
| 215 | switch(e->key()) { |
| 216 | case Qt::Key_Control: |
| 217 | releaseKeyboard(); |
| 218 | break; |
| 219 | default: |
| 220 | break; |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | void PixelWidget::resizeEvent(QResizeEvent *e) |
| 225 | { |
| 226 | QWidget::resizeEvent(e); |
| 227 | } |
| 228 | |
| 229 | void PixelWidget::mouseMoveEvent(QMouseEvent *e) |
| 230 | { |
| 231 | if (m_mouseDown) |
| 232 | m_dragCurrent = e->pos(); |
| 233 | |
| 234 | int x = e->x() / zoomValue(); |
| 235 | int y = e->y() / zoomValue(); |
| 236 | |
| 237 | if (x < m_surface.width() && y < m_surface.height() && x >= 0 && y >= 0) { |
| 238 | m_currentColor = m_surface.pixel(x, y); |
| 239 | } else |
| 240 | m_currentColor = QColor(); |
| 241 | |
| 242 | emit mousePosition(x, y); |
| 243 | update(); |
| 244 | } |
| 245 | |
| 246 | void PixelWidget::mousePressEvent(QMouseEvent *e) |
| 247 | { |
| 248 | if (e->button() == Qt::LeftButton) |
| 249 | m_mouseDown = true; |
| 250 | m_dragStart = e->pos(); |
| 251 | } |
| 252 | |
| 253 | void PixelWidget::mouseReleaseEvent(QMouseEvent *) |
| 254 | { |
| 255 | m_mouseDown = false; |
| 256 | } |
| 257 | |
| 258 | void PixelWidget::contextMenuEvent(QContextMenuEvent *e) |
| 259 | { |
| 260 | QMenu menu; |
| 261 | |
| 262 | QAction title(QLatin1String("Surface Pixel Tool"), &menu); |
| 263 | title.setEnabled(false); |
| 264 | |
| 265 | // Grid color options... |
| 266 | QActionGroup gridGroup(this); |
| 267 | QAction whiteGrid(QLatin1String("White grid"), &gridGroup); |
| 268 | whiteGrid.setCheckable(true); |
| 269 | whiteGrid.setChecked(m_gridActive == 2); |
| 270 | whiteGrid.setShortcut(QKeySequence(Qt::Key_G)); |
| 271 | QAction blackGrid(QLatin1String("Black grid"), &gridGroup); |
| 272 | blackGrid.setCheckable(true); |
| 273 | blackGrid.setChecked(m_gridActive == 1); |
| 274 | blackGrid.setShortcut(QKeySequence(Qt::Key_G)); |
| 275 | QAction noGrid(QLatin1String("No grid"), &gridGroup); |
| 276 | noGrid.setCheckable(true); |
| 277 | noGrid.setChecked(m_gridActive == 0); |
| 278 | noGrid.setShortcut(QKeySequence(Qt::Key_G)); |
| 279 | |
| 280 | // Grid size options |
| 281 | QAction incrGrid(QLatin1String("Increase grid size"), &menu); |
| 282 | incrGrid.setShortcut(QKeySequence(Qt::Key_PageUp)); |
| 283 | connect(&incrGrid, SIGNAL(triggered()), this, SLOT(increaseGridSize())); |
| 284 | QAction decrGrid(QLatin1String("Decrease grid size"), &menu); |
| 285 | decrGrid.setShortcut(QKeySequence(Qt::Key_PageDown)); |
| 286 | connect(&decrGrid, SIGNAL(triggered()), this, SLOT(decreaseGridSize())); |
| 287 | |
| 288 | // Zoom options |
| 289 | QAction incrZoom(QLatin1String("Zoom in"), &menu); |
| 290 | incrZoom.setShortcut(QKeySequence(Qt::Key_Plus)); |
| 291 | connect(&incrZoom, SIGNAL(triggered()), this, SLOT(increaseZoom())); |
| 292 | QAction decrZoom(QLatin1String("Zoom out"), &menu); |
| 293 | decrZoom.setShortcut(QKeySequence(Qt::Key_Minus)); |
| 294 | connect(&decrZoom, SIGNAL(triggered()), this, SLOT(decreaseZoom())); |
| 295 | |
| 296 | // Copy to clipboard / save |
| 297 | QAction save(QLatin1String("Save as image"), &menu); |
| 298 | save.setShortcut(QKeySequence(QLatin1String("Ctrl+S"))); |
| 299 | connect(&save, SIGNAL(triggered()), this, SLOT(saveToFile())); |
| 300 | #ifndef QT_NO_CLIPBOARD |
| 301 | QAction copy(QLatin1String("Copy to clipboard"), &menu); |
| 302 | copy.setShortcut(QKeySequence(QLatin1String("Ctrl+C"))); |
| 303 | connect(©, SIGNAL(triggered()), this, SLOT(copyToClipboard())); |
| 304 | #endif |
| 305 | |
| 306 | menu.addAction(&title); |
| 307 | menu.addSeparator(); |
| 308 | menu.addAction(&whiteGrid); |
| 309 | menu.addAction(&blackGrid); |
| 310 | menu.addAction(&noGrid); |
| 311 | menu.addSeparator(); |
| 312 | menu.addAction(&incrGrid); |
| 313 | menu.addAction(&decrGrid); |
| 314 | menu.addSeparator(); |
| 315 | menu.addAction(&incrZoom); |
| 316 | menu.addAction(&decrZoom); |
| 317 | menu.addSeparator(); |
| 318 | menu.addAction(&save); |
| 319 | #ifndef QT_NO_CLIPBOARD |
| 320 | menu.addAction(©); |
| 321 | #endif |
| 322 | |
| 323 | menu.exec(mapToGlobal(e->pos())); |
| 324 | |
| 325 | if (noGrid.isChecked()) m_gridActive = 0; |
| 326 | else if (blackGrid.isChecked()) m_gridActive = 1; |
| 327 | else m_gridActive = 2; |
| 328 | } |
| 329 | |
| 330 | QSize PixelWidget::sizeHint() const |
| 331 | { |
| 332 | if (m_surface.isNull()) |
| 333 | return m_initialSize; |
| 334 | |
| 335 | QSize sz(m_surface.width() * zoomValue(), |
| 336 | m_surface.height() * zoomValue()); |
| 337 | return sz; |
| 338 | } |
| 339 | |
| 340 | void PixelWidget::startGridSizeVisibleTimer() |
| 341 | { |
| 342 | if (m_gridActive) { |
| 343 | if (m_displayGridSizeId > 0) |
| 344 | killTimer(m_displayGridSizeId); |
| 345 | m_displayGridSizeId = startTimer(5000); |
| 346 | m_displayGridSize = true; |
| 347 | update(); |
| 348 | } |
| 349 | } |
| 350 | |
| 351 | void PixelWidget::setZoom(int zoom) |
| 352 | { |
| 353 | if (zoom > 0 && zoom != m_zoom) { |
| 354 | QPoint pos = m_lastMousePos; |
| 355 | m_lastMousePos = QPoint(); |
| 356 | m_zoom = zoom; |
| 357 | m_lastMousePos = pos; |
| 358 | m_dragStart = m_dragCurrent = QPoint(); |
| 359 | |
| 360 | if (m_zoom == 1) |
| 361 | m_gridActive = 0; |
| 362 | else if (!m_gridActive) |
| 363 | m_gridActive = 1; |
| 364 | |
| 365 | zoomChanged(m_zoom); |
| 366 | updateGeometry(); |
| 367 | update(); |
| 368 | } |
| 369 | } |
| 370 | |
| 371 | void PixelWidget::toggleGrid() |
| 372 | { |
| 373 | if (++m_gridActive > 2) |
| 374 | m_gridActive = 0; |
| 375 | update(); |
| 376 | } |
| 377 | |
| 378 | void PixelWidget::setGridSize(int gridSize) |
| 379 | { |
| 380 | if (m_gridActive && gridSize > 0) { |
| 381 | m_gridSize = gridSize; |
| 382 | startGridSizeVisibleTimer(); |
| 383 | update(); |
| 384 | } |
| 385 | } |
| 386 | |
| 387 | void PixelWidget::copyToClipboard() |
| 388 | { |
| 389 | QClipboard *cb = QApplication::clipboard(); |
| 390 | cb->setImage(m_surface); |
| 391 | } |
| 392 | |
| 393 | void PixelWidget::saveToFile() |
| 394 | { |
| 395 | QString name = QFileDialog::getSaveFileName(this, QLatin1String("Save as image"), QString(), QLatin1String("*.png")); |
| 396 | if (!name.isEmpty()) { |
| 397 | if (!name.endsWith(QLatin1String(".png"))) |
| 398 | name.append(QLatin1String(".png")); |
| 399 | m_surface.save(name, "PNG"); |
| 400 | } |
| 401 | } |
| 402 | |
| 403 | QColor PixelWidget::colorAtCurrentPosition() const |
| 404 | { |
| 405 | return m_currentColor; |
| 406 | } |
| 407 | |
| 408 | #include "pixelwidget.moc" |