qrimagewidget.cpp raw
1 // Copyright (c) 2011-2021 The Limenka developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #include <qt/qrimagewidget.h>
6
7 #include <qt/guiutil.h>
8 #include <qt/optionsmodel.h>
9
10 #include <QApplication>
11 #include <QClipboard>
12 #include <QDrag>
13 #include <QFontDatabase>
14 #include <QMenu>
15 #include <QMimeData>
16 #include <QMouseEvent>
17 #include <QPainter>
18
19 #include <limenka-build-config.h> // IWYU pragma: keep
20
21 #ifdef USE_QRCODE
22 #include <qrencode.h>
23 #endif
24
25 QRImageWidget::QRImageWidget(QWidget* parent)
26 : QLabel(parent)
27 {
28 contextMenu = new QMenu(this);
29 contextMenu->addAction(tr("&Save Imageā¦"), this, &QRImageWidget::saveImage);
30 contextMenu->addAction(tr("&Copy Image"), this, &QRImageWidget::copyImage);
31 }
32
33 bool QRImageWidget::setQR(const QString& data, const QString& text, const OptionsModel::FontChoice& fontchoice)
34 {
35 #ifdef USE_QRCODE
36 setText("");
37 if (data.isEmpty()) return false;
38
39 // limit length
40 if (data.length() > MAX_URI_LENGTH) {
41 setText(tr("Resulting URI too long, try to reduce the text for label / message."));
42 return false;
43 }
44
45 QRcode *code = QRcode_encodeString(data.toUtf8().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1);
46
47 if (!code) {
48 setText(tr("Error encoding URI into QR Code."));
49 return false;
50 }
51
52 QImage qrImage = QImage(code->width, code->width, QImage::Format_RGB32);
53 unsigned char *p = code->data;
54 for (int y = 0; y < code->width; ++y) {
55 for (int x = 0; x < code->width; ++x) {
56 qrImage.setPixel(x, y, ((*p & 1) ? 0x0 : 0xffffff));
57 ++p;
58 }
59 }
60 QRcode_free(code);
61
62 int qr_image_width = QR_IMAGE_SIZE + (2 * QR_IMAGE_MARGIN);
63 int qr_image_height = qr_image_width;
64 int qr_image_x_margin = QR_IMAGE_MARGIN;
65 int text_lines;
66 QFont font;
67 if (text.isEmpty()) {
68 text_lines = 0;
69 } else {
70 const int max_text_width = qr_image_width - (2 * QR_IMAGE_TEXT_MARGIN);
71
72 // Determine font to use
73 if (std::holds_alternative<OptionsModel::FontChoiceAbstract>(fontchoice)) {
74 font = GUIUtil::fixedPitchFont(fontchoice != OptionsModel::UseBestSystemFont);
75 if (fontchoice == OptionsModel::UseBestSystemFont) {
76 font.setWeight(QFont::Bold);
77 font.setStretch(QFont::SemiCondensed);
78 font.setLetterSpacing(QFont::AbsoluteSpacing, 1);
79 }
80
81 const qreal font_size = GUIUtil::calculateIdealFontSize(max_text_width, text, font);
82 font.setPointSizeF(font_size);
83 } else {
84 font = std::get<QFont>(fontchoice);
85 }
86
87 // Plan how many lines are needed
88 QFontMetrics fm(font);
89 const int text_width = GUIUtil::TextWidth(fm, text);
90 if (text_width > max_text_width && text_width < max_text_width * 5 / 4) {
91 // Allow the image to grow up to 25% wider
92 qr_image_width = text_width + (2 * QR_IMAGE_TEXT_MARGIN);
93 qr_image_x_margin = (qr_image_width - QR_IMAGE_SIZE) / 2;
94 text_lines = 1;
95 } else {
96 text_lines = (text_width + max_text_width - 1) / max_text_width;
97 }
98 qr_image_height += (fm.height() * text_lines) + QR_IMAGE_TEXT_MARGIN;
99 }
100 QImage qrAddrImage(qr_image_width, qr_image_height, QImage::Format_RGB32);
101 qrAddrImage.fill(0xffffff);
102 {
103 QPainter painter(&qrAddrImage);
104 painter.drawImage(qr_image_x_margin, QR_IMAGE_MARGIN, qrImage.scaled(QR_IMAGE_SIZE, QR_IMAGE_SIZE));
105
106 if (!text.isEmpty()) {
107 QRect paddedRect = qrAddrImage.rect();
108 paddedRect.setHeight(paddedRect.height() - QR_IMAGE_TEXT_MARGIN);
109
110 QString text_wrapped = text;
111 const int char_per_line = (text.size() + text_lines - 1) / text_lines;
112 for (int line = 1, pos = 0; line < text_lines; ++line) {
113 pos += char_per_line;
114 text_wrapped.insert(pos, QChar{'\n'});
115 }
116
117 painter.setFont(font);
118 painter.drawText(paddedRect, Qt::AlignBottom | Qt::AlignCenter, text_wrapped);
119 }
120 }
121
122 setPixmap(QPixmap::fromImage(qrAddrImage));
123
124 return true;
125 #else
126 setText(tr("QR code support not available."));
127 return false;
128 #endif
129 }
130
131 bool QRImageWidget::setQR(const QString& data)
132 {
133 return setQR(data, "", OptionsModel::FontChoiceAbstract::EmbeddedFont);
134 }
135
136 QImage QRImageWidget::exportImage()
137 {
138 return GUIUtil::GetImage(this);
139 }
140
141 void QRImageWidget::mousePressEvent(QMouseEvent *event)
142 {
143 if (event->button() == Qt::LeftButton && GUIUtil::HasPixmap(this)) {
144 event->accept();
145 QMimeData *mimeData = new QMimeData;
146 mimeData->setImageData(exportImage());
147
148 QDrag *drag = new QDrag(this);
149 drag->setMimeData(mimeData);
150 drag->exec();
151 } else {
152 QLabel::mousePressEvent(event);
153 }
154 }
155
156 void QRImageWidget::saveImage()
157 {
158 if (!GUIUtil::HasPixmap(this))
159 return;
160 QString fn = GUIUtil::getSaveFileName(
161 this, tr("Save QR Code"), QString(),
162 /*: Expanded name of the PNG file format.
163 See: https://en.wikipedia.org/wiki/Portable_Network_Graphics. */
164 tr("PNG Image") + QLatin1String(" (*.png)"), nullptr);
165 if (!fn.isEmpty())
166 {
167 exportImage().save(fn);
168 }
169 }
170
171 void QRImageWidget::copyImage()
172 {
173 if (!GUIUtil::HasPixmap(this))
174 return;
175 QApplication::clipboard()->setImage(exportImage());
176 }
177
178 void QRImageWidget::contextMenuEvent(QContextMenuEvent *event)
179 {
180 if (!GUIUtil::HasPixmap(this))
181 return;
182 contextMenu->exec(event->globalPos());
183 }
184