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