diff --git a/src/capture/capturearea.cpp b/src/capture/capturearea.cpp index a9ed51a..4cc158b 100644 --- a/src/capture/capturearea.cpp +++ b/src/capture/capturearea.cpp @@ -17,6 +17,7 @@ TaskPtr CaptureArea::task(const QPixmap &pixmap) const return {}; auto task = std::make_shared(); + task->generation = generation_; task->captured = pixmap.copy(rect_); task->capturePoint = rect_.topLeft(); task->sourceLanguage = sourceLanguage_; @@ -28,6 +29,11 @@ TaskPtr CaptureArea::task(const QPixmap &pixmap) const return task; } +void CaptureArea::setGeneration(uint generation) +{ + generation_ = generation; +} + bool CaptureArea::isValid() const { return !(rect_.width() < 3 || rect_.height() < 3); diff --git a/src/capture/capturearea.h b/src/capture/capturearea.h index 3edba8b..d303520 100644 --- a/src/capture/capturearea.h +++ b/src/capture/capturearea.h @@ -13,6 +13,7 @@ public: CaptureArea(const QRect& rect, const Settings& settings); TaskPtr task(const QPixmap& pixmap) const; + void setGeneration(uint generation); bool isValid() const; bool isLocked() const; const QRect& rect() const; @@ -23,6 +24,7 @@ public: private: friend class CaptureAreaEditor; + Generation generation_{}; QRect rect_; bool doTranslation_; bool isLocked_{false}; diff --git a/src/capture/captureareaselector.cpp b/src/capture/captureareaselector.cpp index 6b4427e..fa59629 100644 --- a/src/capture/captureareaselector.cpp +++ b/src/capture/captureareaselector.cpp @@ -4,12 +4,21 @@ #include "capturer.h" #include "debug.h" #include "geometryutils.h" -#include "languagecodes.h" #include "settings.h" +#include #include #include +static bool locked(const std::shared_ptr &area) +{ + return area->isLocked(); +} +static bool notLocked(const std::shared_ptr &area) +{ + return !area->isLocked(); +} + CaptureAreaSelector::CaptureAreaSelector(Capturer &capturer, const Settings &settings, const CommonModels &models, @@ -18,6 +27,7 @@ CaptureAreaSelector::CaptureAreaSelector(Capturer &capturer, , settings_(settings) , pixmap_(pixmap) , editor_(std::make_unique(models, this)) + , contextMenu_(new QMenu(this)) { setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); @@ -27,7 +37,20 @@ CaptureAreaSelector::CaptureAreaSelector(Capturer &capturer, help_ = tr(R"(Right click on selection - customize Left click on selection - process -Esc - cancel)"); +Enter - process all selections +Esc - cancel +Ctrl - keep selecting)"); + + { + auto action = contextMenu_->addAction(tr("Capture all")); + connect(action, &QAction::triggered, // + this, &CaptureAreaSelector::captureAll); + } + { + auto action = contextMenu_->addAction(tr("Cancel")); + connect(action, &QAction::triggered, // + this, &CaptureAreaSelector::cancel); + } } CaptureAreaSelector::~CaptureAreaSelector() = default; @@ -41,13 +64,36 @@ void CaptureAreaSelector::activate() bool CaptureAreaSelector::hasLocked() const { - return area_ && area_->isLocked(); + const auto it = std::find_if(areas_.cbegin(), areas_.cend(), locked); + return it != areas_.cend(); } void CaptureAreaSelector::captureLocked() { SOFT_ASSERT(hasLocked(), return ); - capturer_.selected(*area_); + ++generation_; + for (auto &area : areas_) { + if (area->isLocked()) + capture(*area, generation_); + } +} + +void CaptureAreaSelector::capture(CaptureArea &area, uint generation) +{ + area.setGeneration(generation); + capturer_.selected(area); +} + +void CaptureAreaSelector::captureAll() +{ + SOFT_ASSERT(!areas_.empty(), return ); + ++generation_; + for (auto &area : areas_) capture(*area, generation_); +} + +void CaptureAreaSelector::cancel() +{ + capturer_.canceled(); } void CaptureAreaSelector::setScreenRects(const std::vector &screens) @@ -67,7 +113,7 @@ void CaptureAreaSelector::setScreenRects(const std::vector &screens) void CaptureAreaSelector::updateSettings() { - area_.reset(); + areas_.clear(); } void CaptureAreaSelector::paintEvent(QPaintEvent * /*event*/) @@ -77,8 +123,9 @@ void CaptureAreaSelector::paintEvent(QPaintEvent * /*event*/) for (const auto &rect : helpRects_) drawHelpRects(painter, rect); - if (area_) - drawCaptureArea(painter, *area_); + if (!areas_.empty()) { + for (const auto &area : areas_) drawCaptureArea(painter, *area); + } if (editor_->isVisible()) { painter.setBrush(QBrush(QColor(200, 200, 200, 200))); @@ -86,11 +133,10 @@ void CaptureAreaSelector::paintEvent(QPaintEvent * /*event*/) painter.drawRect(editor_->geometry()); } - auto selection = QRect(startSelectPos_, currentSelectPos_).normalized(); - if (!selection.isValid()) + const auto area = CaptureArea( + QRect(startSelectPos_, currentSelectPos_).normalized(), settings_); + if (!area.isValid()) return; - - const auto area = CaptureArea(selection, settings_); drawCaptureArea(painter, area); } @@ -154,9 +200,9 @@ void CaptureAreaSelector::drawCaptureArea(QPainter &painter, void CaptureAreaSelector::showEvent(QShowEvent * /*event*/) { editor_->hide(); - if (area_ && !area_->isLocked()) - area_.reset(); startSelectPos_ = currentSelectPos_ = QPoint(); + areas_.erase(std::remove_if(areas_.begin(), areas_.end(), notLocked), + areas_.end()); } void CaptureAreaSelector::hideEvent(QHideEvent * /*event*/) @@ -166,8 +212,19 @@ void CaptureAreaSelector::hideEvent(QHideEvent * /*event*/) void CaptureAreaSelector::keyPressEvent(QKeyEvent *event) { - if (event->key() == Qt::Key_Escape) - capturer_.canceled(); + if (event->key() == Qt::Key_Escape) { + cancel(); + return; + } + + if (event->key() == Qt::Key_Return) { + if (!areas_.empty()) { + captureAll(); + } else { + cancel(); + } + return; + } } void CaptureAreaSelector::mousePressEvent(QMouseEvent *event) @@ -179,17 +236,22 @@ void CaptureAreaSelector::mousePressEvent(QMouseEvent *event) applyEditor(); } - if (area_ && area_->rect().contains(event->pos())) { - if (event->button() == Qt::LeftButton) { - capturer_.selected(*area_); - } else if (event->button() == Qt::RightButton) { - customize(*area_); + if (!areas_.empty()) { + for (auto &area : areas_) { + if (!area->rect().contains(event->pos())) + continue; + + if (event->button() == Qt::LeftButton) { + capture(*area, ++generation_); + } else if (event->button() == Qt::RightButton) { + customize(area); + } + return; } - return; } if (startSelectPos_.isNull()) - startSelectPos_ = event->pos(); + startSelectPos_ = currentSelectPos_ = event->pos(); } void CaptureAreaSelector::mouseMoveEvent(QMouseEvent *event) @@ -215,27 +277,37 @@ void CaptureAreaSelector::mouseReleaseEvent(QMouseEvent *event) startSelectPos_ = currentSelectPos_ = {}; - const auto area = CaptureArea(selection, settings_); - if (!area.isValid()) { - capturer_.canceled(); + auto area = CaptureArea(selection, settings_); + if (!area.isValid()) { // just a click + if (areas_.empty()) { + cancel(); + return; + } + if (event->button() == Qt::RightButton) { + contextMenu_->popup(QCursor::pos()); + } return; } - if (event->button() != Qt::RightButton) { - capturer_.selected(area); - } else { - area_ = std::make_unique(area); - customize(*area_); + areas_.emplace_back(std::make_unique(area)); + if (event->button() == Qt::RightButton) { + customize(areas_.back()); + return; } + + if (!(event->modifiers() & Qt::ControlModifier)) + captureAll(); } -void CaptureAreaSelector::customize(const CaptureArea &area) +void CaptureAreaSelector::customize(const std::shared_ptr &area) { SOFT_ASSERT(editor_, return ); - editor_->set(area); + SOFT_ASSERT(area, return ); + editor_->set(*area); + edited_ = area; editor_->show(); const auto topLeft = service::geometry::cornerAtPoint( - area.rect().center(), editor_->size(), geometry()); + area->rect().center(), editor_->size(), geometry()); editor_->move(topLeft); update(); } @@ -243,8 +315,9 @@ void CaptureAreaSelector::customize(const CaptureArea &area) void CaptureAreaSelector::applyEditor() { SOFT_ASSERT(editor_, return ); - if (!editor_->isVisible() || !area_) + if (!editor_->isVisible() || edited_.expired()) return; - editor_->apply(*area_); + editor_->apply(*edited_.lock()); editor_->hide(); + update(); } diff --git a/src/capture/captureareaselector.h b/src/capture/captureareaselector.h index 7deec90..c3fb38d 100644 --- a/src/capture/captureareaselector.h +++ b/src/capture/captureareaselector.h @@ -4,6 +4,8 @@ #include +class QMenu; + class CaptureAreaSelector : public QWidget { Q_OBJECT @@ -33,21 +35,27 @@ private: QRect current; std::vector possible; }; + void capture(CaptureArea &area, uint generation); + void captureAll(); + void cancel(); bool updateCurrentHelpRects(); void drawHelpRects(QPainter &painter, const HelpRect &rect) const; - void customize(const CaptureArea &area); + void customize(const std::shared_ptr &area); void applyEditor(); void drawCaptureArea(QPainter &painter, const CaptureArea &area) const; Capturer &capturer_; const Settings &settings_; const QPixmap &pixmap_; + Generation generation_{}; QPoint startSelectPos_; QPoint currentSelectPos_; QString help_; std::vector helpRects_; - std::unique_ptr area_; + std::vector> areas_; + std::weak_ptr edited_; std::unique_ptr editor_; + QMenu *contextMenu_; }; diff --git a/src/represent/representer.cpp b/src/represent/representer.cpp index 72932e3..d6f97a6 100644 --- a/src/represent/representer.cpp +++ b/src/represent/representer.cpp @@ -10,6 +10,7 @@ #include #include +#include #include Representer::Representer(Manager &manager, TrayIcon &tray, @@ -25,15 +26,21 @@ Representer::~Representer() = default; void Representer::showLast() { - SOFT_ASSERT(widget_, return ); - widget_->show(); + SOFT_ASSERT(!widgets_.empty(), return ); + for (auto &widget : widgets_) { + SOFT_ASSERT(widget->task(), continue); + if (widget->task()->generation != generation_) + continue; + widget->show(); + widget->activateWindow(); + } } void Representer::clipboardLast() { - SOFT_ASSERT(widget_, return ); - SOFT_ASSERT(widget_->task(), return ); - clipboardText(widget_->task()); + SOFT_ASSERT(!widgets_.empty(), return ); + SOFT_ASSERT(widgets_.front()->task(), return ); + clipboardText(widgets_.front()->task()); tray_.showInformation( QObject::tr("The last result was copied to the clipboard.")); } @@ -48,19 +55,24 @@ void Representer::represent(const TaskPtr &task) bool Representer::isVisible() const { - return widget_ && widget_->isVisible(); + if (widgets_.empty()) + return false; + return std::any_of(widgets_.cbegin(), widgets_.cend(), + [](const auto &w) { return w->isVisible(); }); } void Representer::hide() { - if (widget_) - widget_->hide(); + if (widgets_.empty()) + return; + for (auto &w : widgets_) w->hide(); } void Representer::updateSettings() { - if (widget_) - widget_->updateSettings(); + if (widgets_.empty()) + return; + for (auto &w : widgets_) w->updateSettings(); } void Representer::clipboardText(const TaskPtr &task) @@ -98,6 +110,22 @@ void Representer::edit(const TaskPtr &task) screen->geometry())); } +bool Representer::eventFilter(QObject * /*watched*/, QEvent *event) +{ + if (event->type() == QEvent::WindowDeactivate) { + for (const auto &w : widgets_) { + if (w->isActiveWindow()) + return false; + } + hide(); + } else if (event->type() == QEvent::MouseButtonPress) { + const auto casted = static_cast(event); + if (casted->button() == Qt::LeftButton) + hide(); + } + return false; +} + void Representer::showTooltip(const TaskPtr &task) { auto message = task->recognized + " - " + task->translated; @@ -106,8 +134,22 @@ void Representer::showTooltip(const TaskPtr &task) void Representer::showWidget(const TaskPtr &task) { - if (!widget_) - widget_ = std::make_unique(*this, settings_); + generation_ = task->generation; - widget_->show(task); + auto index = 0u; + const auto count = widgets_.size(); + for (; index < count; ++index) { + auto &widget = widgets_[index]; + SOFT_ASSERT(widget->task(), continue); + if (widget->task()->generation != generation_) + break; + } + + if (index == count) { + widgets_.emplace_back(std::make_unique(*this, settings_)); + widgets_.back()->installEventFilter(this); + } + + auto &widget = widgets_[index]; + widget->show(task); } diff --git a/src/represent/representer.h b/src/represent/representer.h index 9bfbdc7..54261b4 100644 --- a/src/represent/representer.h +++ b/src/represent/representer.h @@ -2,12 +2,15 @@ #include "stfwd.h" +#include + enum class ResultMode; class ResultWidget; class ResultEditor; -class Representer +class Representer : public QObject { + Q_OBJECT public: Representer(Manager &manager, TrayIcon &tray, const Settings &settings, const CommonModels &models); @@ -24,6 +27,8 @@ public: void clipboardImage(const TaskPtr &task); void edit(const TaskPtr &task); + bool eventFilter(QObject *watched, QEvent *event) override; + private: void showTooltip(const TaskPtr &task); void showWidget(const TaskPtr &task); @@ -32,6 +37,7 @@ private: TrayIcon &tray_; const Settings &settings_; const CommonModels &models_; - std::unique_ptr widget_; + Generation generation_{}; + std::vector> widgets_; std::unique_ptr editor_; }; diff --git a/src/represent/resultwidget.cpp b/src/represent/resultwidget.cpp index 6f69f07..45593cf 100644 --- a/src/represent/resultwidget.cpp +++ b/src/represent/resultwidget.cpp @@ -148,13 +148,6 @@ void ResultWidget::updateSettings() image_->setVisible(settings_.showCaptured); } -bool ResultWidget::eventFilter(QObject *watched, QEvent *event) -{ - if (event->type() == QEvent::WindowDeactivate) - hide(); - return QWidget::eventFilter(watched, event); -} - void ResultWidget::mousePressEvent(QMouseEvent *event) { const auto button = event->button(); @@ -167,8 +160,6 @@ void ResultWidget::mousePressEvent(QMouseEvent *event) lastPos_ = event->pos(); return; } - - hide(); } void ResultWidget::mouseMoveEvent(QMouseEvent *event) diff --git a/src/represent/resultwidget.h b/src/represent/resultwidget.h index 0577e1b..6328af0 100644 --- a/src/represent/resultwidget.h +++ b/src/represent/resultwidget.h @@ -19,8 +19,6 @@ public: using QWidget::show; void updateSettings(); - bool eventFilter(QObject* watched, QEvent* event) override; - protected: void mousePressEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; diff --git a/src/stfwd.h b/src/stfwd.h index 46a74eb..14ffc5d 100644 --- a/src/stfwd.h +++ b/src/stfwd.h @@ -29,3 +29,4 @@ class AutoChecker; using TaskPtr = std::shared_ptr; using LanguageId = QString; using LanguageIds = QStringList; +using Generation = unsigned int; diff --git a/src/task.h b/src/task.h index 48a09d3..a414b3d 100644 --- a/src/task.h +++ b/src/task.h @@ -10,6 +10,8 @@ public: bool isNull() const { return captured.isNull() && !sourceLanguage.isEmpty(); } bool isValid() const { return error.isEmpty(); } + Generation generation{}; + QPoint capturePoint; QPixmap captured; QString recognized;