qt5base-lts/examples/widgets/desktop/screenshot/screenshot.cpp
Giuseppe D'Angelo 25351dcc54 Long live QKeyCombination!
C++20 via P1120 is deprecating arithmetic operations between
unrelated enumeration types, and GCC 10 is already complaining.
Hence, these operations might become illegal in C++23 or C++26 at
the latest.

A case of this that affects Qt is in key combinations: a
QKeySequence can be constructed by summing / ORing modifiers and a
key, for instance:

  Qt::CTRL + Qt::Key_A
  Qt::SHIFT | Qt::CTRL | Qt::Key_G (recommended, see below)

The problem is that the modifiers and the key belong to different
enumerations (and there's 2 enumerations for the modifier, and one
for the key).

To solve this: add a dedicated class to represent a combination of
keys, and operators between those enumerations to build instances
of this class.

I would've simply defined operator|, but again docs and pre-existing
code use operator+ as well, so added both to at least tackle simple
cases (modifier + key).

Multiple modifiers create a problem: operator+ between them yields
int, not the corresponding flags type (because operator+ is not
overloaded for this use case):

  Qt::CTRL + Qt::SHIFT + Qt::Key_A
  \__________________/      /
          int              /
           \______________/
                  int

Not only this loses track of the datatypes involved, but it would
also then "add" the key (with NO warnings, now its int + enum, so
it's not mixing enums!) and yielding int again.

I don't want to special-case this; the point of the class is
that int is the wrong datatype. Everything works just fine when
using operator| instead:

  Qt::CTRL | Qt::SHIFT | Qt::Key_A
  \__________________/      /
      Qt::Modifiers        /
           \______________/
            QKeyCombination

So I'm defining operator+ so that the simple cases still work,
but also deprecating it.

Port some code around Qt to the new class. In certain cases,
it's a huge win for clarity. In some others, I've just added
the necessary casts to make it still compile without warnings,
without attempting refactorings.

[ChangeLog][QtCore][QKeyCombination] New class to represent
a combination of a key and zero or more modifiers, to be used
when defining shortcuts or similar.

[ChangeLog][Potentially Source-Incompatible Changes] A keyboard
modifier (such as Qt::CTRL, Qt::AltModifier, etc.) should be
combined with a key (such as Qt::Key_A, Qt::Key_F1, etc.) by using
operator|, not operator+.  The result is now an object of type
QKeyCombination, that stores the key and the modifiers.

Change-Id: I657a3a328232f059023fff69c5031ee31cc91dd6
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2020-09-03 07:00:31 +02:00

200 lines
7.2 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtWidgets>
#include "screenshot.h"
//! [0]
Screenshot::Screenshot()
: screenshotLabel(new QLabel(this))
{
screenshotLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
screenshotLabel->setAlignment(Qt::AlignCenter);
const QRect screenGeometry = screen()->geometry();
screenshotLabel->setMinimumSize(screenGeometry.width() / 8, screenGeometry.height() / 8);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(screenshotLabel);
QGroupBox *optionsGroupBox = new QGroupBox(tr("Options"), this);
delaySpinBox = new QSpinBox(optionsGroupBox);
delaySpinBox->setSuffix(tr(" s"));
delaySpinBox->setMaximum(60);
connect(delaySpinBox, &QSpinBox::valueChanged,
this, &Screenshot::updateCheckBox);
hideThisWindowCheckBox = new QCheckBox(tr("Hide This Window"), optionsGroupBox);
QGridLayout *optionsGroupBoxLayout = new QGridLayout(optionsGroupBox);
optionsGroupBoxLayout->addWidget(new QLabel(tr("Screenshot Delay:"), this), 0, 0);
optionsGroupBoxLayout->addWidget(delaySpinBox, 0, 1);
optionsGroupBoxLayout->addWidget(hideThisWindowCheckBox, 1, 0, 1, 2);
mainLayout->addWidget(optionsGroupBox);
QHBoxLayout *buttonsLayout = new QHBoxLayout;
newScreenshotButton = new QPushButton(tr("New Screenshot"), this);
connect(newScreenshotButton, &QPushButton::clicked, this, &Screenshot::newScreenshot);
buttonsLayout->addWidget(newScreenshotButton);
QPushButton *saveScreenshotButton = new QPushButton(tr("Save Screenshot"), this);
connect(saveScreenshotButton, &QPushButton::clicked, this, &Screenshot::saveScreenshot);
buttonsLayout->addWidget(saveScreenshotButton);
QPushButton *quitScreenshotButton = new QPushButton(tr("Quit"), this);
quitScreenshotButton->setShortcut(Qt::CTRL | Qt::Key_Q);
connect(quitScreenshotButton, &QPushButton::clicked, this, &QWidget::close);
buttonsLayout->addWidget(quitScreenshotButton);
buttonsLayout->addStretch();
mainLayout->addLayout(buttonsLayout);
shootScreen();
delaySpinBox->setValue(5);
setWindowTitle(tr("Screenshot"));
resize(300, 200);
}
//! [0]
//! [1]
void Screenshot::resizeEvent(QResizeEvent * /* event */)
{
QSize scaledSize = originalPixmap.size();
scaledSize.scale(screenshotLabel->size(), Qt::KeepAspectRatio);
if (scaledSize != screenshotLabel->pixmap(Qt::ReturnByValue).size())
updateScreenshotLabel();
}
//! [1]
//! [2]
void Screenshot::newScreenshot()
{
if (hideThisWindowCheckBox->isChecked())
hide();
newScreenshotButton->setDisabled(true);
QTimer::singleShot(delaySpinBox->value() * 1000, this, &Screenshot::shootScreen);
}
//! [2]
//! [3]
void Screenshot::saveScreenshot()
{
const QString format = "png";
QString initialPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
if (initialPath.isEmpty())
initialPath = QDir::currentPath();
initialPath += tr("/untitled.") + format;
QFileDialog fileDialog(this, tr("Save As"), initialPath);
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
fileDialog.setFileMode(QFileDialog::AnyFile);
fileDialog.setDirectory(initialPath);
QStringList mimeTypes;
const QList<QByteArray> baMimeTypes = QImageWriter::supportedMimeTypes();
for (const QByteArray &bf : baMimeTypes)
mimeTypes.append(QLatin1String(bf));
fileDialog.setMimeTypeFilters(mimeTypes);
fileDialog.selectMimeTypeFilter("image/" + format);
fileDialog.setDefaultSuffix(format);
if (fileDialog.exec() != QDialog::Accepted)
return;
const QString fileName = fileDialog.selectedFiles().first();
if (!originalPixmap.save(fileName)) {
QMessageBox::warning(this, tr("Save Error"), tr("The image could not be saved to \"%1\".")
.arg(QDir::toNativeSeparators(fileName)));
}
}
//! [3]
//! [4]
void Screenshot::shootScreen()
{
QScreen *screen = QGuiApplication::primaryScreen();
if (const QWindow *window = windowHandle())
screen = window->screen();
if (!screen)
return;
if (delaySpinBox->value() != 0)
QApplication::beep();
originalPixmap = screen->grabWindow(0);
updateScreenshotLabel();
newScreenshotButton->setDisabled(false);
if (hideThisWindowCheckBox->isChecked())
show();
}
//! [4]
//! [6]
void Screenshot::updateCheckBox()
{
if (delaySpinBox->value() == 0) {
hideThisWindowCheckBox->setDisabled(true);
hideThisWindowCheckBox->setChecked(false);
} else {
hideThisWindowCheckBox->setDisabled(false);
}
}
//! [6]
//! [10]
void Screenshot::updateScreenshotLabel()
{
screenshotLabel->setPixmap(originalPixmap.scaled(screenshotLabel->size(),
Qt::KeepAspectRatio,
Qt::SmoothTransformation));
}
//! [10]