1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "qpixeltool.h" |
5 | |
6 | #include <qapplication.h> |
7 | #include <qdir.h> |
8 | #include <qapplication.h> |
9 | #include <qscreen.h> |
10 | #if QT_CONFIG(clipboard) |
11 | #include <qclipboard.h> |
12 | #endif |
13 | #include <qpainter.h> |
14 | #include <qevent.h> |
15 | #include <qfiledialog.h> |
16 | #include <qmessagebox.h> |
17 | #include <qsettings.h> |
18 | #include <qmenu.h> |
19 | #include <qactiongroup.h> |
20 | #include <qimagewriter.h> |
21 | #include <qscreen.h> |
22 | #include <qstandardpaths.h> |
23 | #include <qtextstream.h> |
24 | #include <qwindow.h> |
25 | #include <private/qhighdpiscaling_p.h> |
26 | |
27 | #include <qdebug.h> |
28 | |
29 | QT_BEGIN_NAMESPACE |
30 | |
31 | using namespace Qt::StringLiterals; |
32 | |
33 | static QPoint initialPos(const QSettings &settings, const QSize &initialSize) |
34 | { |
35 | const QPoint defaultPos = QGuiApplication::primaryScreen()->availableGeometry().topLeft(); |
36 | const QPoint savedPos = |
37 | settings.value(key: QLatin1String("position" ), defaultValue: QVariant(defaultPos)).toPoint(); |
38 | auto savedScreen = QGuiApplication::screenAt(point: savedPos); |
39 | return savedScreen != nullptr |
40 | && savedScreen->availableGeometry().intersects(r: QRect(savedPos, initialSize)) |
41 | ? savedPos : defaultPos; |
42 | } |
43 | |
44 | QPixelTool::QPixelTool(QWidget *parent) |
45 | : QWidget(parent) |
46 | { |
47 | setWindowTitle(QCoreApplication::applicationName()); |
48 | QSettings settings(QLatin1String("QtProject" ), QLatin1String("QPixelTool" )); |
49 | m_autoUpdate = settings.value(key: QLatin1String("autoUpdate" ), defaultValue: 0).toBool(); |
50 | m_gridSize = settings.value(key: QLatin1String("gridSize" ), defaultValue: 1).toInt(); |
51 | m_gridActive = settings.value(key: QLatin1String("gridActive" ), defaultValue: 1).toInt(); |
52 | m_zoom = settings.value(key: QLatin1String("zoom" ), defaultValue: 4).toInt(); |
53 | m_initialSize = settings.value(key: QLatin1String("initialSize" ), defaultValue: QSize(250, 200)).toSize(); |
54 | m_lcdMode = settings.value(key: QLatin1String("lcdMode" ), defaultValue: 0).toInt(); |
55 | |
56 | move(initialPos(settings, initialSize: m_initialSize)); |
57 | |
58 | setMouseTracking(true); |
59 | setAttribute(Qt::WA_OpaquePaintEvent); |
60 | m_updateId = startTimer(interval: 30); |
61 | } |
62 | |
63 | QPixelTool::~QPixelTool() |
64 | { |
65 | QSettings settings(QLatin1String("QtProject" ), QLatin1String("QPixelTool" )); |
66 | settings.setValue(key: QLatin1String("autoUpdate" ), value: int(m_autoUpdate)); |
67 | settings.setValue(key: QLatin1String("gridSize" ), value: m_gridSize); |
68 | settings.setValue(key: QLatin1String("gridActive" ), value: m_gridActive); |
69 | settings.setValue(key: QLatin1String("zoom" ), value: m_zoom); |
70 | settings.setValue(key: QLatin1String("initialSize" ), value: size()); |
71 | settings.setValue(key: QLatin1String("position" ), value: pos()); |
72 | settings.setValue(key: QLatin1String("lcdMode" ), value: m_lcdMode); |
73 | } |
74 | |
75 | void QPixelTool::setPreviewImage(const QImage &image) |
76 | { |
77 | m_preview_mode = true; |
78 | m_preview_image = image; |
79 | m_freeze = true; |
80 | } |
81 | |
82 | void QPixelTool::timerEvent(QTimerEvent *event) |
83 | { |
84 | if (event->timerId() == m_updateId && !m_freeze) { |
85 | grabScreen(); |
86 | } else if (event->timerId() == m_displayZoomId) { |
87 | killTimer(id: m_displayZoomId); |
88 | m_displayZoomId = 0; |
89 | setZoomVisible(false); |
90 | } else if (event->timerId() == m_displayGridSizeId) { |
91 | killTimer(id: m_displayGridSizeId); |
92 | m_displayGridSizeId = 0; |
93 | m_displayGridSize = false; |
94 | } |
95 | } |
96 | |
97 | void render_string(QPainter *p, int w, int h, const QString &text, int flags) |
98 | { |
99 | p->setBrush(QColor(255, 255, 255, 191)); |
100 | p->setPen(Qt::black); |
101 | QRect bounds; |
102 | p->drawText(x: 0, y: 0, w, h, flags: Qt::TextDontPrint | flags, str: text, br: &bounds); |
103 | |
104 | if (bounds.x() == 0) bounds.adjust(dx1: 0, dy1: 0, dx2: 10, dy2: 0); |
105 | else bounds.adjust(dx1: -10, dy1: 0, dx2: 0, dy2: 0); |
106 | |
107 | if (bounds.y() == 0) bounds.adjust(dx1: 0, dy1: 0, dx2: 0, dy2: 10); |
108 | else bounds.adjust(dx1: 0, dy1: -10, dx2: 0, dy2: 0); |
109 | |
110 | p->drawRect(r: bounds); |
111 | p->drawText(r: bounds, flags, text); |
112 | } |
113 | |
114 | static QImage imageLCDFilter(const QImage &image, int lcdMode) |
115 | { |
116 | Q_ASSERT(lcdMode > 0 && lcdMode < 5); |
117 | const bool vertical = (lcdMode > 2); |
118 | QImage scaled(image.width() * (vertical ? 1 : 3), |
119 | image.height() * (vertical ? 3 : 1), |
120 | image.format()); |
121 | |
122 | const int w = image.width(); |
123 | const int h = image.height(); |
124 | if (!vertical) { |
125 | for (int y = 0; y < h; ++y) { |
126 | const QRgb *in = reinterpret_cast<const QRgb *>(image.scanLine(y)); |
127 | QRgb *out = reinterpret_cast<QRgb *>(scaled.scanLine(y)); |
128 | if (lcdMode == 1) { |
129 | for (int x = 0; x < w; ++x) { |
130 | *out++ = in[x] & 0xffff0000; |
131 | *out++ = in[x] & 0xff00ff00; |
132 | *out++ = in[x] & 0xff0000ff; |
133 | } |
134 | } else { |
135 | for (int x = 0; x < w; ++x) { |
136 | *out++ = in[x] & 0xff0000ff; |
137 | *out++ = in[x] & 0xff00ff00; |
138 | *out++ = in[x] & 0xffff0000; |
139 | } |
140 | } |
141 | } |
142 | } else { |
143 | for (int y = 0; y < h; ++y) { |
144 | const QRgb *in = reinterpret_cast<const QRgb *>(image.scanLine(y)); |
145 | QRgb *out1 = reinterpret_cast<QRgb *>(scaled.scanLine(y * 3 + 0)); |
146 | QRgb *out2 = reinterpret_cast<QRgb *>(scaled.scanLine(y * 3 + 1)); |
147 | QRgb *out3 = reinterpret_cast<QRgb *>(scaled.scanLine(y * 3 + 2)); |
148 | if (lcdMode == 2) { |
149 | for (int x = 0; x < w; ++x) { |
150 | out1[x] = in[x] & 0xffff0000; |
151 | out2[x] = in[x] & 0xff00ff00; |
152 | out3[x] = in[x] & 0xff0000ff; |
153 | } |
154 | } else { |
155 | for (int x = 0; x < w; ++x) { |
156 | out1[x] = in[x] & 0xff0000ff; |
157 | out2[x] = in[x] & 0xff00ff00; |
158 | out3[x] = in[x] & 0xffff0000; |
159 | } |
160 | } |
161 | } |
162 | } |
163 | return scaled; |
164 | } |
165 | |
166 | void QPixelTool::paintEvent(QPaintEvent *) |
167 | { |
168 | QPainter p(this); |
169 | |
170 | if (m_preview_mode) { |
171 | QPixmap pixmap(40, 40); |
172 | QPainter pt(&pixmap); |
173 | pt.fillRect(x: 0, y: 0, w: 20, h: 20, c: Qt::white); |
174 | pt.fillRect(x: 20, y: 20, w: 20, h: 20, c: Qt::white); |
175 | pt.fillRect(x: 20, y: 0, w: 20, h: 20, c: Qt::lightGray); |
176 | pt.fillRect(x: 0, y: 20, w: 20, h: 20, c: Qt::lightGray); |
177 | pt.end(); |
178 | p.fillRect(x: 0, y: 0, w: width(), h: height(), b: pixmap); |
179 | } |
180 | |
181 | int w = width(); |
182 | int h = height(); |
183 | |
184 | p.save(); |
185 | if (m_lcdMode == 0) { |
186 | p.scale(sx: m_zoom, sy: m_zoom); |
187 | p.drawPixmap(x: 0, y: 0, pm: m_buffer); |
188 | } else { |
189 | if (m_lcdMode <= 2) |
190 | p.scale(sx: m_zoom / 3.0, sy: m_zoom); |
191 | else |
192 | p.scale(sx: m_zoom, sy: m_zoom / 3.0); |
193 | p.drawImage(x: 0, y: 0, image: imageLCDFilter(image: m_buffer.toImage(), lcdMode: m_lcdMode)); |
194 | } |
195 | p.restore(); |
196 | |
197 | // Draw the grid on top. |
198 | if (m_gridActive) { |
199 | p.setPen(m_gridActive == 1 ? Qt::black : Qt::white); |
200 | int incr = m_gridSize * m_zoom; |
201 | if (m_lcdMode == 0 || m_lcdMode > 2) { |
202 | for (int x=0; x<w; x+=incr) |
203 | p.drawLine(x1: x, y1: 0, x2: x, y2: h); |
204 | } |
205 | if (m_lcdMode <= 2) { |
206 | for (int y=0; y<h; y+=incr) |
207 | p.drawLine(x1: 0, y1: y, x2: w, y2: y); |
208 | } |
209 | } |
210 | |
211 | QFont f(QStringList{u"courier"_s }, -1, QFont::Bold); |
212 | p.setFont(f); |
213 | |
214 | if (m_displayZoom) { |
215 | render_string(p: &p, w, h, |
216 | text: QLatin1String("Zoom: x" ) + QString::number(m_zoom), |
217 | flags: Qt::AlignTop | Qt::AlignRight); |
218 | } |
219 | |
220 | if (m_displayGridSize) { |
221 | render_string(p: &p, w, h, |
222 | text: QLatin1String("Grid size: " ) + QString::number(m_gridSize), |
223 | flags: Qt::AlignBottom | Qt::AlignLeft); |
224 | } |
225 | |
226 | if (m_freeze) { |
227 | QString str = QString::asprintf(format: "%8X (%3d,%3d,%3d,%3d)" , |
228 | m_currentColor, |
229 | (0xff000000 & m_currentColor) >> 24, |
230 | (0x00ff0000 & m_currentColor) >> 16, |
231 | (0x0000ff00 & m_currentColor) >> 8, |
232 | (0x000000ff & m_currentColor)); |
233 | render_string(p: &p, w, h, |
234 | text: str, |
235 | flags: Qt::AlignBottom | Qt::AlignRight); |
236 | } |
237 | |
238 | if (m_mouseDown && m_dragStart != m_dragCurrent) { |
239 | int x1 = (m_dragStart.x() / m_zoom) * m_zoom; |
240 | int y1 = (m_dragStart.y() / m_zoom) * m_zoom; |
241 | int x2 = (m_dragCurrent.x() / m_zoom) * m_zoom; |
242 | int y2 = (m_dragCurrent.y() / m_zoom) * m_zoom; |
243 | QRect r = QRect(x1, y1, x2 - x1, y2 - y1).normalized(); |
244 | p.setBrush(Qt::NoBrush); |
245 | p.setPen(QPen(Qt::red, 3, Qt::SolidLine)); |
246 | p.drawRect(r); |
247 | p.setPen(QPen(Qt::black, 1, Qt::SolidLine)); |
248 | p.drawRect(r); |
249 | |
250 | QString str = QString::asprintf(format: "Rect: x=%d, y=%d, w=%d, h=%d" , |
251 | r.x() / m_zoom, |
252 | r.y() / m_zoom, |
253 | r.width() / m_zoom, |
254 | r.height() / m_zoom); |
255 | render_string(p: &p, w, h, text: str, flags: Qt::AlignBottom | Qt::AlignLeft); |
256 | } |
257 | |
258 | |
259 | } |
260 | |
261 | void QPixelTool::keyPressEvent(QKeyEvent *e) |
262 | { |
263 | switch (e->key()) { |
264 | case Qt::Key_Space: |
265 | toggleFreeze(); |
266 | break; |
267 | case Qt::Key_Plus: |
268 | increaseZoom(); |
269 | break; |
270 | case Qt::Key_Minus: |
271 | decreaseZoom(); |
272 | break; |
273 | case Qt::Key_PageUp: |
274 | setGridSize(m_gridSize + 1); |
275 | break; |
276 | case Qt::Key_PageDown: |
277 | setGridSize(m_gridSize - 1); |
278 | break; |
279 | case Qt::Key_G: |
280 | toggleGrid(); |
281 | break; |
282 | case Qt::Key_A: |
283 | m_autoUpdate = !m_autoUpdate; |
284 | break; |
285 | #if QT_CONFIG(clipboard) |
286 | case Qt::Key_C: |
287 | if (e->modifiers().testFlag(flag: Qt::ControlModifier)) |
288 | copyToClipboard(); |
289 | else |
290 | copyColorToClipboard(); |
291 | break; |
292 | #endif // QT_CONFIG(clipboard) |
293 | case Qt::Key_S: |
294 | if (e->modifiers() & Qt::ControlModifier) { |
295 | releaseKeyboard(); |
296 | saveToFile(); |
297 | } |
298 | break; |
299 | case Qt::Key_Control: |
300 | grabKeyboard(); |
301 | break; |
302 | case Qt::Key_F1: |
303 | aboutPixelTool(); |
304 | break; |
305 | } |
306 | } |
307 | |
308 | void QPixelTool::keyReleaseEvent(QKeyEvent *e) |
309 | { |
310 | switch(e->key()) { |
311 | case Qt::Key_Control: |
312 | releaseKeyboard(); |
313 | break; |
314 | default: |
315 | break; |
316 | } |
317 | } |
318 | |
319 | void QPixelTool::resizeEvent(QResizeEvent *) |
320 | { |
321 | grabScreen(); |
322 | } |
323 | |
324 | void QPixelTool::mouseMoveEvent(QMouseEvent *e) |
325 | { |
326 | if (m_mouseDown) |
327 | m_dragCurrent = e->pos(); |
328 | |
329 | const auto pos = e->position().toPoint(); |
330 | const int x = pos.x() / m_zoom; |
331 | const int y = pos.y() / m_zoom; |
332 | |
333 | QImage im = m_buffer.toImage().convertToFormat(f: QImage::Format_ARGB32); |
334 | if (x < im.width() && y < im.height() && x >= 0 && y >= 0) { |
335 | m_currentColor = im.pixel(x, y); |
336 | update(); |
337 | } |
338 | } |
339 | |
340 | void QPixelTool::mousePressEvent(QMouseEvent *e) |
341 | { |
342 | if (!m_freeze) |
343 | return; |
344 | m_mouseDown = true; |
345 | m_dragStart = e->pos(); |
346 | } |
347 | |
348 | void QPixelTool::mouseReleaseEvent(QMouseEvent *) |
349 | { |
350 | m_mouseDown = false; |
351 | } |
352 | |
353 | static QAction *(QMenu &, const QString &title, |
354 | bool value, const QKeySequence &key) |
355 | { |
356 | QAction *result = menu.addAction(text: title); |
357 | result->setCheckable(true); |
358 | result->setChecked(value); |
359 | result->setShortcut(key); |
360 | return result; |
361 | } |
362 | |
363 | static QAction *(QMenu &, const QString &title, |
364 | bool value, const QKeySequence &key, |
365 | QActionGroup *group) |
366 | { |
367 | QAction *result = addCheckableAction(menu, title, value, key); |
368 | result->setActionGroup(group); |
369 | return result; |
370 | } |
371 | |
372 | void QPixelTool::(QContextMenuEvent *e) |
373 | { |
374 | const bool tmpFreeze = m_freeze; |
375 | m_freeze = true; |
376 | |
377 | QMenu ; |
378 | menu.addAction(text: QLatin1String("Qt Pixel Zooming Tool" ))->setEnabled(false); |
379 | menu.addSeparator(); |
380 | |
381 | // Grid color options... |
382 | QActionGroup *gridGroup = new QActionGroup(&menu); |
383 | addCheckableAction(menu, title: QLatin1String("White grid" ), value: m_gridActive == 2, |
384 | key: Qt::Key_W, group: gridGroup); |
385 | QAction *blackGrid = addCheckableAction(menu, title: QLatin1String("Black grid" ), |
386 | value: m_gridActive == 1, key: Qt::Key_B, group: gridGroup); |
387 | QAction *noGrid = addCheckableAction(menu, title: QLatin1String("No grid" ), value: m_gridActive == 0, |
388 | key: Qt::Key_N, group: gridGroup); |
389 | menu.addSeparator(); |
390 | |
391 | // Grid size options |
392 | menu.addAction(text: QLatin1String("Increase grid size" ), shortcut: Qt::Key_PageUp, |
393 | args: this, args: &QPixelTool::increaseGridSize); |
394 | menu.addAction(text: QLatin1String("Decrease grid size" ), shortcut: Qt::Key_PageDown, |
395 | args: this, args: &QPixelTool::decreaseGridSize); |
396 | menu.addSeparator(); |
397 | |
398 | QActionGroup *lcdGroup = new QActionGroup(&menu); |
399 | addCheckableAction(menu, title: QLatin1String("No subpixels" ), value: m_lcdMode == 0, |
400 | key: QKeySequence(), group: lcdGroup); |
401 | QAction *rgbPixels = addCheckableAction(menu, title: QLatin1String("RGB subpixels" ), |
402 | value: m_lcdMode == 1, key: QKeySequence(), group: lcdGroup); |
403 | QAction *bgrPixels = addCheckableAction(menu, title: QLatin1String("BGR subpixels" ), |
404 | value: m_lcdMode == 2, key: QKeySequence(), group: lcdGroup); |
405 | QAction *vrgbPixels = addCheckableAction(menu, title: QLatin1String("VRGB subpixels" ), |
406 | value: m_lcdMode == 3, key: QKeySequence(), group: lcdGroup); |
407 | QAction *vbgrPixels = addCheckableAction(menu, title: QLatin1String("VBGR subpixels" ), |
408 | value: m_lcdMode == 4, key: QKeySequence(), group: lcdGroup); |
409 | menu.addSeparator(); |
410 | |
411 | // Zoom options |
412 | menu.addAction(text: QLatin1String("Zoom in" ), shortcut: Qt::Key_Plus, |
413 | args: this, args: &QPixelTool::increaseZoom); |
414 | menu.addAction(text: QLatin1String("Zoom out" ), shortcut: Qt::Key_Minus, |
415 | args: this, args: &QPixelTool::decreaseZoom); |
416 | menu.addSeparator(); |
417 | |
418 | // Freeze / Autoupdate |
419 | QAction *freeze = addCheckableAction(menu, title: QLatin1String("Frozen" ), |
420 | value: tmpFreeze, key: Qt::Key_Space); |
421 | QAction *autoUpdate = addCheckableAction(menu, title: QLatin1String("Continuous update" ), |
422 | value: m_autoUpdate, key: Qt::Key_A); |
423 | menu.addSeparator(); |
424 | |
425 | // Copy to clipboard / save |
426 | menu.addAction(text: QLatin1String("Save as image..." ), shortcut: QKeySequence::SaveAs, |
427 | args: this, args: &QPixelTool::saveToFile); |
428 | #if QT_CONFIG(clipboard) |
429 | menu.addAction(text: QLatin1String("Copy to clipboard" ), shortcut: QKeySequence::Copy, |
430 | args: this, args: &QPixelTool::copyToClipboard); |
431 | menu.addAction(text: QLatin1String("Copy color value to clipboard" ), shortcut: Qt::Key_C, |
432 | args: this, args: &QPixelTool::copyColorToClipboard); |
433 | #endif // QT_CONFIG(clipboard) |
434 | |
435 | menu.addSeparator(); |
436 | menu.addAction(text: QLatin1String("About Qt" ), qApp, args: &QApplication::aboutQt); |
437 | menu.addAction(text: QLatin1String("About Qt Pixeltool" ), args: this, args: &QPixelTool::aboutPixelTool); |
438 | |
439 | menu.exec(pos: mapToGlobal(e->pos())); |
440 | |
441 | // Read out grid settings |
442 | if (noGrid->isChecked()) |
443 | m_gridActive = 0; |
444 | else if (blackGrid->isChecked()) |
445 | m_gridActive = 1; |
446 | else |
447 | m_gridActive = 2; |
448 | |
449 | // Read out lcd settings |
450 | if (rgbPixels->isChecked()) |
451 | m_lcdMode = 1; |
452 | else if (bgrPixels->isChecked()) |
453 | m_lcdMode = 2; |
454 | else if (vrgbPixels->isChecked()) |
455 | m_lcdMode = 3; |
456 | else if (vbgrPixels->isChecked()) |
457 | m_lcdMode = 4; |
458 | else |
459 | m_lcdMode = 0; |
460 | |
461 | m_autoUpdate = autoUpdate->isChecked(); |
462 | m_freeze = freeze->isChecked(); |
463 | |
464 | // LCD mode looks off unless zoom is dividable by 3 |
465 | if (m_lcdMode && m_zoom % 3) |
466 | setZoom(qMax(a: 3, b: (m_zoom + 1) / 3)); |
467 | } |
468 | |
469 | QSize QPixelTool::sizeHint() const |
470 | { |
471 | return m_initialSize; |
472 | } |
473 | |
474 | static inline QString pixelToolTitle(QPoint pos, const QColor ¤tColor) |
475 | { |
476 | if (QHighDpiScaling::isActive()) { |
477 | if (auto screen = QGuiApplication::screenAt(point: pos)) |
478 | pos = QHighDpi::toNativePixels(value: pos, context: screen); |
479 | } |
480 | return QCoreApplication::applicationName() + QLatin1String(" [" ) |
481 | + QString::number(pos.x()) |
482 | + QLatin1String(", " ) + QString::number(pos.y()) + QLatin1String("] " ) |
483 | + currentColor.name(); |
484 | } |
485 | |
486 | void QPixelTool::grabScreen() |
487 | { |
488 | if (m_preview_mode) { |
489 | int w = qMin(a: width() / m_zoom + 1, b: m_preview_image.width()); |
490 | int h = qMin(a: height() / m_zoom + 1, b: m_preview_image.height()); |
491 | m_buffer = QPixmap::fromImage(image: m_preview_image).copy(ax: 0, ay: 0, awidth: w, aheight: h); |
492 | update(); |
493 | return; |
494 | } |
495 | |
496 | QPoint mousePos = QCursor::pos(); |
497 | if (mousePos == m_lastMousePos && !m_autoUpdate) |
498 | return; |
499 | |
500 | if (m_lastMousePos != mousePos) |
501 | setWindowTitle(pixelToolTitle(pos: mousePos, currentColor: m_currentColor)); |
502 | |
503 | int w = int(width() / float(m_zoom)); |
504 | int h = int(height() / float(m_zoom)); |
505 | |
506 | if (width() % m_zoom > 0) |
507 | ++w; |
508 | if (height() % m_zoom > 0) |
509 | ++h; |
510 | |
511 | int x = mousePos.x() - w/2; |
512 | int y = mousePos.y() - h/2; |
513 | |
514 | const QBrush darkBrush = palette().color(cr: QPalette::Dark); |
515 | if (QScreen *screen = this->screen()) { |
516 | m_buffer = screen->grabWindow(window: 0, x, y, w, h); |
517 | } else { |
518 | m_buffer = QPixmap(w, h); |
519 | m_buffer.fill(fillColor: darkBrush.color()); |
520 | } |
521 | QRegion geom(x, y, w, h); |
522 | QRect screenRect; |
523 | const auto screens = QGuiApplication::screens(); |
524 | for (auto screen : screens) |
525 | screenRect |= screen->geometry(); |
526 | geom -= screenRect; |
527 | const auto rectsInRegion = geom.rectCount(); |
528 | if (rectsInRegion > 0) { |
529 | QPainter p(&m_buffer); |
530 | p.translate(dx: -x, dy: -y); |
531 | p.setPen(Qt::NoPen); |
532 | p.setBrush(darkBrush); |
533 | p.drawRects(rects: geom.begin(), rectCount: rectsInRegion); |
534 | } |
535 | |
536 | update(); |
537 | |
538 | m_currentColor = m_buffer.toImage().pixel(pt: m_buffer.rect().center()); |
539 | m_lastMousePos = mousePos; |
540 | } |
541 | |
542 | void QPixelTool::startZoomVisibleTimer() |
543 | { |
544 | if (m_displayZoomId > 0) |
545 | killTimer(id: m_displayZoomId); |
546 | m_displayZoomId = startTimer(interval: 5000); |
547 | setZoomVisible(true); |
548 | } |
549 | |
550 | void QPixelTool::startGridSizeVisibleTimer() |
551 | { |
552 | if (m_gridActive) { |
553 | if (m_displayGridSizeId > 0) |
554 | killTimer(id: m_displayGridSizeId); |
555 | m_displayGridSizeId = startTimer(interval: 5000); |
556 | m_displayGridSize = true; |
557 | update(); |
558 | } |
559 | } |
560 | |
561 | void QPixelTool::setZoomVisible(bool visible) |
562 | { |
563 | m_displayZoom = visible; |
564 | update(); |
565 | } |
566 | |
567 | void QPixelTool::toggleFreeze() |
568 | { |
569 | m_freeze = !m_freeze; |
570 | if (!m_freeze) |
571 | m_dragStart = m_dragCurrent = QPoint(); |
572 | } |
573 | |
574 | void QPixelTool::increaseZoom() |
575 | { |
576 | if (!m_lcdMode) |
577 | setZoom(m_zoom + 1); |
578 | else |
579 | setZoom(m_zoom + 3); |
580 | } |
581 | |
582 | void QPixelTool::decreaseZoom() |
583 | { |
584 | if (!m_lcdMode) |
585 | setZoom(m_zoom - 1); |
586 | else |
587 | setZoom(m_zoom - 3); |
588 | } |
589 | |
590 | void QPixelTool::setZoom(int zoom) |
591 | { |
592 | if (zoom > 0) { |
593 | QPoint pos = m_lastMousePos; |
594 | m_lastMousePos = QPoint(); |
595 | m_zoom = zoom; |
596 | grabScreen(); |
597 | m_lastMousePos = pos; |
598 | m_dragStart = m_dragCurrent = QPoint(); |
599 | startZoomVisibleTimer(); |
600 | } |
601 | } |
602 | |
603 | void QPixelTool::toggleGrid() |
604 | { |
605 | if (++m_gridActive > 2) |
606 | m_gridActive = 0; |
607 | update(); |
608 | } |
609 | |
610 | void QPixelTool::setGridSize(int gridSize) |
611 | { |
612 | if (m_gridActive && gridSize > 0) { |
613 | m_gridSize = gridSize; |
614 | startGridSizeVisibleTimer(); |
615 | update(); |
616 | } |
617 | } |
618 | |
619 | #if QT_CONFIG(clipboard) |
620 | void QPixelTool::copyToClipboard() |
621 | { |
622 | QGuiApplication::clipboard()->setPixmap(m_buffer); |
623 | } |
624 | |
625 | void QPixelTool::copyColorToClipboard() |
626 | { |
627 | QGuiApplication::clipboard()->setText(QColor(m_currentColor).name()); |
628 | } |
629 | #endif // QT_CONFIG(clipboard) |
630 | |
631 | void QPixelTool::saveToFile() |
632 | { |
633 | bool oldFreeze = m_freeze; |
634 | m_freeze = true; |
635 | |
636 | QFileDialog fileDialog(this); |
637 | fileDialog.setWindowTitle(QLatin1String("Save as image" )); |
638 | fileDialog.setAcceptMode(QFileDialog::AcceptSave); |
639 | fileDialog.setDirectory(QStandardPaths::writableLocation(type: QStandardPaths::PicturesLocation)); |
640 | QStringList mimeTypes; |
641 | const QByteArrayList supportedMimeTypes = QImageWriter::supportedMimeTypes(); |
642 | for (const QByteArray &mimeTypeB : supportedMimeTypes) |
643 | mimeTypes.append(t: QString::fromLatin1(ba: mimeTypeB)); |
644 | fileDialog.setMimeTypeFilters(mimeTypes); |
645 | const QString pngType = QLatin1String("image/png" ); |
646 | if (mimeTypes.contains(str: pngType)) { |
647 | fileDialog.selectMimeTypeFilter(filter: pngType); |
648 | fileDialog.setDefaultSuffix(QLatin1String("png" )); |
649 | } |
650 | |
651 | while (fileDialog.exec() == QDialog::Accepted |
652 | && !m_buffer.save(fileName: fileDialog.selectedFiles().constFirst())) { |
653 | QMessageBox::warning(parent: this, title: QLatin1String("Unable to write image" ), |
654 | text: QLatin1String("Unable to write " ) |
655 | + QDir::toNativeSeparators(pathName: fileDialog.selectedFiles().first())); |
656 | } |
657 | m_freeze = oldFreeze; |
658 | } |
659 | |
660 | QTextStream &operator<<(QTextStream &str, const QScreen *screen) |
661 | { |
662 | const QRect geometry = screen->geometry(); |
663 | str << '"' << screen->name() << "\" " << geometry.width() |
664 | << 'x' << geometry.height() << Qt::forcesign << geometry.x() << geometry.y() |
665 | << Qt::noforcesign << ", " << qRound(d: screen->logicalDotsPerInch()) << "DPI" |
666 | << ", Depth: " << screen->depth() << ", " << screen->refreshRate() << "Hz" ; |
667 | const qreal dpr = screen->devicePixelRatio(); |
668 | if (!qFuzzyCompare(p1: dpr, p2: qreal(1))) |
669 | str << ", DPR: " << dpr; |
670 | return str; |
671 | } |
672 | |
673 | QString QPixelTool::aboutText() const |
674 | { |
675 | const QList<QScreen *> screens = QGuiApplication::screens(); |
676 | const QScreen *windowScreen = windowHandle()->screen(); |
677 | |
678 | QString result; |
679 | QTextStream str(&result); |
680 | str << "<html><head></head><body><h2>Qt Pixeltool</h2><p>Qt " << QT_VERSION_STR |
681 | << "</p><p>Copyright (C) 2017 The Qt Company Ltd.</p><h3>Screens</h3><ul>" ; |
682 | for (const QScreen *screen : screens) |
683 | str << "<li>" << (screen == windowScreen ? "* " : " " ) << screen << "</li>" ; |
684 | str << "</ul></body></html>" ; |
685 | return result; |
686 | } |
687 | |
688 | void QPixelTool::aboutPixelTool() |
689 | { |
690 | QMessageBox aboutBox(QMessageBox::Information, tr(s: "About Qt Pixeltool" ), aboutText(), |
691 | QMessageBox::Close, this); |
692 | aboutBox.setWindowFlags(aboutBox.windowFlags() & ~Qt::WindowContextHelpButtonHint); |
693 | aboutBox.setTextInteractionFlags(Qt::TextBrowserInteraction); |
694 | aboutBox.exec(); |
695 | } |
696 | |
697 | QT_END_NAMESPACE |
698 | |