1 #include "widget/wdisplay.h"
2 
3 #include <QPaintEvent>
4 #include <QPixmap>
5 #include <QStyleOption>
6 #include <QStylePainter>
7 #include <QtDebug>
8 
9 #include "moc_wdisplay.cpp"
10 #include "widget/wpixmapstore.h"
11 
WDisplay(QWidget * parent)12 WDisplay::WDisplay(QWidget * parent)
13         : WWidget(parent),
14           m_iCurrentPixmap(0),
15           m_pPixmapBack(nullptr),
16           m_bDisabledLoaded(false) {
17     setPositions(0);
18 }
19 
~WDisplay()20 WDisplay::~WDisplay() {
21     resetPositions();
22 }
23 
setup(const QDomNode & node,const SkinContext & context)24 void WDisplay::setup(const QDomNode& node, const SkinContext& context) {
25     // Set background pixmap if available
26 
27     QDomElement backPathNode = context.selectElement(node, "BackPath");
28     if (!backPathNode.isNull()) {
29         setPixmapBackground(context.getPixmapSource(backPathNode),
30                             context.selectScaleMode(backPathNode, Paintable::TILE),
31                             context.getScaleFactor());
32     }
33 
34     // Number of states
35     setPositions(context.selectInt(node, "NumberStates"));
36 
37     // Load knob pixmaps
38     QDomElement pathNode = context.selectElement(node, "Path");
39     QString path = context.nodeToString(pathNode);
40     // The implicit default in <1.12.0 was FIXED so we keep it for
41     // backwards compatibility.
42     Paintable::DrawMode pathMode =
43             context.selectScaleMode(pathNode, Paintable::FIXED);
44     for (int i = 0; i < m_pixmaps.size(); ++i) {
45         setPixmap(&m_pixmaps, i, context.makeSkinPath(path.arg(i)),
46                   pathMode, context.getScaleFactor());
47     }
48 
49     // See if disabled images is defined, and load them...
50     QDomElement disabledNode = context.selectElement(node, "DisabledPath");
51     if (!disabledNode.isNull()) {
52         QString disabledPath = context.nodeToString(disabledNode);
53         // The implicit default in <1.12.0 was FIXED so we keep it for
54         // backwards compatibility.
55         Paintable::DrawMode disabledMode =
56             context.selectScaleMode(disabledNode, Paintable::FIXED);
57         for (int i = 0; i < m_disabledPixmaps.size(); ++i) {
58             setPixmap(&m_disabledPixmaps, i,
59                       context.makeSkinPath(disabledPath.arg(i)),
60                       disabledMode, context.getScaleFactor());
61         }
62         m_bDisabledLoaded = true;
63     }
64 }
65 
setPositions(int iNoPos)66 void WDisplay::setPositions(int iNoPos) {
67     resetPositions();
68 
69     if (iNoPos < 0) {
70         qWarning() << "Negative NumberStates for Display.";
71         iNoPos = 0;
72     }
73 
74     // QVector inserts NULLs for the new pixmaps.
75     m_pixmaps.resize(iNoPos);
76     m_disabledPixmaps.resize(iNoPos);
77 }
78 
resetPositions()79 void WDisplay::resetPositions() {
80     m_pPixmapBack.clear();
81     m_pixmaps.resize(0);
82     m_disabledPixmaps.resize(0);
83 }
84 
setPixmapBackground(const PixmapSource & source,Paintable::DrawMode mode,double scaleFactor)85 void WDisplay::setPixmapBackground(const PixmapSource& source,
86         Paintable::DrawMode mode,
87         double scaleFactor) {
88     m_pPixmapBack = WPixmapStore::getPaintable(source, mode, scaleFactor);
89     if (m_pPixmapBack.isNull() || m_pPixmapBack->isNull()) {
90         qDebug() << metaObject()->className()
91                  << "Error loading background pixmap:" << source.getPath();
92     }
93 }
94 
setPixmap(QVector<PaintablePointer> * pPixmaps,int iPos,const QString & filename,Paintable::DrawMode mode,double scaleFactor)95 void WDisplay::setPixmap(
96         QVector<PaintablePointer>* pPixmaps,
97         int iPos,
98         const QString& filename,
99         Paintable::DrawMode mode,
100         double scaleFactor) {
101     if (iPos < 0 || iPos >= pPixmaps->size()) {
102         return;
103     }
104 
105     PixmapSource source(filename);
106     PaintablePointer pPixmap = WPixmapStore::getPaintable(source, mode, scaleFactor);
107     if (pPixmap.isNull() || pPixmap->isNull()) {
108         qDebug() << metaObject()->className()
109                  << "Error loading pixmap:" << filename;
110     } else {
111         (*pPixmaps)[iPos] = pPixmap;
112         if (mode == Paintable::FIXED) {
113             setFixedSize(pPixmap->size());
114         }
115     }
116 }
117 
getPixmapForParameter(double dParameter) const118 int WDisplay::getPixmapForParameter(double dParameter) const {
119     // When there are an even number of pixmaps by convention we want a value of
120     // 0.5 to align to the lower of the two middle pixmaps. In Mixxx < 1.12.0 we
121     // accomplished this by the below formula:
122     // index = (m_value - 64.0/127.0) * (numPixmaps() - 1) + numPixmaps() / 2.0;
123 
124     // But it's just as good to use m_value * numPixmaps() - epsilon. Using
125     // numPixmaps() instead of numPixmaps() - 1 ensures that every pixmap shares
126     // an equal slice of the value. Using m_value * (numPixmaps() - 1) gives an
127     // unequal slice of the value to the last pixmaps.
128 
129     // Example:
130     // 3 pixmaps
131     // m_value * numPixmaps()
132     // idx: 0       1       2       3
133     // val: 0.0 ... 0.3 ... 0.6 ... 1.0
134     // Even distribution of value range, value 1 is out of bounds (3).
135 
136     // m_value * (numPixmaps() - 1)
137     // idx: 0       1       2
138     // val: 0.0 ... 0.5 ... 1.0
139     // Pixmap 2 is only shown at value 1.
140 
141     // floor(m_value * (numPixmaps() - 1) + 0.5)
142     // idx: 0       1        2
143     // val: 0.0 ... 0.25 ... 0.75 ... 1.0
144     // Pixmap 0 and Pixmap 2 only shown for 0.25 of value range
145 
146     // 4 pixmaps
147     // m_value * numPixmaps()
148     // idx: 0       1        2       3        4
149     // val: 0.0 ... 0.25 ... 0.5 ... 0.75 ... 1.0
150     // Even distribution of value range, value 1 is out of bounds (4).
151 
152     // Subtracting an epsilon prevents out of bound values at the end of the
153     // range and biases the middle value towards the lower of the 2 center
154     // pixmaps when there are an even number of pixmaps.
155     return static_cast<int>(dParameter * numPixmaps() - 0.00001);
156 }
157 
onConnectedControlChanged(double dParameter,double dValue)158 void WDisplay::onConnectedControlChanged(double dParameter, double dValue) {
159     Q_UNUSED(dValue);
160     int pixmap = getPixmapForParameter(dParameter);
161     if (pixmap != m_iCurrentPixmap) {
162         // paintEvent updates m_iCurrentPixmap.
163         update();
164     }
165 }
166 
paintEvent(QPaintEvent *)167 void WDisplay::paintEvent(QPaintEvent* /*unused*/) {
168     QStyleOption option;
169     option.initFrom(this);
170     QStylePainter p(this);
171     p.drawPrimitive(QStyle::PE_Widget, option);
172 
173     if (m_pPixmapBack) {
174         m_pPixmapBack->draw(rect(), &p);
175     }
176 
177     // If we are disabled, use the disabled pixmaps. If not, use the regular
178     // pixmaps.
179     const QVector<PaintablePointer>& pixmaps = (!isEnabled() && m_bDisabledLoaded) ?
180             m_disabledPixmaps : m_pixmaps;
181 
182     if (pixmaps.empty()) {
183         return;
184     }
185 
186     int idx = getPixmapForParameter(getControlParameterDisplay());
187 
188     // onConnectedControlChanged uses this to detect no-ops but it does not
189     // clamp so don't clamp.
190     m_iCurrentPixmap = idx;
191 
192     // Clamp active pixmap index to valid ranges.
193     if (idx < 0) {
194         idx = 0;
195     } else if (idx >= pixmaps.size()) {
196         idx = pixmaps.size() - 1;
197     }
198 
199     PaintablePointer pPixmap = pixmaps[idx];
200     if (pPixmap) {
201         pPixmap->draw(rect(), &p);
202     }
203 }
204