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 {};
auto task = std::make_shared<Task>();
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);

View File

@ -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};

View File

@ -4,12 +4,21 @@
#include "capturer.h"
#include "debug.h"
#include "geometryutils.h"
#include "languagecodes.h"
#include "settings.h"
#include <QMenu>
#include <QMouseEvent>
#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,
const Settings &settings,
const CommonModels &models,
@ -18,6 +27,7 @@ CaptureAreaSelector::CaptureAreaSelector(Capturer &capturer,
, settings_(settings)
, pixmap_(pixmap)
, editor_(std::make_unique<CaptureAreaEditor>(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<QRect> &screens)
@ -67,7 +113,7 @@ void CaptureAreaSelector::setScreenRects(const std::vector<QRect> &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<CaptureArea>(area);
customize(*area_);
areas_.emplace_back(std::make_unique<CaptureArea>(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<CaptureArea> &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();
}

View File

@ -4,6 +4,8 @@
#include <QWidget>
class QMenu;
class CaptureAreaSelector : public QWidget
{
Q_OBJECT
@ -33,21 +35,27 @@ private:
QRect current;
std::vector<QRect> 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<CaptureArea> &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<HelpRect> helpRects_;
std::unique_ptr<CaptureArea> area_;
std::vector<std::shared_ptr<CaptureArea>> areas_;
std::weak_ptr<CaptureArea> edited_;
std::unique_ptr<CaptureAreaEditor> editor_;
QMenu *contextMenu_;
};

View File

@ -10,6 +10,7 @@
#include <QApplication>
#include <QClipboard>
#include <QMouseEvent>
#include <QScreen>
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<QMouseEvent *>(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<ResultWidget>(*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<ResultWidget>(*this, settings_));
widgets_.back()->installEventFilter(this);
}
auto &widget = widgets_[index];
widget->show(task);
}

View File

@ -2,12 +2,15 @@
#include "stfwd.h"
#include <QObject>
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<ResultWidget> widget_;
Generation generation_{};
std::vector<std::unique_ptr<ResultWidget>> widgets_;
std::unique_ptr<ResultEditor> editor_;
};

View File

@ -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)

View File

@ -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;

View File

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

View File

@ -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;