1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "deviceskin.h" |
5 | |
6 | #include <QtCore/qnamespace.h> |
7 | #include <QtWidgets/QApplication> |
8 | #include <QtGui/QBitmap> |
9 | #include <QtGui/QPixmap> |
10 | #include <QtGui/QPainter> |
11 | #include <QtCore/QTextStream> |
12 | #include <QtCore/QFile> |
13 | #include <QtCore/QFileInfo> |
14 | #include <QtGui/QImage> |
15 | #include <QtCore/QTimer> |
16 | #include <QtCore/QDir> |
17 | #include <QtCore/QRegularExpression> |
18 | #include <QtGui/QMouseEvent> |
19 | #include <QtCore/QDebug> |
20 | |
21 | #ifdef TEST_SKIN |
22 | # include <QtWidgets/QMainWindow> |
23 | # include <QtWidgets/QDialog> |
24 | # include <QtWidgets/QDialogButtonBox> |
25 | # include <QtWidgets/QHBoxLayout> |
26 | #endif |
27 | |
28 | QT_BEGIN_NAMESPACE |
29 | |
30 | using namespace Qt::StringLiterals; |
31 | |
32 | namespace { |
33 | enum { joydistance = 10, key_repeat_period = 50, key_repeat_delay = 500 }; |
34 | enum { debugDeviceSkin = 0 }; |
35 | } |
36 | |
37 | static void parseRect(const QString &value, QRect *rect) { |
38 | const auto l = QStringView{value}.split(sep: QLatin1Char(' ')); |
39 | rect->setRect(ax: l[0].toInt(), ay: l[1].toInt(), aw: l[2].toInt(), ah: l[3].toInt()); |
40 | } |
41 | |
42 | static QString msgImageNotLoaded(const QString &f) { |
43 | return DeviceSkin::tr(s: "The image file '%1' could not be loaded." ).arg(a: f); |
44 | } |
45 | |
46 | // ------------ DeviceSkinButtonArea |
47 | QDebug &operator<<(QDebug &str, const DeviceSkinButtonArea &a) |
48 | { |
49 | |
50 | str << "Area: " << a.name << " keyCode=" << a.keyCode << " area=" << a.area |
51 | << " text=" << a.text << " activeWhenClosed=" << a.activeWhenClosed; |
52 | return str; |
53 | } |
54 | |
55 | // ------------ DeviceSkinParameters |
56 | |
57 | QDebug operator<<(QDebug str, const DeviceSkinParameters &p) |
58 | { |
59 | str << "Images " << p.skinImageUpFileName << ',' |
60 | << p.skinImageDownFileName<< ',' << p.skinImageClosedFileName |
61 | << ',' << p.skinCursorFileName <<"\nScreen: " << p.screenRect |
62 | << " back: " << p.backScreenRect << " closed: " << p.closedScreenRect |
63 | << " cursor: " << p.cursorHot << " Prefix: " << p.prefix |
64 | << " Joystick: " << p.joystick << " MouseHover" << p.hasMouseHover; |
65 | const int numAreas = p.buttonAreas.size(); |
66 | for (int i = 0; i < numAreas; i++) |
67 | str << p.buttonAreas[i]; |
68 | return str; |
69 | } |
70 | |
71 | QSize DeviceSkinParameters::secondaryScreenSize() const |
72 | { |
73 | return backScreenRect.isNull() ? closedScreenRect .size(): backScreenRect.size(); |
74 | } |
75 | |
76 | bool DeviceSkinParameters::hasSecondaryScreen() const |
77 | { |
78 | return secondaryScreenSize() != QSize(0, 0); |
79 | } |
80 | |
81 | bool DeviceSkinParameters::read(const QString &skinDirectory, ReadMode rm, QString *errorMessage) |
82 | { |
83 | // Figure out the name. remove ending '/' if present |
84 | QString skinFile = skinDirectory; |
85 | if (skinFile.endsWith(c: QLatin1Char('/'))) |
86 | skinFile.truncate(pos: skinFile.size() - 1); |
87 | |
88 | QFileInfo fi(skinFile); |
89 | QString fn; |
90 | if ( fi.isDir() ) { |
91 | prefix = skinFile; |
92 | prefix += QLatin1Char('/'); |
93 | fn = prefix; |
94 | fn += fi.baseName(); |
95 | fn += ".skin"_L1 ; |
96 | } else if (fi.isFile()){ |
97 | fn = skinFile; |
98 | prefix = fi.path(); |
99 | prefix += QLatin1Char('/'); |
100 | } else { |
101 | *errorMessage = DeviceSkin::tr(s: "The skin directory '%1' does not contain a configuration file." ).arg(a: skinDirectory); |
102 | return false; |
103 | } |
104 | QFile f(fn); |
105 | if (!f.open(flags: QIODevice::ReadOnly )) { |
106 | *errorMessage = DeviceSkin::tr(s: "The skin configuration file '%1' could not be opened." ).arg(a: fn); |
107 | return false; |
108 | } |
109 | QTextStream ts(&f); |
110 | const bool rc = read(ts, rm, errorMessage); |
111 | if (!rc) |
112 | *errorMessage = DeviceSkin::tr(s: "The skin configuration file '%1' could not be read: %2" ) |
113 | .arg(args&: fn, args&: *errorMessage); |
114 | return rc; |
115 | } |
116 | bool DeviceSkinParameters::read(QTextStream &ts, ReadMode rm, QString *errorMessage) |
117 | { |
118 | QStringList closedAreas; |
119 | QStringList toggleAreas; |
120 | QStringList toggleActiveAreas; |
121 | int nareas = 0; |
122 | screenDepth = 0; |
123 | QString mark; |
124 | ts >> mark; |
125 | hasMouseHover = true; // historical default |
126 | if (mark == "[SkinFile]"_L1 ) { |
127 | const QString UpKey = "Up"_L1 ; |
128 | const QString DownKey = "Down"_L1 ; |
129 | const QString ClosedKey = "Closed"_L1 ; |
130 | const QString ClosedAreasKey = "ClosedAreas"_L1 ; |
131 | const QString ScreenKey = "Screen"_L1 ; |
132 | const QString ScreenDepthKey = "ScreenDepth"_L1 ; |
133 | const QString BackScreenKey = "BackScreen"_L1 ; |
134 | const QString ClosedScreenKey = "ClosedScreen"_L1 ; |
135 | const QString CursorKey = "Cursor"_L1 ; |
136 | const QString AreasKey = "Areas"_L1 ; |
137 | const QString ToggleAreasKey = "ToggleAreas"_L1 ; |
138 | const QString ToggleActiveAreasKey = "ToggleActiveAreas"_L1 ; |
139 | const QString HasMouseHoverKey = "HasMouseHover"_L1 ; |
140 | // New |
141 | while (!nareas) { |
142 | QString line = ts.readLine(); |
143 | if ( line.isNull() ) |
144 | break; |
145 | if (!line.isEmpty() && line.at(i: 0) != u'#') { |
146 | int eq = line.indexOf(c: QLatin1Char('=')); |
147 | if ( eq >= 0 ) { |
148 | const QString key = line.left(n: eq); |
149 | eq++; |
150 | while (eq<line.size()-1 && line[eq].isSpace()) |
151 | eq++; |
152 | const QString value = line.mid(position: eq); |
153 | if ( key == UpKey ) { |
154 | skinImageUpFileName = value; |
155 | } else if ( key == DownKey ) { |
156 | skinImageDownFileName = value; |
157 | } else if ( key == ClosedKey ) { |
158 | skinImageClosedFileName = value; |
159 | } else if ( key == ClosedAreasKey ) { |
160 | closedAreas = value.split(sep: QLatin1Char(' ')); |
161 | } else if ( key == ScreenKey ) { |
162 | parseRect( value, rect: &screenRect); |
163 | } else if ( key == ScreenDepthKey ) { |
164 | screenDepth = value.toInt(); |
165 | } else if ( key == BackScreenKey ) { |
166 | parseRect(value, rect: &backScreenRect); |
167 | } else if ( key == ClosedScreenKey ) { |
168 | parseRect( value, rect: &closedScreenRect ); |
169 | } else if ( key == CursorKey ) { |
170 | QStringList l = value.split(sep: QLatin1Char(' ')); |
171 | skinCursorFileName = l[0]; |
172 | cursorHot = QPoint(l[1].toInt(),l[2].toInt()); |
173 | } else if ( key == AreasKey ) { |
174 | nareas = value.toInt(); |
175 | } else if ( key == ToggleAreasKey ) { |
176 | toggleAreas = value.split(sep: QLatin1Char(' ')); |
177 | } else if ( key == ToggleActiveAreasKey ) { |
178 | toggleActiveAreas = value.split(sep: QLatin1Char(' ')); |
179 | } else if ( key == HasMouseHoverKey ) { |
180 | hasMouseHover = value == "true"_L1 || value == "1"_L1 ; |
181 | } |
182 | } else { |
183 | *errorMessage = DeviceSkin::tr(s: "Syntax error: %1" ).arg(a: line); |
184 | return false; |
185 | } |
186 | } |
187 | } |
188 | } else { |
189 | // Old |
190 | skinImageUpFileName = mark; |
191 | QString s; |
192 | int x,y,w,h,na; |
193 | ts >> s >> x >> y >> w >> h >> na; |
194 | skinImageDownFileName = s; |
195 | screenRect.setRect(ax: x, ay: y, aw: w, ah: h); |
196 | nareas = na; |
197 | } |
198 | // Done for short mode |
199 | if (rm == ReadSizeOnly) |
200 | return true; |
201 | // verify skin files exist |
202 | skinImageUpFileName.insert(i: 0, s: prefix); |
203 | if (!QFile(skinImageUpFileName).exists()) { |
204 | *errorMessage = DeviceSkin::tr(s: "The skin \"up\" image file '%1' does not exist." ).arg(a: skinImageUpFileName); |
205 | return false; |
206 | } |
207 | if (!skinImageUp.load(fileName: skinImageUpFileName)) { |
208 | *errorMessage = msgImageNotLoaded(f: skinImageUpFileName); |
209 | return false; |
210 | } |
211 | |
212 | skinImageDownFileName.insert(i: 0, s: prefix); |
213 | if (!QFile(skinImageDownFileName).exists()) { |
214 | *errorMessage = DeviceSkin::tr(s: "The skin \"down\" image file '%1' does not exist." ).arg(a: skinImageDownFileName); |
215 | return false; |
216 | } |
217 | if (!skinImageDown.load(fileName: skinImageDownFileName)) { |
218 | *errorMessage = msgImageNotLoaded(f: skinImageDownFileName); |
219 | return false; |
220 | } |
221 | |
222 | if (!skinImageClosedFileName.isEmpty()) { |
223 | skinImageClosedFileName.insert(i: 0, s: prefix); |
224 | if (!QFile(skinImageClosedFileName).exists()) { |
225 | *errorMessage = DeviceSkin::tr(s: "The skin \"closed\" image file '%1' does not exist." ).arg(a: skinImageClosedFileName); |
226 | return false; |
227 | } |
228 | if (!skinImageClosed.load(fileName: skinImageClosedFileName)) { |
229 | *errorMessage = msgImageNotLoaded(f: skinImageClosedFileName); |
230 | return false; |
231 | } |
232 | } |
233 | |
234 | if (!skinCursorFileName.isEmpty()) { |
235 | skinCursorFileName.insert(i: 0, s: prefix); |
236 | if (!QFile(skinCursorFileName).exists()) { |
237 | *errorMessage = DeviceSkin::tr(s: "The skin cursor image file '%1' does not exist." ).arg(a: skinCursorFileName); |
238 | return false; |
239 | } |
240 | if (!skinCursor.load(fileName: skinCursorFileName)) { |
241 | *errorMessage = msgImageNotLoaded(f: skinCursorFileName); |
242 | return false; |
243 | } |
244 | } |
245 | |
246 | // read areas |
247 | if (!nareas) |
248 | return true; |
249 | buttonAreas.reserve(asize: nareas); |
250 | |
251 | int i = 0; |
252 | ts.readLine(); // eol |
253 | joystick = -1; |
254 | const QString Joystick = "Joystick"_L1 ; |
255 | const QRegularExpression splitRe("[ \t][ \t]*"_L1 ); |
256 | Q_ASSERT(splitRe.isValid()); |
257 | while (i < nareas && !ts.atEnd() ) { |
258 | buttonAreas.push_back(t: DeviceSkinButtonArea()); |
259 | DeviceSkinButtonArea &area = buttonAreas.back(); |
260 | const QString line = ts.readLine(); |
261 | if ( !line.isEmpty() && line[0] != QLatin1Char('#') ) { |
262 | const QStringList tok = line.split(sep: splitRe); |
263 | if ( tok.size()<6 ) { |
264 | *errorMessage = DeviceSkin::tr(s: "Syntax error in area definition: %1" ).arg(a: line); |
265 | return false; |
266 | } else { |
267 | area.name = tok[0]; |
268 | QString k = tok[1]; |
269 | if ( k.left(n: 2).toLower() == "0x"_L1 ) { |
270 | area.keyCode = k.mid(position: 2).toInt(ok: 0,base: 16); |
271 | } else { |
272 | area.keyCode = k.toInt(); |
273 | } |
274 | |
275 | int p=0; |
276 | for (int j=2; j < tok.size() - 1; ) { |
277 | const int x = tok[j++].toInt(); |
278 | const int y = tok[j++].toInt(); |
279 | area.area.putPoints(index: p++,nPoints: 1,firstx: x,firsty: y); |
280 | } |
281 | |
282 | const QChar doubleQuote = QLatin1Char('"'); |
283 | if ( area.name[0] == doubleQuote && area.name.endsWith(c: doubleQuote)) { |
284 | area.name.truncate(pos: area.name.size() - 1); |
285 | area.name.remove(i: 0, len: 1); |
286 | } |
287 | if ( area.name.size() == 1 ) |
288 | area.text = area.name; |
289 | if ( area.name == Joystick) |
290 | joystick = i; |
291 | area.activeWhenClosed = closedAreas.contains(str: area.name) |
292 | || area.keyCode == Qt::Key_Flip; // must be to work |
293 | area.toggleArea = toggleAreas.contains(str: area.name); |
294 | area.toggleActiveArea = toggleActiveAreas.contains(str: area.name); |
295 | if (area.toggleArea) |
296 | toggleAreaList += i; |
297 | i++; |
298 | } |
299 | } |
300 | } |
301 | if (i != nareas) { |
302 | qWarning() << DeviceSkin::tr(s: "Mismatch in number of areas, expected %1, got %2." ) |
303 | .arg(a: nareas).arg(a: i); |
304 | } |
305 | if (debugDeviceSkin) |
306 | qDebug() << *this; |
307 | return true; |
308 | } |
309 | |
310 | // --------- CursorWindow declaration |
311 | |
312 | namespace qvfb_internal { |
313 | |
314 | class CursorWindow : public QWidget |
315 | { |
316 | public: |
317 | explicit CursorWindow(const QImage &cursor, QPoint hot, QWidget *sk); |
318 | |
319 | void setView(QWidget*); |
320 | void setPos(QPoint); |
321 | bool handleMouseEvent(QEvent *ev); |
322 | |
323 | protected: |
324 | bool event(QEvent *) override; |
325 | bool eventFilter(QObject*, QEvent *) override; |
326 | |
327 | private: |
328 | QWidget *mouseRecipient; |
329 | QWidget *m_view; |
330 | QWidget *skin; |
331 | QPoint hotspot; |
332 | }; |
333 | } |
334 | |
335 | // --------- Skin |
336 | |
337 | DeviceSkin::DeviceSkin(const DeviceSkinParameters ¶meters, QWidget *p ) : |
338 | QWidget(p), |
339 | m_parameters(parameters), |
340 | buttonRegions(parameters.buttonAreas.size(), QRegion()), |
341 | parent(p), |
342 | m_view(0), |
343 | m_secondaryView(0), |
344 | buttonPressed(false), |
345 | buttonIndex(0), |
346 | cursorw(0), |
347 | joydown(0), |
348 | t_skinkey(new QTimer(this)), |
349 | t_parentmove(new QTimer(this)), |
350 | flipped_open(true) |
351 | { |
352 | Q_ASSERT(p); |
353 | setMouseTracking(true); |
354 | setAttribute(Qt::WA_NoSystemBackground); |
355 | |
356 | setZoom(1.0); |
357 | connect(sender: t_skinkey, signal: &QTimer::timeout, context: this, slot: &DeviceSkin::skinKeyRepeat ); |
358 | t_parentmove->setSingleShot( true ); |
359 | connect(sender: t_parentmove, signal: &QTimer::timeout, context: this, slot: &DeviceSkin::moveParent ); |
360 | } |
361 | |
362 | void DeviceSkin::skinKeyRepeat() |
363 | { |
364 | if ( m_view ) { |
365 | const DeviceSkinButtonArea &area = m_parameters.buttonAreas[buttonIndex]; |
366 | emit skinKeyReleaseEvent(code: area.keyCode,text: area.text, autorep: true); |
367 | emit skinKeyPressEvent(code: area.keyCode, text: area.text, autorep: true); |
368 | t_skinkey->start(msec: key_repeat_period); |
369 | } |
370 | } |
371 | |
372 | void DeviceSkin::calcRegions() |
373 | { |
374 | const int numAreas = m_parameters.buttonAreas.size(); |
375 | for (int i=0; i<numAreas; i++) { |
376 | QPolygon xa(m_parameters.buttonAreas[i].area.size()); |
377 | int n = m_parameters.buttonAreas[i].area.size(); |
378 | for (int p = 0; p < n; p++) { |
379 | xa.setPoint(index: p,pt: transform.map(p: m_parameters.buttonAreas[i].area[p])); |
380 | } |
381 | if (n == 2) { |
382 | buttonRegions[i] = QRegion(xa.boundingRect()); |
383 | } else { |
384 | buttonRegions[i] = QRegion(xa); |
385 | } |
386 | } |
387 | } |
388 | |
389 | void DeviceSkin::loadImages() |
390 | { |
391 | QImage iup = m_parameters.skinImageUp; |
392 | QImage idown = m_parameters.skinImageDown; |
393 | |
394 | QImage iclosed; |
395 | const bool hasClosedImage = !m_parameters.skinImageClosed.isNull(); |
396 | |
397 | if (hasClosedImage) |
398 | iclosed = m_parameters.skinImageClosed; |
399 | QImage icurs; |
400 | const bool hasCursorImage = !m_parameters.skinCursor.isNull(); |
401 | if (hasCursorImage) |
402 | icurs = m_parameters.skinCursor; |
403 | |
404 | if (!transform.isIdentity()) { |
405 | iup = iup.transformed(matrix: transform, mode: Qt::SmoothTransformation); |
406 | idown = idown.transformed(matrix: transform, mode: Qt::SmoothTransformation); |
407 | if (hasClosedImage) |
408 | iclosed = iclosed.transformed(matrix: transform, mode: Qt::SmoothTransformation); |
409 | if (hasCursorImage) |
410 | icurs = icurs.transformed(matrix: transform, mode: Qt::SmoothTransformation); |
411 | } |
412 | const Qt::ImageConversionFlags conv = Qt::ThresholdAlphaDither|Qt::AvoidDither; |
413 | skinImageUp = QPixmap::fromImage(image: iup); |
414 | skinImageDown = QPixmap::fromImage(image: idown, flags: conv); |
415 | if (hasClosedImage) |
416 | skinImageClosed = QPixmap::fromImage(image: iclosed, flags: conv); |
417 | if (hasCursorImage) |
418 | skinCursor = QPixmap::fromImage(image: icurs, flags: conv); |
419 | |
420 | setFixedSize( skinImageUp.size() ); |
421 | if (!skinImageUp.mask()) |
422 | skinImageUp.setMask(skinImageUp.createHeuristicMask()); |
423 | if (!skinImageClosed.mask()) |
424 | skinImageClosed.setMask(skinImageClosed.createHeuristicMask()); |
425 | |
426 | QWidget* parent = parentWidget(); |
427 | parent->setMask( skinImageUp.mask() ); |
428 | parent->setFixedSize( skinImageUp.size() ); |
429 | |
430 | delete cursorw; |
431 | cursorw = 0; |
432 | if (hasCursorImage) { |
433 | cursorw = new qvfb_internal::CursorWindow(m_parameters.skinCursor, m_parameters.cursorHot, this); |
434 | if (m_view) |
435 | cursorw->setView(m_view); |
436 | } |
437 | } |
438 | |
439 | DeviceSkin::~DeviceSkin( ) |
440 | { |
441 | delete cursorw; |
442 | } |
443 | |
444 | void DeviceSkin::setTransform(const QTransform &wm) |
445 | { |
446 | transform = QImage::trueMatrix(wm,w: m_parameters.skinImageUp.width(),h: m_parameters.skinImageUp.height()); |
447 | calcRegions(); |
448 | loadImages(); |
449 | if ( m_view ) { |
450 | QPoint p = transform.map(a: QPolygon(m_parameters.screenRect)).boundingRect().topLeft(); |
451 | m_view->move(p); |
452 | } |
453 | updateSecondaryScreen(); |
454 | } |
455 | |
456 | void DeviceSkin::setZoom( double z ) |
457 | { |
458 | setTransform(QTransform().scale(sx: z,sy: z)); |
459 | } |
460 | |
461 | void DeviceSkin::updateSecondaryScreen() |
462 | { |
463 | if (!m_secondaryView) |
464 | return; |
465 | if (flipped_open) { |
466 | if (m_parameters.backScreenRect.isNull()) { |
467 | m_secondaryView->hide(); |
468 | } else { |
469 | m_secondaryView->move(transform.map(a: QPolygon(m_parameters.backScreenRect)).boundingRect().topLeft()); |
470 | m_secondaryView->show(); |
471 | } |
472 | } else { |
473 | if (m_parameters.closedScreenRect.isNull()) { |
474 | m_secondaryView->hide(); |
475 | } else { |
476 | m_secondaryView->move(transform.map(a: QPolygon(m_parameters.closedScreenRect)).boundingRect().topLeft()); |
477 | m_secondaryView->show(); |
478 | } |
479 | } |
480 | } |
481 | |
482 | void DeviceSkin::setView( QWidget *v ) |
483 | { |
484 | m_view = v; |
485 | m_view->setFocus(); |
486 | m_view->move(transform.map(a: QPolygon(m_parameters.screenRect)).boundingRect().topLeft()); |
487 | if ( cursorw ) |
488 | cursorw->setView(v); |
489 | } |
490 | |
491 | void DeviceSkin::setSecondaryView( QWidget *v ) |
492 | { |
493 | m_secondaryView = v; |
494 | updateSecondaryScreen(); |
495 | } |
496 | |
497 | void DeviceSkin::paintEvent( QPaintEvent *) |
498 | { |
499 | QPainter p( this ); |
500 | if ( flipped_open ) { |
501 | p.drawPixmap(x: 0, y: 0, pm: skinImageUp); |
502 | } else { |
503 | p.drawPixmap(x: 0, y: 0, pm: skinImageClosed); |
504 | } |
505 | QList<int> toDraw; |
506 | if ( buttonPressed == true ) { |
507 | toDraw += buttonIndex; |
508 | } |
509 | for (int toggle : std::as_const(t: m_parameters.toggleAreaList)) { |
510 | const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[toggle]; |
511 | if (flipped_open || ba.activeWhenClosed) { |
512 | if (ba.toggleArea && ba.toggleActiveArea) |
513 | toDraw += toggle; |
514 | } |
515 | } |
516 | for (int button : std::as_const(t&: toDraw)) { |
517 | const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[button]; |
518 | const QRect r = buttonRegions[button].boundingRect(); |
519 | if ( ba.area.size() > 2 ) |
520 | p.setClipRegion(buttonRegions[button]); |
521 | p.drawPixmap( p: r.topLeft(), pm: skinImageDown, sr: r); |
522 | } |
523 | } |
524 | |
525 | void DeviceSkin::mousePressEvent( QMouseEvent *e ) |
526 | { |
527 | if (e->button() == Qt::RightButton) { |
528 | emit popupMenu(); |
529 | } else { |
530 | buttonPressed = false; |
531 | |
532 | onjoyrelease = -1; |
533 | const int numAreas = m_parameters.buttonAreas.size(); |
534 | for (int i = 0; i < numAreas ; i++) { |
535 | const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[i]; |
536 | if ( buttonRegions[i].contains( p: e->position().toPoint() ) ) { |
537 | if ( flipped_open || ba.activeWhenClosed ) { |
538 | if ( m_parameters.joystick == i ) { |
539 | joydown = true; |
540 | } else { |
541 | if ( joydown ) |
542 | onjoyrelease = i; |
543 | else |
544 | startPress(i); |
545 | break; |
546 | if (debugDeviceSkin)// Debug message to be sure we are clicking the right areas |
547 | qDebug()<< m_parameters.buttonAreas[i].name << " clicked" ; |
548 | } |
549 | } |
550 | } |
551 | } |
552 | clickPos = e->position().toPoint(); |
553 | // This is handy for finding the areas to define rectangles for new skins |
554 | if (debugDeviceSkin) |
555 | qDebug()<< "Clicked in " << e->position().toPoint().x() << ',' << e->position().toPoint().y(); |
556 | clickPos = e->position().toPoint(); |
557 | } |
558 | } |
559 | |
560 | void DeviceSkin::flip(bool open) |
561 | { |
562 | if ( flipped_open == open ) |
563 | return; |
564 | if ( open ) { |
565 | parent->setMask(skinImageUp.mask()); |
566 | emit skinKeyReleaseEvent(code: Qt::Key(Qt::Key_Flip), text: QString(), autorep: false); |
567 | } else { |
568 | parent->setMask(skinImageClosed.mask()); |
569 | emit skinKeyPressEvent(code: Qt::Key(Qt::Key_Flip), text: QString(), autorep: false); |
570 | } |
571 | flipped_open = open; |
572 | updateSecondaryScreen(); |
573 | repaint(); |
574 | } |
575 | |
576 | void DeviceSkin::startPress(int i) |
577 | { |
578 | buttonPressed = true; |
579 | buttonIndex = i; |
580 | if (m_view) { |
581 | const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[buttonIndex]; |
582 | if (ba.keyCode == Qt::Key_Flip) { |
583 | flip(open: !flipped_open); |
584 | } else if (ba.toggleArea) { |
585 | bool active = !ba.toggleActiveArea; |
586 | const_cast<DeviceSkinButtonArea &>(ba).toggleActiveArea = active; |
587 | if (active) |
588 | emit skinKeyPressEvent(code: ba.keyCode, text: ba.text, autorep: false); |
589 | else |
590 | emit skinKeyReleaseEvent(code: ba.keyCode, text: ba.text, autorep: false); |
591 | } else { |
592 | emit skinKeyPressEvent(code: ba.keyCode, text: ba.text, autorep: false); |
593 | t_skinkey->start(msec: key_repeat_delay); |
594 | } |
595 | repaint(buttonRegions[buttonIndex].boundingRect()); |
596 | } |
597 | } |
598 | |
599 | void DeviceSkin::endPress() |
600 | { |
601 | const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[buttonIndex]; |
602 | if (m_view && ba.keyCode != Qt::Key_Flip && !ba.toggleArea ) |
603 | emit skinKeyReleaseEvent(code: ba.keyCode, text: ba.text, autorep: false); |
604 | t_skinkey->stop(); |
605 | buttonPressed = false; |
606 | repaint( buttonRegions[buttonIndex].boundingRect() ); |
607 | } |
608 | |
609 | void DeviceSkin::mouseMoveEvent( QMouseEvent *e ) |
610 | { |
611 | if ( e->buttons() & Qt::LeftButton ) { |
612 | const int joystick = m_parameters.joystick; |
613 | QPoint newpos = e->globalPosition().toPoint() - clickPos; |
614 | if (joydown) { |
615 | int k1=0, k2=0; |
616 | if (newpos.x() < -joydistance) { |
617 | k1 = joystick+1; |
618 | } else if (newpos.x() > +joydistance) { |
619 | k1 = joystick+3; |
620 | } |
621 | if (newpos.y() < -joydistance) { |
622 | k2 = joystick+2; |
623 | } else if (newpos.y() > +joydistance) { |
624 | k2 = joystick+4; |
625 | } |
626 | if (k1 || k2) { |
627 | if (!buttonPressed) { |
628 | onjoyrelease = -1; |
629 | if (k1 && k2) { |
630 | startPress(i: k2); |
631 | endPress(); |
632 | } |
633 | startPress(i: k1 ? k1 : k2); |
634 | } |
635 | } else if (buttonPressed) { |
636 | endPress(); |
637 | } |
638 | } else if (buttonPressed == false) { |
639 | parentpos = newpos; |
640 | if (!t_parentmove->isActive()) |
641 | t_parentmove->start(msec: 50); |
642 | } |
643 | } |
644 | if ( cursorw ) |
645 | cursorw->setPos(e->globalPosition().toPoint()); |
646 | } |
647 | |
648 | void DeviceSkin::moveParent() |
649 | { |
650 | parent->move( parentpos ); |
651 | } |
652 | |
653 | void DeviceSkin::mouseReleaseEvent( QMouseEvent * ) |
654 | { |
655 | if ( buttonPressed ) |
656 | endPress(); |
657 | if ( joydown ) { |
658 | joydown = false; |
659 | if (onjoyrelease >= 0) { |
660 | startPress(i: onjoyrelease); |
661 | endPress(); |
662 | } |
663 | } |
664 | } |
665 | |
666 | bool DeviceSkin::hasCursor() const |
667 | { |
668 | return !skinCursor.isNull(); |
669 | } |
670 | |
671 | // ------------------ CursorWindow implementation |
672 | |
673 | namespace qvfb_internal { |
674 | |
675 | bool CursorWindow::eventFilter( QObject *, QEvent *ev) |
676 | { |
677 | handleMouseEvent(ev); |
678 | return false; |
679 | } |
680 | |
681 | bool CursorWindow::event( QEvent *ev ) |
682 | { |
683 | if (handleMouseEvent(ev)) |
684 | return true; |
685 | return QWidget::event(event: ev); |
686 | } |
687 | |
688 | bool CursorWindow::handleMouseEvent(QEvent *ev) |
689 | { |
690 | bool handledEvent = false; |
691 | static int inhere=0; |
692 | if ( !inhere ) { |
693 | inhere++; |
694 | if (m_view) { |
695 | if (ev->type() >= QEvent::MouseButtonPress && ev->type() <= QEvent::MouseMove) { |
696 | QMouseEvent *e = (QMouseEvent*)ev; |
697 | QPoint gp = e->globalPosition().toPoint(); |
698 | QPoint vp = m_view->mapFromGlobal(gp); |
699 | QPoint sp = skin->mapFromGlobal(gp); |
700 | if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) { |
701 | if (m_view->rect().contains(p: vp)) |
702 | mouseRecipient = m_view; |
703 | else if (skin->parentWidget()->geometry().contains(p: gp)) |
704 | mouseRecipient = skin; |
705 | else |
706 | mouseRecipient = 0; |
707 | } |
708 | if (mouseRecipient) { |
709 | setPos(gp); |
710 | QMouseEvent me(e->type(),mouseRecipient==skin ? sp : vp,gp,e->button(),e->buttons(),e->modifiers()); |
711 | QApplication::sendEvent(receiver: mouseRecipient, event: &me); |
712 | } else if (!skin->parentWidget()->geometry().contains(p: gp)) { |
713 | hide(); |
714 | } else { |
715 | setPos(gp); |
716 | } |
717 | if (e->type() == QEvent::MouseButtonRelease) |
718 | mouseRecipient = 0; |
719 | handledEvent = true; |
720 | } |
721 | } |
722 | inhere--; |
723 | } |
724 | return handledEvent; |
725 | } |
726 | |
727 | void CursorWindow::setView(QWidget* v) |
728 | { |
729 | if ( m_view ) { |
730 | m_view->removeEventFilter(obj: this); |
731 | m_view->removeEventFilter(obj: this); |
732 | } |
733 | m_view = v; |
734 | m_view->installEventFilter(filterObj: this); |
735 | m_view->installEventFilter(filterObj: this); |
736 | mouseRecipient = 0; |
737 | } |
738 | |
739 | CursorWindow::CursorWindow(const QImage &img, QPoint hot, QWidget* sk) |
740 | : QWidget(0), |
741 | m_view(0), |
742 | skin(sk), |
743 | hotspot(hot) |
744 | { |
745 | setWindowFlags( Qt::FramelessWindowHint ); |
746 | mouseRecipient = 0; |
747 | setMouseTracking(true); |
748 | #ifndef QT_NO_CURSOR |
749 | setCursor(Qt::BlankCursor); |
750 | #endif |
751 | QPixmap p; |
752 | p = QPixmap::fromImage(image: img); |
753 | if (!p.mask()) { |
754 | QBitmap bm = img.hasAlphaChannel() ? QBitmap::fromImage(image: img.createAlphaMask()) |
755 | : QBitmap::fromImage(image: img.createHeuristicMask()); |
756 | p.setMask(bm); |
757 | } |
758 | QPalette palette; |
759 | palette.setBrush(acr: backgroundRole(), abrush: QBrush(p)); |
760 | setPalette(palette); |
761 | setFixedSize( p.size() ); |
762 | if ( !p.mask().isNull() ) |
763 | setMask(p.mask()); |
764 | } |
765 | |
766 | void CursorWindow::setPos(QPoint p) |
767 | { |
768 | move(p-hotspot); |
769 | show(); |
770 | raise(); |
771 | } |
772 | } |
773 | |
774 | #ifdef TEST_SKIN |
775 | |
776 | int main(int argc,char *argv[]) |
777 | { |
778 | if (argc < 1) |
779 | return 1; |
780 | const QString skinFile = QString::fromUtf8(argv[1]); |
781 | QApplication app(argc,argv); |
782 | QMainWindow mw; |
783 | |
784 | DeviceSkinParameters params; |
785 | QString errorMessage; |
786 | if (!params.read(skinFile, DeviceSkinParameters::ReadAll, &errorMessage)) { |
787 | qWarning() << errorMessage; |
788 | return 1; |
789 | } |
790 | DeviceSkin ds(params, &mw); |
791 | // View Dialog |
792 | QDialog *dialog = new QDialog(); |
793 | QHBoxLayout *dialogLayout = new QHBoxLayout(); |
794 | dialog->setLayout(dialogLayout); |
795 | QDialogButtonBox *dialogButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); |
796 | QObject::connect(dialogButtonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); |
797 | QObject::connect(dialogButtonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); |
798 | dialogLayout->addWidget(dialogButtonBox); |
799 | dialog->setFixedSize(params.screenSize()); |
800 | dialog->setParent(&ds, Qt::SubWindow); |
801 | dialog->setAutoFillBackground(true); |
802 | ds.setView(dialog); |
803 | |
804 | QObject::connect(&ds, &DeviceSkin::popupMenu, &mw, &QWidget::close); |
805 | QObject::connect(&ds, &DeviceSkin::skinKeyPressEvent, &mw, &QWidget::close); |
806 | mw.show(); |
807 | return app.exec(); |
808 | } |
809 | |
810 | #endif |
811 | |
812 | QT_END_NAMESPACE |
813 | |
814 | |