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