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