1 /*
2     Scan Tailor - Interactive post-processing tool for scanned pages.
3     Copyright (C)  Joseph Artsimovich <joseph.artsimovich@gmail.com>
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "OptionsWidget.h"
20 #include "ApplyDialog.h"
21 #include "ScopedIncDec.h"
22 #include "Settings.h"
23 
24 #include <UnitsProvider.h>
25 #include <boost/bind.hpp>
26 #include <iostream>
27 #include <utility>
28 
29 namespace select_content {
OptionsWidget(intrusive_ptr<Settings> settings,const PageSelectionAccessor & page_selection_accessor)30 OptionsWidget::OptionsWidget(intrusive_ptr<Settings> settings, const PageSelectionAccessor& page_selection_accessor)
31     : m_settings(std::move(settings)), m_pageSelectionAccessor(page_selection_accessor), m_ignorePageSizeChanges(0) {
32   setupUi(this);
33 
34   setupUiConnections();
35 }
36 
37 OptionsWidget::~OptionsWidget() = default;
38 
preUpdateUI(const PageInfo & page_info)39 void OptionsWidget::preUpdateUI(const PageInfo& page_info) {
40   removeUiConnections();
41 
42   m_pageId = page_info.id();
43   m_dpi = page_info.metadata().dpi();
44 
45   contentBoxGroup->setEnabled(false);
46   pageBoxGroup->setEnabled(false);
47 
48   pageDetectOptions->setVisible(false);
49   fineTuneBtn->setVisible(false);
50   dimensionsWidget->setVisible(false);
51 
52   updateUnits(UnitsProvider::getInstance()->getUnits());
53 
54   setupUiConnections();
55 }
56 
postUpdateUI(const UiData & ui_data)57 void OptionsWidget::postUpdateUI(const UiData& ui_data) {
58   removeUiConnections();
59 
60   m_uiData = ui_data;
61 
62   updateContentModeIndication(ui_data.contentDetectionMode());
63   updatePageModeIndication(ui_data.pageDetectionMode());
64 
65   contentBoxGroup->setEnabled(true);
66   pageBoxGroup->setEnabled(true);
67 
68   updatePageDetectOptionsDisplay();
69   updatePageRectSize(m_uiData.pageRect().size());
70 
71   setupUiConnections();
72 }
73 
manualContentRectSet(const QRectF & content_rect)74 void OptionsWidget::manualContentRectSet(const QRectF& content_rect) {
75   m_uiData.setContentRect(content_rect);
76   m_uiData.setContentDetectionMode(MODE_MANUAL);
77   updateContentModeIndication(MODE_MANUAL);
78 
79   commitCurrentParams();
80 
81   emit invalidateThumbnail(m_pageId);
82 }
83 
manualPageRectSet(const QRectF & page_rect)84 void OptionsWidget::manualPageRectSet(const QRectF& page_rect) {
85   m_uiData.setPageRect(page_rect);
86   m_uiData.setPageDetectionMode(MODE_MANUAL);
87   updatePageModeIndication(MODE_MANUAL);
88   updatePageDetectOptionsDisplay();
89   updatePageRectSize(page_rect.size());
90 
91   commitCurrentParams();
92 
93   emit invalidateThumbnail(m_pageId);
94 }
95 
updatePageRectSize(const QSizeF & size)96 void OptionsWidget::updatePageRectSize(const QSizeF& size) {
97   const ScopedIncDec<int> ignore_scope(m_ignorePageSizeChanges);
98 
99   double width = size.width();
100   double height = size.height();
101   UnitsProvider::getInstance()->convertFrom(width, height, PIXELS, m_dpi);
102 
103   widthSpinBox->setValue(width);
104   heightSpinBox->setValue(height);
105 }
106 
contentDetectToggled(const AutoManualMode mode)107 void OptionsWidget::contentDetectToggled(const AutoManualMode mode) {
108   m_uiData.setContentDetectionMode(mode);
109   commitCurrentParams();
110 
111   if (mode != MODE_MANUAL) {
112     emit reloadRequested();
113   }
114 }
115 
pageDetectToggled(const AutoManualMode mode)116 void OptionsWidget::pageDetectToggled(const AutoManualMode mode) {
117   const bool need_update_state = ((mode == MODE_MANUAL) && (m_uiData.pageDetectionMode() == MODE_DISABLED));
118 
119   m_uiData.setPageDetectionMode(mode);
120   updatePageDetectOptionsDisplay();
121   commitCurrentParams();
122 
123   if (mode != MODE_MANUAL) {
124     emit reloadRequested();
125   } else if (need_update_state) {
126     emit pageRectStateChanged(true);
127     emit invalidateThumbnail(m_pageId);
128   }
129 }
130 
fineTuningChanged(bool checked)131 void OptionsWidget::fineTuningChanged(bool checked) {
132   m_uiData.setFineTuneCornersEnabled(checked);
133   commitCurrentParams();
134   if (m_uiData.pageDetectionMode() == MODE_AUTO) {
135     emit reloadRequested();
136   }
137 }
138 
updateContentModeIndication(const AutoManualMode mode)139 void OptionsWidget::updateContentModeIndication(const AutoManualMode mode) {
140   switch (mode) {
141     case MODE_AUTO:
142       contentDetectAutoBtn->setChecked(true);
143       break;
144     case MODE_MANUAL:
145       contentDetectManualBtn->setChecked(true);
146       break;
147     case MODE_DISABLED:
148       contentDetectDisableBtn->setChecked(true);
149       break;
150   }
151 }
152 
updatePageModeIndication(const AutoManualMode mode)153 void OptionsWidget::updatePageModeIndication(const AutoManualMode mode) {
154   switch (mode) {
155     case MODE_AUTO:
156       pageDetectAutoBtn->setChecked(true);
157       break;
158     case MODE_MANUAL:
159       pageDetectManualBtn->setChecked(true);
160       break;
161     case MODE_DISABLED:
162       pageDetectDisableBtn->setChecked(true);
163       break;
164   }
165 }
166 
updatePageDetectOptionsDisplay()167 void OptionsWidget::updatePageDetectOptionsDisplay() {
168   fineTuneBtn->setChecked(m_uiData.isFineTuningCornersEnabled());
169   pageDetectOptions->setVisible(m_uiData.pageDetectionMode() != MODE_DISABLED);
170   fineTuneBtn->setVisible(m_uiData.pageDetectionMode() == MODE_AUTO);
171   dimensionsWidget->setVisible(m_uiData.pageDetectionMode() == MODE_MANUAL);
172 }
173 
dimensionsChangedLocally(double)174 void OptionsWidget::dimensionsChangedLocally(double) {
175   if (m_ignorePageSizeChanges) {
176     return;
177   }
178 
179   double widthSpinBoxValue = widthSpinBox->value();
180   double heightSpinBoxValue = heightSpinBox->value();
181   UnitsProvider::getInstance()->convertTo(widthSpinBoxValue, heightSpinBoxValue, PIXELS, m_dpi);
182 
183   QRectF newPageRect = m_uiData.pageRect();
184   newPageRect.setSize(QSizeF(widthSpinBoxValue, heightSpinBoxValue));
185 
186   emit pageRectChangedLocally(newPageRect);
187 }
188 
commitCurrentParams()189 void OptionsWidget::commitCurrentParams() {
190   updateDependenciesIfNecessary();
191 
192   Params params(m_uiData.contentRect(), m_uiData.contentSizeMM(), m_uiData.pageRect(), m_uiData.dependencies(),
193                 m_uiData.contentDetectionMode(), m_uiData.pageDetectionMode(), m_uiData.isFineTuningCornersEnabled());
194   m_settings->setPageParams(m_pageId, params);
195 }
196 
updateDependenciesIfNecessary()197 void OptionsWidget::updateDependenciesIfNecessary() {
198   // On switching to manual mode the page dependencies isn't updated
199   // as Task::process isn't called, so we need to update it manually.
200   if (!(m_uiData.contentDetectionMode() == MODE_MANUAL || m_uiData.pageDetectionMode() == MODE_MANUAL)) {
201     return;
202   }
203 
204   Dependencies deps = m_uiData.dependencies();
205   if (m_uiData.contentDetectionMode() == MODE_MANUAL) {
206     deps.setContentDetectionMode(MODE_MANUAL);
207   }
208   if (m_uiData.pageDetectionMode() == MODE_MANUAL) {
209     deps.setPageDetectionMode(MODE_MANUAL);
210   }
211   m_uiData.setDependencies(deps);
212 }
213 
showApplyToDialog()214 void OptionsWidget::showApplyToDialog() {
215   auto* dialog = new ApplyDialog(this, m_pageId, m_pageSelectionAccessor);
216   dialog->setAttribute(Qt::WA_DeleteOnClose);
217   connect(dialog, SIGNAL(applySelection(const std::set<PageId>&, bool, bool)), this,
218           SLOT(applySelection(const std::set<PageId>&, bool, bool)));
219   dialog->show();
220 }
221 
applySelection(const std::set<PageId> & pages,const bool apply_content_box,const bool apply_page_box)222 void OptionsWidget::applySelection(const std::set<PageId>& pages,
223                                    const bool apply_content_box,
224                                    const bool apply_page_box) {
225   if (pages.empty()) {
226     return;
227   }
228 
229   const Params params(m_uiData.contentRect(), m_uiData.contentSizeMM(), m_uiData.pageRect(), Dependencies(),
230                       m_uiData.contentDetectionMode(), m_uiData.pageDetectionMode(),
231                       m_uiData.isFineTuningCornersEnabled());
232 
233   for (const PageId& page_id : pages) {
234     if (m_pageId == page_id) {
235       continue;
236     }
237 
238     Params new_params(params);
239     std::unique_ptr<Params> old_params = m_settings->getPageParams(page_id);
240     if (old_params) {
241       if (new_params.pageDetectionMode() == MODE_MANUAL) {
242         if (!apply_page_box) {
243           new_params.setPageRect(old_params->pageRect());
244         } else {
245           QRectF corrected_page_rect = new_params.pageRect();
246           const QRectF source_image_rect = new_params.dependencies().rotatedPageOutline().boundingRect();
247           const QRectF current_image_rect = old_params->dependencies().rotatedPageOutline().boundingRect();
248           if (source_image_rect.isValid() && current_image_rect.isValid()) {
249             corrected_page_rect.translate((current_image_rect.width() - source_image_rect.width()) / 2,
250                                           (current_image_rect.height() - source_image_rect.height()) / 2);
251             new_params.setPageRect(corrected_page_rect);
252           }
253         }
254       }
255       if (new_params.contentDetectionMode() == MODE_MANUAL) {
256         if (!apply_content_box) {
257           new_params.setContentRect(old_params->contentRect());
258         } else if (!new_params.contentRect().isEmpty()) {
259           QRectF corrected_content_rect = new_params.contentRect();
260           const QRectF& source_page_rect = m_uiData.pageRect();
261           const QRectF& new_page_rect = new_params.pageRect();
262           corrected_content_rect.translate(new_page_rect.x() - source_page_rect.x(),
263                                            new_page_rect.y() - source_page_rect.y());
264           new_params.setContentRect(corrected_content_rect);
265         }
266       }
267     }
268 
269     m_settings->setPageParams(page_id, new_params);
270   }
271 
272   if (pages.size() > 1) {
273     emit invalidateAllThumbnails();
274   } else {
275     for (const PageId& page_id : pages) {
276       emit invalidateThumbnail(page_id);
277     }
278   }
279 
280   emit reloadRequested();
281 }  // OptionsWidget::applySelection
282 
283 
updateUnits(Units units)284 void OptionsWidget::updateUnits(Units units) {
285   removeUiConnections();
286 
287   int decimals;
288   double step;
289   switch (units) {
290     case PIXELS:
291     case MILLIMETRES:
292       decimals = 1;
293       step = 1.0;
294       break;
295     default:
296       decimals = 2;
297       step = 0.01;
298       break;
299   }
300 
301   widthSpinBox->setDecimals(decimals);
302   widthSpinBox->setSingleStep(step);
303   heightSpinBox->setDecimals(decimals);
304   heightSpinBox->setSingleStep(step);
305 
306   updatePageRectSize(m_uiData.pageRect().size());
307 
308   setupUiConnections();
309 }
310 
311 #define CONNECT(...) m_connectionList.push_back(connect(__VA_ARGS__));
312 
setupUiConnections()313 void OptionsWidget::setupUiConnections() {
314   CONNECT(widthSpinBox, SIGNAL(valueChanged(double)), this, SLOT(dimensionsChangedLocally(double)));
315   CONNECT(heightSpinBox, SIGNAL(valueChanged(double)), this, SLOT(dimensionsChangedLocally(double)));
316   CONNECT(contentDetectAutoBtn, &QPushButton::pressed, this,
317           boost::bind(&OptionsWidget::contentDetectToggled, this, MODE_AUTO));
318   CONNECT(contentDetectManualBtn, &QPushButton::pressed, this,
319           boost::bind(&OptionsWidget::contentDetectToggled, this, MODE_MANUAL));
320   CONNECT(contentDetectDisableBtn, &QPushButton::pressed, this,
321           boost::bind(&OptionsWidget::contentDetectToggled, this, MODE_DISABLED));
322   CONNECT(pageDetectAutoBtn, &QPushButton::pressed, this,
323           boost::bind(&OptionsWidget::pageDetectToggled, this, MODE_AUTO));
324   CONNECT(pageDetectManualBtn, &QPushButton::pressed, this,
325           boost::bind(&OptionsWidget::pageDetectToggled, this, MODE_MANUAL));
326   CONNECT(pageDetectDisableBtn, &QPushButton::pressed, this,
327           boost::bind(&OptionsWidget::pageDetectToggled, this, MODE_DISABLED));
328   CONNECT(fineTuneBtn, SIGNAL(toggled(bool)), this, SLOT(fineTuningChanged(bool)));
329   CONNECT(applyToBtn, SIGNAL(clicked()), this, SLOT(showApplyToDialog()));
330 }
331 
332 #undef CONNECT
333 
removeUiConnections()334 void OptionsWidget::removeUiConnections() {
335   for (const auto& connection : m_connectionList) {
336     disconnect(connection);
337   }
338   m_connectionList.clear();
339 }
340 
341 
342 /*========================= OptionsWidget::UiData ======================*/
343 
UiData()344 OptionsWidget::UiData::UiData()
345     : m_contentDetectionMode(MODE_AUTO), m_pageDetectionMode(MODE_DISABLED), m_fineTuneCornersEnabled(false) {}
346 
347 OptionsWidget::UiData::~UiData() = default;
348 
setSizeCalc(const PhysSizeCalc & calc)349 void OptionsWidget::UiData::setSizeCalc(const PhysSizeCalc& calc) {
350   m_sizeCalc = calc;
351 }
352 
setContentRect(const QRectF & content_rect)353 void OptionsWidget::UiData::setContentRect(const QRectF& content_rect) {
354   m_contentRect = content_rect;
355 }
356 
contentRect() const357 const QRectF& OptionsWidget::UiData::contentRect() const {
358   return m_contentRect;
359 }
360 
setPageRect(const QRectF & page_rect)361 void OptionsWidget::UiData::setPageRect(const QRectF& page_rect) {
362   m_pageRect = page_rect;
363 }
364 
pageRect() const365 const QRectF& OptionsWidget::UiData::pageRect() const {
366   return m_pageRect;
367 }
368 
contentSizeMM() const369 QSizeF OptionsWidget::UiData::contentSizeMM() const {
370   return m_sizeCalc.sizeMM(m_contentRect);
371 }
372 
setDependencies(const Dependencies & deps)373 void OptionsWidget::UiData::setDependencies(const Dependencies& deps) {
374   m_deps = deps;
375 }
376 
dependencies() const377 const Dependencies& OptionsWidget::UiData::dependencies() const {
378   return m_deps;
379 }
380 
setContentDetectionMode(AutoManualMode mode)381 void OptionsWidget::UiData::setContentDetectionMode(AutoManualMode mode) {
382   m_contentDetectionMode = mode;
383 }
384 
contentDetectionMode() const385 AutoManualMode OptionsWidget::UiData::contentDetectionMode() const {
386   return m_contentDetectionMode;
387 }
388 
setPageDetectionMode(AutoManualMode mode)389 void OptionsWidget::UiData::setPageDetectionMode(AutoManualMode mode) {
390   m_pageDetectionMode = mode;
391 }
392 
pageDetectionMode() const393 AutoManualMode OptionsWidget::UiData::pageDetectionMode() const {
394   return m_pageDetectionMode;
395 }
setFineTuneCornersEnabled(bool fine_tune)396 void OptionsWidget::UiData::setFineTuneCornersEnabled(bool fine_tune) {
397   m_fineTuneCornersEnabled = fine_tune;
398 }
399 
isFineTuningCornersEnabled() const400 bool OptionsWidget::UiData::isFineTuningCornersEnabled() const {
401   return m_fineTuneCornersEnabled;
402 }
403 }  // namespace select_content