Add ability to capture multiple areas

This commit is contained in:
Gres 2020-04-04 21:03:27 +03:00
parent 5f4ef955e1
commit 8d2e726715
10 changed files with 192 additions and 63 deletions

View File

@ -17,6 +17,7 @@ TaskPtr CaptureArea::task(const QPixmap &pixmap) const
return {}; return {};
auto task = std::make_shared<Task>(); auto task = std::make_shared<Task>();
task->generation = generation_;
task->captured = pixmap.copy(rect_); task->captured = pixmap.copy(rect_);
task->capturePoint = rect_.topLeft(); task->capturePoint = rect_.topLeft();
task->sourceLanguage = sourceLanguage_; task->sourceLanguage = sourceLanguage_;
@ -28,6 +29,11 @@ TaskPtr CaptureArea::task(const QPixmap &pixmap) const
return task; return task;
} }
void CaptureArea::setGeneration(uint generation)
{
generation_ = generation;
}
bool CaptureArea::isValid() const bool CaptureArea::isValid() const
{ {
return !(rect_.width() < 3 || rect_.height() < 3); return !(rect_.width() < 3 || rect_.height() < 3);

View File

@ -13,6 +13,7 @@ public:
CaptureArea(const QRect& rect, const Settings& settings); CaptureArea(const QRect& rect, const Settings& settings);
TaskPtr task(const QPixmap& pixmap) const; TaskPtr task(const QPixmap& pixmap) const;
void setGeneration(uint generation);
bool isValid() const; bool isValid() const;
bool isLocked() const; bool isLocked() const;
const QRect& rect() const; const QRect& rect() const;
@ -23,6 +24,7 @@ public:
private: private:
friend class CaptureAreaEditor; friend class CaptureAreaEditor;
Generation generation_{};
QRect rect_; QRect rect_;
bool doTranslation_; bool doTranslation_;
bool isLocked_{false}; bool isLocked_{false};

View File

@ -4,12 +4,21 @@
#include "capturer.h" #include "capturer.h"
#include "debug.h" #include "debug.h"
#include "geometryutils.h" #include "geometryutils.h"
#include "languagecodes.h"
#include "settings.h" #include "settings.h"
#include <QMenu>
#include <QMouseEvent> #include <QMouseEvent>
#include <QPainter> #include <QPainter>
static bool locked(const std::shared_ptr<CaptureArea> &area)
{
return area->isLocked();
}
static bool notLocked(const std::shared_ptr<CaptureArea> &area)
{
return !area->isLocked();
}
CaptureAreaSelector::CaptureAreaSelector(Capturer &capturer, CaptureAreaSelector::CaptureAreaSelector(Capturer &capturer,
const Settings &settings, const Settings &settings,
const CommonModels &models, const CommonModels &models,
@ -18,6 +27,7 @@ CaptureAreaSelector::CaptureAreaSelector(Capturer &capturer,
, settings_(settings) , settings_(settings)
, pixmap_(pixmap) , pixmap_(pixmap)
, editor_(std::make_unique<CaptureAreaEditor>(models, this)) , editor_(std::make_unique<CaptureAreaEditor>(models, this))
, contextMenu_(new QMenu(this))
{ {
setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint |
Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint);
@ -27,7 +37,20 @@ CaptureAreaSelector::CaptureAreaSelector(Capturer &capturer,
help_ = tr(R"(Right click on selection - customize help_ = tr(R"(Right click on selection - customize
Left click on selection - process 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; CaptureAreaSelector::~CaptureAreaSelector() = default;
@ -41,13 +64,36 @@ void CaptureAreaSelector::activate()
bool CaptureAreaSelector::hasLocked() const 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() void CaptureAreaSelector::captureLocked()
{ {
SOFT_ASSERT(hasLocked(), return ); 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<QRect> &screens) void CaptureAreaSelector::setScreenRects(const std::vector<QRect> &screens)
@ -67,7 +113,7 @@ void CaptureAreaSelector::setScreenRects(const std::vector<QRect> &screens)
void CaptureAreaSelector::updateSettings() void CaptureAreaSelector::updateSettings()
{ {
area_.reset(); areas_.clear();
} }
void CaptureAreaSelector::paintEvent(QPaintEvent * /*event*/) void CaptureAreaSelector::paintEvent(QPaintEvent * /*event*/)
@ -77,8 +123,9 @@ void CaptureAreaSelector::paintEvent(QPaintEvent * /*event*/)
for (const auto &rect : helpRects_) drawHelpRects(painter, rect); for (const auto &rect : helpRects_) drawHelpRects(painter, rect);
if (area_) if (!areas_.empty()) {
drawCaptureArea(painter, *area_); for (const auto &area : areas_) drawCaptureArea(painter, *area);
}
if (editor_->isVisible()) { if (editor_->isVisible()) {
painter.setBrush(QBrush(QColor(200, 200, 200, 200))); painter.setBrush(QBrush(QColor(200, 200, 200, 200)));
@ -86,11 +133,10 @@ void CaptureAreaSelector::paintEvent(QPaintEvent * /*event*/)
painter.drawRect(editor_->geometry()); painter.drawRect(editor_->geometry());
} }
auto selection = QRect(startSelectPos_, currentSelectPos_).normalized(); const auto area = CaptureArea(
if (!selection.isValid()) QRect(startSelectPos_, currentSelectPos_).normalized(), settings_);
if (!area.isValid())
return; return;
const auto area = CaptureArea(selection, settings_);
drawCaptureArea(painter, area); drawCaptureArea(painter, area);
} }
@ -154,9 +200,9 @@ void CaptureAreaSelector::drawCaptureArea(QPainter &painter,
void CaptureAreaSelector::showEvent(QShowEvent * /*event*/) void CaptureAreaSelector::showEvent(QShowEvent * /*event*/)
{ {
editor_->hide(); editor_->hide();
if (area_ && !area_->isLocked())
area_.reset();
startSelectPos_ = currentSelectPos_ = QPoint(); startSelectPos_ = currentSelectPos_ = QPoint();
areas_.erase(std::remove_if(areas_.begin(), areas_.end(), notLocked),
areas_.end());
} }
void CaptureAreaSelector::hideEvent(QHideEvent * /*event*/) void CaptureAreaSelector::hideEvent(QHideEvent * /*event*/)
@ -166,8 +212,19 @@ void CaptureAreaSelector::hideEvent(QHideEvent * /*event*/)
void CaptureAreaSelector::keyPressEvent(QKeyEvent *event) void CaptureAreaSelector::keyPressEvent(QKeyEvent *event)
{ {
if (event->key() == Qt::Key_Escape) if (event->key() == Qt::Key_Escape) {
capturer_.canceled(); cancel();
return;
}
if (event->key() == Qt::Key_Return) {
if (!areas_.empty()) {
captureAll();
} else {
cancel();
}
return;
}
} }
void CaptureAreaSelector::mousePressEvent(QMouseEvent *event) void CaptureAreaSelector::mousePressEvent(QMouseEvent *event)
@ -179,17 +236,22 @@ void CaptureAreaSelector::mousePressEvent(QMouseEvent *event)
applyEditor(); applyEditor();
} }
if (area_ && area_->rect().contains(event->pos())) { if (!areas_.empty()) {
for (auto &area : areas_) {
if (!area->rect().contains(event->pos()))
continue;
if (event->button() == Qt::LeftButton) { if (event->button() == Qt::LeftButton) {
capturer_.selected(*area_); capture(*area, ++generation_);
} else if (event->button() == Qt::RightButton) { } else if (event->button() == Qt::RightButton) {
customize(*area_); customize(area);
} }
return; return;
} }
}
if (startSelectPos_.isNull()) if (startSelectPos_.isNull())
startSelectPos_ = event->pos(); startSelectPos_ = currentSelectPos_ = event->pos();
} }
void CaptureAreaSelector::mouseMoveEvent(QMouseEvent *event) void CaptureAreaSelector::mouseMoveEvent(QMouseEvent *event)
@ -215,27 +277,37 @@ void CaptureAreaSelector::mouseReleaseEvent(QMouseEvent *event)
startSelectPos_ = currentSelectPos_ = {}; startSelectPos_ = currentSelectPos_ = {};
const auto area = CaptureArea(selection, settings_); auto area = CaptureArea(selection, settings_);
if (!area.isValid()) { if (!area.isValid()) { // just a click
capturer_.canceled(); if (areas_.empty()) {
cancel();
return;
}
if (event->button() == Qt::RightButton) {
contextMenu_->popup(QCursor::pos());
}
return; return;
} }
if (event->button() != Qt::RightButton) { areas_.emplace_back(std::make_unique<CaptureArea>(area));
capturer_.selected(area); if (event->button() == Qt::RightButton) {
} else { customize(areas_.back());
area_ = std::make_unique<CaptureArea>(area); return;
customize(*area_);
} }
if (!(event->modifiers() & Qt::ControlModifier))
captureAll();
} }
void CaptureAreaSelector::customize(const CaptureArea &area) void CaptureAreaSelector::customize(const std::shared_ptr<CaptureArea> &area)
{ {
SOFT_ASSERT(editor_, return ); SOFT_ASSERT(editor_, return );
editor_->set(area); SOFT_ASSERT(area, return );
editor_->set(*area);
edited_ = area;
editor_->show(); editor_->show();
const auto topLeft = service::geometry::cornerAtPoint( const auto topLeft = service::geometry::cornerAtPoint(
area.rect().center(), editor_->size(), geometry()); area->rect().center(), editor_->size(), geometry());
editor_->move(topLeft); editor_->move(topLeft);
update(); update();
} }
@ -243,8 +315,9 @@ void CaptureAreaSelector::customize(const CaptureArea &area)
void CaptureAreaSelector::applyEditor() void CaptureAreaSelector::applyEditor()
{ {
SOFT_ASSERT(editor_, return ); SOFT_ASSERT(editor_, return );
if (!editor_->isVisible() || !area_) if (!editor_->isVisible() || edited_.expired())
return; return;
editor_->apply(*area_); editor_->apply(*edited_.lock());
editor_->hide(); editor_->hide();
update();
} }

View File

@ -4,6 +4,8 @@
#include <QWidget> #include <QWidget>
class QMenu;
class CaptureAreaSelector : public QWidget class CaptureAreaSelector : public QWidget
{ {
Q_OBJECT Q_OBJECT
@ -33,21 +35,27 @@ private:
QRect current; QRect current;
std::vector<QRect> possible; std::vector<QRect> possible;
}; };
void capture(CaptureArea &area, uint generation);
void captureAll();
void cancel();
bool updateCurrentHelpRects(); bool updateCurrentHelpRects();
void drawHelpRects(QPainter &painter, const HelpRect &rect) const; void drawHelpRects(QPainter &painter, const HelpRect &rect) const;
void customize(const CaptureArea &area); void customize(const std::shared_ptr<CaptureArea> &area);
void applyEditor(); void applyEditor();
void drawCaptureArea(QPainter &painter, const CaptureArea &area) const; void drawCaptureArea(QPainter &painter, const CaptureArea &area) const;
Capturer &capturer_; Capturer &capturer_;
const Settings &settings_; const Settings &settings_;
const QPixmap &pixmap_; const QPixmap &pixmap_;
Generation generation_{};
QPoint startSelectPos_; QPoint startSelectPos_;
QPoint currentSelectPos_; QPoint currentSelectPos_;
QString help_; QString help_;
std::vector<HelpRect> helpRects_; std::vector<HelpRect> helpRects_;
std::unique_ptr<CaptureArea> area_; std::vector<std::shared_ptr<CaptureArea>> areas_;
std::weak_ptr<CaptureArea> edited_;
std::unique_ptr<CaptureAreaEditor> editor_; std::unique_ptr<CaptureAreaEditor> editor_;
QMenu *contextMenu_;
}; };

View File

@ -10,6 +10,7 @@
#include <QApplication> #include <QApplication>
#include <QClipboard> #include <QClipboard>
#include <QMouseEvent>
#include <QScreen> #include <QScreen>
Representer::Representer(Manager &manager, TrayIcon &tray, Representer::Representer(Manager &manager, TrayIcon &tray,
@ -25,15 +26,21 @@ Representer::~Representer() = default;
void Representer::showLast() void Representer::showLast()
{ {
SOFT_ASSERT(widget_, return ); SOFT_ASSERT(!widgets_.empty(), return );
widget_->show(); for (auto &widget : widgets_) {
SOFT_ASSERT(widget->task(), continue);
if (widget->task()->generation != generation_)
continue;
widget->show();
widget->activateWindow();
}
} }
void Representer::clipboardLast() void Representer::clipboardLast()
{ {
SOFT_ASSERT(widget_, return ); SOFT_ASSERT(!widgets_.empty(), return );
SOFT_ASSERT(widget_->task(), return ); SOFT_ASSERT(widgets_.front()->task(), return );
clipboardText(widget_->task()); clipboardText(widgets_.front()->task());
tray_.showInformation( tray_.showInformation(
QObject::tr("The last result was copied to the clipboard.")); QObject::tr("The last result was copied to the clipboard."));
} }
@ -48,19 +55,24 @@ void Representer::represent(const TaskPtr &task)
bool Representer::isVisible() const 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() void Representer::hide()
{ {
if (widget_) if (widgets_.empty())
widget_->hide(); return;
for (auto &w : widgets_) w->hide();
} }
void Representer::updateSettings() void Representer::updateSettings()
{ {
if (widget_) if (widgets_.empty())
widget_->updateSettings(); return;
for (auto &w : widgets_) w->updateSettings();
} }
void Representer::clipboardText(const TaskPtr &task) void Representer::clipboardText(const TaskPtr &task)
@ -98,6 +110,22 @@ void Representer::edit(const TaskPtr &task)
screen->geometry())); 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<QMouseEvent *>(event);
if (casted->button() == Qt::LeftButton)
hide();
}
return false;
}
void Representer::showTooltip(const TaskPtr &task) void Representer::showTooltip(const TaskPtr &task)
{ {
auto message = task->recognized + " - " + task->translated; auto message = task->recognized + " - " + task->translated;
@ -106,8 +134,22 @@ void Representer::showTooltip(const TaskPtr &task)
void Representer::showWidget(const TaskPtr &task) void Representer::showWidget(const TaskPtr &task)
{ {
if (!widget_) generation_ = task->generation;
widget_ = std::make_unique<ResultWidget>(*this, settings_);
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<ResultWidget>(*this, settings_));
widgets_.back()->installEventFilter(this);
}
auto &widget = widgets_[index];
widget->show(task);
} }

