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>
17#include <QtCore/QRegularExpression>
18#include <QtGui/QMouseEvent>
19#include <QtCore/QDebug>
22# include <QtWidgets/QMainWindow>
23# include <QtWidgets/QDialog>
24# include <QtWidgets/QDialogButtonBox>
25# include <QtWidgets/QHBoxLayout>
30using namespace Qt::StringLiterals;
33 enum { joydistance = 10, key_repeat_period = 50, key_repeat_delay = 500 };
34 enum { debugDeviceSkin = 0 };
37static void parseRect(
const QString &value, QRect *rect) {
38 const auto l = QStringView{value}.split(QLatin1Char(
' '));
39 rect->setRect(l[0].toInt(), l[1].toInt(), l[2].toInt(), l[3].toInt());
43 return DeviceSkin::tr(
"The image file '%1' could not be loaded.").arg(f);
50 str <<
"Area: " << a.name <<
" keyCode=" << a
.keyCode <<
" area=" << a.area
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
65 const int numAreas = p.buttonAreas.size();
66 for (
int i = 0; i < numAreas; i++)
67 str << p.buttonAreas[i];
73 return backScreenRect.isNull() ? closedScreenRect .size(): backScreenRect.size();
78 return secondaryScreenSize() != QSize(0, 0);
84 QString skinFile = skinDirectory;
85 if (skinFile.endsWith(QLatin1Char(
'/')))
86 skinFile.truncate(skinFile.size() - 1);
88 QFileInfo fi(skinFile);
92 prefix += QLatin1Char(
'/');
96 }
else if (fi.isFile()){
99 prefix += QLatin1Char(
'/');
101 *errorMessage = DeviceSkin::tr(
"The skin directory '%1' does not contain a configuration file.").arg(skinDirectory);
105 if (!f.open(QIODevice::ReadOnly )) {
106 *errorMessage = DeviceSkin::tr(
"The skin configuration file '%1' could not be opened.").arg(fn);
110 const bool rc = read(ts, rm, errorMessage);
112 *errorMessage = DeviceSkin::tr(
"The skin configuration file '%1' could not be read: %2")
113 .arg(fn, *errorMessage);
118 QStringList closedAreas;
119 QStringList toggleAreas;
120 QStringList toggleActiveAreas;
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;
142 QString line = ts.readLine();
145 if (!line.isEmpty() && line.at(0) != u'#') {
146 int eq = line.indexOf(QLatin1Char(
'='));
148 const QString key = line.left(eq);
150 while (eq<line.size()-1 && line[eq].isSpace())
152 const QString value = line.mid(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(QLatin1Char(
' '));
161 }
else if ( key == ScreenKey ) {
162 parseRect( value, &screenRect);
163 }
else if ( key == ScreenDepthKey ) {
165 }
else if ( key == BackScreenKey ) {
166 parseRect(value, &backScreenRect);
167 }
else if ( key == ClosedScreenKey ) {
168 parseRect( value, &closedScreenRect );
169 }
else if ( key == CursorKey ) {
170 QStringList l = value.split(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(QLatin1Char(
' '));
177 }
else if ( key == ToggleActiveAreasKey ) {
178 toggleActiveAreas = value.split(QLatin1Char(
' '));
179 }
else if ( key == HasMouseHoverKey ) {
180 hasMouseHover = value ==
"true"_L1 || value ==
"1"_L1;
183 *errorMessage = DeviceSkin::tr(
"Syntax error: %1").arg(line);
190 skinImageUpFileName = mark;
193 ts >> s >> x >> y >> w >> h >> na;
194 skinImageDownFileName = s;
195 screenRect.setRect(x, y, w, h);
202 skinImageUpFileName.insert(0, prefix);
203 if (!QFile(skinImageUpFileName).exists()) {
204 *errorMessage = DeviceSkin::tr(
"The skin \"up\" image file '%1' does not exist.").arg(skinImageUpFileName);
207 if (!skinImageUp.load(skinImageUpFileName)) {
208 *errorMessage = msgImageNotLoaded(skinImageUpFileName);
212 skinImageDownFileName.insert(0, prefix);
213 if (!QFile(skinImageDownFileName).exists()) {
214 *errorMessage = DeviceSkin::tr(
"The skin \"down\" image file '%1' does not exist.").arg(skinImageDownFileName);
217 if (!skinImageDown.load(skinImageDownFileName)) {
218 *errorMessage = msgImageNotLoaded(skinImageDownFileName);
222 if (!skinImageClosedFileName.isEmpty()) {
223 skinImageClosedFileName.insert(0, prefix);
224 if (!QFile(skinImageClosedFileName).exists()) {
225 *errorMessage = DeviceSkin::tr(
"The skin \"closed\" image file '%1' does not exist.").arg(skinImageClosedFileName);
228 if (!skinImageClosed.load(skinImageClosedFileName)) {
229 *errorMessage = msgImageNotLoaded(skinImageClosedFileName);
234 if (!skinCursorFileName.isEmpty()) {
235 skinCursorFileName.insert(0, prefix);
236 if (!QFile(skinCursorFileName).exists()) {
237 *errorMessage = DeviceSkin::tr(
"The skin cursor image file '%1' does not exist.").arg(skinCursorFileName);
240 if (!skinCursor.load(skinCursorFileName)) {
241 *errorMessage = msgImageNotLoaded(skinCursorFileName);
249 buttonAreas.reserve(nareas);
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(DeviceSkinButtonArea());
260 const QString line = ts.readLine();
261 if ( !line.isEmpty() && line[0] != QLatin1Char(
'#') ) {
262 const QStringList tok = line.split(splitRe);
263 if ( tok.size()<6 ) {
264 *errorMessage = DeviceSkin::tr(
"Syntax error in area definition: %1").arg(line);
269 if ( k.left(2).toLower() ==
"0x"_L1) {
270 area
.keyCode = k.mid(2).toInt(0,16);
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(p++,1,x,y);
282 const QChar doubleQuote = QLatin1Char(
'"');
283 if ( area.name[0] == doubleQuote && area.name.endsWith(doubleQuote)) {
284 area.name.truncate(area.name.size() - 1);
285 area.name.remove(0, 1);
287 if ( area.name.size() == 1 )
288 area.text = area.name;
289 if ( area.name == Joystick)
291 area.activeWhenClosed = closedAreas.contains(area.name)
292 || area.keyCode == Qt::Key_Flip;
302 qWarning() << DeviceSkin::tr(
"Mismatch in number of areas, expected %1, got %2.")
328 QWidget *mouseRecipient;
339 m_parameters(parameters),
340 buttonRegions(parameters.buttonAreas.size(), QRegion()),
342 t_skinkey(
new QTimer(
this)),
343 t_parentmove(
new QTimer(
this))
346 setMouseTracking(
true);
347 setAttribute(Qt::WA_NoSystemBackground);
350 connect(t_skinkey, &QTimer::timeout,
this, &DeviceSkin::skinKeyRepeat );
351 t_parentmove->setSingleShot(
true );
352 connect(t_parentmove, &QTimer::timeout,
this, &DeviceSkin::moveParent );
359 emit skinKeyReleaseEvent(area.keyCode,area.text,
true);
360 emit skinKeyPressEvent(area.keyCode, area.text,
true);
361 t_skinkey->start(key_repeat_period);
367 const int numAreas = m_parameters.buttonAreas.size();
368 for (
int i=0; i<numAreas; i++) {
369 QPolygon xa(m_parameters.buttonAreas[i].area.size());
370 int n = m_parameters.buttonAreas[i].area.size();
371 for (
int p = 0; p < n; p++) {
372 xa.setPoint(p,transform.map(m_parameters.buttonAreas[i].area[p]));
375 buttonRegions[i] = QRegion(xa.boundingRect());
377 buttonRegions[i] = QRegion(xa);
384 QImage iup = m_parameters.skinImageUp;
385 QImage idown = m_parameters.skinImageDown;
388 const bool hasClosedImage = !m_parameters.skinImageClosed.isNull();
391 iclosed = m_parameters.skinImageClosed;
393 const bool hasCursorImage = !m_parameters.skinCursor.isNull();
395 icurs = m_parameters.skinCursor;
397 if (!transform.isIdentity()) {
398 iup = iup.transformed(transform, Qt::SmoothTransformation);
399 idown = idown.transformed(transform, Qt::SmoothTransformation);
401 iclosed = iclosed.transformed(transform, Qt::SmoothTransformation);
403 icurs = icurs.transformed(transform, Qt::SmoothTransformation);
405 const Qt::ImageConversionFlags conv = Qt::ThresholdAlphaDither|Qt::AvoidDither;
406 skinImageUp = QPixmap::fromImage(iup);
407 skinImageDown = QPixmap::fromImage(idown, conv);
409 skinImageClosed = QPixmap::fromImage(iclosed, conv);
411 skinCursor = QPixmap::fromImage(icurs, conv);
413 setFixedSize( skinImageUp.size() );
414 if (!skinImageUp.mask())
415 skinImageUp.setMask(skinImageUp.createHeuristicMask());
416 if (!skinImageClosed.mask())
417 skinImageClosed.setMask(skinImageClosed.createHeuristicMask());
419 QWidget* parent = parentWidget();
420 parent->setMask( skinImageUp.mask() );
421 parent->setFixedSize( skinImageUp.size() );
425 if (hasCursorImage) {
426 cursorw =
new qvfb_internal::CursorWindow(m_parameters.skinCursor, m_parameters.cursorHot,
this);
428 cursorw->setView(m_view);
439 transform = QImage::trueMatrix(wm,m_parameters.skinImageUp.width(),m_parameters.skinImageUp.height());
443 QPoint p = transform.map(QPolygon(m_parameters.screenRect)).boundingRect().topLeft();
446 updateSecondaryScreen();
451 setTransform(QTransform().scale(z,z));
456 if (!m_secondaryView)
459 if (m_parameters.backScreenRect.isNull()) {
460 m_secondaryView->hide();
462 m_secondaryView->move(transform.map(QPolygon(m_parameters.backScreenRect)).boundingRect().topLeft());
463 m_secondaryView->show();
466 if (m_parameters.closedScreenRect.isNull()) {
467 m_secondaryView->hide();
469 m_secondaryView->move(transform.map(QPolygon(m_parameters.closedScreenRect)).boundingRect().topLeft());
470 m_secondaryView->show();
479 m_view->move(transform.map(QPolygon(m_parameters.screenRect)).boundingRect().topLeft());
487 updateSecondaryScreen();
493 if ( flipped_open ) {
494 p.drawPixmap(0, 0, skinImageUp);
496 p.drawPixmap(0, 0, skinImageClosed);
499 if ( buttonPressed ==
true ) {
500 toDraw += buttonIndex;
502 for (
int toggle : std::as_const(m_parameters.toggleAreaList)) {
503 const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[toggle];
504 if (flipped_open || ba.activeWhenClosed) {
505 if (ba.toggleArea && ba.toggleActiveArea)
509 for (
int button : std::as_const(toDraw)) {
510 const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[button];
511 const QRect r = buttonRegions[button].boundingRect();
512 if ( ba.area.size() > 2 )
513 p.setClipRegion(buttonRegions[button]);
514 p.drawPixmap( r.topLeft(), skinImageDown, r);
520 if (e->button() == Qt::RightButton) {
523 buttonPressed =
false;
526 const int numAreas = m_parameters.buttonAreas.size();
527 for (
int i = 0; i < numAreas ; i++) {
529 if ( buttonRegions[i].contains( e->position().toPoint() ) ) {
531 if ( m_parameters.joystick == i ) {
540 qDebug()<< m_parameters.buttonAreas[i].name <<
" clicked";
545 clickPos = e->position().toPoint();
548 qDebug()<<
"Clicked in " << e->position().toPoint().x() <<
',' << e->position().toPoint().y();
549 clickPos = e->position().toPoint();
555 if ( flipped_open == open )
558 parent->setMask(skinImageUp.mask());
559 emit skinKeyReleaseEvent(Qt::Key(Qt::Key_Flip), QString(),
false);
561 parent->setMask(skinImageClosed.mask());
562 emit skinKeyPressEvent(Qt::Key(Qt::Key_Flip), QString(),
false);
565 updateSecondaryScreen();
571 buttonPressed =
true;
575 if (ba.keyCode == Qt::Key_Flip) {
581 emit skinKeyPressEvent(ba.keyCode, ba.text,
false);
583 emit skinKeyReleaseEvent(ba.keyCode, ba.text,
false);
585 emit skinKeyPressEvent(ba.keyCode, ba.text,
false);
586 t_skinkey->start(key_repeat_delay);
588 repaint(buttonRegions[buttonIndex].boundingRect());
595 if (m_view && ba.keyCode != Qt::Key_Flip && !ba.toggleArea )
596 emit skinKeyReleaseEvent(ba.keyCode, ba.text,
false);
598 buttonPressed =
false;
599 repaint( buttonRegions[buttonIndex].boundingRect() );
604 if ( e->buttons() & Qt::LeftButton ) {
605 const int joystick = m_parameters.joystick;
606 QPoint newpos = e->globalPosition().toPoint() - clickPos;
609 if (newpos.x() < -joydistance) {
611 }
else if (newpos.x() > +joydistance) {
614 if (newpos.y() < -joydistance) {
616 }
else if (newpos.y() > +joydistance) {
620 if (!buttonPressed) {
626 startPress(k1 ? k1 : k2);
628 }
else if (buttonPressed) {
631 }
else if (buttonPressed ==
false) {
633 if (!t_parentmove->isActive())
634 t_parentmove->start(50);
638 cursorw->setPos(e->globalPosition().toPoint());
643 parent->move( parentpos );
652 if (onjoyrelease >= 0) {
653 startPress(onjoyrelease);
661 return !skinCursor.isNull();
670 handleMouseEvent(ev);
676 if (handleMouseEvent(ev))
678 return QWidget::event(ev);
683 bool handledEvent =
false;
688 if (ev->type() >= QEvent::MouseButtonPress && ev->type() <= QEvent::MouseMove) {
689 QMouseEvent *e = (QMouseEvent*)ev;
690 QPoint gp = e->globalPosition().toPoint();
691 QPoint vp = m_view->mapFromGlobal(gp);
692 QPoint sp = skin->mapFromGlobal(gp);
693 if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) {
694 if (m_view->rect().contains(vp))
695 mouseRecipient = m_view;
696 else if (skin->parentWidget()->geometry().contains(gp))
697 mouseRecipient = skin;
701 if (mouseRecipient) {
703 QMouseEvent me(e->type(),mouseRecipient==skin ? sp : vp,gp,e->button(),e->buttons(),e->modifiers());
704 QApplication::sendEvent(mouseRecipient, &me);
705 }
else if (!skin->parentWidget()->geometry().contains(gp)) {
710 if (e->type() == QEvent::MouseButtonRelease)
723 m_view->removeEventFilter(
this);
724 m_view->removeEventFilter(
this);
727 m_view->installEventFilter(
this);
728 m_view->installEventFilter(
this);
738 setWindowFlags( Qt::FramelessWindowHint );
740 setMouseTracking(
true);
742 setCursor(Qt::BlankCursor);
745 p = QPixmap::fromImage(img);
747 QBitmap bm = img.hasAlphaChannel() ? QBitmap::fromImage(img.createAlphaMask())
748 : QBitmap::fromImage(img.createHeuristicMask());
752 palette.setBrush(backgroundRole(), QBrush(p));
754 setFixedSize( p.size() );
755 if ( !p.mask().isNull() )
769int main(
int argc,
char *argv[])
773 const QString skinFile = QString::fromUtf8(argv[1]);
774 QApplication app(argc,argv);
777 DeviceSkinParameters params;
778 QString errorMessage;
779 if (!params.read(skinFile, DeviceSkinParameters::ReadAll, &errorMessage)) {
780 qWarning() << errorMessage;
783 DeviceSkin ds(params, &mw);
785 QDialog *dialog =
new QDialog();
786 QHBoxLayout *dialogLayout =
new QHBoxLayout();
787 dialog->setLayout(dialogLayout);
788 QDialogButtonBox *dialogButtonBox =
new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
789 QObject::connect(dialogButtonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
790 QObject::connect(dialogButtonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
791 dialogLayout->addWidget(dialogButtonBox);
792 dialog->setFixedSize(params.screenSize());
793 dialog->setParent(&ds, Qt::SubWindow);
794 dialog->setAutoFillBackground(
true);
797 QObject::connect(&ds, &DeviceSkin::popupMenu, &mw, &QWidget::close);
798 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