1import QtQuick 1.1
2import Qt.labs.shaders 1.0
3import "ToxicWaste.js" as ToxicWaste
4import "./animations"
5import "./components"
6import "./effects"
7import Wheel 1.0
8
9Rectangle {
10    id: toxicWasteMain
11
12    property int fps: 0
13    property bool ignoreLaunch: false
14    property bool invertFlip: false
15    property bool horizontalFlip: true
16    property bool flipDirectionChanged: false
17    property bool iconsReady: false
18    property bool icoSevenZipped: viewer.isSevenZippedImageType("ico") && !viewer.iconCacheDatabaseEnabled()
19    property int currentMachineIndex: 0
20
21    // restored properties
22    property bool fpsVisible: false
23    property bool showBackgroundAnimation: false
24    property bool showShaderEffect: false
25    property bool animateInForeground: false
26    property bool fullScreen: false
27    property string cabinetImageType: "preview"
28    property string secondaryImageType: "preview"
29    property bool cabinetFlipped: false
30    property int lastIndex: 0
31    property bool menuHidden: false
32    property string version: ""
33    property bool confirmQuit: true
34    property int preferencesTab: 0
35    property int machineCardPage: 0
36    property bool autoPositionOverlay: true
37    property real overlayScale: 1.0
38    property real overlayOffsetX: 0
39    property real overlayOffsetY: 0
40    property real overlayOpacity: 1
41    property real backgroundOpacity: 0.7
42    property real machineListOpacity: 1
43    property bool autoStopAnimations: true
44
45    // delayed init
46    Timer {
47        id: initTimer
48        interval: 1
49        running: false
50        repeat: false
51        onTriggered: {
52            viewer.log("ToxicWaste: " + qsTr("Starting initialization"));
53            ToxicWaste.init();
54            restoreLastIndexTimer.start();
55        }
56    }
57
58    Timer {
59        id: restoreLastIndexTimer
60        interval: 1
61        running: false
62        repeat: false
63        onTriggered: ToxicWaste.restoreLastIndex()
64    }
65
66    Component.onCompleted: initTimer.start()
67
68    Timer {
69        id: launchButtonFlashTimer
70        interval: 100
71        running: false
72        onTriggered: launchButton.opacity = 0.8
73    }
74
75    Timer {
76        id: resetIgnoreLaunchTimer
77        interval: 100
78        running: false
79        repeat: false
80        onTriggered: toxicWasteMain.ignoreLaunch = false
81    }
82
83    Timer {
84        id: flipDirectionResetTimer
85        interval: 200
86        running: false
87        repeat: false
88        onTriggered: toxicWasteMain.flipDirectionChanged = false
89    }
90
91    Timer {
92        id: detailUpdateTimer
93        interval: 10
94        running: false
95        repeat: false
96        onTriggered: toxicWasteMain.currentMachineIndex = machineListView.currentIndex;
97    }
98
99    Connections {
100        target: viewer
101        onEmulatorStarted: ToxicWaste.emulatorStarted()
102        onEmulatorFinished: ToxicWaste.emulatorStopped()
103    }
104
105    width: ToxicWaste.baseWidth
106    height: ToxicWaste.baseHeight
107    z: 0
108    Image {
109        id: backgroundImage
110        anchors.fill: parent
111        fillMode: Image.PreserveAspectFit
112        source: "images/shadereffectsource.png"
113        smooth: true
114        z: 0
115    }
116    Image {
117        id: toxicImage
118        anchors.fill: parent
119        fillMode: Image.PreserveAspectFit
120        source: "images/toxic.png"
121        z: 2
122        smooth: true
123        opacity: toxicWasteMain.backgroundOpacity
124    }
125    Item {
126        id: overlayItem
127        z: 3
128        anchors.top: parent.top
129        anchors.topMargin: 10 * ToxicWaste.scaleFactorY()
130        anchors.bottom: parent.bottom
131        anchors.bottomMargin: 10 * ToxicWaste.scaleFactorY()
132        anchors.left: parent.left
133        anchors.leftMargin: 20 * ToxicWaste.scaleFactorX()
134        anchors.right: parent.right
135        anchors.rightMargin: 320 * ToxicWaste.scaleFactorX()
136        Flipable {
137            id: overlayFlip
138            anchors.fill: parent
139            front: Item {
140                id: frontItem
141                anchors.fill: parent
142                scale: toxicWasteMain.overlayScale
143                property bool shiftPressed: false
144                property bool altPressed: false
145                property bool ctrlPressed: false
146                Image {
147                    id: overlayImage
148                    source: "images/overlay.png"
149                    opacity: toxicWasteMain.overlayOpacity
150                    fillMode: Image.PreserveAspectFit
151                    width: 380
152                    scale: ToxicWaste.scaleFactorX()
153                    anchors.centerIn: parent
154                    anchors.verticalCenterOffset: toxicWasteMain.autoPositionOverlay ? ToxicWaste.overlayOffset(height) : toxicWasteMain.overlayOffsetY
155                    anchors.horizontalCenterOffset: toxicWasteMain.autoPositionOverlay ? 0 : (toxicWasteMain.overlayOffsetX + toxicWasteMain.mapToItem(parent, toxicWasteMain.width/2, 0).x - parent.width/2) * toxicWasteMain.overlayScale / toxicWasteMain.overlayScale // <= this is funny, I know, but seems to be the only way to trigger a recalculation when overlayScale changes
156                    smooth: true
157                }
158                Rectangle {
159                    id: previewRect
160                    width: 239
161                    height: 165
162                    opacity: toxicWasteMain.overlayOpacity
163                    anchors.centerIn: overlayImage
164                    anchors.verticalCenterOffset: -124 * ToxicWaste.scaleFactorX()
165                    scale: overlayImage.scale
166                    color: "#202020"
167                    smooth: true
168                    z: -1
169                    Image {
170                        id: previewImage
171                        cache: false
172                        source: ToxicWaste.imageUrl(toxicWasteMain.cabinetImageType)
173                        smooth: true
174                        anchors.fill: parent
175                        anchors.centerIn: parent
176                        anchors.margins: 1
177                        fillMode: Image.PreserveAspectFit
178                        asynchronous: viewer.isSevenZippedImageType(toxicWasteMain.cabinetImageType) ? false : true
179                        Connections {
180                            target: viewer
181                            onImageDataUpdated: {
182                                if ( cachePrefix === ToxicWaste.cachePrefix(toxicWasteMain.cabinetImageType) )
183                                    previewImage.cache = true;
184                            }
185                        }
186                        onStatusChanged: {
187                            if ( status == Image.Ready )
188                                previewOverlay.source = source
189                        }
190                    }
191                    Image {
192                        id: previewOverlay
193                        cache: previewImage.cache
194                        smooth: true
195                        anchors.fill: parent
196                        anchors.centerIn: parent
197                        anchors.margins: 1
198                        fillMode: Image.PreserveAspectFit
199                        opacity: videoSnap.playing ? 0 : 1
200                        asynchronous: previewImage.asynchronous
201                    }
202                }
203                WheelArea {
204                    anchors.fill: overlayImage
205                    onWheel: {
206                        var wheelEventHandled = false;
207                        if ( !toxicWasteMain.autoPositionOverlay ) {
208                            frontItem.focus = true;
209                            if ( frontItem.altPressed ) {
210                                wheelEventHandled = true;
211                                toxicWasteMain.overlayOffsetX += (frontItem.shiftPressed ? 0.05 : 0.25) * delta
212                            }
213                            if ( frontItem.ctrlPressed ) {
214                                wheelEventHandled = true;
215                                toxicWasteMain.overlayOffsetY += (frontItem.shiftPressed ? 0.05 : 0.25) * delta
216                            }
217                        }
218                        if ( !wheelEventHandled ) {
219                            var newScale = toxicWasteMain.overlayScale * (1 + (frontItem.shiftPressed ? 0.01 : 0.1) * (delta / Math.abs(delta)));
220                            if ( newScale > 10.0 )
221                                newScale = 10.0;
222                            else if ( newScale < 0.01 )
223                                newScale = 0.01;
224                            toxicWasteMain.overlayScale = newScale;
225                            preferencesSlidersTab.cabinetZoomValue = toxicWasteMain.overlayScale;
226                        }
227                    }
228                }
229                focus: true
230                Keys.onPressed: {
231                    frontItem.shiftPressed = event.modifiers & Qt.ShiftModifier
232                    frontItem.altPressed = event.modifiers & Qt.AltModifier
233                    frontItem.ctrlPressed = event.modifiers & Qt.ControlModifier
234                }
235                Keys.onReleased: {
236                    frontItem.shiftPressed = event.modifiers & Qt.ShiftModifier
237                    frontItem.altPressed = event.modifiers & Qt.AltModifier
238                    frontItem.ctrlPressed = event.modifiers & Qt.ControlModifier
239                }
240            }
241            back: Rectangle {
242                id: overlayRectBack
243                anchors.fill: parent
244                border.color: "black"
245                border.width: 2 * ToxicWaste.scaleFactorX()
246                radius: 5
247                smooth: true
248                scale: toxicWasteMain.flipDirectionChanged ? -1 : 1
249                gradient: Gradient {
250                    GradientStop { position: 0.00; color: "#ffffff" }
251                    GradientStop { position: 0.50; color: "#007bff" }
252                }
253                Text {
254                    id: itemDescription
255                    text: ToxicWaste.machineCardHeader()
256                    font.pixelSize: 12 * ToxicWaste.scaleFactorY()
257                    anchors.top: parent.top
258                    anchors.topMargin: 10 * ToxicWaste.scaleFactorX()
259                    anchors.horizontalCenter: parent.horizontalCenter
260                    width: parent.width - 76 * ToxicWaste.scaleFactorX()
261                    horizontalAlignment: Text.AlignHCenter
262                    verticalAlignment: Text.AlignVCenter
263                    wrapMode: Text.WordWrap
264                }
265                TabWidget {
266                    id: machineCardTabWidget
267                    anchors.fill: parent
268                    anchors.leftMargin: 10 * ToxicWaste.scaleFactorX()
269                    anchors.rightMargin: anchors.leftMargin
270                    anchors.bottom: parent.bottom
271                    anchors.topMargin: itemDescription.height + 25 * ToxicWaste.scaleFactorY()
272                    anchors.horizontalCenter: parent.horizontalCenter
273                    current: toxicWasteMain.machineCardPage
274                    onCurrentChanged: toxicWasteMain.machineCardPage = current
275                    baseColor: "#55a5ff"
276                    activeBorderColor: "black"
277                    inactiveBorderColor: "#202020"
278                    activeTextColor: "black"
279                    inactiveTextColor: "#202020"
280                    fontSize: 12 * ToxicWaste.scaleFactorX()
281                    smooth: true
282                    scaleFactor: ToxicWaste.scaleFactorX()
283                    Rectangle {
284                        id: imageViewerRect
285                        property string title: qsTr("Images")
286                        anchors.fill: parent
287                        anchors.topMargin: 5 * ToxicWaste.scaleFactorY()
288                        anchors.bottomMargin: 30 * ToxicWaste.scaleFactorY()
289                        smooth: true
290                        border.color: "black"
291                        border.width: ToxicWaste.scaleFactorX()
292                        radius: 5
293                        color: "#202020"
294                        Image {
295                            id: imageViewer
296                            cache: false
297                            source: ToxicWaste.imageUrl(toxicWasteMain.secondaryImageType)
298                            smooth: true
299                            anchors.fill: parent
300                            anchors.centerIn: parent
301                            anchors.margins: 2
302                            fillMode: Image.PreserveAspectFit
303                            asynchronous: viewer.isSevenZippedImageType(toxicWasteMain.secondaryImageType) ? false : true
304                            Connections {
305                                target: viewer
306                                onImageDataUpdated: {
307                                    if ( cachePrefix === ToxicWaste.cachePrefix(toxicWasteMain.secondaryImageType) )
308                                        imageViewer.cache = true;
309                                }
310                            }
311                            onStatusChanged: {
312                                if ( status == Image.Ready )
313                                    imageViewerOverlay.source = source
314                            }
315                        }
316                        Image {
317                            id: imageViewerOverlay
318                            cache: imageViewer.cache
319                            smooth: true
320                            anchors.fill: parent
321                            anchors.centerIn: parent
322                            anchors.margins: 1
323                            fillMode: Image.PreserveAspectFit
324                            asynchronous: imageViewer.asynchronous
325                        }
326                        Rectangle {
327                            id: imageTypeSelector
328                            gradient: Gradient {
329                                GradientStop { position: 0.00; color: "black" }
330                                GradientStop { position: 0.75; color: "white" }
331                                GradientStop { position: 1.00; color: "black" }
332                            }
333                            radius: height/2
334                            anchors.horizontalCenter: parent.horizontalCenter
335                            anchors.bottom: parent.bottom
336                            anchors.bottomMargin: -25 * ToxicWaste.scaleFactorY()
337                            height: 20 * ToxicWaste.scaleFactorY()
338                            width: 300 * ToxicWaste.scaleFactorX()
339                            smooth: true
340                            Text {
341                                id: imageTypeText
342                                text: ToxicWaste.machineImageType(toxicWasteMain.secondaryImageType)
343                                color: "black"
344                                font.bold: true
345                                font.pixelSize: 12 * ToxicWaste.scaleFactorY()
346                                anchors.fill: parent
347                                anchors.centerIn: parent
348                                anchors.leftMargin: nextImageButton.width
349                                anchors.rightMargin: previousImageButton.width
350                                horizontalAlignment: Text.AlignHCenter
351                                verticalAlignment: Text.AlignVCenter
352                                smooth: true
353                                elide: Text.ElideRight
354                            }
355                            Image {
356                                id: nextImageButton
357                                opacity: 0.5
358                                source: "images/arrow.png"
359                                anchors.horizontalCenter: parent.horizontalCenter
360                                anchors.horizontalCenterOffset: 150 * ToxicWaste.scaleFactorX() - width/2
361                                anchors.verticalCenter: parent.verticalCenter
362                                height: parent.height - 2
363                                fillMode: Image.PreserveAspectFit
364                                smooth: true
365                                MouseArea {
366                                    anchors.fill: parent
367                                    hoverEnabled: true
368                                    onContainsMouseChanged: containsMouse ? parent.opacity = 1.0 : parent.opacity = 0.5
369                                    onClicked: {
370                                        toxicWasteMain.secondaryImageType = ToxicWaste.nextImageType(toxicWasteMain.secondaryImageType);
371                                        searchTextInput.focus = false;
372                                        if ( toxicWasteMain.cabinetImageType == toxicWasteMain.secondaryImageType )
373                                            miniCabinetImage.opacity = 1.0;
374                                        else
375                                            miniCabinetImage.opacity = 0.3;
376                                    }
377                                }
378                            }
379                            Image {
380                                id: previousImageButton
381                                opacity: 0.5
382                                source: "images/arrow.png"
383                                mirror: true
384                                anchors.horizontalCenter: parent.horizontalCenter
385                                anchors.horizontalCenterOffset: -150 * ToxicWaste.scaleFactorX() + width/2
386                                anchors.verticalCenter: parent.verticalCenter
387                                height: parent.height - 2
388                                fillMode: Image.PreserveAspectFit
389                                smooth: true
390                                MouseArea {
391                                    anchors.fill: parent
392                                    hoverEnabled: true
393                                    onContainsMouseChanged: containsMouse ? parent.opacity = 1.0 : parent.opacity = 0.5
394                                    onClicked: {
395                                        toxicWasteMain.secondaryImageType = ToxicWaste.previousImageType(toxicWasteMain.secondaryImageType);
396                                        searchTextInput.focus = false;
397                                        if ( toxicWasteMain.cabinetImageType == toxicWasteMain.secondaryImageType )
398                                            miniCabinetImage.opacity = 1.0;
399                                        else
400                                            miniCabinetImage.opacity = 0.3;
401                                    }
402                                }
403                            }
404                        }
405                        Image {
406                            id: miniCabinetImage
407                            source: "images/cabinet_small.png"
408                            anchors.left: parent.left
409                            anchors.verticalCenter: imageTypeSelector.verticalCenter
410                            smooth: true
411                            fillMode: Image.PreserveAspectFit
412                            height: imageTypeSelector.height
413                            opacity: toxicWasteMain.cabinetImageType == toxicWasteMain.secondaryImageType ? 1.0 : 0.3
414                            MouseArea {
415                                anchors.fill: parent
416                                hoverEnabled: true
417                                onEntered: miniCabinetImage.opacity = 1.0
418                                onExited: {
419                                    if ( toxicWasteMain.cabinetImageType == toxicWasteMain.secondaryImageType )
420                                        miniCabinetImage.opacity = 1.0;
421                                    else
422                                        miniCabinetImage.opacity = 0.3;
423                                }
424                                onClicked: toxicWasteMain.cabinetImageType = toxicWasteMain.secondaryImageType
425                            }
426                        }
427                    }
428                    Rectangle {
429                        id: emuInfoViewer
430                        property string title: qsTr("Emu info")
431                        anchors.fill: parent
432                        anchors.topMargin: 5 * ToxicWaste.scaleFactorY()
433                        anchors.bottomMargin: 10 * ToxicWaste.scaleFactorY()
434                        smooth: true
435                        border.color: "black"
436                        border.width: ToxicWaste.scaleFactorX()
437                        radius: 5
438                        color: "#202020"
439                        TextArea {
440                            anchors.fill: parent
441                            anchors.margins: 5
442                            color: "transparent"
443                            fontSize: 12 * ToxicWaste.scaleFactorX()
444                            fontColor: "white"
445                            arrowIcon: "images/down_arrow_white.png"
446                            displayText: viewer.requestInfo(machineListModel[toxicWasteMain.currentMachineIndex].id, "emuinfo");
447                        }
448                    }
449                    Rectangle {
450                        id: machineInfoViewer
451                        property string title: qsTr("Machine info")
452                        anchors.fill: parent
453                        anchors.topMargin: 5 * ToxicWaste.scaleFactorY()
454                        anchors.bottomMargin: 10 * ToxicWaste.scaleFactorY()
455                        smooth: true
456                        border.color: "black"
457                        border.width: ToxicWaste.scaleFactorX()
458                        radius: 5
459                        color: "#202020"
460                        TextArea {
461                            anchors.fill: parent
462                            anchors.margins: 5
463                            color: "transparent"
464                            fontSize: 12 * ToxicWaste.scaleFactorX()
465                            fontColor: "white"
466                            arrowIcon: "images/down_arrow_white.png"
467                            displayText: viewer.requestInfo(machineListModel[toxicWasteMain.currentMachineIndex].id, "sysinfo");
468                        }
469                    }
470                }
471            }
472            transform: Rotation {
473                id: overlayRotation
474                origin.x: overlayFlip.width/2
475                origin.y: overlayFlip.height/2
476                axis.x: toxicWasteMain.horizontalFlip ? 0 : (toxicWasteMain.invertFlip ? -1 : 1)
477                axis.y: toxicWasteMain.horizontalFlip ? (toxicWasteMain.invertFlip ? -1 : 1) : 0
478                axis.z: 0
479                angle: 0
480            }
481            states: [
482                State {
483                    name: "back"
484                    PropertyChanges { target: overlayRotation; angle: 180 }
485                    PropertyChanges { target: toxicWasteMain; focus: true }
486                    when: toxicWasteMain.cabinetFlipped
487                },
488                State {
489                    name: "front"
490                    PropertyChanges { target: overlayRotation; angle: 0 }
491                    PropertyChanges { target: toxicWasteMain; focus: true }
492                    when: !toxicWasteMain.cabinetFlipped
493                }
494            ]
495            transitions: Transition {
496                NumberAnimation { target: overlayRotation; property: "angle"; duration: 400 }
497            }
498            MouseArea { // right
499                anchors.fill: parent
500                anchors.leftMargin: parent.width/2
501                anchors.bottomMargin: parent.height/4
502                anchors.topMargin: parent.height/4
503                onClicked: {
504                    toxicWasteMain.flipDirectionChanged = !toxicWasteMain.horizontalFlip;
505                    toxicWasteMain.invertFlip = flipDirectionChanged ? toxicWasteMain.cabinetFlipped : false;
506                    toxicWasteMain.horizontalFlip = true;
507                    toxicWasteMain.focus = true;
508                    toxicWasteMain.cabinetFlipped = !toxicWasteMain.cabinetFlipped;
509                    flipDirectionResetTimer.restart();
510                }
511                z: -1
512            }
513            MouseArea { // left
514                anchors.fill: parent
515                anchors.rightMargin: parent.width/2
516                anchors.bottomMargin: parent.height/4
517                anchors.topMargin: parent.height/4
518                onClicked: {
519                    toxicWasteMain.flipDirectionChanged = !toxicWasteMain.horizontalFlip;
520                    toxicWasteMain.invertFlip = flipDirectionChanged ? !toxicWasteMain.cabinetFlipped : true;
521                    toxicWasteMain.horizontalFlip = true;
522                    toxicWasteMain.focus = true;
523                    toxicWasteMain.cabinetFlipped = !toxicWasteMain.cabinetFlipped;
524                    flipDirectionResetTimer.restart();
525                }
526                z: -1
527            }
528            MouseArea { // top
529                anchors.fill: parent
530                anchors.bottomMargin: parent.height - parent.height/4
531                onClicked: {
532                    toxicWasteMain.flipDirectionChanged = toxicWasteMain.horizontalFlip;
533                    toxicWasteMain.invertFlip = flipDirectionChanged ? toxicWasteMain.cabinetFlipped : false;
534                    toxicWasteMain.horizontalFlip = false;
535                    toxicWasteMain.focus = true;
536                    toxicWasteMain.cabinetFlipped = !toxicWasteMain.cabinetFlipped;
537                    flipDirectionResetTimer.restart();
538                }
539                z: -1
540            }
541            MouseArea { // bottom
542                anchors.fill: parent
543                anchors.topMargin: parent.height - parent.height/4
544                onClicked: {
545                    toxicWasteMain.flipDirectionChanged = toxicWasteMain.horizontalFlip;
546                    toxicWasteMain.invertFlip = flipDirectionChanged ? !toxicWasteMain.cabinetFlipped : true;
547                    toxicWasteMain.horizontalFlip = false;
548                    toxicWasteMain.focus = true;
549                    toxicWasteMain.cabinetFlipped = !toxicWasteMain.cabinetFlipped;
550                    flipDirectionResetTimer.restart();
551                }
552                z: -1
553            }
554        }
555    }
556    Rectangle {
557        anchors.verticalCenter: parent.verticalCenter
558        anchors.verticalCenterOffset: 29 * ToxicWaste.scaleFactorX() - toxicWasteMain.height/2
559        anchors.horizontalCenter: parent.horizontalCenter
560        anchors.horizontalCenterOffset: 29 * ToxicWaste.scaleFactorX() - toxicWasteMain.width/2
561        scale: ToxicWaste.scaleFactorX()
562        width: 48
563        height: 48
564        z: 4
565        color: "darkgrey"
566        radius: 26
567        smooth: true
568        Image {
569            id: launchButton
570            source: ToxicWaste.launchButtonSource()
571            opacity: 0.8
572            smooth: true
573            anchors.fill: parent
574            fillMode: Image.PreserveAspectFit
575            z: 4
576            MouseArea {
577                anchors.fill: parent
578                hoverEnabled: true
579                onEntered: parent.opacity = 1.0
580                onExited: parent.opacity = 0.8
581                onClicked: {
582                    viewer.launchEmulator(machineListModel[machineListView.currentIndex].id);
583                    searchTextInput.focus = false;
584                }
585            }
586        }
587    }
588    ListView {
589        id: machineListView
590        opacity: toxicWasteMain.machineListOpacity
591        scale: ToxicWaste.scaleFactorX()
592        flickDeceleration: 3500
593        maximumFlickVelocity: 4000
594        snapMode: ListView.NoSnap
595        interactive: true
596        keyNavigationWraps: false
597        z: 3
598        width: 280
599        height: parent.height / scale - 20
600        anchors.horizontalCenterOffset: 240 * scale
601        anchors.horizontalCenter: parent.horizontalCenter
602        anchors.verticalCenter: parent.verticalCenter
603        spacing: 10
604        orientation: ListView.Vertical
605        flickableDirection: Flickable.AutoFlickDirection
606        smooth: true
607        preferredHighlightBegin: 0
608        preferredHighlightEnd: 72
609        highlight: Rectangle {
610            id: itemHighlighter
611            smooth: true
612            color: "white"
613            radius: 10
614            border.color: "black"
615            border.width: 2
616            width: 280
617            height: 72
618            opacity: 1.0
619            z: 0
620        }
621        highlightRangeMode: ListView.StrictlyEnforceRange
622        highlightFollowsCurrentItem: true
623        highlightMoveDuration: 500
624        cacheBuffer: 72 * itemsPerPage() // 72 = machineListItemDelegate.height
625        delegate: Item {
626            property string gameId: id
627            id: machineListItemDelegate
628            width: 280
629            height: 72
630            Rectangle {
631                id: machineListItemBackground
632                smooth: true
633                anchors.fill: machineListItemDelegate
634                gradient: Gradient {
635                    GradientStop { position: 0.0; color: "lightgrey" }
636                    GradientStop { position: 0.5; color: "white" }
637                    GradientStop { position: 1.0; color: "lightgrey" }
638                }
639                opacity: 0.8
640                radius: 10
641                border.color: "black"
642                border.width: 2
643                Image {
644                    id: machineListItemIcon
645                    cache: icoSevenZipped ? toxicWasteMain.iconsReady : true
646                    source: "image://qmc2/ico/" + model.modelData.id + "/" + model.modelData.parentId
647                    anchors.left: machineListItemBackground.left
648                    anchors.verticalCenter: machineListItemBackground.verticalCenter
649                    anchors.margins: 10
650                    smooth: true
651                    fillMode: Image.PreserveAspectFit
652                    height: 24
653                    asynchronous: !icoSevenZipped
654                    Connections {
655                        target: viewer
656                        onImageDataUpdated: {
657                            if ( cachePrefix === "ico" )
658                                toxicWasteMain.iconsReady = true;
659                        }
660                    }
661                }
662                Text {
663                    property bool fontResized: false
664                    id: machineListItemText
665                    text: model.modelData.description
666                    color: "black"
667                    font.bold: true
668                    font.italic: true
669                    font.pixelSize: machineListItemBackground.height / 3
670                    elide: Text.ElideRight
671                    wrapMode: Text.NoWrap
672                    anchors.centerIn: machineListItemBackground
673                    anchors.horizontalCenterOffset: machineListItemIcon.width > 1 ? (paintedWidth + machineListItemIcon.width + 45 < machineListItemBackground.width ? 0 : machineListItemIcon.width - 10) : 0
674                    width: machineListItemIcon.width > 1 ? machineListItemBackground.width - machineListItemIcon.width - 20 : machineListItemBackground.width - 20
675                    smooth: true
676                    verticalAlignment: Text.AlignVCenter
677                    horizontalAlignment: Text.AlignHCenter
678                }
679                MouseArea {
680                    id: machineListItemMouseArea
681                    anchors.fill: machineListItemBackground
682                    hoverEnabled: true
683                    acceptedButtons: Qt.LeftButton
684                    onContainsMouseChanged: {
685                        if ( mapToItem(menuAndStatusBar, mouseX, mouseY).y < 0 ) {
686                            if ( containsMouse )
687                                ToxicWaste.itemEntered(machineListItemText, machineListItemBackground, machineListItemIcon);
688                            else
689                                ToxicWaste.itemExited(machineListItemText, machineListItemBackground, machineListItemIcon);
690                        }
691                    }
692                    onDoubleClicked: {
693                        machineListView.currentIndex = index;
694                        ToxicWaste.itemClicked(machineListItemText, machineListItemBackground, machineListItemIcon);
695                        searchTextInput.focus = false;
696                        launchButton.opacity = 1.0;
697                        viewer.launchEmulator(machineListModel[machineListView.currentIndex].id);
698                        launchButtonFlashTimer.start();
699                    }
700                    onClicked: {
701                        machineListView.currentIndex = index;
702                        ToxicWaste.itemClicked(machineListItemText, machineListItemBackground, machineListItemIcon);
703                        searchTextInput.focus = false;
704                    }
705                }
706            }
707        }
708        model: machineListModel
709        function firstVisibleItem() { return indexAt(contentX + 10, contentY + 10); }
710        function lastVisibleItem() { return indexAt(contentX + width - 10, contentY + height - 10); }
711        function itemsPerPage() { return Math.floor(height / 82); }
712        Keys.onPressed: {
713            switch ( event.key ) {
714            case Qt.Key_PageUp:
715                if ( currentIndex - itemsPerPage() > 0 ) {
716                    incrementCurrentIndex();
717                    contentY = contentY - height + 70;
718                    if ( currentIndex > 0 )
719                        decrementCurrentIndex();
720                    else {
721                        contentY = 0;
722                        currentIndex = 0;
723                    }
724                } else
725                    currentIndex = 0;
726                event.accepted = true;
727                break;
728            case Qt.Key_PageDown:
729                if ( currentIndex + itemsPerPage() < machineListModelCount - 1 ) {
730                    decrementCurrentIndex();
731                    contentY = contentY + height - 70;
732                    if ( currentIndex < machineListModelCount - 1 )
733                        incrementCurrentIndex();
734                    else {
735                        contentY = contentHeight - 82;
736                        currentIndex = machineListModelCount - 1;
737                    }
738                } else
739                    currentIndex = machineListModelCount - 1;
740                event.accepted = true;
741                break;
742            case Qt.Key_End:
743                positionViewAtEnd();
744                event.accepted = true;
745                break;
746            case Qt.Key_Home:
747                positionViewAtBeginning();
748                event.accepted = true;
749                break;
750            case Qt.Key_Enter:
751            case Qt.Key_Return:
752                if ( !searchTextInput.focus && !(event.modifiers & Qt.AltModifier) && !toxicWasteMain.ignoreLaunch ) {
753                    launchButton.opacity = 1.0;
754                    viewer.launchEmulator(machineListModel[machineListView.currentIndex].id);
755                    launchButtonFlashTimer.start();
756                    event.accepted = true;
757                }
758                break;
759            }
760        }
761        onCurrentIndexChanged: {
762            toxicWasteMain.lastIndex = currentIndex;
763            if ( !flicking )
764                detailUpdateTimer.restart();
765        }
766        onFlickingChanged: {
767            if ( !flicking )
768                detailUpdateTimer.restart();
769        }
770    }
771    Rectangle {
772        id: confirmQuitDialog
773        smooth: true
774        radius: 10
775        scale: ToxicWaste.scaleFactorX()
776        x: parent.width / 2 - width / 2
777        y: parent.height / 2 - height / 2
778        width: 120
779        height: 80
780        border.color: "black"
781        border.width: 2
782        color: "#007bff"
783        opacity: 0.0
784        state: "hidden"
785        z: 5
786        MouseArea {
787            anchors.fill: parent
788        }
789        Rectangle {
790            anchors.fill: parent
791            anchors.margins: 5
792            color: "#55a5ff"
793            border.color: "black"
794            border.width: 1
795            radius: 5
796            smooth: true
797        }
798        Text {
799            text: qsTr("Really quit?")
800            anchors.top: parent.top
801            anchors.topMargin: 15
802            anchors.horizontalCenter: parent.horizontalCenter
803            anchors.horizontalCenterOffset: 0
804            font.pixelSize: 12
805            color: "black"
806            smooth: true
807        }
808        Row {
809            anchors.bottom: parent.bottom
810            anchors.bottomMargin: 10
811            anchors.horizontalCenterOffset: 0
812            anchors.horizontalCenter: parent.horizontalCenter
813            spacing: 10
814            Button {
815                id: buttonYes
816                text: qsTr("Yes")
817                onClicked: {
818                    toxicWasteMain.ignoreLaunch = true;
819                    Qt.quit();
820                }
821                onFocusChanged: {
822                    if ( !focus )
823                        toxicWasteMain.focus = true;
824                }
825                KeyNavigation.tab: buttonNo
826                KeyNavigation.backtab: buttonNo
827                KeyNavigation.left: buttonNo
828                KeyNavigation.right: buttonNo
829            }
830            Button {
831                id: buttonNo
832                text: qsTr("No")
833                onClicked: {
834                    toxicWasteMain.ignoreLaunch = true;
835                    confirmQuitDialog.state = "hidden";
836                    resetIgnoreLaunchTimer.restart();
837                }
838                onFocusChanged: {
839                    if ( !focus )
840                        toxicWasteMain.focus = true;
841                }
842                KeyNavigation.tab: buttonYes
843                KeyNavigation.backtab: buttonYes
844                KeyNavigation.left: buttonYes
845                KeyNavigation.right: buttonYes
846            }
847        }
848        onStateChanged: {
849            if ( state == "shown" )
850                buttonNo.focus = true;
851            else {
852                buttonNo.focus = false;
853                buttonYes.focus = false;
854                if ( preferencesDialog.state == "shown" )
855                    closeButton.focus = true;
856            }
857        }
858        states: [
859            State {
860                name: "hidden"
861                PropertyChanges { target: confirmQuitDialog; opacity: 0.0 }
862            },
863            State {
864                name: "shown"
865                PropertyChanges { target: confirmQuitDialog; opacity: 1.0 }
866            }
867        ]
868        transitions: Transition {
869            from: "hidden"
870            to: "shown"
871            reversible: true
872            PropertyAnimation { property: "opacity"; duration: 250 }
873        }
874        onOpacityChanged: {
875            if ( opacity > 0 )
876                searchTextInput.focus = false;
877        }
878    }
879    Rectangle {
880        id: preferencesDialog
881        smooth: true
882        radius: 10
883        scale: ToxicWaste.scaleFactorX()
884        x: parent.width / 2 - width / 2
885        y: parent.height / 2 - height / 2
886        width: 300
887        height: 250
888        border.color: "black"
889        border.width: 2
890        color: "#007bff"
891        opacity: 0.0
892        state: "hidden"
893        z: 4
894        MouseArea {
895            anchors.fill: parent
896        }
897        TabWidget {
898            id: preferencesTabWidget
899            anchors.fill: parent
900            anchors.margins: 5
901            baseColor: "#55a5ff"
902            activeBorderColor: "black"
903            inactiveBorderColor: "#202020"
904            activeTextColor: "black"
905            inactiveTextColor: "#202020"
906            fontSize: 12
907            smooth: true
908            current: toxicWasteMain.preferencesTab
909            onCurrentChanged: toxicWasteMain.preferencesTab = current
910            scaleFactor: ToxicWaste.scaleFactorX()
911            Rectangle {
912                id: preferencesSwitchesTab
913                property string title: qsTr("Switches")
914                property alias firstKeyNavItem: showBgAnimCheckBox
915                property alias lastKeyNavItem: autoPositionOverlayCheckBox
916                anchors.fill: parent
917                color: preferencesTabWidget.baseColor
918                radius: 5
919                smooth: true
920                border.color: preferencesTabWidget.activeBorderColor
921                CheckBox {
922                    id: showBgAnimCheckBox
923                    anchors.top: preferencesSwitchesTab.top
924                    anchors.topMargin: 10
925                    anchors.bottom: preferencesSwitchesTab.top
926                    anchors.bottomMargin: -26
927                    anchors.left: parent.left
928                    anchors.leftMargin: 5
929                    anchors.right: parent.right
930                    anchors.rightMargin: 10
931                    checked: toxicWasteMain.showBackgroundAnimation
932                    text: qsTr("Show floating-bubbles animation?")
933                    textColor: "black"
934                    onClicked: {
935                        toxicWasteMain.showBackgroundAnimation = checked;
936                        toxicWasteMain.ignoreLaunch = true;
937                        resetIgnoreLaunchTimer.restart();
938                        if ( checked )
939                            backgroundAnim.opacity = 1.0;
940                        else
941                            backgroundAnim.opacity = 0.0;
942                        focus = true;
943                    }
944                    onFocusChanged: {
945                        if ( !focus )
946                            toxicWasteMain.focus = true;
947                    }
948                    KeyNavigation.tab: preferencesTabWidget.nextKeyNavItem(animInFgCheckBox, preferencesSwitchesTab)
949                    KeyNavigation.backtab: preferencesTabWidget.previousKeyNavItem(closeButton, preferencesSwitchesTab)
950                    KeyNavigation.right: preferencesTabWidget.nextKeyNavItem(animInFgCheckBox, preferencesSwitchesTab)
951                    KeyNavigation.left: preferencesTabWidget.previousKeyNavItem(closeButton, preferencesSwitchesTab)
952                    smooth: true
953                }
954                CheckBox {
955                    id: animInFgCheckBox
956                    anchors.top: showBgAnimCheckBox.bottom
957                    anchors.topMargin: 10
958                    anchors.bottom: showBgAnimCheckBox.bottom
959                    anchors.bottomMargin: -26
960                    anchors.left: parent.left
961                    anchors.leftMargin: 5
962                    anchors.right: parent.right
963                    anchors.rightMargin: 10
964                    checked: toxicWasteMain.animateInForeground
965                    text: qsTr("Draw animation in the foreground?")
966                    textColor: "black"
967                    onClicked: {
968                        toxicWasteMain.animateInForeground = checked;
969                        toxicWasteMain.ignoreLaunch = true;
970                        resetIgnoreLaunchTimer.restart();
971                        focus = true;
972                    }
973                    onFocusChanged: {
974                        if ( !focus )
975                            toxicWasteMain.focus = true;
976                    }
977                    KeyNavigation.tab: preferencesTabWidget.nextKeyNavItem(showShaderEffectCheckBox, preferencesSwitchesTab)
978                    KeyNavigation.backtab: preferencesTabWidget.previousKeyNavItem(showBgAnimCheckBox, preferencesSwitchesTab)
979                    KeyNavigation.right: preferencesTabWidget.nextKeyNavItem(showShaderEffectCheckBox, preferencesSwitchesTab)
980                    KeyNavigation.left: preferencesTabWidget.previousKeyNavItem(showBgAnimCheckBox, preferencesSwitchesTab)
981                    smooth: true
982                }
983                CheckBox {
984                    id: showShaderEffectCheckBox
985                    anchors.top: animInFgCheckBox.bottom
986                    anchors.topMargin: 10
987                    anchors.bottom: animInFgCheckBox.bottom
988                    anchors.bottomMargin: -26
989                    anchors.left: parent.left
990                    anchors.leftMargin: 5
991                    anchors.right: parent.right
992                    anchors.rightMargin: 10
993                    checked: toxicWasteMain.showShaderEffect
994                    text: qsTr("Show radial wave effect on background?")
995                    textColor: "black"
996                    onClicked: {
997                        toxicWasteMain.showShaderEffect = checked;
998                        waveEffect.running = checked;
999                        toxicWasteMain.ignoreLaunch = true;
1000                        resetIgnoreLaunchTimer.restart();
1001                        focus = true;
1002                    }
1003                    onFocusChanged: {
1004                        if ( !focus )
1005                            toxicWasteMain.focus = true;
1006                    }
1007                    KeyNavigation.tab: preferencesTabWidget.nextKeyNavItem(autoStopAnimationsCheckBox, preferencesSwitchesTab)
1008                    KeyNavigation.backtab: preferencesTabWidget.previousKeyNavItem(animInFgCheckBox, preferencesSwitchesTab)
1009                    KeyNavigation.right: preferencesTabWidget.nextKeyNavItem(autoStopAnimationsCheckBox, preferencesSwitchesTab)
1010                    KeyNavigation.left: preferencesTabWidget.previousKeyNavItem(animInFgCheckBox, preferencesSwitchesTab)
1011                    smooth: true
1012                }
1013                CheckBox {
1014                    id: autoStopAnimationsCheckBox
1015                    anchors.top: showShaderEffectCheckBox.bottom
1016                    anchors.topMargin: 10
1017                    anchors.bottom: showShaderEffectCheckBox.bottom
1018                    anchors.bottomMargin: -26
1019                    anchors.left: parent.left
1020                    anchors.leftMargin: 5
1021                    anchors.right: parent.right
1022                    anchors.rightMargin: 10
1023                    checked: toxicWasteMain.autoStopAnimations
1024                    text: qsTr("Auto-stop animation and wave effect?")
1025                    textColor: "black"
1026                    onClicked: {
1027                        toxicWasteMain.autoStopAnimations = checked;
1028                        toxicWasteMain.ignoreLaunch = true;
1029                        resetIgnoreLaunchTimer.restart();
1030                        focus = true;
1031                    }
1032                    onFocusChanged: {
1033                        if ( !focus )
1034                            toxicWasteMain.focus = true;
1035                    }
1036                    KeyNavigation.tab: preferencesTabWidget.nextKeyNavItem(showFpsCheckBox, preferencesSwitchesTab)
1037                    KeyNavigation.backtab: preferencesTabWidget.previousKeyNavItem(showShaderEffectCheckBox, preferencesSwitchesTab)
1038                    KeyNavigation.right: preferencesTabWidget.nextKeyNavItem(showFpsCheckBox, preferencesSwitchesTab)
1039                    KeyNavigation.left: preferencesTabWidget.previousKeyNavItem(showShaderEffectCheckBox, preferencesSwitchesTab)
1040                    smooth: true
1041                }
1042                CheckBox {
1043                    id: showFpsCheckBox
1044                    anchors.top: autoStopAnimationsCheckBox.bottom
1045                    anchors.topMargin: 10
1046                    anchors.bottom: autoStopAnimationsCheckBox.bottom
1047                    anchors.bottomMargin: -26
1048                    anchors.left: parent.left
1049                    anchors.leftMargin: 5
1050                    anchors.right: parent.right
1051                    anchors.rightMargin: 10
1052                    checked: toxicWasteMain.fpsVisible
1053                    text: qsTr("Show FPS counter in the menu-bar?")
1054                    textColor: "black"
1055                    onClicked: {
1056                        toxicWasteMain.fpsVisible = checked;
1057                        toxicWasteMain.ignoreLaunch = true;
1058                        resetIgnoreLaunchTimer.restart();
1059                        focus = true;
1060                    }
1061                    onFocusChanged: {
1062                        if ( !focus )
1063                            toxicWasteMain.focus = true;
1064                    }
1065                    KeyNavigation.tab: preferencesTabWidget.nextKeyNavItem(confirmQuitCheckBox, preferencesSwitchesTab)
1066                    KeyNavigation.backtab: preferencesTabWidget.previousKeyNavItem(autoStopAnimationsCheckBox, preferencesSwitchesTab)
1067                    KeyNavigation.right: preferencesTabWidget.nextKeyNavItem(confirmQuitCheckBox, preferencesSwitchesTab)
1068                    KeyNavigation.left: preferencesTabWidget.previousKeyNavItem(autoStopAnimationsCheckBox, preferencesSwitchesTab)
1069                    smooth: true
1070                }
1071                CheckBox {
1072                    id: confirmQuitCheckBox
1073                    anchors.top: showFpsCheckBox.bottom
1074                    anchors.topMargin: 10
1075                    anchors.bottom: showFpsCheckBox.bottom
1076                    anchors.bottomMargin: -26
1077                    anchors.left: parent.left
1078                    anchors.leftMargin: 5
1079                    anchors.right: parent.right
1080                    anchors.rightMargin: 10
1081                    checked: toxicWasteMain.confirmQuit
1082                    text: qsTr("Confirm when quitting the application?")
1083                    textColor: "black"
1084                    onClicked: {
1085                        toxicWasteMain.confirmQuit = checked;
1086                        toxicWasteMain.ignoreLaunch = true;
1087                        resetIgnoreLaunchTimer.restart();
1088                        focus = true;
1089                    }
1090                    onFocusChanged: {
1091                        if ( !focus )
1092                            toxicWasteMain.focus = true;
1093                    }
1094                    KeyNavigation.tab: preferencesTabWidget.nextKeyNavItem(autoPositionOverlayCheckBox, preferencesSwitchesTab)
1095                    KeyNavigation.backtab: preferencesTabWidget.previousKeyNavItem(showFpsCheckBox, preferencesSwitchesTab)
1096                    KeyNavigation.right: preferencesTabWidget.nextKeyNavItem(autoPositionOverlayCheckBox, preferencesSwitchesTab)
1097                    KeyNavigation.left: preferencesTabWidget.previousKeyNavItem(showFpsCheckBox, preferencesSwitchesTab)
1098                    smooth: true
1099                }
1100                CheckBox {
1101                    id: autoPositionOverlayCheckBox
1102                    anchors.top: confirmQuitCheckBox.bottom
1103                    anchors.topMargin: 10
1104                    anchors.bottom: confirmQuitCheckBox.bottom
1105                    anchors.bottomMargin: -26
1106                    anchors.left: parent.left
1107                    anchors.leftMargin: 5
1108                    anchors.right: parent.right
1109                    anchors.rightMargin: 10
1110                    checked: toxicWasteMain.autoPositionOverlay
1111                    text: qsTr("Position the cabinet automatically?")
1112                    textColor: "black"
1113                    onClicked: {
1114                        toxicWasteMain.autoPositionOverlay = checked;
1115                        toxicWasteMain.ignoreLaunch = true;
1116                        resetIgnoreLaunchTimer.restart();
1117                        focus = true;
1118                    }
1119                    onFocusChanged: {
1120                        if ( !focus )
1121                            toxicWasteMain.focus = true;
1122                    }
1123                    KeyNavigation.tab: preferencesTabWidget.nextKeyNavItem(closeButton, preferencesSwitchesTab)
1124                    KeyNavigation.backtab: preferencesTabWidget.previousKeyNavItem(confirmQuitCheckBox, preferencesSwitchesTab)
1125                    KeyNavigation.right: preferencesTabWidget.nextKeyNavItem(closeButton, preferencesSwitchesTab)
1126                    KeyNavigation.left: preferencesTabWidget.previousKeyNavItem(confirmQuitCheckBox, preferencesSwitchesTab)
1127                    smooth: true
1128                }
1129            }
1130            Rectangle {
1131                id: preferencesSlidersTab
1132                property string title: qsTr("Sliders")
1133                property alias firstKeyNavItem: cliOptionCombo
1134                property alias lastKeyNavItem: cliValueCombo
1135                property alias cabinetZoomValue: cabinetZoomSlider.value
1136                anchors.fill: parent
1137                color: preferencesTabWidget.baseColor
1138                border.color: preferencesTabWidget.activeBorderColor
1139                radius: 5
1140                smooth: true
1141                Slider {
1142                    id: cabinetZoomSlider
1143                    anchors.top: parent.top
1144                    anchors.left: parent.left
1145                    anchors.right: parent.right
1146                    anchors.margins: 5
1147                    sliderText: qsTr("Cabinet zoom")
1148                    minimum: 0.01
1149                    maximum: 10
1150                    value: toxicWasteMain.overlayScale
1151                    defaultValue: 1
1152                    onValueChanged: toxicWasteMain.overlayScale = value
1153                }
1154                Slider {
1155                    id: cabinetOffsetXSlider
1156                    anchors.top: cabinetZoomSlider.bottom
1157                    anchors.left: parent.left
1158                    anchors.right: parent.right
1159                    anchors.margins: 5
1160                    sliderText: qsTr("Cabinet X center offset")
1161                    minimum: (-toxicWasteMain.width/2 - 514 * toxicWasteMain.overlayScale) / toxicWasteMain.overlayScale
1162                    maximum: (toxicWasteMain.width/2 + 514 * toxicWasteMain.overlayScale) / toxicWasteMain.overlayScale
1163                    showAsPercent: false
1164                    value: toxicWasteMain.overlayOffsetX
1165                    defaultValue: 0
1166                    onValueChanged: toxicWasteMain.overlayOffsetX = value
1167                }
1168                Slider {
1169                    id: cabinetOffsetYSlider
1170                    anchors.top: cabinetOffsetXSlider.bottom
1171                    anchors.left: parent.left
1172                    anchors.right: parent.right
1173                    anchors.margins: 5
1174                    sliderText: qsTr("Cabinet Y center offset")
1175                    minimum: (-toxicWasteMain.height/2 - 1104 * toxicWasteMain.overlayScale) / toxicWasteMain.overlayScale
1176                    maximum: (toxicWasteMain.height/2 + 1104 * toxicWasteMain.overlayScale) / toxicWasteMain.overlayScale
1177                    showAsPercent: false
1178                    value: toxicWasteMain.overlayOffsetY
1179                    defaultValue: 0
1180                    onValueChanged: toxicWasteMain.overlayOffsetY = value
1181                }
1182                Slider {
1183                    id: cabinetOpacitySlider
1184                    anchors.top: cabinetOffsetYSlider.bottom
1185                    anchors.left: parent.left
1186                    anchors.right: parent.right
1187                    anchors.margins: 5
1188                    sliderText: qsTr("Cabinet opacity")
1189                    minimum: 0
1190                    maximum: 1
1191                    value: toxicWasteMain.overlayOpacity
1192                    defaultValue: 1
1193                    onValueChanged: toxicWasteMain.overlayOpacity = value
1194                }
1195                Slider {
1196                    id: backgroundOpacitySlider
1197                    anchors.top: cabinetOpacitySlider.bottom
1198                    anchors.left: parent.left
1199                    anchors.right: parent.right
1200                    anchors.margins: 5
1201                    sliderText: qsTr("Background opacity")
1202                    minimum: 0
1203                    maximum: 1
1204                    value: toxicWasteMain.backgroundOpacity
1205                    defaultValue: 0.7
1206                    onValueChanged: toxicWasteMain.backgroundOpacity = value
1207                }
1208                Slider {
1209                    id: machineListOpacitySlider
1210                    anchors.top: backgroundOpacitySlider.bottom
1211                    anchors.left: parent.left
1212                    anchors.right: parent.right
1213                    anchors.margins: 5
1214                    sliderText: qsTr("Machine list opacity")
1215                    minimum: 0
1216                    maximum: 1
1217                    value: toxicWasteMain.machineListOpacity
1218                    defaultValue: 1
1219                    onValueChanged: toxicWasteMain.machineListOpacity = value
1220                }
1221            }
1222            Rectangle {
1223                id: preferencesBackendTab
1224                property string title: qsTr("Backend")
1225                property alias firstKeyNavItem: cliOptionCombo
1226                property alias lastKeyNavItem: cliValueCombo
1227                anchors.fill: parent
1228                color: preferencesTabWidget.baseColor
1229                border.color: preferencesTabWidget.activeBorderColor
1230                radius: 5
1231                smooth: true
1232                Text {
1233                    id: cliOptionText
1234                    anchors.top: parent.top
1235                    anchors.left: parent.left
1236                    anchors.right: parent.right
1237                    anchors.margins: 5
1238                    anchors.rightMargin: parent.width/2 + 2
1239                    smooth: true
1240                    font.pixelSize: 12
1241                    text: qsTr("Option")
1242                    horizontalAlignment: Text.Center
1243                }
1244                ComboBox {
1245                    id: cliOptionCombo
1246                    anchors.top: cliOptionText.bottom
1247                    anchors.left: parent.left
1248                    anchors.right: parent.right
1249                    anchors.margins: 5
1250                    anchors.rightMargin: parent.width/2 + 2
1251                    smooth: true
1252                    model: ListModel {}
1253                    arrowIcon: "images/down_arrow.png"
1254                    font.pixelSize: 12
1255                    z: +1
1256                    onFocusChanged: {
1257                        if ( !focus )
1258                            toxicWasteMain.focus = true;
1259                    }
1260                    onIndexChosen: {
1261                        cliValueCombo.ready = false;
1262                        cliValueCombo.model.clear();
1263                        var cliParamName = viewer.cliParamNames()[index];
1264                        var cliParamOptions = viewer.cliParamAllowedValues(cliParamName);
1265                        var cliParamValue = viewer.cliParamValue(cliParamName);
1266                        var indexToSelect = 0;
1267                        for (var i = 0; i < cliParamOptions.length; i++) {
1268                            var cliParamOption = cliParamOptions[i];
1269                            cliValueCombo.model.append({'name': cliParamOption});
1270                            if ( cliParamOption === cliParamValue )
1271                                indexToSelect = i;
1272                        }
1273                        cliValueCombo.close(true);
1274                        cliValueCombo.positionAtTop();
1275                        cliValueCombo.setCurrentIndex(indexToSelect);
1276                        cliValueCombo.ready = true;
1277                    }
1278                    KeyNavigation.tab: preferencesTabWidget.nextKeyNavItem(cliValueCombo, preferencesBackendTab);
1279                    KeyNavigation.backtab: preferencesTabWidget.previousKeyNavItem(closeButton, preferencesBackendTab);
1280                    KeyNavigation.right: preferencesTabWidget.nextKeyNavItem(cliValueCombo, preferencesBackendTab);
1281                    KeyNavigation.left: preferencesTabWidget.previousKeyNavItem(closeButton, preferencesBackendTab);
1282                }
1283                Text {
1284                    id: cliValueText
1285                    anchors.top: parent.top
1286                    anchors.left: parent.left
1287                    anchors.right: parent.right
1288                    anchors.margins: 5
1289                    anchors.leftMargin: parent.width/2 + 2
1290                    smooth: true
1291                    font.pixelSize: 12
1292                    horizontalAlignment: Text.Center
1293                    text: qsTr("Value")
1294                }
1295                ComboBox {
1296                    id: cliValueCombo
1297                    property bool ready: false
1298                    anchors.left: parent.left
1299                    anchors.right: parent.right
1300                    anchors.top: cliValueText.bottom
1301                    anchors.margins: 5
1302                    anchors.leftMargin: parent.width/2 + 2
1303                    smooth: true
1304                    model: ListModel {}
1305                    arrowIcon: "images/down_arrow.png"
1306                    font.pixelSize: 12
1307                    z: +1
1308                    onFocusChanged: {
1309                        if ( !focus )
1310                            toxicWasteMain.focus = true;
1311                    }
1312                    onIndexChosen: {
1313                        if ( ready ) {
1314                            var cliParamName = viewer.cliParamNames()[cliOptionCombo.currentIndex()];
1315                            var cliParamValue = viewer.cliParamAllowedValues(cliParamName)[cliValueCombo.currentIndex()];
1316                            if ( cliParamName !== "" && cliParamValue !== "" )
1317                                viewer.setCliParamValue(cliParamName, cliParamValue);
1318                        }
1319                    }
1320                    KeyNavigation.tab: preferencesTabWidget.nextKeyNavItem(closeButton, preferencesBackendTab);
1321                    KeyNavigation.backtab: preferencesTabWidget.previousKeyNavItem(cliOptionCombo, preferencesBackendTab);
1322                    KeyNavigation.right: preferencesTabWidget.nextKeyNavItem(closeButton, preferencesBackendTab);
1323                    KeyNavigation.left: preferencesTabWidget.previousKeyNavItem(cliOptionCombo, preferencesBackendTab);
1324                }
1325                Text {
1326                    id: cliInfoText
1327                    anchors.top: cliValueCombo.bottom
1328                    anchors.left: parent.left
1329                    anchors.right: parent.right
1330                    anchors.margins: 5
1331                    anchors.topMargin: 20
1332                    smooth: true
1333                    font.pixelSize: 12
1334                    horizontalAlignment: Text.Center
1335                    text: qsTr("For customized default backend options to\ntake effect, please restart QMC2 Arcade!")
1336                }
1337                Component.onCompleted: {
1338                    var cliParamNames = viewer.cliParamNames();
1339                    var cliParamOptions = viewer.cliParamAllowedValues(cliParamNames[0]);
1340                    var cliParamValue = viewer.cliParamValue(cliParamNames[0]);
1341                    var indexToSelect = 0;
1342                    for (var i = 0; i < cliParamOptions.length; i++) {
1343                        var cliParamOption = cliParamOptions[i];
1344                        cliValueCombo.model.append({'name': cliParamOption});
1345                        if ( cliParamOption === cliParamValue )
1346                            indexToSelect = i;
1347                    }
1348                    for (var i = 0; i < cliParamNames.length; i++)
1349                        cliOptionCombo.model.append({'name': viewer.cliParamDescription(cliParamNames[i])});
1350                    cliValueCombo.setCurrentIndex(indexToSelect);
1351                    cliValueCombo.ready = true;
1352                }
1353            }
1354        }
1355        Button {
1356            id: closeButton
1357            anchors.bottom: parent.bottom
1358            anchors.bottomMargin: 10
1359            anchors.horizontalCenterOffset: 0
1360            anchors.horizontalCenter: parent.horizontalCenter
1361            text: qsTr("Close")
1362            onClicked: {
1363                toxicWasteMain.ignoreLaunch = true;
1364                preferencesDialog.state = "hidden";
1365                toxicWasteMain.focus = true;
1366                resetIgnoreLaunchTimer.restart();
1367            }
1368            onFocusChanged: {
1369                if ( !focus )
1370                    toxicWasteMain.focus = true;
1371            }
1372            KeyNavigation.tab: preferencesTabWidget.firstKeyNavItem()
1373            KeyNavigation.backtab: preferencesTabWidget.lastKeyNavItem()
1374            KeyNavigation.right: preferencesTabWidget.firstKeyNavItem()
1375            KeyNavigation.left: preferencesTabWidget.lastKeyNavItem()
1376        }
1377        onStateChanged: {
1378            if ( state == "shown" && confirmQuitDialog.state == "hidden" )
1379                closeButton.focus = true;
1380            else {
1381                closeButton.focus = false;
1382                showBgAnimCheckBox.focus = false;
1383                showFpsCheckBox = false;
1384            }
1385        }
1386        states: [
1387            State {
1388                name: "hidden"
1389                PropertyChanges { target: preferencesDialog; opacity: 0.0 }
1390            },
1391            State {
1392                name: "shown"
1393                PropertyChanges { target: preferencesDialog; opacity: 1.0 }
1394            }
1395        ]
1396        transitions: Transition {
1397            from: "hidden"
1398            to: "shown"
1399            reversible: true
1400            PropertyAnimation { property: "opacity"; duration: 250 }
1401        }
1402    }
1403    Image {
1404        id: showHideMenuBarButton
1405        source: "images/hide_show_menu.png"
1406        height: menuAndStatusBar.height
1407        anchors.left: parent.left
1408        anchors.leftMargin: 2 * ToxicWaste.scaleFactorX()
1409        anchors.bottom: parent.bottom
1410        fillMode: Image.PreserveAspectFit
1411        opacity: 0.5
1412        rotation: toxicWasteMain.menuHidden ? 0 : 180
1413        smooth: true
1414        z: 5
1415        MouseArea {
1416            anchors.fill: parent
1417            hoverEnabled: true
1418            onEntered: parent.opacity = 1.0
1419            onMouseXChanged: parent.opacity = 1.0
1420            onMouseYChanged: parent.opacity = 1.0
1421            onExited: parent.opacity = 0.5
1422            onClicked: {
1423                if ( !menuAndStatusBar.showHideAnim.running ) {
1424                    parent.opacity = 0.5;
1425                    if ( parent.rotation == 0 ) {
1426                        parent.rotation = 180;
1427                        toxicWasteMain.menuHidden = false;
1428                    } else {
1429                        parent.rotation = 0;
1430                        toxicWasteMain.menuHidden = true;
1431                    }
1432                    searchTextInput.focus = false;
1433                }
1434            }
1435        }
1436    }
1437    onMenuHiddenChanged: {
1438        if ( menuHidden ) {
1439            menuAndStatusBar.anchors.bottomMargin -= 64;
1440            searchTextInput.focus = false;
1441        } else
1442            menuAndStatusBar.anchors.bottomMargin += 64;
1443    }
1444    Rectangle {
1445        id: menuAndStatusBar
1446        property alias showHideAnim: menuAndStatusBarAnimation
1447        x: 0
1448        z: 4
1449        Behavior on anchors.bottomMargin {
1450            NumberAnimation { id: menuAndStatusBarAnimation; duration: 250 }
1451        }
1452        width: parent.width
1453        height: ToxicWaste.scaleFactorY() > 1 ? 24 : (ToxicWaste.scaleFactorY() < 0.5 ? 12 : 24 * ToxicWaste.scaleFactorY())
1454        anchors.horizontalCenter: parent.horizontalCenter
1455        anchors.bottom: parent.bottom
1456        anchors.bottomMargin: 0
1457        transformOrigin: Rectangle.Bottom
1458        opacity: 1.0
1459        smooth: true
1460        gradient: Gradient {
1461            GradientStop { position: 0.00; color: "#40AAAAAA" }
1462            GradientStop { position: 0.33; color: "#60AAAAAA" }
1463            GradientStop { position: 0.66; color: "#60AAAAAA" }
1464            GradientStop { position: 1.00; color: "#40AAAAAA" }
1465        }
1466        Text {
1467            id: fpsText
1468            anchors.left: parent.left
1469            anchors.leftMargin: showHideMenuBarButton.width + 10 * ToxicWaste.scaleFactorX()
1470            anchors.verticalCenter: menuAndStatusBar.verticalCenter
1471            opacity: 0.8
1472            smooth: true
1473            color: "white"
1474            text: qsTr("FPS") + ": " + toxicWasteMain.fps.toString()
1475            font.pixelSize: parent.height - 4 * ToxicWaste.scaleFactorY()
1476            visible: toxicWasteMain.fpsVisible
1477        }
1478        Image {
1479            id: exitButton
1480            anchors.top: parent.top
1481            anchors.topMargin: 2 * ToxicWaste.scaleFactorY()
1482            anchors.bottom: parent.bottom
1483            anchors.bottomMargin: 2 * ToxicWaste.scaleFactorY()
1484            anchors.right: parent.right
1485            anchors.rightMargin: 2 * ToxicWaste.scaleFactorX()
1486            source: "images/exit.png"
1487            smooth: true
1488            fillMode: Image.PreserveAspectFit
1489            opacity: 0.5
1490            MouseArea {
1491                anchors.fill: parent
1492                hoverEnabled: true
1493                onEntered: parent.opacity = 1.0
1494                onMouseXChanged: parent.opacity = 1.0
1495                onMouseYChanged: parent.opacity = 1.0
1496                onExited: parent.opacity = 0.5
1497                onClicked: {
1498                    if ( toxicWasteMain.confirmQuit ) {
1499                        parent.opacity = 1.0;
1500                        confirmQuitDialog.state = "shown";
1501                        searchTextInput.focus = false;
1502                    } else
1503                        Qt.quit();
1504                }
1505            }
1506        }
1507        Image {
1508            id: fullScreenToggleButton
1509            anchors.top: parent.top
1510            anchors.topMargin: 2 * ToxicWaste.scaleFactorY()
1511            anchors.bottom: parent.bottom
1512            anchors.bottomMargin: 2 * ToxicWaste.scaleFactorY()
1513            anchors.right: exitButton.left
1514            anchors.rightMargin: 5 * ToxicWaste.scaleFactorX()
1515            source: "images/fullscreen.png"
1516            state: toxicWasteMain.fullScreen ? "fullscreen" : "windowed"
1517            smooth: true
1518            fillMode: Image.PreserveAspectFit
1519            opacity: 0.5
1520            MouseArea {
1521                anchors.fill: parent
1522                hoverEnabled: true
1523                onEntered: parent.opacity = 1.0
1524                onMouseXChanged: parent.opacity = 1.0
1525                onMouseYChanged: parent.opacity = 1.0
1526                onExited: parent.opacity = 0.5
1527                onClicked: {
1528                    if ( fullScreenToggleButton.state == "windowed" ) {
1529                        fullScreenToggleButton.state = "fullscreen"
1530                        toxicWasteMain.fullScreen = true;
1531                    } else {
1532                        fullScreenToggleButton.state = "windowed"
1533                        toxicWasteMain.fullScreen = false;
1534                    }
1535                    parent.opacity = 1.0;
1536                    searchTextInput.focus = false;
1537                }
1538            }
1539            states: [
1540                State {
1541                    name: "fullscreen"
1542                    PropertyChanges { target: fullScreenToggleButton; source: "images/windowed.png" }
1543                },
1544                State {
1545                    name: "windowed"
1546                    PropertyChanges { target: fullScreenToggleButton; source: "images/fullscreen.png" }
1547                }
1548            ]
1549        }
1550        Image {
1551            id: preferencesButton
1552            anchors.top: parent.top
1553            anchors.topMargin: 2 * ToxicWaste.scaleFactorY()
1554            anchors.bottom: parent.bottom
1555            anchors.bottomMargin: 2 * ToxicWaste.scaleFactorY()
1556            anchors.right: fullScreenToggleButton.left
1557            anchors.rightMargin: 5 * ToxicWaste.scaleFactorX()
1558            source: "images/preferences.png"
1559            smooth: true
1560            fillMode: Image.PreserveAspectFit
1561            opacity: 0.5
1562            MouseArea {
1563                anchors.fill: parent
1564                hoverEnabled: true
1565                onEntered: parent.opacity = 1.0
1566                onMouseXChanged: parent.opacity = 1.0
1567                onMouseYChanged: parent.opacity = 1.0
1568                onExited: parent.opacity = 0.5
1569                onClicked: {
1570                    parent.opacity = 1.0;
1571                    preferencesDialog.state = "shown";
1572                    searchTextInput.focus = false;
1573                }
1574            }
1575        }
1576        Item {
1577            id: searchBox
1578            anchors.centerIn: parent
1579            width: 230 * ToxicWaste.scaleFactorX()
1580            height: menuAndStatusBar.height - 2 * ToxicWaste.scaleFactorY()
1581            Image {
1582                id: searchImage
1583                source: "images/find.png"
1584                height: parent.height - 4 * ToxicWaste.scaleFactorY()
1585                fillMode: Image.PreserveAspectFit
1586                smooth: true
1587                anchors.verticalCenter: parent.verticalCenter
1588                opacity: 0.5
1589                MouseArea {
1590                    anchors.fill: parent
1591                    hoverEnabled: true
1592                    onEntered: parent.opacity = 1.0
1593                    onMouseXChanged: parent.opacity = 1.0
1594                    onMouseYChanged: parent.opacity = 1.0
1595                    onExited: parent.opacity = 0.5
1596                    onClicked: {
1597                        parent.opacity = 1.0;
1598                        machineListView.positionViewAtIndex(viewer.findIndex(searchTextInput.text, machineListView.currentIndex), ListView.Beginning);
1599                        searchTextInput.focus = false;
1600                    }
1601                }
1602            }
1603            Rectangle {
1604                id: searchTextInputBox
1605                height: parent.height - 2 * ToxicWaste.scaleFactorY()
1606                width: 200 * ToxicWaste.scaleFactorX()
1607                radius: height/2
1608                smooth: true
1609                anchors.left: searchImage.right
1610                anchors.leftMargin: 5 * ToxicWaste.scaleFactorX()
1611                anchors.verticalCenter: searchImage.verticalCenter
1612                TextInput {
1613                    id: searchTextInput
1614                    anchors.verticalCenter: parent.verticalCenter
1615                    anchors.left: parent.left
1616                    anchors.leftMargin: 2 * ToxicWaste.scaleFactorX()
1617                    anchors.right: parent.right
1618                    anchors.rightMargin: 2 * ToxicWaste.scaleFactorX()
1619                    font.pointSize: parent.height - 2
1620                    smooth: true
1621                    focus: false
1622                    autoScroll: true
1623                    clip: true
1624                    selectByMouse: true
1625                    opacity: 0.8
1626                    cursorDelegate: Rectangle {
1627                        id: searchTextCursorDelegate
1628                        color: "black"
1629                        width: 1
1630                        height: parent.height
1631                        anchors.verticalCenter: parent.verticalCenter
1632                        visible: parent.activeFocus
1633                        SequentialAnimation on opacity {
1634                            loops: Animation.Infinite
1635                            running: searchTextCursorDelegate.visible
1636                            PropertyAction { target: searchTextCursorDelegate; property: "opacity"; value: 1.0 }
1637                            PauseAnimation { duration: 500 }
1638                            PropertyAction { target: searchTextCursorDelegate; property: "opacity"; value: 0.0 }
1639                            PauseAnimation { duration: 500 }
1640                        }
1641                    }
1642                    onAccepted: machineListView.positionViewAtIndex(viewer.findIndex(searchTextInput.text, machineListView.currentIndex), ListView.Beginning);
1643                    onFocusChanged: {
1644                        if ( !focus )
1645                            toxicWasteMain.focus = true;
1646                    }
1647                }
1648            }
1649            Image {
1650                id: clearImage
1651                source: "images/clear.png"
1652                height: parent.height + 2 * ToxicWaste.scaleFactorY()
1653                fillMode: Image.PreserveAspectFit
1654                smooth: true
1655                anchors.verticalCenter: parent.verticalCenter
1656                anchors.left: searchTextInputBox.right
1657                anchors.leftMargin: 5 * ToxicWaste.scaleFactorY()
1658                opacity: 0.5
1659                MouseArea {
1660                    anchors.fill: parent
1661                    hoverEnabled: true
1662                    onEntered: parent.opacity = 1.0
1663                    onMouseXChanged: parent.opacity = 1.0
1664                    onMouseYChanged: parent.opacity = 1.0
1665                    onExited: parent.opacity = 0.5
1666                    onClicked: {
1667                        parent.opacity = 1.0;
1668                        searchTextInput.text = "";
1669                        searchTextInput.focus = false;
1670                    }
1671                }
1672            }
1673        }
1674    }
1675    BackgroundAnimation {
1676        id: backgroundAnim
1677        opacity: toxicWasteMain.showBackgroundAnimation ? 1.0 : 0.0
1678        Behavior on opacity {
1679            NumberAnimation { properties: "opacity"; duration: 1000 }
1680        }
1681        z: toxicWasteMain.animateInForeground ? 5 : 2
1682    }
1683    ShaderEffectSource {
1684        id: effectSource
1685        anchors.fill: parent
1686        sourceItem: Image {
1687            source: "images/shadereffectsource.png"
1688            anchors.fill: parent
1689        }
1690        live: false
1691        hideSource: true
1692    }
1693    RadialWaveEffect {
1694        id: waveEffect
1695        anchors.fill: parent
1696        source: effectSource
1697        wave: 0.0
1698        waveOriginX: 0.5
1699        waveOriginY: 0.5
1700        waveWidth: 0.01
1701        z: 1
1702        property alias running: waveAnim.running
1703        NumberAnimation on wave {
1704            id: waveAnim
1705            running: toxicWasteMain.showShaderEffect
1706            loops: Animation.Infinite
1707            easing.type: Easing.Linear
1708            from: 0.0000;
1709            to: 2.0000;
1710            duration: 3000
1711            onRunningChanged: {
1712                if ( running )
1713                    viewer.log("ToxicWaste: " + qsTr("Starting shader effect"));
1714                else
1715                    viewer.log("ToxicWaste: " + qsTr("Shader effect stopped"));
1716            }
1717        }
1718    }
1719    focus: true
1720    Keys.onPressed: {
1721        switch ( event.key ) {
1722        case Qt.Key_Escape:
1723            if ( searchTextInput.focus )
1724                searchTextInput.focus = false;
1725            else {
1726                if ( toxicWasteMain.confirmQuit ) {
1727                    if ( confirmQuitDialog.state == "hidden" )
1728                        confirmQuitDialog.state = "shown";
1729                    else
1730                        confirmQuitDialog.state = "hidden";
1731                } else
1732                    Qt.quit();
1733            }
1734            event.accepted = true;
1735            break;
1736        case Qt.Key_F11:
1737            fullScreen = !fullScreen;
1738            event.accepted = true;
1739            break;
1740        case Qt.Key_Enter:
1741        case Qt.Key_Return:
1742            if ( event.modifiers & Qt.AltModifier ) {
1743                fullScreen = !fullScreen;
1744                event.accepted = true;
1745            }
1746            break;
1747        default:
1748            if ( event.modifiers & Qt.ControlModifier) {
1749                switch ( event.key ) {
1750                case Qt.Key_P:
1751                    if ( !toxicWasteMain.ignoreLaunch ) {
1752                        launchButton.opacity = 1.0;
1753                        viewer.launchEmulator(machineListModel[machineListView.currentIndex].id);
1754                        launchButtonFlashTimer.start();
1755                    }
1756                    event.accepted = true;
1757                    break;
1758                case Qt.Key_O:
1759                    preferencesDialog.state = preferencesDialog.state == "shown" ? "hidden" : "shown";
1760                    event.accepted = true;
1761                    break;
1762                case Qt.Key_M:
1763                    toxicWasteMain.menuHidden = !toxicWasteMain.menuHidden;
1764                    event.accepted = true;
1765                    break;
1766                case Qt.Key_X:
1767                    if ( toxicWasteMain.confirmQuit ) {
1768                        if ( confirmQuitDialog.state == "hidden" )
1769                            confirmQuitDialog.state = "shown";
1770                        else
1771                            confirmQuitDialog.state = "hidden";
1772                    } else
1773                        Qt.quit();
1774                    break;
1775                case Qt.Key_F:
1776                    if ( !toxicWasteMain.menuHidden ) {
1777                        searchTextInput.text = "";
1778                        searchTextInput.focus = true;
1779                        event.accepted = true;
1780                    }
1781                    break;
1782                case Qt.Key_Backspace:
1783                    toxicWasteMain.cabinetFlipped = !toxicWasteMain.cabinetFlipped;
1784                    event.accepted = true;
1785                    break;
1786                }
1787            } else if ( !toxicWasteMain.menuHidden ) {
1788                if ( ToxicWaste.validateKey(event.text) ) {
1789                    searchTextInput.text += event.text;
1790                    searchTextInput.focus = true;
1791                } else if ( ToxicWaste.validateSpecialKey(event.text) ) {
1792                    searchTextInput.focus = true;
1793                    switch ( event.text ) {
1794                    case "\b":
1795                        if ( searchTextInput.text.length > 0)
1796                            searchTextInput.text = searchTextInput.text.substring(0, searchTextInput.text.length - 1);
1797                        break;
1798                    }
1799                }
1800            }
1801        }
1802    }
1803    Keys.forwardTo: [machineListView]
1804    onFullScreenChanged: {
1805        if ( !ToxicWaste.initializing ) {
1806            if ( fullScreen ) {
1807                viewer.switchToFullScreen();
1808                fullScreenToggleButton.state = "fullscreen";
1809            } else {
1810                viewer.switchToWindowed();
1811                fullScreenToggleButton.state = "windowed";
1812            }
1813        }
1814    }
1815}
1816