View File

@ -2,12 +2,15 @@
#include "stfwd.h" #include "stfwd.h"
#include <QObject>
enum class ResultMode; enum class ResultMode;
class ResultWidget; class ResultWidget;
class ResultEditor; class ResultEditor;
class Representer class Representer : public QObject
{ {
Q_OBJECT
public: public:
Representer(Manager &manager, TrayIcon &tray, const Settings &settings, Representer(Manager &manager, TrayIcon &tray, const Settings &settings,
const CommonModels &models); const CommonModels &models);
@ -24,6 +27,8 @@ public:
void clipboardImage(const TaskPtr &task); void clipboardImage(const TaskPtr &task);
void edit(const TaskPtr &task); void edit(const TaskPtr &task);
bool eventFilter(QObject *watched, QEvent *event) override;
private: private:
void showTooltip(const TaskPtr &task); void showTooltip(const TaskPtr &task);
void showWidget(const TaskPtr &task); void showWidget(const TaskPtr &task);
@ -32,6 +37,7 @@ private:
TrayIcon &tray_; TrayIcon &tray_;
const Settings &settings_; const Settings &settings_;
const CommonModels &models_; const CommonModels &models_;
std::unique_ptr<ResultWidget> widget_; Generation generation_{};
std::vector<std::unique_ptr<ResultWidget>> widgets_;
std::unique_ptr<ResultEditor> editor_; std::unique_ptr<ResultEditor> editor_;
}; };

