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