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
21 #include <utility>
22 #include "ApplyDialog.h"
23 #include "ScopedIncDec.h"
24 #include "Settings.h"
25
26 namespace deskew {
27 const double OptionsWidget::MAX_ANGLE = 45.0;
28
OptionsWidget(intrusive_ptr<Settings> settings,const PageSelectionAccessor & page_selection_accessor)29 OptionsWidget::OptionsWidget(intrusive_ptr<Settings> settings, const PageSelectionAccessor& page_selection_accessor)
30 : m_settings(std::move(settings)),
31 m_ignoreAutoManualToggle(0),
32 m_ignoreSpinBoxChanges(0),
33 m_pageSelectionAccessor(page_selection_accessor) {
34 setupUi(this);
35 angleSpinBox->setSuffix(QChar(0x00B0)); // the degree symbol
36 angleSpinBox->setRange(-MAX_ANGLE, MAX_ANGLE);
37 angleSpinBox->adjustSize();
38 setSpinBoxUnknownState();
39
40 setupUiConnections();
41 }
42
43 OptionsWidget::~OptionsWidget() = default;
44
showDeskewDialog()45 void OptionsWidget::showDeskewDialog() {
46 auto* dialog = new ApplyDialog(this, m_pageId, m_pageSelectionAccessor);
47 dialog->setAttribute(Qt::WA_DeleteOnClose);
48 dialog->setWindowTitle(tr("Apply Deskew"));
49 connect(dialog, SIGNAL(appliedTo(const std::set<PageId>&)), this, SLOT(appliedTo(const std::set<PageId>&)));
50 connect(dialog, SIGNAL(appliedToAllPages(const std::set<PageId>&)), this,
51 SLOT(appliedToAllPages(const std::set<PageId>&)));
52 dialog->show();
53 }
54
appliedTo(const std::set<PageId> & pages)55 void OptionsWidget::appliedTo(const std::set<PageId>& pages) {
56 if (pages.empty()) {
57 return;
58 }
59
60 const Params params(m_uiData.effectiveDeskewAngle(), m_uiData.dependencies(), m_uiData.mode());
61 m_settings->setDegrees(pages, params);
62
63 if (pages.size() > 1) {
64 emit invalidateAllThumbnails();
65 } else {
66 for (const PageId& page_id : pages) {
67 emit invalidateThumbnail(page_id);
68 }
69 }
70 }
71
appliedToAllPages(const std::set<PageId> & pages)72 void OptionsWidget::appliedToAllPages(const std::set<PageId>& pages) {
73 if (pages.empty()) {
74 return;
75 }
76
77 const Params params(m_uiData.effectiveDeskewAngle(), m_uiData.dependencies(), m_uiData.mode());
78 m_settings->setDegrees(pages, params);
79 emit invalidateAllThumbnails();
80 }
81
manualDeskewAngleSetExternally(const double degrees)82 void OptionsWidget::manualDeskewAngleSetExternally(const double degrees) {
83 m_uiData.setEffectiveDeskewAngle(degrees);
84 m_uiData.setMode(MODE_MANUAL);
85 updateModeIndication(MODE_MANUAL);
86 setSpinBoxKnownState(degreesToSpinBox(degrees));
87 commitCurrentParams();
88
89 emit invalidateThumbnail(m_pageId);
90 }
91
preUpdateUI(const PageId & page_id)92 void OptionsWidget::preUpdateUI(const PageId& page_id) {
93 removeUiConnections();
94
95 ScopedIncDec<int> guard(m_ignoreAutoManualToggle);
96
97 m_pageId = page_id;
98 setSpinBoxUnknownState();
99 autoBtn->setChecked(true);
100 autoBtn->setEnabled(false);
101 manualBtn->setEnabled(false);
102
103 setupUiConnections();
104 }
105
postUpdateUI(const UiData & ui_data)106 void OptionsWidget::postUpdateUI(const UiData& ui_data) {
107 removeUiConnections();
108
109 m_uiData = ui_data;
110 autoBtn->setEnabled(true);
111 manualBtn->setEnabled(true);
112 updateModeIndication(ui_data.mode());
113 setSpinBoxKnownState(degreesToSpinBox(ui_data.effectiveDeskewAngle()));
114
115 setupUiConnections();
116 }
117
spinBoxValueChanged(const double value)118 void OptionsWidget::spinBoxValueChanged(const double value) {
119 if (m_ignoreSpinBoxChanges) {
120 return;
121 }
122
123 const double degrees = spinBoxToDegrees(value);
124 m_uiData.setEffectiveDeskewAngle(degrees);
125 m_uiData.setMode(MODE_MANUAL);
126 updateModeIndication(MODE_MANUAL);
127 commitCurrentParams();
128
129 emit manualDeskewAngleSet(degrees);
130 emit invalidateThumbnail(m_pageId);
131 }
132
modeChanged(const bool auto_mode)133 void OptionsWidget::modeChanged(const bool auto_mode) {
134 if (m_ignoreAutoManualToggle) {
135 return;
136 }
137
138 if (auto_mode) {
139 m_uiData.setMode(MODE_AUTO);
140 m_settings->clearPageParams(m_pageId);
141 emit reloadRequested();
142 } else {
143 m_uiData.setMode(MODE_MANUAL);
144 commitCurrentParams();
145 }
146 }
147
updateModeIndication(const AutoManualMode mode)148 void OptionsWidget::updateModeIndication(const AutoManualMode mode) {
149 ScopedIncDec<int> guard(m_ignoreAutoManualToggle);
150
151 if (mode == MODE_AUTO) {
152 autoBtn->setChecked(true);
153 } else {
154 manualBtn->setChecked(true);
155 }
156 }
157
setSpinBoxUnknownState()158 void OptionsWidget::setSpinBoxUnknownState() {
159 ScopedIncDec<int> guard(m_ignoreSpinBoxChanges);
160
161 angleSpinBox->setSpecialValueText("?");
162 angleSpinBox->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
163 angleSpinBox->setValue(angleSpinBox->minimum());
164 angleSpinBox->setEnabled(false);
165 }
166
setSpinBoxKnownState(const double angle)167 void OptionsWidget::setSpinBoxKnownState(const double angle) {
168 ScopedIncDec<int> guard(m_ignoreSpinBoxChanges);
169
170 angleSpinBox->setSpecialValueText("");
171 angleSpinBox->setValue(angle);
172
173 // Right alignment doesn't work correctly, so we use the left one.
174 angleSpinBox->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
175 angleSpinBox->setEnabled(true);
176 }
177
commitCurrentParams()178 void OptionsWidget::commitCurrentParams() {
179 Params params(m_uiData.effectiveDeskewAngle(), m_uiData.dependencies(), m_uiData.mode());
180 m_settings->setPageParams(m_pageId, params);
181 }
182
spinBoxToDegrees(const double sb_value)183 double OptionsWidget::spinBoxToDegrees(const double sb_value) {
184 // The spin box shows the angle in a usual geometric way,
185 // with positive angles going counter-clockwise.
186 // Internally, we operate with angles going clockwise,
187 // because the Y axis points downwards in computer graphics.
188 return -sb_value;
189 }
190
degreesToSpinBox(const double degrees)191 double OptionsWidget::degreesToSpinBox(const double degrees) {
192 // See above.
193 return -degrees;
194 }
195
196 #define CONNECT(...) m_connectionList.push_back(connect(__VA_ARGS__));
197
setupUiConnections()198 void OptionsWidget::setupUiConnections() {
199 CONNECT(angleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(spinBoxValueChanged(double)));
200 CONNECT(autoBtn, SIGNAL(toggled(bool)), this, SLOT(modeChanged(bool)));
201 CONNECT(applyDeskewBtn, SIGNAL(clicked()), this, SLOT(showDeskewDialog()));
202 }
203
204 #undef CONNECT
205
removeUiConnections()206 void OptionsWidget::removeUiConnections() {
207 for (const auto& connection : m_connectionList) {
208 disconnect(connection);
209 }
210 m_connectionList.clear();
211 }
212
213 /*========================== OptionsWidget::UiData =========================*/
214
UiData()215 OptionsWidget::UiData::UiData() : m_effDeskewAngle(0.0), m_mode(MODE_AUTO) {}
216
217 OptionsWidget::UiData::~UiData() = default;
218
setEffectiveDeskewAngle(const double degrees)219 void OptionsWidget::UiData::setEffectiveDeskewAngle(const double degrees) {
220 m_effDeskewAngle = degrees;
221 }
222
effectiveDeskewAngle() const223 double OptionsWidget::UiData::effectiveDeskewAngle() const {
224 return m_effDeskewAngle;
225 }
226
setDependencies(const Dependencies & deps)227 void OptionsWidget::UiData::setDependencies(const Dependencies& deps) {
228 m_deps = deps;
229 }
230
dependencies() const231 const Dependencies& OptionsWidget::UiData::dependencies() const {
232 return m_deps;
233 }
234
setMode(const AutoManualMode mode)235 void OptionsWidget::UiData::setMode(const AutoManualMode mode) {
236 m_mode = mode;
237 }
238
mode() const239 AutoManualMode OptionsWidget::UiData::mode() const {
240 return m_mode;
241 }
242 } // namespace deskew