Add ability to capture multiple areas
This commit is contained in:
parent
5f4ef955e1
commit
8d2e726715
@ -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);
|
||||
|
@ -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};
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -29,3 +29,4 @@ class AutoChecker;
|
||||
using TaskPtr = std::shared_ptr<Task>;
|
||||
using LanguageId = QString;
|
||||
using LanguageIds = QStringList;
|
||||
using Generation = unsigned int;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user