6#include <QtWidgets/qapplication.h>
8#include <QtGui/qbitmap.h>
9#include <QtGui/qevent.h>
10#include <QtGui/qimage.h>
11#include <QtGui/qpainter.h>
12#include <QtGui/qpixmap.h>
14#include <QtCore/qdebug.h>
15#include <QtCore/qdir.h>
16#include <QtCore/qfile.h>
17#include <QtCore/qfileinfo.h>
18#include <QtCore/qnamespace.h>
19#include <QtCore/qregularexpression.h>
20#include <QtCore/qtextstream.h>
21#include <QtCore/qtimer.h>
24# include <QtWidgets/qmainwindow.h>
25# include <QtWidgets/qboxlayout.h>
26# include <QtWidgets/qdialog.h>
27# include <QtWidgets/qdialogbuttonbox.h>
32using namespace Qt::StringLiterals;
35 enum { joydistance = 10, key_repeat_period = 50, key_repeat_delay = 500 };
36 enum { debugDeviceSkin = 0 };
39static void parseRect(
const QString &value, QRect *rect) {
40 const auto l = QStringView{value}.split(QLatin1Char(
' '));
41 rect->setRect(l[0].toInt(), l[1].toInt(), l[2].toInt(), l[3].toInt());
45 return DeviceSkin::tr(
"The image file '%1' could not be loaded.").arg(f);
52 str <<
"Area: " << a.name <<
" keyCode=" << a
.keyCode <<
" area=" << a.area
61 str <<
"Images " << p.skinImageUpFileName <<
','
62 << p.skinImageDownFileName<<
',' << p.skinImageClosedFileName
63 <<
',' << p.skinCursorFileName <<
"\nScreen: " << p.screenRect
64 <<
" back: " << p.backScreenRect <<
" closed: " << p.closedScreenRect
65 <<
" cursor: " << p.cursorHot <<
" Prefix: " << p.prefix
67 const int numAreas = p.buttonAreas.size();
68 for (
int i = 0; i < numAreas; i++)
69 str << p.buttonAreas[i];
75 return backScreenRect.isNull() ? closedScreenRect .size(): backScreenRect.size();
80 return secondaryScreenSize() != QSize(0, 0);
86 QString skinFile = skinDirectory;
87 if (skinFile.endsWith(QLatin1Char(
'/')))
88 skinFile.truncate(skinFile.size() - 1);
90 QFileInfo fi(skinFile);
94 prefix += QLatin1Char(
'/');
98 }
else if (fi.isFile()){
101 prefix += QLatin1Char(
'/');
103 *errorMessage = DeviceSkin::tr(
"The skin directory '%1' does not contain a configuration file.").arg(skinDirectory);
107 if (!f.open(QIODevice::ReadOnly )) {
108 *errorMessage = DeviceSkin::tr(
"The skin configuration file '%1' could not be opened.").arg(fn);
112 const bool rc = read(ts, rm, errorMessage);
114 *errorMessage = DeviceSkin::tr(
"The skin configuration file '%1' could not be read: %2")
115 .arg(fn, *errorMessage);
120 QStringList closedAreas;
121 QStringList toggleAreas;
122 QStringList toggleActiveAreas;
128 if (mark ==
"[SkinFile]"_L1) {
129 const QString UpKey =
"Up"_L1;
130 const QString DownKey =
"Down"_L1;
131 const QString ClosedKey =
"Closed"_L1;
132 const QString ClosedAreasKey =
"ClosedAreas"_L1;
133 const QString ScreenKey =
"Screen"_L1;
134 const QString ScreenDepthKey =
"ScreenDepth"_L1;
135 const QString BackScreenKey =
"BackScreen"_L1;
136 const QString ClosedScreenKey =
"ClosedScreen"_L1;
137 const QString CursorKey =
"Cursor"_L1;
138 const QString AreasKey =
"Areas"_L1;
139 const QString ToggleAreasKey =
"ToggleAreas"_L1;
140 const QString ToggleActiveAreasKey =
"ToggleActiveAreas"_L1;
141 const QString HasMouseHoverKey =
"HasMouseHover"_L1;
144 QString line = ts.readLine();
147 if (!line.isEmpty() && line.at(0) != u'#') {
148 int eq = line.indexOf(QLatin1Char(
'='));
150 const QString key = line.left(eq);
152 while (eq<line.size()-1 && line[eq].isSpace())
154 const QString value = line.mid(eq);
155 if ( key == UpKey ) {
156 skinImageUpFileName = value;
157 }
else if ( key == DownKey ) {
158 skinImageDownFileName = value;
159 }
else if ( key == ClosedKey ) {
160 skinImageClosedFileName = value;
161 }
else if ( key == ClosedAreasKey ) {
162 closedAreas = value.split(QLatin1Char(
' '));
163 }
else if ( key == ScreenKey ) {
164 parseRect( value, &screenRect);
165 }
else if ( key == ScreenDepthKey ) {
167 }
else if ( key == BackScreenKey ) {
168 parseRect(value, &backScreenRect);
169 }
else if ( key == ClosedScreenKey ) {
170 parseRect( value, &closedScreenRect );
171 }
else if ( key == CursorKey ) {
172 QStringList l = value.split(QLatin1Char(
' '));
173 skinCursorFileName = l[0];
174 cursorHot = QPoint(l[1].toInt(),l[2].toInt());
175 }
else if ( key == AreasKey ) {
176 nareas = value.toInt();
177 }
else if ( key == ToggleAreasKey ) {
178 toggleAreas = value.split(QLatin1Char(
' '));
179 }
else if ( key == ToggleActiveAreasKey ) {
180 toggleActiveAreas = value.split(QLatin1Char(
' '));
181 }
else if ( key == HasMouseHoverKey ) {
182 hasMouseHover = value ==
"true"_L1 || value ==
"1"_L1;
185 *errorMessage = DeviceSkin::tr(
"Syntax error: %1").arg(line);
192 skinImageUpFileName = mark;
195 ts >> s >> x >> y >> w >> h >> na;
196 skinImageDownFileName = s;
197 screenRect.setRect(x, y, w, h);
204 skinImageUpFileName.insert(0, prefix);
205 if (!QFile(skinImageUpFileName).exists()) {
206 *errorMessage = DeviceSkin::tr(
"The skin \"up\" image file '%1' does not exist.").arg(skinImageUpFileName);
209 if (!skinImageUp.load(skinImageUpFileName)) {
210 *errorMessage = msgImageNotLoaded(skinImageUpFileName);
214 skinImageDownFileName.insert(0, prefix);
215 if (!QFile(skinImageDownFileName).exists()) {
216 *errorMessage = DeviceSkin::tr(
"The skin \"down\" image file '%1' does not exist.").arg(skinImageDownFileName);
219 if (!skinImageDown.load(skinImageDownFileName)) {
220 *errorMessage = msgImageNotLoaded(skinImageDownFileName);
224 if (!skinImageClosedFileName.isEmpty()) {
225 skinImageClosedFileName.insert(0, prefix);
226 if (!QFile(skinImageClosedFileName).exists()) {
227 *errorMessage = DeviceSkin::tr(
"The skin \"closed\" image file '%1' does not exist.").arg(skinImageClosedFileName);
230 if (!skinImageClosed.load(skinImageClosedFileName)) {
231 *errorMessage = msgImageNotLoaded(skinImageClosedFileName);
236 if (!skinCursorFileName.isEmpty()) {
237 skinCursorFileName.insert(0, prefix);
238 if (!QFile(skinCursorFileName).exists()) {
239 *errorMessage = DeviceSkin::tr(
"The skin cursor image file '%1' does not exist.").arg(skinCursorFileName);
242 if (!skinCursor.load(skinCursorFileName)) {
243 *errorMessage = msgImageNotLoaded(skinCursorFileName);
251 buttonAreas.reserve(nareas);
256 const QString Joystick =
"Joystick"_L1;
257 const QRegularExpression splitRe(
"[ \t][ \t]*"_L1);
258 Q_ASSERT(splitRe.isValid());
259 while (i < nareas && !ts.atEnd() ) {
260 buttonAreas.push_back(DeviceSkinButtonArea());
262 const QString line = ts.readLine();
263 if ( !line.isEmpty() && line[0] != QLatin1Char(
'#') ) {
264 const QStringList tok = line.split(splitRe);
265 if ( tok.size()<6 ) {
266 *errorMessage = DeviceSkin::tr(
"Syntax error in area definition: %1").arg(line);
271 if ( k.left(2).toLower() ==
"0x"_L1) {
272 area
.keyCode = k.mid(2).toInt(0,16);
278 for (
int j=2; j < tok.size() - 1; ) {
279 const int x = tok[j++].toInt();
280 const int y = tok[j++].toInt();
281 area.area.putPoints(p++,1,x,y);
284 const QChar doubleQuote = QLatin1Char(
'"');
285 if ( area.name[0] == doubleQuote && area.name.endsWith(doubleQuote)) {
286 area.name.truncate(area.name.size() - 1);
287 area.name.remove(0, 1);
289 if ( area.name.size() == 1 )
290 area.text = area.name;
291 if ( area.name == Joystick)
293 area.activeWhenClosed = closedAreas.contains(area.name)
294 || area.keyCode == Qt::Key_Flip;
304 qWarning() << DeviceSkin::tr(
"Mismatch in number of areas, expected %1, got %2.")
330 QWidget *mouseRecipient;
341 m_parameters(parameters),
342 buttonRegions(parameters.buttonAreas.size(), QRegion()),
344 t_skinkey(
new QTimer(
this)),
345 t_parentmove(
new QTimer(
this))
348 setMouseTracking(
true);
349 setAttribute(Qt::WA_NoSystemBackground);
352 connect(t_skinkey, &QTimer::timeout,
this, &DeviceSkin::skinKeyRepeat );
353 t_parentmove->setSingleShot(
true );
361 emit skinKeyReleaseEvent(area
.keyCode,area.text,
true);
362 emit skinKeyPressEvent(area
.keyCode, area.text,
true);
363 t_skinkey->start(key_repeat_period);
369 const int numAreas = m_parameters.buttonAreas.size();
370 for (
int i=0; i<numAreas; i++) {
371 QPolygon xa(m_parameters.buttonAreas[i].area.size());
372 int n = m_parameters.buttonAreas[i].area.size();
373 for (
int p = 0; p < n; p++) {
374 xa.setPoint(p,transform.map(m_parameters.buttonAreas[i].area[p]));
377 buttonRegions[i] = QRegion(xa.boundingRect());
379 buttonRegions[i] = QRegion(xa);
386 QImage iup = m_parameters.skinImageUp;
387 QImage idown = m_parameters.skinImageDown;
390 const bool hasClosedImage = !m_parameters.skinImageClosed.isNull();
393 iclosed = m_parameters.skinImageClosed;
395 const bool hasCursorImage = !m_parameters.skinCursor.isNull();
397 icurs = m_parameters.skinCursor;
399 if (!transform.isIdentity()) {
400 iup = iup.transformed(transform, Qt::SmoothTransformation);
401 idown = idown.transformed(transform, Qt::SmoothTransformation);
403 iclosed = iclosed.transformed(transform, Qt::SmoothTransformation);
405 icurs = icurs.transformed(transform, Qt::SmoothTransformation);
407 const Qt::ImageConversionFlags conv = Qt::ThresholdAlphaDither|Qt::AvoidDither;
408 skinImageUp = QPixmap::fromImage(iup);
409 skinImageDown = QPixmap::fromImage(idown, conv);
411 skinImageClosed = QPixmap::fromImage(iclosed, conv);
413 skinCursor = QPixmap::fromImage(icurs, conv);
415 setFixedSize( skinImageUp.size() );
416 if (!skinImageUp.mask())
417 skinImageUp.setMask(skinImageUp.createHeuristicMask());
418 if (!skinImageClosed.mask())
419 skinImageClosed.setMask(skinImageClosed.createHeuristicMask());
421 QWidget* parent = parentWidget();
422 parent->setMask( skinImageUp.mask() );
423 parent->setFixedSize( skinImageUp.size() );
427 if (hasCursorImage) {
428 cursorw =
new qvfb_internal::CursorWindow(m_parameters.skinCursor, m_parameters.cursorHot,
this);
430 cursorw->setView(m_view);
441 transform = QImage::trueMatrix(wm,m_parameters.skinImageUp.width(),m_parameters.skinImageUp.height());
445 QPoint p = transform.map(QPolygon(m_parameters.screenRect)).boundingRect().topLeft();
448 updateSecondaryScreen();
453 setTransform(QTransform().scale(z,z));
458 if (!m_secondaryView)
461 if (m_parameters.backScreenRect.isNull()) {
462 m_secondaryView->hide();
464 m_secondaryView->move(transform.map(QPolygon(m_parameters.backScreenRect)).boundingRect().topLeft());
465 m_secondaryView->show();
468 if (m_parameters.closedScreenRect.isNull()) {
469 m_secondaryView->hide();
471 m_secondaryView->move(transform.map(QPolygon(m_parameters.closedScreenRect)).boundingRect().topLeft());
472 m_secondaryView->show();
481 m_view->move(transform.map(QPolygon(m_parameters.screenRect)).boundingRect().topLeft());
489 updateSecondaryScreen();
495 if ( flipped_open ) {
496 p.drawPixmap(0, 0, skinImageUp);
498 p.drawPixmap(0, 0, skinImageClosed);
501 if ( buttonPressed ==
true ) {
502 toDraw += buttonIndex;
504 for (
int toggle : std::as_const(m_parameters.toggleAreaList)) {
505 const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[toggle];
506 if (flipped_open || ba.activeWhenClosed) {
507 if (ba.toggleArea && ba.toggleActiveArea)
511 for (
int button : std::as_const(toDraw)) {
512 const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[button];
513 const QRect r = buttonRegions[button].boundingRect();
514 if ( ba.area.size() > 2 )
515 p.setClipRegion(buttonRegions[button]);
516 p.drawPixmap( r.topLeft(), skinImageDown, r);
522 if (e->button() == Qt::RightButton) {
525 buttonPressed =
false;
528 const int numAreas = m_parameters.buttonAreas.size();
529 for (
int i = 0; i < numAreas ; i++) {
531 if ( buttonRegions[i].contains( e->position().toPoint() ) ) {
533 if ( m_parameters.joystick == i ) {
542 qDebug()<< m_parameters.buttonAreas[i].name <<
" clicked";
547 clickPos = e->position().toPoint();
550 qDebug()<<
"Clicked in " << e->position().toPoint().x() <<
',' << e->position().toPoint().y();
551 clickPos = e->position().toPoint();
557 if ( flipped_open == open )
560 parent->setMask(skinImageUp.mask());
561 emit skinKeyReleaseEvent(Qt::Key(Qt::Key_Flip), QString(),
false);
563 parent->setMask(skinImageClosed.mask());
564 emit skinKeyPressEvent(Qt::Key(Qt::Key_Flip), QString(),
false);
567 updateSecondaryScreen();
573 buttonPressed =
true;
577 if (ba.keyCode == Qt::Key_Flip) {
583 emit skinKeyPressEvent(ba
.keyCode, ba.text,
false);
585 emit skinKeyReleaseEvent(ba
.keyCode, ba.text,
false);
587 emit skinKeyPressEvent(ba
.keyCode, ba.text,
false);
588 t_skinkey->start(key_repeat_delay);
590 repaint(buttonRegions[buttonIndex].boundingRect());
597 if (m_view && ba.keyCode != Qt::Key_Flip && !ba.toggleArea )
598 emit skinKeyReleaseEvent(ba
.keyCode, ba.text,
false);
600 buttonPressed =
false;
601 repaint( buttonRegions[buttonIndex].boundingRect() );
606 if ( e->buttons() & Qt::LeftButton ) {
607 const int joystick = m_parameters.joystick;
608 QPoint newpos = e->globalPosition().toPoint() - clickPos;
611 if (newpos.x() < -joydistance) {
613 }
else if (newpos.x() > +joydistance) {
616 if (newpos.y() < -joydistance) {
618 }
else if (newpos.y() > +joydistance) {
622 if (!buttonPressed) {
628 startPress(k1 ? k1 : k2);
630 }
else if (buttonPressed) {
633 }
else if (buttonPressed ==
false) {
635 if (!t_parentmove->isActive())
636 t_parentmove->start(50);
640 cursorw->setPos(e->globalPosition().toPoint());
645 parent->move( parentpos );
654 if (onjoyrelease >= 0) {
655 startPress(onjoyrelease);
663 return !skinCursor.isNull();
672 handleMouseEvent(ev);
678 if (handleMouseEvent(ev))
680 return QWidget::event(ev);
685 bool handledEvent =
false;
690 if (ev->type() >= QEvent::MouseButtonPress && ev->type() <= QEvent::MouseMove) {
691 QMouseEvent *e = (QMouseEvent*)ev;
692 QPoint gp = e->globalPosition().toPoint();
693 QPoint vp = m_view->mapFromGlobal(gp);
694 QPoint sp = skin->mapFromGlobal(gp);
695 if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) {
696 if (m_view->rect().contains(vp))
697 mouseRecipient = m_view;
698 else if (skin->parentWidget()->geometry().contains(gp))
699 mouseRecipient = skin;
703 if (mouseRecipient) {
705 QMouseEvent me(e->type(),mouseRecipient==skin ? sp : vp,gp,e->button(),e->buttons(),e->modifiers());
706 QApplication::sendEvent(mouseRecipient, &me);
707 }
else if (!skin->parentWidget()->geometry().contains(gp)) {
712 if (e->type() == QEvent::MouseButtonRelease)
725 m_view->removeEventFilter(
this);
726 m_view->removeEventFilter(
this);
729 m_view->installEventFilter(
this);
730 m_view->installEventFilter(
this);
740 setWindowFlags( Qt::FramelessWindowHint );
742 setMouseTracking(
true);
744 setCursor(Qt::BlankCursor);
747 p = QPixmap::fromImage(img);
749 QBitmap bm = img.hasAlphaChannel() ? QBitmap::fromImage(img.createAlphaMask())
750 : QBitmap::fromImage(img.createHeuristicMask());
754 palette.setBrush(backgroundRole(), QBrush(p));
756 setFixedSize( p.size() );
757 if ( !p.mask().isNull() )
771int main(
int argc,
char *argv[])
775 const QString skinFile = QString::fromUtf8(argv[1]);
776 QApplication app(argc,argv);
779 DeviceSkinParameters params;
780 QString errorMessage;
781 if (!params.read(skinFile, DeviceSkinParameters::ReadAll, &errorMessage)) {
782 qWarning() << errorMessage;
785 DeviceSkin ds(params, &mw);
787 QDialog *dialog =
new QDialog();
788 QHBoxLayout *dialogLayout =
new QHBoxLayout();
789 dialog->setLayout(dialogLayout);
790 QDialogButtonBox *dialogButtonBox =
new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
791 QObject::connect(dialogButtonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
792 QObject::connect(dialogButtonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
793 dialogLayout->addWidget(dialogButtonBox);
794 dialog->setFixedSize(params.screenSize());
795 dialog->setParent(&ds, Qt::SubWindow);
796 dialog->setAutoFillBackground(
true);
799 QObject::connect(&ds, &DeviceSkin::popupMenu, &mw, &QWidget::close);
800 QObject::connect(&ds, &DeviceSkin::skinKeyPressEvent, &mw, &QWidget::close);
void mouseReleaseEvent(QMouseEvent *) override
This event handler, for event event, can be reimplemented in a subclass to receive mouse release even...
void mousePressEvent(QMouseEvent *e) override
This event handler, for event event, can be reimplemented in a subclass to receive mouse press events...
void paintEvent(QPaintEvent *) override
This event handler can be reimplemented in a subclass to receive paint events passed in event.
void mouseMoveEvent(QMouseEvent *e) override
This event handler, for event event, can be reimplemented in a subclass to receive mouse move events ...
void setTransform(const QTransform &)
bool eventFilter(QObject *, QEvent *) override
Filters events if this object has been installed as an event filter for the watched object.
bool handleMouseEvent(QEvent *ev)
CursorWindow(const QImage &cursor, QPoint hot, QWidget *sk)
bool event(QEvent *) override
This virtual function receives events to an object and should return true if the event e was recogniz...
QDebug operator<<(QDebug str, const DeviceSkinParameters &p)
QDebug & operator<<(QDebug &str, const DeviceSkinButtonArea &a)
static void parseRect(const QString &value, QRect *rect)
static QString msgImageNotLoaded(const QString &f)
Combined button and popup list for selecting options.
bool hasSecondaryScreen() const
QSize secondaryScreenSize() const