1 //=============================================================================
2 // MuseScore
3 // Linux Music Score Editor
4 //
5 // Copyright (C) 2002-2008 Werner Schweer and others
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2.
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, write to the Free Software
17 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19
20 #include "preferences.h"
21 #include "zoombox.h"
22 #include "scoreview.h"
23 #include "libmscore/page.h"
24 #include "musescore.h"
25 #include "libmscore/score.h"
26 #include "libmscore/mscore.h"
27
28 namespace Ms {
29
30 //---------------------------------------------------------
31 // zoomEntries
32 //---------------------------------------------------------
33
34 const std::array<ZoomEntry, 13> zoomEntries { {
35 { ZoomIndex::ZOOM_25, 25, "25%" },
36 { ZoomIndex::ZOOM_50, 50, "50%" },
37 { ZoomIndex::ZOOM_75, 75, "75%" },
38 { ZoomIndex::ZOOM_100, 100, "100%" },
39 { ZoomIndex::ZOOM_150, 150, "150%" },
40 { ZoomIndex::ZOOM_200, 200, "200%" },
41 { ZoomIndex::ZOOM_400, 400, "400%" },
42 { ZoomIndex::ZOOM_800, 800, "800%" },
43 { ZoomIndex::ZOOM_1600, 1600, "1600%" },
44 { ZoomIndex::ZOOM_PAGE_WIDTH, 0, QT_TRANSLATE_NOOP("magTable", "Page Width") },
45 { ZoomIndex::ZOOM_WHOLE_PAGE, 0, QT_TRANSLATE_NOOP("magTable", "Whole Page") },
46 { ZoomIndex::ZOOM_TWO_PAGES, 0, QT_TRANSLATE_NOOP("magTable", "Two Pages") },
47 { ZoomIndex::ZOOM_FREE, 0, "" },
48 } };
49
getDefaultLogicalZoom()50 ZoomState ZoomBox::getDefaultLogicalZoom()
51 {
52 ZoomState result { ZoomIndex::ZOOM_100, 1.0 };
53
54 // Convert the default-zoom preferences into a usable zoom index and logical zoom level.
55 switch (static_cast<ZoomType>(preferences.getInt(PREF_UI_CANVAS_ZOOM_DEFAULT_TYPE))) {
56 case ZoomType::PERCENTAGE: {
57 // Select a numeric preset zoom entry if the percentage corresponds to one; otherwise, select free zoom.
58 const auto logicalLevelPercentage = preferences.getInt(PREF_UI_CANVAS_ZOOM_DEFAULT_LEVEL);
59 const auto i = std::find(zoomEntries.cbegin(), zoomEntries.cend(), logicalLevelPercentage);
60 result.index = ((i != zoomEntries.cend()) && i->isNumericPreset()) ? i->index : ZoomIndex::ZOOM_FREE;
61 result.level = logicalLevelPercentage / 100.0;
62 }
63 break;
64 case ZoomType::PAGE_WIDTH:
65 result.index = ZoomIndex::ZOOM_PAGE_WIDTH;
66 break;
67 case ZoomType::WHOLE_PAGE:
68 result.index = ZoomIndex::ZOOM_WHOLE_PAGE;
69 break;
70 case ZoomType::TWO_PAGES:
71 result.index = ZoomIndex::ZOOM_TWO_PAGES;
72 break;
73 default:
74 Q_ASSERT(false);
75 break;
76 }
77
78 return result;
79 }
80
81 //---------------------------------------------------------
82 // ZoomBox
83 //---------------------------------------------------------
84
ZoomBox(QWidget * parent)85 ZoomBox::ZoomBox(QWidget* parent)
86 : QComboBox(parent)
87 , _previousLogicalLevel(0.0)
88 , _previousScoreView(nullptr)
89 {
90 setEditable(true);
91 setInsertPolicy(QComboBox::InsertAtBottom);
92 setToolTip(tr("Zoom"));
93 setWhatsThis(tr("Zoom"));
94 setAccessibleName(tr("Zoom"));
95 setValidator(new ZoomValidator(this));
96 setCompleter(nullptr);
97 setFocusPolicy(Qt::StrongFocus);
98 setFixedHeight(preferences.getInt(PREF_UI_THEME_ICONHEIGHT) + 8); // hack
99 setMaxCount(static_cast<int>(zoomEntries.size()));
100 setMaxVisibleItems(static_cast<int>(zoomEntries.size()));
101 for (const ZoomEntry& e : zoomEntries) {
102 QString ts(QCoreApplication::translate("magTable", e.txt));
103 addItem(ts, QVariant::fromValue(e.index));
104 }
105 resetToDefaultLogicalZoom();
106 connect(this, SIGNAL(currentIndexChanged(int)), SLOT(indexChanged(int)));
107 connect(lineEdit(), SIGNAL(returnPressed()), SLOT(textChanged()));
108 }
109
110 //---------------------------------------------------------
111 // textChanged
112 //---------------------------------------------------------
113
textChanged()114 void ZoomBox::textChanged()
115 {
116 if (!mscore->currentScoreView())
117 return;
118
119 const auto validation = ZoomValidator::validationHelper(currentText());
120
121 if (std::get<0>(validation) == QValidator::Acceptable)
122 setLogicalZoom(std::get<1>(validation), std::get<2>(validation) / 100.0);
123 }
124
125 //---------------------------------------------------------
126 // indexChanged
127 //---------------------------------------------------------
128
indexChanged(int index)129 void ZoomBox::indexChanged(int index)
130 {
131 emit zoomChanged(itemData(index).value<ZoomIndex>(), _previousLogicalLevel);
132 }
133
134 //---------------------------------------------------------
135 // setLogicalZoom
136 //---------------------------------------------------------
137
setLogicalZoom(const ZoomIndex index,const qreal logicalLevel)138 void ZoomBox::setLogicalZoom(const ZoomIndex index, const qreal logicalLevel)
139 {
140 // Check of the zoom type has changed.
141 if (static_cast<int>(index) != currentIndex()) {
142 // Set the new zoom type, but don't emit any signals because that will be done below if needed.
143 const QSignalBlocker blocker(this);
144 setCurrentIndex(static_cast<int>(index));
145 }
146
147 // Check if either the logical zoom level has changed or the user has switched to a different score.
148 if ((logicalLevel != _previousLogicalLevel) || (mscore && (mscore->currentScoreView() != _previousScoreView))) {
149 if ((logicalLevel != _previousLogicalLevel)) {
150 // Convert the value to an integer percentage using half-to-even rounding (a.k.a. banker's rounding).
151 const auto logicalLevelPercentage = static_cast<int>((100.0 * logicalLevel) - std::remainder(100.0 * logicalLevel, 1.0));
152
153 qDebug("ZoomBox::setLogicalZoom(): Formatting logical zoom level as %d%% (rounded from %f)", logicalLevelPercentage, logicalLevel);
154 setItemText(static_cast<int>(ZoomIndex::ZOOM_FREE), QString("%1%").arg(logicalLevelPercentage));
155
156 _previousLogicalLevel = logicalLevel;
157 }
158
159 if (mscore && (mscore->currentScoreView() != _previousScoreView))
160 _previousScoreView = mscore->currentScoreView();
161
162 emit zoomChanged(static_cast<ZoomIndex>(currentIndex()), logicalLevel);
163 }
164 }
165
166 //---------------------------------------------------------
167 // resetToDefaultLogicalZoom
168 //---------------------------------------------------------
169
resetToDefaultLogicalZoom()170 void ZoomBox::resetToDefaultLogicalZoom()
171 {
172 const auto defaultZoom = getDefaultLogicalZoom();
173 setLogicalZoom(defaultZoom.index, defaultZoom.level);
174 }
175
176 //---------------------------------------------------------
177 // ZoomValidator
178 //---------------------------------------------------------
179
ZoomValidator(QObject * parent)180 ZoomValidator::ZoomValidator(QObject* parent)
181 : QValidator(parent)
182 {
183 }
184
185 //---------------------------------------------------------
186 // validate
187 //---------------------------------------------------------
188
validate(QString & input,int &) const189 QValidator::State ZoomValidator::validate(QString& input, int& /*pos*/) const
190 {
191 return std::get<0>(validationHelper(input));
192 }
193
194 //---------------------------------------------------------
195 // validationHelper
196 //---------------------------------------------------------
197
validationHelper(const QString & input)198 std::tuple<QValidator::State, ZoomIndex, int> ZoomValidator::validationHelper(const QString& input)
199 {
200 // Strip off the trailing '%', if any.
201 const QString s = (input.right(1) == '%') ? input.left(input.length() - 1) : input;
202
203 // Check if it's an empty string.
204 if (s.isEmpty())
205 return std::make_tuple(QValidator::Intermediate, ZoomIndex::ZOOM_FREE, 0);
206
207 // Check if it's anything other than a valid integer.
208 bool ok;
209 const auto level = s.toInt(&ok);
210 if (!ok)
211 return std::make_tuple(QValidator::Invalid, ZoomIndex::ZOOM_FREE, 0);
212
213 // Check if it's out of range.
214 if ((level < 100.0 * ZOOM_LEVEL_MIN) || (level > 100.0 * ZOOM_LEVEL_MAX))
215 return std::make_tuple(QValidator::Intermediate, ZoomIndex::ZOOM_FREE, level);
216
217 // Check if it corresponds to one of the numeric presets.
218 const auto i = std::find(zoomEntries.cbegin(), zoomEntries.cend(), level);
219 if ((i != zoomEntries.cend()) && i->isNumericPreset())
220 return std::make_tuple(QValidator::Acceptable, i->index, i->level);
221
222 // Must be free zoom.
223 return std::make_tuple(QValidator::Acceptable, ZoomIndex::ZOOM_FREE, level);
224 }
225 }
226