Files
TZShot/src/widgets/capture_overlay_widget.cpp
T

1150 lines
38 KiB
C++

#include "widgets/capture_overlay_widget.h"
#include "widgets/gif_record_overlay_widget.h"
#include "widgets/magnifier_widget.h"
#include "widgets/sticky_canvas_widget.h"
#include "widgets/widget_window_bridge.h"
#include "viewmodel/gif_record_view_model.h"
#include "viewmodel/ocr_view_model.h"
#include "viewmodel/screenshot_view_model.h"
#include "viewmodel/sticky_view_model.h"
#include "viewmodel/storage_view_model.h"
#include <QApplication>
#include <QCheckBox>
#include <QCloseEvent>
#include <QDateTime>
#include <QEvent>
#include <QDir>
#include <QFileDialog>
#include <QFile>
#include <QHBoxLayout>
#include <QImage>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QPainter>
#include <QProgressBar>
#include <QScreen>
#include <QSizePolicy>
#include <QStyle>
#include <QToolButton>
#include <QFrame>
#include <QLabel>
#include <QLineEdit>
#include <QResizeEvent>
#include <QTimer>
#include <QVBoxLayout>
#ifdef Q_OS_WIN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
namespace {
QString loadCaptureOverlayStyleSheet()
{
QFile file(QStringLiteral(":/resource/qss/capture_overlay_widget.qss"));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return {};
}
return QString::fromUtf8(file.readAll());
}
QToolButton *createToolbarButton(const QString &iconPath,
const QString &toolTip,
QWidget *parent)
{
auto *button = new QToolButton(parent);
button->setIcon(QIcon(iconPath));
button->setToolTip(toolTip);
button->setAutoRaise(false);
button->setCheckable(false);
button->setFixedSize(32, 32);
button->setIconSize(QSize(18, 18));
button->setCursor(Qt::PointingHandCursor);
return button;
}
}
CaptureOverlayWidget::CaptureOverlayWidget(ScreenshotViewModel *screenCapture,
StickyViewModel *stickyViewModel,
StorageViewModel *storageViewModel,
OcrViewModel *ocrViewModel,
GifRecordViewModel *gifRecordViewModel,
WidgetWindowBridge *widgetWindowBridge,
QWidget *parent)
: QWidget(parent)
, m_screenCapture(screenCapture)
, m_stickyViewModel(stickyViewModel)
, m_storageViewModel(storageViewModel)
, m_ocrViewModel(ocrViewModel)
, m_gifRecordViewModel(gifRecordViewModel)
, m_widgetWindowBridge(widgetWindowBridge)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint);
setAttribute(Qt::WA_DeleteOnClose, false);
setMouseTracking(true);
setFocusPolicy(Qt::StrongFocus);
setStyleSheet(loadCaptureOverlayStyleSheet());
m_magnifier = new MagnifierWidget(this);
m_canvas = new StickyCanvasWidget(this);
m_canvas->hide();
m_canvas->setViewScale(1.0);
m_canvas->setBackgroundVisible(false);
m_canvas->setPenColor(m_currentPenColor);
m_canvas->setPenSize(m_currentPenSize);
m_canvas->setActiveShapeType(PEN);
m_canvas->setAnnotationText(m_annotationText);
m_canvas->setTextBackgroundEnabled(m_textBackgroundEnabled);
m_canvas->setNumberAutoIncrement(m_numberAutoIncrement);
m_canvas->setNumberValue(m_numberValue);
connect(m_canvas, &StickyCanvasWidget::numberValueChanged, this, [this](int value) {
m_numberValue = value;
if (m_numberLabel) {
m_numberLabel->setText(QString::number(value));
}
if (m_numberAutoIncrementCheck) {
m_numberAutoIncrementCheck->setChecked(m_numberAutoIncrement);
}
});
connect(m_canvas, &StickyCanvasWidget::textPlacementRequested, this, [this](const QPoint &point) {
m_pendingTextPoint = point;
if (!m_inlineTextEditor || !m_inlineTextPanel || !m_canvas) {
return;
}
m_inlineTextEditor->setText(m_annotationText);
const QRect canvasRect = m_canvas->geometry();
const int panelW = 260;
const int panelH = 44;
int x = canvasRect.x() + point.x();
int y = canvasRect.y() + point.y() + 6;
x = qMax(canvasRect.x(), qMin(x, canvasRect.right() - panelW + 1));
y = qMax(canvasRect.y(), qMin(y, canvasRect.bottom() - panelH + 1));
m_inlineTextPanel->setGeometry(x, y, panelW, panelH);
m_inlineTextPanel->show();
m_inlineTextPanel->raise();
m_inlineTextEditor->setFocus();
m_inlineTextEditor->selectAll();
});
m_toolOptions = new QWidget(this);
m_toolOptions->setObjectName(QStringLiteral("captureOverlayToolOptions"));
m_toolOptions->setAttribute(Qt::WA_StyledBackground, true);
m_toolOptions->hide();
const QVector<QColor> colors {
QColor("#F43F5E"), QColor("#EF4444"), QColor("#F97316"), QColor("#EAB308"),
QColor("#22C55E"), QColor("#3B82F6"), QColor("#8B5CF6"), QColor("#000000"), QColor("#FFFFFF")
};
for (const QColor &color : colors) {
auto *button = new QToolButton(m_toolOptions);
button->setCheckable(true);
button->setAutoRaise(false);
button->setToolButtonStyle(Qt::ToolButtonIconOnly);
button->setCursor(Qt::PointingHandCursor);
button->setStyleSheet(QStringLiteral(
"QToolButton { border: 1px solid #CBD5E1; border-radius: 9px; background-color: %1; }"
"QToolButton:checked { border: 2px solid #3B82F6; background-color: %1; }"
).arg(color.name()));
connect(button, &QToolButton::clicked, this, [this, button, color]() {
for (QToolButton *other : m_colorButtons) {
if (other != button) {
other->setChecked(false);
}
}
button->setChecked(true);
m_currentPenColor = color;
syncCanvasToolSettings();
});
m_colorButtons.append(button);
}
struct SizePreset { int value; const char *label; };
const QVector<SizePreset> sizePresets {
{ 2, "细" }, { 4, "中" }, { 6, "粗" }, { 10, "特粗" }
};
for (const SizePreset &preset : sizePresets) {
auto *button = new QToolButton(m_toolOptions);
button->setCheckable(true);
button->setText(QString::fromUtf8(preset.label));
button->setCursor(Qt::PointingHandCursor);
connect(button, &QToolButton::clicked, this, [this, button, preset]() {
for (QToolButton *other : m_sizeButtons) {
if (other != button) {
other->setChecked(false);
}
}
button->setChecked(true);
m_currentPenSize = preset.value;
syncCanvasToolSettings();
});
m_sizeButtons.append(button);
}
m_textInput = new QLineEdit(m_toolOptions);
m_textInput->setPlaceholderText(tr("输入文字"));
m_textInput->setText(m_annotationText);
connect(m_textInput, &QLineEdit::textChanged, this, [this](const QString &text) {
m_annotationText = text;
if (m_canvas) {
m_canvas->setAnnotationText(m_annotationText);
}
});
m_inlineTextPanel = new QWidget(this);
m_inlineTextPanel->setObjectName(QStringLiteral("captureOverlayInlineTextPanel"));
m_inlineTextPanel->setAttribute(Qt::WA_StyledBackground, true);
m_inlineTextPanel->hide();
auto *inlineLayout = new QHBoxLayout(m_inlineTextPanel);
inlineLayout->setContentsMargins(6, 6, 6, 6);
inlineLayout->setSpacing(6);
m_inlineTextEditor = new QLineEdit(m_inlineTextPanel);
m_inlineTextEditor->setObjectName(QStringLiteral("captureOverlayInlineTextEditor"));
m_inlineTextEditor->setPlaceholderText(tr("输入文字"));
m_inlineTextEditor->installEventFilter(this);
inlineLayout->addWidget(m_inlineTextEditor, 1);
m_inlineTextConfirmButton = createToolbarButton(QStringLiteral(":/resource/img/lc_check.svg"), tr("确认"), m_inlineTextPanel);
m_inlineTextCancelButton = createToolbarButton(QStringLiteral(":/resource/img/lc_x.svg"), tr("取消"), m_inlineTextPanel);
inlineLayout->addWidget(m_inlineTextConfirmButton);
inlineLayout->addWidget(m_inlineTextCancelButton);
const auto submitInlineText = [this]() {
const QString text = m_inlineTextEditor->text().trimmed();
if (m_canvas && !text.isEmpty()) {
m_annotationText = text;
if (m_textInput) {
m_textInput->setText(text);
}
m_canvas->setAnnotationText(text);
m_canvas->addTextAnnotation(m_pendingTextPoint, text);
}
if (m_inlineTextPanel) {
m_inlineTextPanel->hide();
}
};
connect(m_inlineTextEditor, &QLineEdit::returnPressed, this, submitInlineText);
connect(m_inlineTextConfirmButton, &QToolButton::clicked, this, submitInlineText);
connect(m_inlineTextCancelButton, &QToolButton::clicked, this, [this]() {
if (m_inlineTextPanel) {
m_inlineTextPanel->hide();
}
});
connect(m_inlineTextEditor, &QLineEdit::textChanged, this, [this](const QString &text) {
if (m_textInput && m_textInput->text() != text) {
m_textInput->setText(text);
}
});
m_textBackgroundCheck = new QCheckBox(tr("背景"), m_toolOptions);
m_textBackgroundCheck->setChecked(m_textBackgroundEnabled);
connect(m_textBackgroundCheck, &QCheckBox::toggled, this, [this](bool checked) {
m_textBackgroundEnabled = checked;
if (m_canvas) {
m_canvas->setTextBackgroundEnabled(checked);
}
});
m_numberAutoIncrementCheck = new QCheckBox(tr("自动递增"), m_toolOptions);
m_numberAutoIncrementCheck->setChecked(m_numberAutoIncrement);
connect(m_numberAutoIncrementCheck, &QCheckBox::toggled, this, [this](bool checked) {
m_numberAutoIncrement = checked;
if (m_canvas) {
m_canvas->setNumberAutoIncrement(checked);
}
});
m_numberMinusButton = new QToolButton(m_toolOptions);
m_numberMinusButton->setText(QStringLiteral("-"));
m_numberMinusButton->setCursor(Qt::PointingHandCursor);
connect(m_numberMinusButton, &QToolButton::clicked, this, [this]() {
const int nextValue = qMax(1, m_numberValue - 1);
m_numberValue = nextValue;
if (m_numberLabel) {
m_numberLabel->setText(QString::number(nextValue));
}
if (m_canvas) {
m_canvas->setNumberValue(nextValue);
}
});
m_numberLabel = new QLabel(QString::number(m_numberValue), m_toolOptions);
m_numberLabel->setObjectName(QStringLiteral("captureOverlayNumberLabel"));
m_numberLabel->setAlignment(Qt::AlignCenter);
m_numberPlusButton = new QToolButton(m_toolOptions);
m_numberPlusButton->setText(QStringLiteral("+"));
m_numberPlusButton->setCursor(Qt::PointingHandCursor);
connect(m_numberPlusButton, &QToolButton::clicked, this, [this]() {
const int nextValue = qMin(9999, m_numberValue + 1);
m_numberValue = nextValue;
if (m_numberLabel) {
m_numberLabel->setText(QString::number(nextValue));
}
if (m_canvas) {
m_canvas->setNumberValue(nextValue);
}
});
m_toolbar = new QWidget(this);
m_toolbar->setObjectName(QStringLiteral("captureOverlayToolbar"));
m_toolbar->setAttribute(Qt::WA_StyledBackground, true);
auto *toolbarLayout = new QHBoxLayout(m_toolbar);
toolbarLayout->setContentsMargins(6, 5, 6, 5);
toolbarLayout->setSpacing(2);
m_pencilButton = createToolbarButton(QStringLiteral(":/resource/img/lc_pencil.svg"), tr("画笔"), m_toolbar);
m_rectButton = createToolbarButton(QStringLiteral(":/resource/img/lc_square.svg"), tr("矩形"), m_toolbar);
m_circleButton = createToolbarButton(QStringLiteral(":/resource/img/lc_circle.svg"), tr("圆形"), m_toolbar);
m_arrowButton = createToolbarButton(QStringLiteral(":/resource/img/lc_arrow.svg"), tr("箭头"), m_toolbar);
m_mosaicButton = createToolbarButton(QStringLiteral(":/resource/img/lc_mosaic.svg"), tr("马赛克"), m_toolbar);
m_blurButton = createToolbarButton(QStringLiteral(":/resource/img/lc_blur.svg"), tr("高斯模糊"), m_toolbar);
m_textButton = createToolbarButton(QStringLiteral(":/resource/img/lc_text.svg"), tr("文字"), m_toolbar);
m_numberButton = createToolbarButton(QStringLiteral(":/resource/img/lc_list_ordered.svg"), tr("序号"), m_toolbar);
m_undoButton = createToolbarButton(QStringLiteral(":/resource/img/lc_undo.svg"), tr("撤销"), m_toolbar);
m_copyButton = createToolbarButton(QStringLiteral(":/resource/img/lc_copy.svg"), tr("复制"), m_toolbar);
m_saveButton = createToolbarButton(QStringLiteral(":/resource/img/lc_save.svg"), tr("保存"), m_toolbar);
m_stickyButton = createToolbarButton(QStringLiteral(":/resource/img/lc_pin.svg"), tr("贴图"), m_toolbar);
m_ocrButton = createToolbarButton(QStringLiteral(":/resource/img/lc_ocr.svg"), tr("OCR"), m_toolbar);
m_longCaptureButton = createToolbarButton(QStringLiteral(":/resource/img/lc_longshot.svg"), tr("长截图"), m_toolbar);
m_gifButton = createToolbarButton(QStringLiteral(":/resource/img/lc_gif_record.svg"), tr("录制 GIF"), m_toolbar);
m_cancelButton = createToolbarButton(QStringLiteral(":/resource/img/lc_x.svg"), tr("取消"), m_toolbar);
const QVector<QToolButton*> drawButtons {
m_pencilButton, m_rectButton, m_circleButton, m_arrowButton, m_mosaicButton, m_blurButton, m_textButton, m_numberButton
};
for (QToolButton *button : drawButtons) {
button->setCheckable(true);
toolbarLayout->addWidget(button);
}
toolbarLayout->addWidget(m_undoButton);
auto *divider = new QFrame(m_toolbar);
divider->setObjectName(QStringLiteral("captureOverlayToolbarDivider"));
divider->setFixedSize(1, 18);
toolbarLayout->addWidget(divider, 0, Qt::AlignVCenter);
toolbarLayout->addWidget(m_copyButton);
toolbarLayout->addWidget(m_saveButton);
toolbarLayout->addWidget(m_stickyButton);
toolbarLayout->addWidget(m_ocrButton);
toolbarLayout->addWidget(m_longCaptureButton);
toolbarLayout->addWidget(m_gifButton);
auto *divider2 = new QFrame(m_toolbar);
divider2->setObjectName(QStringLiteral("captureOverlayToolbarDivider"));
divider2->setFixedSize(1, 18);
toolbarLayout->addWidget(divider2, 0, Qt::AlignVCenter);
toolbarLayout->addWidget(m_cancelButton);
m_toolbar->hide();
connect(m_copyButton, &QToolButton::clicked, this, [this]() { performAction(CaptureAction::Copy); });
connect(m_saveButton, &QToolButton::clicked, this, [this]() { performAction(CaptureAction::Save); });
connect(m_stickyButton, &QToolButton::clicked, this, [this]() { performAction(CaptureAction::Sticky); });
connect(m_ocrButton, &QToolButton::clicked, this, [this]() { performAction(CaptureAction::Ocr); });
connect(m_longCaptureButton, &QToolButton::clicked, this, [this]() { performAction(CaptureAction::LongCapture); });
connect(m_gifButton, &QToolButton::clicked, this, [this]() { performAction(CaptureAction::Gif); });
connect(m_cancelButton, &QToolButton::clicked, this, [this]() { finishCapture(); });
connect(m_undoButton, &QToolButton::clicked, this, [this]() {
if (m_canvas) {
m_canvas->undo();
}
});
const auto setOnlyChecked = [drawButtons](QToolButton *activeButton) {
for (QToolButton *button : drawButtons) {
if (button != activeButton) {
button->setChecked(false);
}
}
};
connect(m_pencilButton, &QToolButton::clicked, this, [=]() { setOnlyChecked(m_pencilButton); setActiveTool(PEN, m_pencilButton); });
connect(m_rectButton, &QToolButton::clicked, this, [=]() { setOnlyChecked(m_rectButton); setActiveTool(RECTANGLE, m_rectButton); });
connect(m_circleButton, &QToolButton::clicked, this, [=]() { setOnlyChecked(m_circleButton); setActiveTool(ELLIPSE, m_circleButton); });
connect(m_arrowButton, &QToolButton::clicked, this, [=]() { setOnlyChecked(m_arrowButton); setActiveTool(ARROW, m_arrowButton); });
connect(m_mosaicButton, &QToolButton::clicked, this, [=]() { setOnlyChecked(m_mosaicButton); setActiveTool(MOSAIC, m_mosaicButton); });
connect(m_blurButton, &QToolButton::clicked, this, [=]() { setOnlyChecked(m_blurButton); setActiveTool(BLUR, m_blurButton); });
connect(m_textButton, &QToolButton::clicked, this, [=]() { setOnlyChecked(m_textButton); setActiveTool(TEXT, m_textButton); });
connect(m_numberButton, &QToolButton::clicked, this, [=]() { setOnlyChecked(m_numberButton); setActiveTool(NUMBER, m_numberButton); });
const QList<QToolButton *> tipButtons {
m_pencilButton,
m_rectButton,
m_circleButton,
m_arrowButton,
m_mosaicButton,
m_blurButton,
m_textButton,
m_numberButton,
m_undoButton,
m_copyButton,
m_saveButton,
m_stickyButton,
m_ocrButton,
m_longCaptureButton,
m_gifButton,
m_cancelButton
};
for (QToolButton *button : tipButtons) {
if (button) {
button->installEventFilter(this);
}
}
m_gifOverlay = new GifRecordOverlayWidget();
connect(this, &QObject::destroyed, m_gifOverlay, &QObject::deleteLater);
connect(m_gifOverlay, &GifRecordOverlayWidget::stopRequested, this, [this]() {
if (m_gifRecordViewModel) {
m_gifRecordViewModel->stopRecording();
}
});
connect(m_gifOverlay, &GifRecordOverlayWidget::cancelRequested, this, [this]() {
if (m_gifRecordViewModel) {
m_gifRecordViewModel->cancelRecording();
}
finishCapture();
});
m_tipBubble = new QWidget(this);
m_tipBubble->setObjectName(QStringLiteral("captureOverlayTipBubble"));
m_tipBubble->setAttribute(Qt::WA_TransparentForMouseEvents, true);
m_tipBubble->setAttribute(Qt::WA_StyledBackground, true);
auto *tipLayout = new QHBoxLayout(m_tipBubble);
tipLayout->setContentsMargins(14, 4, 14, 4);
tipLayout->setSpacing(0);
m_tipLabel = new QLabel(m_tipBubble);
m_tipLabel->setObjectName(QStringLiteral("captureOverlayTipLabel"));
tipLayout->addWidget(m_tipLabel);
m_tipBubble->hide();
if (m_gifRecordViewModel) {
connect(m_gifRecordViewModel, &GifRecordViewModel::frameCountChanged, this, [this]() {
if (m_gifOverlay) {
m_gifOverlay->setFrameCount(m_gifRecordViewModel->frameCount());
}
});
connect(m_gifRecordViewModel, &GifRecordViewModel::elapsedSecsChanged, this, [this]() {
if (m_gifOverlay) {
m_gifOverlay->setElapsedSecs(m_gifRecordViewModel->elapsedSecs());
}
});
connect(m_gifRecordViewModel, &GifRecordViewModel::isRecordingChanged, this, [this]() {
if (m_gifOverlay) {
m_gifOverlay->setRecordingState(m_gifRecordViewModel->isRecording(),
m_gifRecordViewModel->isEncoding());
}
});
connect(m_gifRecordViewModel, &GifRecordViewModel::isEncodingChanged, this, [this]() {
if (m_gifOverlay) {
m_gifOverlay->setRecordingState(m_gifRecordViewModel->isRecording(),
m_gifRecordViewModel->isEncoding());
}
});
connect(m_gifRecordViewModel, &GifRecordViewModel::encodingFinished, this, [this](const QString &) {
finishCapture();
});
connect(m_gifRecordViewModel, &GifRecordViewModel::encodingFailed, this, [this](const QString &) {
finishCapture();
});
}
if (!m_colorButtons.isEmpty()) {
m_colorButtons.first()->setChecked(true);
}
for (QToolButton *sizeButton : m_sizeButtons) {
if (sizeButton->text() == tr("粗")) {
sizeButton->setChecked(true);
break;
}
}
syncCanvasToolSettings();
}
void CaptureOverlayWidget::showAndActivate(const QString &mode)
{
setDefaultAction(mode);
refreshSnapshot();
resetState();
if (!m_virtualGeometry.isEmpty()) {
setGeometry(m_virtualGeometry);
}
show();
#ifdef Q_OS_WIN
if (!m_virtualGeometry.isEmpty()) {
if (HWND hwnd = reinterpret_cast<HWND>(winId())) {
SetWindowPos(hwnd,
HWND_TOPMOST,
m_virtualGeometry.x(),
m_virtualGeometry.y(),
m_virtualGeometry.width(),
m_virtualGeometry.height(),
SWP_NOOWNERZORDER | SWP_SHOWWINDOW);
}
}
#endif
raise();
activateWindow();
setFocus(Qt::ActiveWindowFocusReason);
if (m_magnifier) {
m_magnifier->hide();
}
}
void CaptureOverlayWidget::toggleCapture(const QString &mode)
{
if (isVisible()) {
finishCapture();
return;
}
showAndActivate(mode);
}
void CaptureOverlayWidget::setWidgetWindowBridge(WidgetWindowBridge *widgetWindowBridge)
{
m_widgetWindowBridge = widgetWindowBridge;
}
void CaptureOverlayWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
if (m_screenCapture) {
painter.drawPixmap(rect(), m_screenCapture->desktopSnapshot());
} else {
painter.fillRect(rect(), Qt::black);
}
m_selection.paint(painter, rect());
}
void CaptureOverlayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
if (m_canvas && m_canvas->isVisible()) {
m_canvas->setGeometry(rect());
}
}
void CaptureOverlayWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() != Qt::LeftButton) {
QWidget::mousePressEvent(event);
return;
}
if (m_gifRecordingMode) {
event->accept();
return;
}
if (m_canvas && m_canvas->drawingEnabled()) {
event->ignore();
return;
}
m_toolbar->hide();
if (m_toolOptions) {
m_toolOptions->hide();
}
hideTipBubble();
if (m_inlineTextPanel) {
m_inlineTextPanel->hide();
}
m_selection.beginInteraction(event->position().toPoint());
setCursor(m_selection.cursorShape());
update();
}
void CaptureOverlayWidget::mouseMoveEvent(QMouseEvent *event)
{
const QPoint pos = event->position().toPoint();
if (m_gifRecordingMode) {
QWidget::mouseMoveEvent(event);
return;
}
if (m_canvas && m_canvas->drawingEnabled()) {
QWidget::mouseMoveEvent(event);
return;
}
if (event->buttons() & Qt::LeftButton) {
m_selection.updateInteraction(pos, size());
setCursor(m_selection.cursorShape());
updateToolbarGeometry();
updateMagnifier(pos);
update();
return;
}
m_selection.updateHover(pos);
if (m_toolbar && !m_toolbar->isVisible()) {
updateMagnifier(pos);
} else if (m_magnifier) {
m_magnifier->hide();
}
setCursor(m_selection.cursorShape());
QWidget::mouseMoveEvent(event);
}
void CaptureOverlayWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (m_canvas && m_canvas->drawingEnabled()) {
QWidget::mouseReleaseEvent(event);
return;
}
if (event->button() == Qt::LeftButton) {
if (m_gifRecordingMode) {
event->accept();
return;
}
m_selection.endInteraction();
m_selection.updateHover(event->position().toPoint());
setCursor(m_selection.cursorShape());
if (m_selection.hasSelection()) {
refreshCanvasForSelection(false);
updateToolbarGeometry();
m_toolbar->show();
m_toolbar->raise();
updateToolOptionsPanel();
updateToolOptionsGeometry();
}
if (m_magnifier) {
m_magnifier->hide();
}
update();
}
QWidget::mouseReleaseEvent(event);
}
void CaptureOverlayWidget::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Escape:
if (m_gifRecordingMode && m_gifRecordViewModel) {
m_gifRecordViewModel->cancelRecording();
}
finishCapture();
event->accept();
return;
case Qt::Key_Return:
case Qt::Key_Enter:
if (m_selection.hasSelection()) {
performAction(m_defaultAction);
}
event->accept();
return;
default:
break;
}
QWidget::keyPressEvent(event);
}
void CaptureOverlayWidget::closeEvent(QCloseEvent *event)
{
if (m_screenCapture) {
m_screenCapture->releaseDesktopSnapshot();
}
QWidget::closeEvent(event);
}
bool CaptureOverlayWidget::eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_inlineTextEditor && m_inlineTextEditor) {
if (event->type() == QEvent::KeyPress) {
auto *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Escape) {
if (m_inlineTextPanel) {
m_inlineTextPanel->hide();
}
return true;
}
}
return QWidget::eventFilter(watched, event);
}
auto *button = qobject_cast<QToolButton *>(watched);
if (!button) {
return QWidget::eventFilter(watched, event);
}
switch (event->type()) {
case QEvent::Enter:
showTipBubble(button);
break;
case QEvent::Leave:
case QEvent::MouseButtonPress:
case QEvent::Hide:
hideTipBubble();
break;
default:
break;
}
return QWidget::eventFilter(watched, event);
}
void CaptureOverlayWidget::resetState()
{
m_selection.reset();
m_pendingTextPoint = QPoint();
m_toolbar->hide();
if (m_toolOptions) {
m_toolOptions->hide();
}
hideTipBubble();
if (m_inlineTextPanel) {
m_inlineTextPanel->hide();
}
if (m_canvas) {
m_canvas->setDrawingEnabled(false);
m_canvas->reset();
m_canvas->hide();
}
const QVector<QToolButton*> drawButtons {
m_pencilButton, m_rectButton, m_circleButton, m_arrowButton, m_mosaicButton, m_blurButton, m_textButton, m_numberButton
};
for (QToolButton *button : drawButtons) {
if (button) {
button->setChecked(false);
}
}
if (m_numberLabel) {
m_numberLabel->setText(QString::number(m_numberValue));
}
if (m_textInput) {
m_textInput->setText(m_annotationText);
}
if (m_magnifier) {
m_magnifier->hide();
}
if (m_gifOverlay) {
m_gifOverlay->resetOverlay();
}
m_gifRecordingMode = false;
m_gifGlobalCaptureRect = {};
setCursor(Qt::CrossCursor);
update();
}
void CaptureOverlayWidget::refreshSnapshot()
{
if (!m_screenCapture) {
return;
}
hide();
qApp->processEvents();
m_screenCapture->captureDesktop();
m_virtualGeometry = m_screenCapture->virtualGeometry();
if (m_canvas) {
m_canvas->setGeometry(QRect(QPoint(0, 0), size()));
m_canvas->setBackgroundImage(m_screenCapture->desktopSnapshot().toImage());
}
if (m_magnifier) {
m_magnifier->setSnapshot(m_screenCapture->desktopSnapshot(), m_virtualGeometry);
}
}
QRect CaptureOverlayWidget::selectionInGlobal() const
{
const QRect selection = m_selection.rect();
if (selection.isEmpty()) {
return {};
}
return selection.translated(m_virtualGeometry.topLeft());
}
QRect CaptureOverlayWidget::currentSelectionRect() const
{
return m_selection.rect().normalized();
}
QImage CaptureOverlayWidget::currentCaptureImage() const
{
if (!m_screenCapture) {
return {};
}
const QRect globalRect = selectionInGlobal();
if (globalRect.isEmpty()) {
return {};
}
return m_screenCapture->captureRectToImage(globalRect);
}
QImage CaptureOverlayWidget::currentOutputImage() const
{
const QRect selection = currentSelectionRect();
if (selection.isEmpty()) {
return {};
}
if (m_canvas && m_canvas->hasAnnotations()) {
const QImage composited = m_canvas->compositedImage(selection);
if (!composited.isNull()) {
return composited;
}
}
return currentCaptureImage();
}
void CaptureOverlayWidget::refreshCanvasForSelection(bool clearAnnotations)
{
if (!m_canvas) {
return;
}
if (clearAnnotations) {
m_canvas->reset();
}
if (!m_screenCapture) {
m_canvas->hide();
return;
}
const QPixmap snapshot = m_screenCapture->desktopSnapshot();
if (snapshot.isNull()) {
m_canvas->hide();
return;
}
m_canvas->setGeometry(rect());
m_canvas->setViewScale(1.0);
m_canvas->setBackgroundImage(snapshot.toImage());
syncCanvasToolSettings();
m_canvas->show();
m_canvas->raise();
}
void CaptureOverlayWidget::setActiveTool(Shapeype type, QToolButton *button)
{
const bool active = button && button->isChecked();
if (!m_canvas) {
return;
}
if (m_inlineTextPanel) {
m_inlineTextPanel->hide();
}
m_canvas->setDrawingEnabled(active);
if (active) {
m_canvas->setActiveShapeType(type);
}
syncCanvasToolSettings();
updateToolOptionsPanel();
updateToolOptionsGeometry();
}
void CaptureOverlayWidget::syncCanvasToolSettings()
{
if (!m_canvas) {
return;
}
m_canvas->setPenColor(m_currentPenColor);
m_canvas->setPenSize(m_currentPenSize);
m_canvas->setAnnotationText(m_annotationText);
m_canvas->setTextBackgroundEnabled(m_textBackgroundEnabled);
m_canvas->setNumberAutoIncrement(m_numberAutoIncrement);
m_canvas->setNumberValue(m_numberValue);
}
void CaptureOverlayWidget::updateToolOptionsPanel()
{
if (!m_toolOptions || !m_canvas) {
return;
}
const bool visible = m_toolbar->isVisible() && m_canvas->drawingEnabled() && !m_gifRecordingMode;
m_toolOptions->setVisible(visible);
if (!visible) {
if (m_inlineTextPanel) {
m_inlineTextPanel->hide();
}
return;
}
const Shapeype type = m_canvas->activeShapeType();
const bool showTextOptions = (type == TEXT);
const bool showNumberOptions = (type == NUMBER);
const bool showColorOptions = (type != MOSAIC && type != BLUR);
int x = 10;
int y = 10;
for (QToolButton *button : m_colorButtons) {
button->setVisible(showColorOptions);
if (showColorOptions) {
button->setGeometry(x, y, 18, 18);
x += 24;
}
}
y = showColorOptions ? (y + 26) : 10;
x = 10;
for (QToolButton *button : m_sizeButtons) {
button->setVisible(true);
button->setGeometry(x, y, 48, 28);
x += 54;
}
if (m_textInput && m_textBackgroundCheck) {
m_textInput->setVisible(showTextOptions);
m_textBackgroundCheck->setVisible(showTextOptions);
m_textInput->setGeometry(10, 68, 160, 32);
m_textBackgroundCheck->setGeometry(182, 72, 64, 24);
}
if (m_numberAutoIncrementCheck && m_numberMinusButton && m_numberLabel && m_numberPlusButton) {
m_numberAutoIncrementCheck->setVisible(showNumberOptions);
m_numberMinusButton->setVisible(showNumberOptions);
m_numberLabel->setVisible(showNumberOptions);
m_numberPlusButton->setVisible(showNumberOptions);
m_numberAutoIncrementCheck->setGeometry(10, 68, 100, 24);
m_numberMinusButton->setGeometry(118, 66, 28, 28);
m_numberLabel->setGeometry(152, 66, 48, 28);
m_numberPlusButton->setGeometry(206, 66, 28, 28);
}
}
QToolButton *CaptureOverlayWidget::currentActiveToolButton() const
{
const QVector<QToolButton*> drawButtons {
m_pencilButton, m_rectButton, m_circleButton, m_arrowButton, m_mosaicButton, m_blurButton, m_textButton, m_numberButton
};
for (QToolButton *button : drawButtons) {
if (button && button->isChecked()) {
return button;
}
}
return nullptr;
}
void CaptureOverlayWidget::updateToolbarGeometry()
{
const QRect selection = m_selection.rect();
if (selection.isEmpty()) {
m_toolbar->hide();
if (m_toolOptions) {
m_toolOptions->hide();
}
return;
}
const QSize hint = m_toolbar->sizeHint();
const int x = qBound(8, selection.center().x() - hint.width() / 2, width() - hint.width() - 8);
int y = selection.bottom() + 12;
if (y + hint.height() > height() - 8) {
y = qMax(8, selection.top() - hint.height() - 12);
}
m_toolbar->setGeometry(x, y, hint.width(), hint.height());
updateToolOptionsGeometry();
}
void CaptureOverlayWidget::updateMagnifier(const QPoint &localPos)
{
if (!m_magnifier || !m_screenCapture) {
return;
}
const QPoint globalPos = localPos + m_virtualGeometry.topLeft();
const QColor pixelColor = m_screenCapture->getPixelColor(globalPos.x(), globalPos.y());
m_magnifier->updateView(localPos, globalPos, pixelColor, size());
}
void CaptureOverlayWidget::updateToolOptionsGeometry()
{
if (!m_toolOptions || !m_toolOptions->isVisible()) {
return;
}
m_toolOptions->setGeometry(toolOptionsRect());
m_toolOptions->raise();
}
QRect CaptureOverlayWidget::toolOptionsRect() const
{
if (!m_toolOptions || !m_toolbar) {
return {};
}
const int width = 260;
const Shapeype type = m_canvas ? m_canvas->activeShapeType() : PEN;
int height = 88;
if (type == TEXT || type == NUMBER) {
height = 116;
} else if (type == MOSAIC || type == BLUR) {
height = 48;
}
int x = m_toolbar->geometry().center().x() - width / 2;
if (QToolButton *anchor = currentActiveToolButton()) {
const QPoint anchorTopLeft = anchor->mapTo(this, QPoint(0, 0));
x = anchorTopLeft.x() + anchor->width() / 2 - width / 2;
}
x = qBound(8, x, qMax(8, this->width() - width - 8));
int y = m_toolbar->geometry().bottom() + 8;
if (y + height > this->height() - 8) {
y = qMax(8, m_toolbar->geometry().top() - height - 8);
}
return QRect(x, y, width, height);
}
void CaptureOverlayWidget::showTipBubble(QToolButton *button)
{
if (!button || !m_tipBubble || !m_tipLabel || !button->isVisible()) {
return;
}
const QString tipText = button->toolTip();
if (tipText.isEmpty()) {
hideTipBubble();
return;
}
m_tipLabel->setText(tipText);
m_tipBubble->adjustSize();
const QPoint topLeft = button->mapTo(this, QPoint(0, 0));
const QRect buttonRect(topLeft, button->size());
const QPoint center = buttonRect.center();
int x = center.x() - m_tipBubble->width() / 2;
x = qBound(8, x, width() - m_tipBubble->width() - 8);
int y = buttonRect.bottom() + 8;
if (y + m_tipBubble->height() > height() - 8) {
y = buttonRect.top() - m_tipBubble->height() - 8;
}
m_tipBubble->move(x, y);
m_tipBubble->show();
m_tipBubble->raise();
}
void CaptureOverlayWidget::hideTipBubble()
{
if (m_tipBubble) {
m_tipBubble->hide();
}
}
void CaptureOverlayWidget::setDefaultAction(const QString &mode)
{
if (mode == QStringLiteral("save")) {
m_defaultAction = CaptureAction::Save;
} else if (mode == QStringLiteral("sticky")) {
m_defaultAction = CaptureAction::Sticky;
} else if (mode == QStringLiteral("ocr")) {
m_defaultAction = CaptureAction::Ocr;
} else {
m_defaultAction = CaptureAction::Copy;
}
}
void CaptureOverlayWidget::performAction(CaptureAction action)
{
const QRect globalRect = selectionInGlobal();
if (globalRect.isEmpty() || !m_screenCapture) {
return;
}
switch (action) {
case CaptureAction::Copy:
m_screenCapture->copyImageToClipboard(currentOutputImage());
break;
case CaptureAction::Save: {
const QImage image = currentOutputImage();
if (!image.isNull()) {
const QString filePath = QFileDialog::getSaveFileName(this,
tr("保存截图"),
defaultSaveFilePath(),
tr("PNG 图片 (*.png)"));
if (!filePath.isEmpty()) {
image.save(filePath);
}
}
break;
}
case CaptureAction::Sticky: {
const QImage image = currentOutputImage();
if (!image.isNull() && m_stickyViewModel) {
m_stickyViewModel->requestStickyImage(image, globalRect);
}
break;
}
case CaptureAction::Ocr:
if (m_ocrViewModel) {
const QImage image = m_screenCapture->captureRectToImage(globalRect);
if (!image.isNull()) {
m_ocrViewModel->recognize(image);
}
}
break;
case CaptureAction::LongCapture:
if (m_widgetWindowBridge) {
m_widgetWindowBridge->requestLongCapture(globalRect);
}
finishCapture();
return;
case CaptureAction::Gif:
if (!m_gifRecordViewModel) {
break;
}
m_gifGlobalCaptureRect = globalRect;
m_gifRecordingMode = true;
m_toolbar->hide();
if (m_toolOptions) {
m_toolOptions->hide();
}
if (m_canvas) {
m_canvas->setDrawingEnabled(false);
}
hideTipBubble();
if (m_magnifier) {
m_magnifier->hide();
}
if (m_gifOverlay) {
m_gifOverlay->setCaptureRect(globalRect);
m_gifOverlay->setElapsedSecs(0);
m_gifOverlay->setFrameCount(0);
m_gifOverlay->setRecordingState(true, false);
}
hide();
qApp->processEvents();
m_gifRecordViewModel->startRecording(globalRect.x(), globalRect.y(), globalRect.width(), globalRect.height());
return;
}
finishCapture();
}
void CaptureOverlayWidget::finishCapture()
{
hide();
hideTipBubble();
if (m_screenCapture) {
m_screenCapture->releaseDesktopSnapshot();
}
resetState();
}
QString CaptureOverlayWidget::defaultSaveFilePath() const
{
QString baseDir;
if (m_storageViewModel) {
baseDir = m_storageViewModel->savePath().toLocalFile();
}
if (baseDir.isEmpty()) {
baseDir = QDir::homePath();
}
const QString fileName = QStringLiteral("TZshot_%1.png")
.arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd_HHmmss")));
return QDir(baseDir).filePath(fileName);
}