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