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

source code of qttools/src/pixeltool/qpixeltool.cpp