7#include <QtWidgets/qapplication.h>
9#include <QtGui/qbitmap.h>
10#include <QtGui/qevent.h>
11#include <QtGui/qimage.h>
12#include <QtGui/qpainter.h>
13#include <QtGui/qpixmap.h>
15#include <QtCore/qdebug.h>
16#include <QtCore/qdir.h>
17#include <QtCore/qfile.h>
18#include <QtCore/qfileinfo.h>
19#include <QtCore/qnamespace.h>
20#include <QtCore/qregularexpression.h>
21#include <QtCore/qtextstream.h>
22#include <QtCore/qtimer.h>
25# include <QtWidgets/qmainwindow.h>
26# include <QtWidgets/qboxlayout.h>
27# include <QtWidgets/qdialog.h>
28# include <QtWidgets/qdialogbuttonbox.h>
33using namespace Qt::StringLiterals;
36 enum { joydistance = 10, key_repeat_period = 50, key_repeat_delay = 500 };
37 enum { debugDeviceSkin = 0 };
40static void parseRect(
const QString &value, QRect *rect) {
41 const auto l = QStringView{value}.split(QLatin1Char(
' '));
42 rect->setRect(l[0].toInt(), l[1].toInt(), l[2].toInt(), l[3].toInt());
46 return DeviceSkin::tr(
"The image file '%1' could not be loaded.").arg(f);
53 str <<
"Area: " << a.name <<
" keyCode=" << a
.keyCode <<
" area=" << a.area
62 str <<
"Images " << p.skinImageUpFileName <<
','
63 << p.skinImageDownFileName<<
',' << p.skinImageClosedFileName
64 <<
',' << p.skinCursorFileName <<
"\nScreen: " << p.screenRect
65 <<
" back: " << p.backScreenRect <<
" closed: " << p.closedScreenRect
66 <<
" cursor: " << p.cursorHot <<
" Prefix: " << p.prefix
68 const int numAreas = p.buttonAreas.size();
69 for (
int i = 0; i < numAreas; i++)
70 str << p.buttonAreas[i];
76 return backScreenRect.isNull() ? closedScreenRect .size(): backScreenRect.size();
81 return secondaryScreenSize() != QSize(0, 0);
87 QString skinFile = skinDirectory;
88 if (skinFile.endsWith(QLatin1Char(
'/')))
89 skinFile.truncate(skinFile.size() - 1);
91 QFileInfo fi(skinFile);
95 prefix += QLatin1Char(
'/');
99 }
else if (fi.isFile()){
102 prefix += QLatin1Char(
'/');
104 *errorMessage = DeviceSkin::tr(
"The skin directory '%1' does not contain a configuration file.").arg(skinDirectory);
108 if (!f.open(QIODevice::ReadOnly )) {
109 *errorMessage = DeviceSkin::tr(
"The skin configuration file '%1' could not be opened.").arg(fn);
113 const bool rc = read(ts, rm, errorMessage);
115 *errorMessage = DeviceSkin::tr(
"The skin configuration file '%1' could not be read: %2")
116 .arg(fn, *errorMessage);
121 QStringList closedAreas;
122 QStringList toggleAreas;
123 QStringList toggleActiveAreas;
129 if (mark ==
"[SkinFile]"_L1) {
130 const QString UpKey =
"Up"_L1;
131 const QString DownKey =
"Down"_L1;
132 const QString ClosedKey =
"Closed"_L1;
133 const QString ClosedAreasKey =
"ClosedAreas"_L1;
134 const QString ScreenKey =
"Screen"_L1;
135 const QString ScreenDepthKey =
"ScreenDepth"_L1;
136 const QString BackScreenKey =
"BackScreen"_L1;
137 const QString ClosedScreenKey =
"ClosedScreen"_L1;
138 const QString CursorKey =
"Cursor"_L1;
139 const QString AreasKey =
"Areas"_L1;
140 const QString ToggleAreasKey =
"ToggleAreas"_L1;
141 const QString ToggleActiveAreasKey =
"ToggleActiveAreas"_L1;
142 const QString HasMouseHoverKey =
"HasMouseHover"_L1;
145 QString line = ts.readLine();
148 if (!line.isEmpty() && line.at(0) != u'#') {
149 int eq = line.indexOf(QLatin1Char(
'='));
151 const QString key = line.left(eq);
153 while (eq<line.size()-1 && line[eq].isSpace())
155 const QString value = line.mid(eq);
156 if ( key == UpKey ) {
157 skinImageUpFileName = value;
158 }
else if ( key == DownKey ) {
159 skinImageDownFileName = value;
160 }
else if ( key == ClosedKey ) {
161 skinImageClosedFileName = value;
162 }
else if ( key == ClosedAreasKey ) {
163 closedAreas = value.split(QLatin1Char(
' '));
164 }
else if ( key == ScreenKey ) {
165 parseRect( value, &screenRect);
166 }
else if ( key == ScreenDepthKey ) {
168 }
else if ( key == BackScreenKey ) {
169 parseRect(value, &backScreenRect);
170 }
else if ( key == ClosedScreenKey ) {
171 parseRect( value, &closedScreenRect );
172 }
else if ( key == CursorKey ) {
173 QStringList l = value.split(QLatin1Char(
' '));
174 skinCursorFileName = l[0];
175 cursorHot = QPoint(l[1].toInt(),l[2].toInt());
176 }
else if ( key == AreasKey ) {
177 nareas = value.toInt();
178 }
else if ( key == ToggleAreasKey ) {
179 toggleAreas = value.split(QLatin1Char(
' '));
180 }
else if ( key == ToggleActiveAreasKey ) {
181 toggleActiveAreas = value.split(QLatin1Char(
' '));
182 }
else if ( key == HasMouseHoverKey ) {
183 hasMouseHover = value ==
"true"_L1 || value ==
"1"_L1;
186 *errorMessage = DeviceSkin::tr(
"Syntax error: %1").arg(line);
193 skinImageUpFileName = mark;
196 ts >> s >> x >> y >> w >> h >> na;
197 skinImageDownFileName = s;
198 screenRect.setRect(x, y, w, h);
205 skinImageUpFileName.insert(0, prefix);
206 if (!QFile(skinImageUpFileName).exists()) {
207 *errorMessage = DeviceSkin::tr(
"The skin \"up\" image file '%1' does not exist.").arg(skinImageUpFileName);
210 if (!skinImageUp.load(skinImageUpFileName)) {
211 *errorMessage = msgImageNotLoaded(skinImageUpFileName);
215 skinImageDownFileName.insert(0, prefix);
216 if (!QFile(skinImageDownFileName).exists()) {
217 *errorMessage = DeviceSkin::tr(
"The skin \"down\" image file '%1' does not exist.").arg(skinImageDownFileName);
220 if (!skinImageDown.load(skinImageDownFileName)) {
221 *errorMessage = msgImageNotLoaded(skinImageDownFileName);
225 if (!skinImageClosedFileName.isEmpty()) {
226 skinImageClosedFileName.insert(0, prefix);
227 if (!QFile(skinImageClosedFileName).exists()) {
228 *errorMessage = DeviceSkin::tr(
"The skin \"closed\" image file '%1' does not exist.").arg(skinImageClosedFileName);
231 if (!skinImageClosed.load(skinImageClosedFileName)) {
232 *errorMessage = msgImageNotLoaded(skinImageClosedFileName);
237 if (!skinCursorFileName.isEmpty()) {
238 skinCursorFileName.insert(0, prefix);
239 if (!QFile(skinCursorFileName).exists()) {
240 *errorMessage = DeviceSkin::tr(
"The skin cursor image file '%1' does not exist.").arg(skinCursorFileName);
243 if (!skinCursor.load(skinCursorFileName)) {
244 *errorMessage = msgImageNotLoaded(skinCursorFileName);
252 buttonAreas.reserve(nareas);
257 const QString Joystick =
"Joystick"_L1;
258 const QRegularExpression splitRe(
"[ \t][ \t]*"_L1);
259 Q_ASSERT(splitRe.isValid());
260 while (i < nareas && !ts.atEnd() ) {
261 buttonAreas.push_back(DeviceSkinButtonArea());
263 const QString line = ts.readLine();
264 if ( !line.isEmpty() && line[0] != QLatin1Char(
'#') ) {
265 const QStringList tok = line.split(splitRe);
266 if ( tok.size()<6 ) {
267 *errorMessage = DeviceSkin::tr(
"Syntax error in area definition: %1").arg(line);
272 if ( k.left(2).toLower() ==
"0x"_L1) {
273 area
.keyCode = k.mid(2).toInt(0,16);
279 for (
int j=2; j < tok.size() - 1; ) {
280 const int x = tok[j++].toInt();
281 const int y = tok[j++].toInt();
282 area.area.putPoints(p++,1,x,y);
285 const QChar doubleQuote = QLatin1Char(
'"');
286 if ( area.name[0] == doubleQuote && area.name.endsWith(doubleQuote)) {
287 area.name.truncate(area.name.size() - 1);
288 area.name.remove(0, 1);
290 if ( area.name.size() == 1 )
291 area.text = area.name;
292 if ( area.name == Joystick)
294 area.activeWhenClosed = closedAreas.contains(area.name)
295 || area.keyCode == Qt::Key_Flip;
305 qWarning() << DeviceSkin::tr(
"Mismatch in number of areas, expected %1, got %2.")
331 QWidget *mouseRecipient;
342 m_parameters(parameters),
343 buttonRegions(parameters.buttonAreas.size(), QRegion()),
345 t_skinkey(
new QTimer(
this)),
346 t_parentmove(
new QTimer(
this))
349 setMouseTracking(
true);
350 setAttribute(Qt::WA_NoSystemBackground);
353 connect(t_skinkey, &QTimer::timeout,
this, &DeviceSkin::skinKeyRepeat );
354 t_parentmove->setSingleShot(
true );
362 emit skinKeyReleaseEvent(area
.keyCode,area.text,
true);
363 emit skinKeyPressEvent(area
.keyCode, area.text,
true);
364 t_skinkey->start(key_repeat_period);
370 const int numAreas = m_parameters.buttonAreas.size();
371 for (
int i=0; i<numAreas; i++) {
372 QPolygon xa(m_parameters.buttonAreas[i].area.size());
373 int n = m_parameters.buttonAreas[i].area.size();
374 for (
int p = 0; p < n; p++) {
375 xa.setPoint(p,transform.map(m_parameters.buttonAreas[i].area[p]));
378 buttonRegions[i] = QRegion(xa.boundingRect());
380 buttonRegions[i] = QRegion(xa);
387 QImage iup = m_parameters.skinImageUp;
388 QImage idown = m_parameters.skinImageDown;
391 const bool hasClosedImage = !m_parameters.skinImageClosed.isNull();
394 iclosed = m_parameters.skinImageClosed;
396 const bool hasCursorImage = !m_parameters.skinCursor.isNull();
398 icurs = m_parameters.skinCursor;
400 if (!transform.isIdentity()) {
401 iup = iup.transformed(transform, Qt::SmoothTransformation);
402 idown = idown.transformed(transform, Qt::SmoothTransformation);
404 iclosed = iclosed.transformed(transform, Qt::SmoothTransformation);
406 icurs = icurs.transformed(transform, Qt::SmoothTransformation);
408 const Qt::ImageConversionFlags conv = Qt::ThresholdAlphaDither|Qt::AvoidDither;
409 skinImageUp = QPixmap::fromImage(iup);
410 skinImageDown = QPixmap::fromImage(idown, conv);
412 skinImageClosed = QPixmap::fromImage(iclosed, conv);
414 skinCursor = QPixmap::fromImage(icurs, conv);
416 setFixedSize( skinImageUp.size() );
417 if (!skinImageUp.mask())
418 skinImageUp.setMask(skinImageUp.createHeuristicMask());
419 if (!skinImageClosed.mask())
420 skinImageClosed.setMask(skinImageClosed.createHeuristicMask());
422 QWidget* parent = parentWidget();
423 parent->setMask( skinImageUp.mask() );
424 parent->setFixedSize( skinImageUp.size() );
428 if (hasCursorImage) {
429 cursorw =
new qvfb_internal::CursorWindow(m_parameters.skinCursor, m_parameters.cursorHot,
this);
431 cursorw->setView(m_view);
442 transform = QImage::trueMatrix(wm,m_parameters.skinImageUp.width(),m_parameters.skinImageUp.height());
446 QPoint p = transform.map(QPolygon(m_parameters.screenRect)).boundingRect().topLeft();
449 updateSecondaryScreen();
454 setTransform(QTransform().scale(z,z));
459 if (!m_secondaryView)
462 if (m_parameters.backScreenRect.isNull()) {
463 m_secondaryView->hide();
465 m_secondaryView->move(transform.map(QPolygon(m_parameters.backScreenRect)).boundingRect().topLeft());
466 m_secondaryView->show();
469 if (m_parameters.closedScreenRect.isNull()) {
470 m_secondaryView->hide();
472 m_secondaryView->move(transform.map(QPolygon(m_parameters.closedScreenRect)).boundingRect().topLeft());
473 m_secondaryView->show();
482 m_view->move(transform.map(QPolygon(m_parameters.screenRect)).boundingRect().topLeft());
490 updateSecondaryScreen();
496 if ( flipped_open ) {
497 p.drawPixmap(0, 0, skinImageUp);
499 p.drawPixmap(0, 0, skinImageClosed);
502 if ( buttonPressed ==
true ) {
503 toDraw += buttonIndex;
505 for (
int toggle : std::as_const(m_parameters.toggleAreaList)) {
506 const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[toggle];
507 if (flipped_open || ba.activeWhenClosed) {
508 if (ba.toggleArea && ba.toggleActiveArea)
512 for (
int button : std::as_const(toDraw)) {
513 const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[button];
514 const QRect r = buttonRegions[button].boundingRect();
515 if ( ba.area.size() > 2 )
516 p.setClipRegion(buttonRegions[button]);
517 p.drawPixmap( r.topLeft(), skinImageDown, r);
523 if (e->button() == Qt::RightButton) {
526 buttonPressed =
false;
529 const int numAreas = m_parameters.buttonAreas.size();
530 for (
int i = 0; i < numAreas ; i++) {
532 if ( buttonRegions[i].contains( e->position().toPoint() ) ) {
534 if ( m_parameters.joystick == i ) {
543 qDebug()<< m_parameters.buttonAreas[i].name <<
" clicked";
548 clickPos = e->position().toPoint();
551 qDebug()<<
"Clicked in " << e->position().toPoint().x() <<
',' << e->position().toPoint().y();
552 clickPos = e->position().toPoint();
558 if ( flipped_open == open )
561 parent->setMask(skinImageUp.mask());
562 emit skinKeyReleaseEvent(Qt::Key(Qt::Key_Flip), QString(),
false);
564 parent->setMask(skinImageClosed.mask());
565 emit skinKeyPressEvent(Qt::Key(Qt::Key_Flip), QString(),
false);
568 updateSecondaryScreen();
574 buttonPressed =
true;
578 if (ba.keyCode == Qt::Key_Flip) {
584 emit skinKeyPressEvent(ba
.keyCode, ba.text,
false);
586 emit skinKeyReleaseEvent(ba
.keyCode, ba.text,
false);
588 emit skinKeyPressEvent(ba
.keyCode, ba.text,
false);
589 t_skinkey->start(key_repeat_delay);
591 repaint(buttonRegions[buttonIndex].boundingRect());
598 if (m_view && ba.keyCode != Qt::Key_Flip && !ba.toggleArea )
599 emit skinKeyReleaseEvent(ba
.keyCode, ba.text,
false);
601 buttonPressed =
false;
602 repaint( buttonRegions[buttonIndex].boundingRect() );
607 if ( e->buttons() & Qt::LeftButton ) {
608 const int joystick = m_parameters.joystick;
609 QPoint newpos = e->globalPosition().toPoint() - clickPos;
612 if (newpos.x() < -joydistance) {
614 }
else if (newpos.x() > +joydistance) {
617 if (newpos.y() < -joydistance) {
619 }
else if (newpos.y() > +joydistance) {
623 if (!buttonPressed) {
629 startPress(k1 ? k1 : k2);
631 }
else if (buttonPressed) {
634 }
else if (buttonPressed ==
false) {
636 if (!t_parentmove->isActive())
637 t_parentmove->start(50);
641 cursorw->setPos(e->globalPosition().toPoint());
646 parent->move( parentpos );
655 if (onjoyrelease >= 0) {
656 startPress(onjoyrelease);
664 return !skinCursor.isNull();
673 handleMouseEvent(ev);
679 if (handleMouseEvent(ev))
681 return QWidget::event(ev);
686 bool handledEvent =
false;
691 if (ev->type() >= QEvent::MouseButtonPress && ev->type() <= QEvent::MouseMove) {
692 QMouseEvent *e = (QMouseEvent*)ev;
693 QPoint gp = e->globalPosition().toPoint();
694 QPoint vp = m_view->mapFromGlobal(gp);
695 QPoint sp = skin->mapFromGlobal(gp);
696 if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) {
697 if (m_view->rect().contains(vp))
698 mouseRecipient = m_view;
699 else if (skin->parentWidget()->geometry().contains(gp))
700 mouseRecipient = skin;
704 if (mouseRecipient) {
706 QMouseEvent me(e->type(),mouseRecipient==skin ? sp : vp,gp,e->button(),e->buttons(),e->modifiers());
707 QApplication::sendEvent(mouseRecipient, &me);
708 }
else if (!skin->parentWidget()->geometry().contains(gp)) {
713 if (e->type() == QEvent::MouseButtonRelease)
726 m_view->removeEventFilter(
this);
727 m_view->removeEventFilter(
this);
730 m_view->installEventFilter(
this);
731 m_view->installEventFilter(
this);
741 setWindowFlags( Qt::FramelessWindowHint );
743 setMouseTracking(
true);
745 setCursor(Qt::BlankCursor);
748 p = QPixmap::fromImage(img);
750 QBitmap bm = img.hasAlphaChannel() ? QBitmap::fromImage(img.createAlphaMask())
751 : QBitmap::fromImage(img.createHeuristicMask());
755 palette.setBrush(backgroundRole(), QBrush(p));
757 setFixedSize( p.size() );
758 if ( !p.mask().isNull() )
772int main(
int argc,
char *argv[])
776 const QString skinFile = QString::fromUtf8(argv[1]);
777 QApplication app(argc,argv);
780 DeviceSkinParameters params;
781 QString errorMessage;
782 if (!params.read(skinFile, DeviceSkinParameters::ReadAll, &errorMessage)) {
783 qWarning() << errorMessage;
786 DeviceSkin ds(params, &mw);
788 QDialog *dialog =
new QDialog();
789 QHBoxLayout *dialogLayout =
new QHBoxLayout();
790 dialog->setLayout(dialogLayout);
791 QDialogButtonBox *dialogButtonBox =
new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
792 QObject::connect(dialogButtonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
793 QObject::connect(dialogButtonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
794 dialogLayout->addWidget(dialogButtonBox);
795 dialog->setFixedSize(params.screenSize());
796 dialog->setParent(&ds, Qt::SubWindow);
797 dialog->setAutoFillBackground(
true);
800 QObject::connect(&ds, &DeviceSkin::popupMenu, &mw, &QWidget::close);
801 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