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