View File

@ -148,13 +148,6 @@ void ResultWidget::updateSettings()
image_->setVisible(settings_.showCaptured); 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) void ResultWidget::mousePressEvent(QMouseEvent *event)
{ {
const auto button = event->button(); const auto button = event->button();
@ -167,8 +160,6 @@ void ResultWidget::mousePressEvent(QMouseEvent *event)
lastPos_ = event->pos(); lastPos_ = event->pos();
return; return;
} }
hide();
} }
void ResultWidget::mouseMoveEvent(QMouseEvent *event) void ResultWidget::mouseMoveEvent(QMouseEvent *event)

View File

@ -19,8 +19,6 @@ public:
using QWidget::show; using QWidget::show;
void updateSettings(); void updateSettings();
bool eventFilter(QObject* watched, QEvent* event) override;
protected: protected:
void mousePressEvent(QMouseEvent* event) override; void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override;

View File

@ -29,3 +29,4 @@ class AutoChecker;
using TaskPtr = std::shared_ptr<Task>; using TaskPtr = std::shared_ptr<Task>;
using LanguageId = QString; using LanguageId = QString;
using LanguageIds = QStringList; using LanguageIds = QStringList;
using Generation = unsigned int;

View File

@ -10,6 +10,8 @@ public:
bool isNull() const { return captured.isNull() && !sourceLanguage.isEmpty(); } bool isNull() const { return captured.isNull() && !sourceLanguage.isEmpty(); }
bool isValid() const { return error.isEmpty(); } bool isValid() const { return error.isEmpty(); }
Generation generation{};
QPoint capturePoint; QPoint capturePoint;
QPixmap captured; QPixmap captured;
QString recognized; QString recognized;