1 /* RetroArch - A frontend for libretro.
2  *  Copyright (C) 2011-2017 - Daniel De Matteis
3  *  Copyright (C) 2016-2019 - Brad Parker
4  *
5  * RetroArch is free software: you can redistribute it and/or modify it under the terms
6  * of the GNU General Public License as published by the Free Software Found-
7  * ation, either version 3 of the License, or (at your option) any later version.
8  *
9  * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
11  * PURPOSE. See the GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along with RetroArch.
14  * If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include <QApplication>
18 #include <QAbstractEventDispatcher>
19 #include <QtWidgets>
20 #include <QtWidgets/QFileDialog>
21 #include <QtWidgets/QMessageBox>
22 #include <QtCore/QString>
23 #include <QDesktopWidget>
24 #include <QtGlobal>
25 #include <QCloseEvent>
26 #include <QResizeEvent>
27 #include <QStyle>
28 #include <QString>
29 #include <QTimer>
30 #include <QLabel>
31 #include <QFileDialog>
32 #include <QFileSystemModel>
33 #include <QListWidgetItem>
34 #include <QTableWidgetItem>
35 #include <QHash>
36 #include <QPushButton>
37 #include <QToolButton>
38 #include <QMenu>
39 #include <QDockWidget>
40 #include <QList>
41 #include <QInputDialog>
42 #include <QMimeData>
43 #include <QProgressDialog>
44 #include <QDragEnterEvent>
45 #include <QDropEvent>
46 #include <QtConcurrentRun>
47 #include <QtNetwork>
48 
49 #include "ui_qt.h"
50 #include "qt/gridview.h"
51 #include "qt/ui_qt_load_core_window.h"
52 #include "qt/coreinfodialog.h"
53 #include "qt/playlistentrydialog.h"
54 #if defined(HAVE_MENU)
55 #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
56 #include "qt/shaderparamsdialog.h"
57 #endif
58 #endif
59 #include "qt/coreoptionsdialog.h"
60 #include "qt/viewoptionsdialog.h"
61 
62 #ifndef CXX_BUILD
63 extern "C" {
64 #endif
65 
66 #include <file/file_path.h>
67 #include <file/archive_file.h>
68 #include <retro_timers.h>
69 #include <string/stdstring.h>
70 #include <retro_miscellaneous.h>
71 
72 #ifdef Q_OS_UNIX
73 #include <locale.h>
74 #endif
75 
76 #ifdef HAVE_OPENSSL
77 #include <openssl/ssl.h>
78 #include <openssl/opensslv.h>
79 #endif
80 
81 #ifdef HAVE_CONFIG_H
82 #include "../../config.h"
83 #endif
84 
85 #ifdef HAVE_MENU
86 #include "../../menu/menu_driver.h"
87 #endif
88 
89 #include "../../core_info.h"
90 #include "../../command.h"
91 #include "../ui_companion_driver.h"
92 #include "../../configuration.h"
93 #include "../../frontend/frontend.h"
94 #include "../../frontend/frontend_driver.h"
95 #include "../../file_path_special.h"
96 #include "../../paths.h"
97 #include "../../retroarch.h"
98 #include "../../verbosity.h"
99 #include "../../version.h"
100 #include "../../msg_hash.h"
101 #include "../../tasks/task_content.h"
102 #include "../../tasks/tasks_internal.h"
103 #include "../../AUTHORS.h"
104 #ifdef HAVE_GIT_VERSION
105 #include "../../version_git.h"
106 #endif
107 
108 #ifndef CXX_BUILD
109 }
110 #endif
111 
112 #define INITIAL_WIDTH 1280
113 #define INITIAL_HEIGHT 720
114 
115 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
116 #define GROUPED_DRAGGING QMainWindow::GroupedDragging
117 #else
118 #define GROUPED_DRAGGING static_cast<QMainWindow::DockOption>(0)
119 #endif
120 
121 #define TIMER_MSEC                  1000 /* periodic timer for gathering statistics */
122 #define STATUS_MSG_THROTTLE_MSEC    250
123 
124 #define GENERIC_FOLDER_ICON         "/xmb/dot-art/png/folder.png"
125 #define HIRAGANA_START              0x3041U
126 #define HIRAGANA_END                0x3096U
127 #define KATAKANA_START              0x30A1U
128 #define KATAKANA_END                0x30F6U
129 #define HIRA_KATA_OFFSET            (KATAKANA_START - HIRAGANA_START)
130 #define DOCS_URL                    "http://docs.libretro.com/"
131 
132 enum core_selection
133 {
134    CORE_SELECTION_CURRENT = 0,
135    CORE_SELECTION_PLAYLIST_SAVED,
136    CORE_SELECTION_PLAYLIST_DEFAULT,
137    CORE_SELECTION_ASK,
138    CORE_SELECTION_LOAD_CORE
139 };
140 
141 static AppHandler *app_handler;
142 static ui_application_qt_t ui_application;
143 
144 /* %1 is a placeholder for palette(highlight) or the equivalent chosen by the user */
145 static const QString qt_theme_default_stylesheet = QStringLiteral(R"(
146    QPushButton[flat="true"] {
147       min-height:20px;
148       min-width:80px;
149       padding:1px 3px 1px 3px;
150       background-color: transparent;
151       border: 1px solid #ddd;
152    }
153    ThumbnailWidget#thumbnailWidget, ThumbnailLabel#thumbnailGridLabel, QLabel#thumbnailQLabel {
154       background-color:#d4d4d4;
155    }
156    QLabel#dropIndicator {
157       font-size: 9pt;
158       color: darkgrey;
159       border: 2px dashed lightgrey;
160       border-radius: 5px;
161       margin: 20px;
162    }
163    ThumbnailWidget#thumbnailWidgetSelected {
164       background-color:#d4d4d4;
165       border:3px solid %1;
166    }
167    QFrame#playlistWidget, QFrame#browserWidget, QFrame#logWidget {
168       padding: 8px;
169    }
170    QListWidget {
171       icon-size: 32px;
172    }
173    /* color of the icons on the settings dialog */
174    /* QLabel#iconColor {
175       color: black;
176    } */
177 )");
178 
179 static const QString qt_theme_dark_stylesheet = QStringLiteral(R"(
180    QWidget {
181       color:white;
182       background-color:rgb(53,53,53);
183       selection-background-color:%1;
184    }
185    QWidget:disabled {
186       color:rgb(127,127,127);
187    }
188    QFrame#playlistWidget, QFrame#browserWidget, QStackedWidget#centralWidget, QFrame#logWidget {
189       padding: 8px;
190       background-color:rgb(66,66,66);
191       border-top:1px solid rgba(175,175,175,50%);
192       border-left:1px solid rgba(125,125,125,50%);
193       border-right:1px solid rgba(125,125,125,50%);
194       border-bottom:1px solid rgba(25,25,25,75%);
195    }
196    QListWidget {
197       icon-size: 32px;
198    }
199    QLabel#dropIndicator {
200       font-size: 9pt;
201       color: #575757;
202       border: 2px dashed #575757;
203       border-radius: 5px;
204       margin: 20px;
205    }
206    QTextEdit, LogTextEdit {
207       background-color:rgb(25,25,25);
208    }
209    QSpinBox, QDoubleSpinBox, QCheckBox, QRadioButton {
210       background-color:rgb(25,25,25);
211    }
212    QCheckBox:checked, QCheckBox:unchecked, QRadioButton:checked, QRadioButton:unchecked {
213       background-color:transparent;
214    }
215    /* Groupboxes for the settings window, can be restricted later with ViewOptionsDialog QGroupBox */
216    QGroupBox {
217       background-color:rgba(80,80,80,50%);
218       margin-top:27px;
219       border:1px solid rgba(25,25,25,127);
220       border-top-left-radius:0px;
221       border-top-right-radius:4px;
222    }
223    QGroupBox::title {
224       min-height:28px;
225       subcontrol-origin:margin;
226       subcontrol-position:left top;
227       padding:4px 6px 5px 6px;
228       margin-left:0px;
229       background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgb(65,65,65),stop: 0.4 rgb(70,70,70),stop:1 rgb(90,90,90));
230       border:1px solid rgba(25,25,25,127);
231       border-bottom:1px solid rgb(65,65,65);
232       border-top-left-radius:4px;
233       border-top-right-radius:4px;
234    }
235    QGroupBox::indicator:checked {
236       background-color:%1;
237       border:4px solid rgb(45,45,45);
238    }
239    QGroupBox::indicator:unchecked {
240       background-color:rgba(25,25,25,50%);
241    }
242    QGroupBox::indicator {
243       width:16px;
244       height:16px;
245    }
246    QWidget#shaderParamsWidget {
247       background-color:rgb(25,25,25);
248    }
249    QDialog#shaderParamsDialog QGroupBox {
250       background-color:rgb(53,53,53);
251       border-top-left-radius:0px;
252    }
253    QDialog#shaderParamsDialog QGroupBox::title {
254       margin-left:0px;
255       min-height:28px;
256       padding:4px 10px;
257       background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgb(53,53,53),stop:1 rgba(125,125,125,127));
258       border:1px solid rgba(25,25,25,75);
259       border-top:1px solid rgba(175,175,175,50%);
260       border-bottom:none transparent;
261    }
262    QToolTip {
263       color:white;
264       background-color:rgb(53,53,53);
265       border:1px solid rgb(80,80,80);
266       border-radius:4px;
267    }
268    QMenuBar {
269       background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
270       border-bottom:2px solid rgba(25,25,25,75);
271    }
272    QMenuBar::item {
273       spacing:2px;
274       padding:3px 4px;
275       background-color:transparent;
276    }
277    QMenuBar::item:selected {
278       background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(106,106,106,255),stop:1 rgba(106,106,106,75));
279       border:1px solid %1;
280    }
281    QMenuBar::item:pressed {
282       background-color:%1;
283       border-left:1px solid rgba(25,25,25,127);
284       border-right:1px solid rgba(25,25,25,127);
285    }
286    QMenu {
287       background-color:rgb(45,45,45);
288       border:1px solid palette(shadow);
289    }
290    QMenu::item {
291       padding:3px 25px 3px 25px;
292       border:1px solid transparent;
293    }
294    QMenu::item:disabled {
295       color:rgb(127,127,127);
296    }
297    QMenu::item:selected {
298       border-color:rgba(200,200,200,127);
299       background-color:%1;
300    }
301    QMenu::icon:checked {
302       background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
303       border:1px solid %1;
304       border-radius:2px;
305    }
306    QMenu::separator {
307       height:1px;
308       background-color:rgb(100,100,100);
309       margin-left:5px;
310       margin-right:5px;
311    }
312    QMenu::indicator {
313       width:18px;
314       height:18px;
315    }
316    QToolBar::top {
317       background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
318       border-bottom:3px solid qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
319    }
320    QToolBar::bottom {
321       background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
322       border-top:3px solid qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
323    }
324    QToolBar::left {
325       background-color:qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
326       border-right:3px solid qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
327    }
328    QToolBar::right {
329       background-color:qlineargradient(x1:1,y1:0,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
330       border-left:3px solid qlineargradient(x1:1,y1:0,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
331    }
332    QMainWindow {
333       background-color:rgb(53,53,53);
334    }
335    QMainWindow::separator {
336       width:6px;
337       height:5px;
338       padding:2px;
339       background-color:rgba(25,25,25,50%);
340    }
341    QLineEdit {
342       color:white;
343       background-color:rgb(25,25,25);
344    }
345    QLineEdit::focus {
346       border:1px solid %1;
347       border-radius:3px;
348       color:white;
349       background-color:rgb(25,25,25);
350    }
351    QSplitter::handle:horizontal {
352       width:10px;
353    }
354    QSplitter::handle:vertical {
355       height:10px;
356    }
357    QMainWindow::separator:hover, QSplitter::handle:hover {
358    }
359    QDockWidget {
360       font-family:"Segoe UI";
361       font-size:9pt;
362    }
363    QDockWidget::title {
364       padding:3px 4px;
365       background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,175),stop:1 rgba(53,53,53,75));
366       border:1px solid rgba(25,25,25,75);
367       border-top:1px solid rgba(175,175,175,50%);
368       border-bottom:1px solid rgba(25,25,25,127);
369    }
370    QDockWidget::close-button, QDockWidget::float-button {
371       subcontrol-position:top right;
372       subcontrol-origin:margin;
373       position:absolute;
374       top:3px;
375       bottom:0px;
376       width:20px;
377       height:20px;
378    }
379    QDockWidget::close-button:hover, QDockWidget::float-button:hover {
380       border:1px solid %1;
381       border-radius:4px;
382    }
383    QDockWidget::close-button {
384       right:3px;
385    }
386    QDockWidget::float-button {
387       right:25px;
388    }
389    QTabWidget::pane {
390       background-color:rgba(66,66,66,50%);
391    }
392    QTabWidget::tab-bar {
393    }
394    QTabBar {
395       background-color:transparent;
396       qproperty-drawBase:0;
397       border-bottom:1px solid rgba(25,25,25,50%);
398    }
399    QTabBar::tab {
400       padding:4px 6px;
401       background-color:rgba(25,25,25,127);
402       border:1px solid rgba(25,25,25,75);
403    }
404    QTabBar::tab:selected {
405       background-color:rgb(66,66,66);
406       border-bottom-color:rgba(66,66,66,75%);
407    }
408    QTabBar::tab:!selected {
409       color:rgb(175,175,175);
410    }
411    QComboBox {
412       min-height:20px;
413       padding:1px 6px 1px 6px;
414    }
415    QComboBox::focus {
416       background:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgba(255,255,255,50), stop: 1 rgba(100,100,100,25));
417       border:1px solid %1;
418       border-radius:4px;
419    }
420    QComboBox::hover {
421       background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgba(255,255,255,50), stop: 1 rgba(127,127,127,50));
422       border:1px solid %1;
423       border-radius:4px;
424    }
425    QComboBox::drop-down {
426       background-color:transparent;
427       width:0px;
428    }
429    QComboBox::selected:on, QComboBox::selected:off {
430       background-color:%1;
431    }
432    QTabBar::tab:hover {
433       color:white;
434       background-color:%1;
435    }
436    QComboBox::separator {
437       background-color:rgb(100,100,100);
438       height:1px;
439       margin-left:4px;
440       margin-right:4px;
441    }
442    QCheckBox::indicator {
443       width:18px;
444       height:18px;
445    }
446    QPushButton {
447       min-height:20px;
448       min-width:80px;
449       padding:1px 3px 1px 3px;
450       outline:none;
451    }
452    QPushButton::disabled, QToolButton::disabled {
453       color:grey;
454       background-color:rgb(25,25,25);
455    }
456    QPushButton::focus, QToolButton::focus {
457       background:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgba(255,255,255,50), stop: 1 rgba(100,100,100,25));
458       border:1px solid %1;
459       border-radius:4px;
460    }
461    QPushButton::hover, QToolButton::hover {
462       background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgba(255,255,255,75), stop: 1 rgba(100,100,100,50));
463       border:1px solid %1;
464       border-radius:4px;
465    }
466    QPushButton::pressed, QToolButton::pressed {
467       background-color:transparent;
468       border:1px solid %1;
469       border-radius:4px;
470    }
471    QPushButton[flat="true"] {
472       background-color:transparent;
473    }
474    QPushButton[flat="true"]::menu-indicator {
475       position:relative;
476       bottom:4px;
477       right:4px;
478    }
479    QRadioButton::indicator {
480       width:18px;
481       height:18px;
482    }
483    QListWidget::item:selected, QTreeView::item:selected, QTableView::item:selected {
484       color:white;
485       background-color:%1;
486    }
487    QTreeView {
488       background-color:rgb(25,25,25);
489       selection-background-color:%1;
490    }
491    QTreeView::branch:selected {
492       background-color:%1;
493    }
494    QTreeView::item:selected:disabled, QTableView::item:selected:disabled {
495       background-color:rgb(80,80,80);
496    }
497    QTreeView::branch:open, QTreeView::branch:closed {
498       background-color:solid;
499    }
500    QTableView, QListWidget {
501       background-color:rgb(25,25,25);
502    }
503    QTreeView QHeaderView::section, QTableView QHeaderView::section {
504       /*height:24px;*/
505       background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
506       border-style:none;
507       border-bottom:1px solid rgb(65,65,65);
508       padding-left:5px;
509       padding-right:5px;
510    }
511    QTableView {
512       background-color:rgb(25,25,25);
513       alternate-background-color:rgb(40,40,40);
514    }
515    QScrollBar:vertical, QScrollBar:horizontal {
516       background-color:rgb(35,35,35);
517    }
518    QScrollBar::handle:vertical, QScrollBar::handle:horizontal {
519       background-color:rgb(65,65,65);
520       border-right:1px solid rgba(175,175,175,50%);
521       border-top:1px solid rgba(175,175,175,50%);
522       border-bottom:1px solid rgba(25,25,25,75);
523       border-radius:2px;
524    }
525    QScrollBar::handle:horizontal:hover, QScrollBar::handle:vertical:hover {
526       border:1px solid %1;
527       background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgba(255,255,255,75), stop: 1 rgba(127,127,127,75));
528    }
529    QScrollBar:vertical {
530       border-top-right-radius:2px;
531       border-bottom-right-radius:2px;
532       width:16px;
533       margin:0px;
534    }
535    QScrollBar::handle:vertical {
536       min-height:20px;
537       margin:2px 4px 2px 4px;
538    }
539    QScrollBar::add-line:vertical {
540       background:none;
541       height:0px;
542       subcontrol-position:right;
543       subcontrol-origin:margin;
544    }
545    QScrollBar::sub-line:vertical {
546       background:none;
547       height:0px;
548       subcontrol-position:left;
549       subcontrol-origin:margin;
550    }
551    QScrollBar:horizontal {
552       height:16px;
553       margin:0px;
554    }
555    QScrollBar::handle:horizontal {
556       min-width:20px;
557       margin:4px 2px 4px 2px;
558    }
559    QScrollBar::add-line:horizontal {
560       background:none;
561       width:0px;
562       subcontrol-position:bottom;
563       subcontrol-origin:margin;
564    }
565    QScrollBar::sub-line:horizontal {
566       background:none;
567       width:0px;
568       subcontrol-position:top;
569       subcontrol-origin:margin;
570    }
571    QSlider {
572       background:transparent;
573    }
574    QSlider::sub-page {
575       background:%1;
576    }
577    QSlider::groove:vertical {
578       width:3px;
579       background:rgb(25,25,25);
580    }
581    QSlider::handle:vertical {
582       background:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgb(175,175,175), stop: 1 rgb(75,75,75));
583       border:1px solid rgb(35,35,35);
584       border-radius:2px;
585       height:16px;
586       margin:0 -4px;
587    }
588    QSlider::handle:vertical:hover {
589       background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgb(200,200,200), stop: 1 rgba(100,100,100));
590       border:1px solid %1;
591       border-radius:2px;
592       height:16px;
593       margin:0 -4px;
594    }
595    QSlider::groove:horizontal {
596       height:3px;
597       background:rgb(25,25,25);
598    }
599    QSlider::handle:horizontal {
600       background:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgb(175,175,175), stop: 1 rgb(75,75,75));
601       border:1px solid rgb(35,35,35);
602       border-radius:2px;
603       width:16px;
604       margin:-4px 0;
605    }
606    QSlider::handle:horizontal:hover {
607       background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgb(200,200,200), stop: 1 rgba(100,100,100));
608       border:1px solid %1;
609       border-radius:2px;
610       width:16px;
611       margin:-4px 0;
612    }
613    QStatusBar {
614       color:white;
615       background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
616    }
617    QStatusBar QLabel {
618       background-color:transparent;
619    }
620    QLabel {
621       background-color:transparent;
622    }
623    QSizeGrip {
624       background-color:solid;
625    }
626    GridView::item {
627       background-color:rgb(40,40,40);
628    }
629    GridView::item:selected {
630       border:3px solid %1;
631    }
632    GridView {
633       background-color:rgb(25,25,25);
634       selection-color: white;
635       qproperty-layout: "fixed";
636    }
637    GridItem {
638       qproperty-thumbnailvalign: "center";
639    }
640    QLabel#itemsCountLabel {
641       padding-left: 5px;
642    }
643 )");
644 
645 /* ARGB 16x16 */
646 static const unsigned retroarch_qt_icon_data[] = {
647 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
648 0x00000000,0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,0x00000000,
649 0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,
650 0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,
651 0x00000000,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,
652 0x00000000,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0x00000000,0x00000000,
653 0x00000000,0xff333333,0xfff2f2f2,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0xfff2f2f2,0xff333333,0x00000000,0x00000000,
654 0x00000000,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0x00000000,0x00000000,
655 0x00000000,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0x00000000,0x00000000,
656 0x00000000,0xff333333,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0xff333333,0x00000000,0x00000000,
657 0x00000000,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,
658 0x00000000,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0x00000000,0x00000000,
659 0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,
660 0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,
661 0x00000000,0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,0x00000000,
662 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000
663 };
664 
665 static unsigned char invader_png[] = {
666   0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
667   0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x44,
668   0x08, 0x06, 0x00, 0x00, 0x00, 0xac, 0xf5, 0x3a, 0x40, 0x00, 0x00, 0x00,
669   0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72,
670   0x65, 0x00, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x49, 0x6d, 0x61, 0x67,
671   0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x71, 0xc9, 0x65, 0x3c, 0x00, 0x00,
672   0x0f, 0x4a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x5d, 0x79, 0x50,
673   0x95, 0xd7, 0x15, 0xbf, 0x6f, 0xe3, 0xf1, 0x58, 0xa2, 0x28, 0x8a, 0xec,
674   0x82, 0x58, 0x8c, 0xc6, 0x25, 0xa8, 0xc4, 0xa5, 0x21, 0x1a, 0x97, 0xd6,
675   0x50, 0x26, 0xd5, 0x6a, 0x3b, 0x6d, 0x4d, 0xeb, 0x42, 0x27, 0x13, 0xd3,
676   0x24, 0xda, 0x31, 0xa6, 0x63, 0xa7, 0xda, 0x3f, 0xda, 0xce, 0x44, 0x27,
677   0xc6, 0x24, 0xd3, 0x4c, 0x26, 0xea, 0x38, 0x4d, 0xc6, 0x4e, 0x4d, 0x62,
678   0x27, 0x13, 0x71, 0xa3, 0x46, 0x31, 0x46, 0x4c, 0x80, 0x04, 0x0b, 0xc4,
679   0x22, 0x8a, 0x82, 0x86, 0x25, 0x8a, 0x02, 0x0a, 0xc8, 0xe3, 0x6d, 0x3d,
680   0x3f, 0x38, 0xcf, 0x12, 0xf3, 0xbe, 0xe5, 0xc1, 0x7b, 0xbc, 0x4f, 0x7c,
681   0x67, 0xe6, 0x37, 0xe3, 0x72, 0xdf, 0xf7, 0xdd, 0xef, 0xfc, 0xee, 0x3d,
682   0xf7, 0x9c, 0x73, 0x37, 0x9d, 0xcb, 0xe5, 0x12, 0x1a, 0x12, 0x3d, 0x61,
683   0x24, 0x21, 0x96, 0x60, 0x50, 0x28, 0xdb, 0x45, 0xa8, 0x23, 0x5c, 0x0f,
684   0x40, 0x3d, 0x43, 0x08, 0xc9, 0x84, 0x07, 0x08, 0x3a, 0x85, 0xb2, 0x6d,
685   0x84, 0xcb, 0x84, 0x0e, 0xad, 0x28, 0xd9, 0x28, 0xb4, 0x25, 0xe1, 0x84,
686   0x25, 0x84, 0xe5, 0x84, 0x30, 0x15, 0xca, 0x2c, 0x20, 0xec, 0x26, 0x54,
687   0x0f, 0x30, 0xe1, 0x8b, 0x08, 0x2b, 0x98, 0x78, 0xbd, 0x4c, 0x59, 0xf4,
688   0xa8, 0x72, 0xc2, 0x36, 0x42, 0x19, 0xc1, 0x19, 0x24, 0xfd, 0xbb, 0x82,
689   0xde, 0xd0, 0xcc, 0xbd, 0x3d, 0xa5, 0xa9, 0xa9, 0x49, 0x77, 0xfb, 0xf6,
690   0x6d, 0x8f, 0x05, 0x87, 0x0e, 0x1d, 0xea, 0x8a, 0x8c, 0x8c, 0x4c, 0xe5,
691   0x86, 0xf2, 0x0a, 0xe1, 0xeb, 0x01, 0xd2, 0xd7, 0x42, 0xc2, 0xf3, 0x84,
692   0x99, 0xf5, 0xf5, 0xf5, 0xa1, 0x0e, 0x87, 0xc3, 0x63, 0xc1, 0x51, 0xa3,
693   0x46, 0x09, 0x93, 0xc9, 0xd4, 0x4e, 0x7f, 0x2c, 0x21, 0xdc, 0xd0, 0x0a,
694   0xe1, 0x3d, 0x4d, 0x91, 0xcc, 0xbb, 0xc6, 0x30, 0x9c, 0xb0, 0x8d, 0x70,
695   0x35, 0x3f, 0x3f, 0xdf, 0x49, 0x8a, 0x73, 0x71, 0x8f, 0xf9, 0x16, 0xe2,
696   0xe3, 0xe3, 0x5d, 0xe7, 0xcf, 0x9f, 0x77, 0x50, 0xb9, 0xcb, 0x84, 0xdf,
697   0x13, 0x22, 0xfc, 0x5c, 0x2f, 0x1d, 0x61, 0x21, 0xe1, 0xa8, 0xd3, 0xe9,
698   0x6c, 0xdf, 0xb8, 0x71, 0xa3, 0xc7, 0x7a, 0x01, 0x4b, 0x97, 0x2e, 0x75,
699   0x51, 0x19, 0x2b, 0x95, 0x3d, 0x41, 0x98, 0xc9, 0xbf, 0xd5, 0x8c, 0x8e,
700   0xb5, 0x48, 0x3a, 0x30, 0x9e, 0xf0, 0x4f, 0x42, 0xeb, 0x8e, 0x1d, 0x3b,
701   0x9c, 0x52, 0xca, 0x9d, 0x32, 0x65, 0x8a, 0xab, 0xb3, 0xb3, 0xd3, 0x4e,
702   0xe5, 0xbe, 0x22, 0x2c, 0x27, 0x18, 0xfd, 0x58, 0xa7, 0x34, 0xae, 0x53,
703   0xfb, 0x9b, 0x6f, 0xbe, 0x29, 0x59, 0xa7, 0xd1, 0xa3, 0x47, 0xbb, 0x9a,
704   0x9b, 0x9b, 0x51, 0xa7, 0xb3, 0x84, 0x95, 0x04, 0x83, 0xd6, 0xf4, 0xab,
705   0x55, 0xd2, 0xa1, 0xa8, 0x2c, 0xc2, 0x01, 0xc2, 0xed, 0x67, 0x9f, 0x7d,
706   0x56, 0xb2, 0x57, 0xcd, 0x9f, 0x3f, 0xdf, 0xd5, 0xd6, 0xd6, 0xd6, 0xc5,
707   0xbd, 0xea, 0x07, 0x7e, 0x22, 0x3e, 0x81, 0xad, 0x4f, 0xd3, 0xc1, 0x83,
708   0x07, 0x9d, 0x61, 0x61, 0x61, 0x1e, 0xeb, 0x12, 0x17, 0x17, 0xe7, 0xaa,
709   0xae, 0xae, 0x76, 0x52, 0xb9, 0x1a, 0xc2, 0x1f, 0xd9, 0x6a, 0x89, 0x20,
710   0xe9, 0xea, 0xa1, 0x27, 0x2c, 0x25, 0x94, 0x50, 0x6f, 0xee, 0x4a, 0x4f,
711   0x4f, 0x97, 0x24, 0x3e, 0x37, 0x37, 0x97, 0x8a, 0xb9, 0xda, 0x08, 0x1f,
712   0x11, 0xbe, 0xef, 0xe3, 0x7a, 0x0c, 0x23, 0xfc, 0x01, 0xc3, 0x48, 0x71,
713   0x71, 0xb1, 0xe4, 0x70, 0x03, 0x1c, 0x38, 0x70, 0x00, 0x84, 0xdf, 0x20,
714   0xfc, 0x8d, 0x10, 0xa7, 0x55, 0xdd, 0x6a, 0x99, 0x74, 0x20, 0x92, 0xf0,
715   0x5b, 0x42, 0x55, 0x49, 0x49, 0x89, 0x23, 0x22, 0x22, 0xc2, 0xa3, 0xb2,
716   0x75, 0x3a, 0x9d, 0x6b, 0xf7, 0xee, 0xdd, 0x50, 0xf8, 0x4d, 0xc2, 0x5e,
717   0xc2, 0x18, 0x1f, 0xbd, 0xdf, 0x42, 0x78, 0x1e, 0xa6, 0xba, 0xb1, 0xb1,
718   0xd1, 0x96, 0x9a, 0x9a, 0x2a, 0x49, 0x38, 0xac, 0x11, 0xbf, 0xff, 0x1f,
719   0x84, 0x87, 0xb5, 0xac, 0x57, 0xad, 0x93, 0x0e, 0x3c, 0xc0, 0x8e, 0xda,
720   0xd5, 0xa2, 0xa2, 0x22, 0xd7, 0x90, 0x21, 0x43, 0x3c, 0x2a, 0x1d, 0x3d,
721   0xf0, 0xdd, 0x77, 0xdf, 0x85, 0xe2, 0x9b, 0x09, 0xaf, 0x13, 0x52, 0xfb,
722   0xf9, 0xde, 0x10, 0xc2, 0x2f, 0x08, 0xe7, 0x5a, 0x5b, 0x5b, 0xed, 0x99,
723   0x99, 0x99, 0x92, 0x84, 0x2f, 0x5b, 0xb6, 0xcc, 0xd5, 0xd5, 0xd5, 0x65,
724   0xe3, 0xe1, 0x28, 0x53, 0x6b, 0x8e, 0xdb, 0xbd, 0x48, 0xba, 0x60, 0x02,
725   0x3f, 0x21, 0x38, 0x76, 0xee, 0xdc, 0x29, 0xa9, 0x7c, 0xf4, 0x78, 0x78,
726   0xfc, 0x54, 0xae, 0x9e, 0xb0, 0x95, 0x10, 0xdb, 0xc7, 0xf7, 0xc1, 0x2f,
727   0xc8, 0x26, 0x1c, 0xb3, 0x5a, 0xad, 0x5d, 0xb3, 0x67, 0xcf, 0x96, 0x7c,
728   0xe7, 0xb4, 0x69, 0xd3, 0x5c, 0x36, 0x9b, 0xcd, 0xdd, 0xd8, 0x9e, 0xd1,
729   0x3a, 0xe1, 0x80, 0x4e, 0x45, 0x46, 0x0e, 0xb1, 0x69, 0x34, 0x61, 0x98,
730   0x42, 0x96, 0x0c, 0x31, 0x69, 0x23, 0xe1, 0x36, 0x2b, 0x44, 0x4a, 0x90,
731   0xc5, 0x1a, 0x45, 0x30, 0x7b, 0x11, 0x59, 0xc6, 0x11, 0xfe, 0x4c, 0xc8,
732   0xc0, 0x58, 0x4f, 0x3d, 0x4b, 0xec, 0xdb, 0xb7, 0x4f, 0x32, 0x3e, 0xfe,
733   0xec, 0xb3, 0xcf, 0x9c, 0xc9, 0xc9, 0xc9, 0xc8, 0x82, 0xed, 0x21, 0xec,
734   0xf7, 0x32, 0x1b, 0x86, 0x6f, 0x9c, 0xcc, 0xc9, 0x97, 0x4c, 0xf2, 0x17,
735   0xc2, 0x76, 0xed, 0xda, 0xe5, 0xb1, 0x20, 0x59, 0x1d, 0xf1, 0xf9, 0xe7,
736   0x9f, 0x0b, 0xf2, 0x37, 0xf0, 0xd7, 0x5b, 0x9c, 0x84, 0xd9, 0xcf, 0xd9,
737   0x42, 0x35, 0x82, 0x28, 0xa0, 0x95, 0xf0, 0x0d, 0xc1, 0xa6, 0xc0, 0x41,
738   0x0c, 0x21, 0x4a, 0x21, 0x03, 0xe8, 0xe6, 0x40, 0xf6, 0x7b, 0xd5, 0x90,
739   0x8e, 0x04, 0xc8, 0x6a, 0xc2, 0x0c, 0x82, 0x49, 0xa6, 0x5c, 0x0d, 0xe1,
740   0x2d, 0x42, 0x11, 0xc1, 0x2e, 0x51, 0x26, 0x94, 0xf0, 0x24, 0xe1, 0xe7,
741   0xdc, 0x88, 0xd4, 0x88, 0x8e, 0x1b, 0xca, 0x18, 0x4e, 0xc4, 0x88, 0x96,
742   0x96, 0x16, 0x41, 0xe1, 0x9a, 0xa8, 0xad, 0xad, 0xf5, 0xf8, 0x83, 0x47,
743   0x1e, 0x79, 0x44, 0x7c, 0xfa, 0xe9, 0xa7, 0x4e, 0xa3, 0xd1, 0x88, 0xa4,
744   0xc8, 0x25, 0x42, 0xa7, 0x97, 0x09, 0x18, 0xa4, 0x81, 0xe3, 0xc8, 0x4f,
745   0x30, 0xad, 0x5a, 0xb5, 0x4a, 0x52, 0xc9, 0x68, 0x0c, 0xf4, 0xff, 0xee,
746   0xbf, 0x3a, 0x58, 0xe1, 0x57, 0x14, 0x08, 0xbc, 0x9b, 0xf4, 0x33, 0x84,
747   0xb7, 0x09, 0x67, 0x65, 0xca, 0xa1, 0x3e, 0xcf, 0x10, 0xb2, 0x14, 0x32,
748   0x80, 0x48, 0x50, 0xbd, 0x43, 0x38, 0x26, 0xd7, 0xf0, 0x8c, 0x2a, 0x5a,
749   0x3d, 0xc8, 0x5e, 0x56, 0x51, 0x51, 0x91, 0x52, 0x53, 0x53, 0xe3, 0x51,
750   0x01, 0xd4, 0xab, 0xc4, 0xc4, 0x89, 0x13, 0x13, 0xe8, 0x8f, 0x87, 0x99,
751   0x74, 0x29, 0x49, 0x02, 0xe1, 0xed, 0xed, 0xed, 0x0b, 0x8f, 0x1f, 0x3f,
752   0x1e, 0xe2, 0x05, 0x11, 0xba, 0xbb, 0x3f, 0x76, 0xed, 0xda, 0xb5, 0xe2,
753   0xc5, 0x17, 0x5f, 0x14, 0x76, 0xfb, 0x77, 0xdb, 0x17, 0x7a, 0xdf, 0xe2,
754   0xc5, 0x8b, 0xf5, 0x4f, 0x3f, 0xfd, 0xb4, 0xdb, 0x42, 0xb9, 0xbc, 0x7c,
755   0x97, 0xee, 0xda, 0xb5, 0x6b, 0xba, 0x17, 0x5e, 0x78, 0x41, 0xb2, 0x50,
756   0x46, 0x46, 0x86, 0x18, 0x31, 0x62, 0x84, 0xc8, 0xcb, 0xcb, 0xeb, 0xad,
757   0xab, 0x38, 0x26, 0x48, 0xd5, 0xfb, 0x62, 0x62, 0x62, 0xc4, 0xf4, 0xe9,
758   0xd3, 0x53, 0x39, 0x8d, 0x0c, 0x58, 0x25, 0x8a, 0x5a, 0x08, 0x53, 0x6e,
759   0xdd, 0xba, 0x35, 0xeb, 0xc4, 0x89, 0x13, 0x92, 0xa4, 0x2f, 0x58, 0xb0,
760   0xa0, 0xdd, 0x6c, 0x36, 0x83, 0xec, 0xaf, 0xb8, 0xf1, 0xf5, 0x29, 0x23,
761   0x07, 0xef, 0xf9, 0x2f, 0x84, 0x96, 0xac, 0xac, 0x2c, 0xc9, 0x71, 0x6d,
762   0xcb, 0x96, 0x2d, 0x18, 0x47, 0xff, 0x43, 0x98, 0xab, 0xf0, 0xbc, 0x1f,
763   0x11, 0x2a, 0xc9, 0x34, 0x4b, 0x26, 0x37, 0xee, 0x27, 0xa4, 0xa4, 0xa4,
764   0xc0, 0x17, 0xe8, 0x20, 0xec, 0x24, 0x8c, 0x90, 0xd1, 0x5b, 0x14, 0xe1,
765   0xed, 0x9b, 0x37, 0x6f, 0x5a, 0xf5, 0x7a, 0xbd, 0xe4, 0xf3, 0xc8, 0xba,
766   0x21, 0x43, 0x59, 0xc2, 0x59, 0x40, 0x49, 0x1e, 0xf4, 0x0a, 0x8d, 0x11,
767   0xbd, 0x64, 0x1c, 0xb5, 0x30, 0x0b, 0x8d, 0x93, 0x92, 0x85, 0x66, 0xcc,
768   0x98, 0xe1, 0xe2, 0x96, 0x55, 0x2b, 0xf3, 0x2c, 0x98, 0xf6, 0x71, 0x84,
769   0xe1, 0x07, 0x0f, 0x1e, 0xd4, 0x89, 0xa0, 0x88, 0x4b, 0x97, 0x2e, 0x61,
770   0x88, 0x82, 0xc5, 0x4b, 0x63, 0x3f, 0x47, 0x4a, 0xe0, 0x2f, 0x9c, 0x8f,
771   0x8c, 0x8c, 0xb4, 0xce, 0x9a, 0x35, 0x4b, 0xb2, 0x10, 0x71, 0xa4, 0xe7,
772   0xe7, 0x8c, 0x55, 0x9a, 0xca, 0x94, 0x93, 0xd1, 0xa8, 0x10, 0xb5, 0x20,
773   0x23, 0x85, 0x24, 0x1e, 0x0b, 0x50, 0x45, 0x30, 0x86, 0xe2, 0x3f, 0xff,
774   0xcb, 0x0e, 0x89, 0x94, 0xc0, 0x09, 0x99, 0xe4, 0x70, 0x38, 0x22, 0x3e,
775   0xfc, 0xf0, 0xc3, 0x20, 0xe3, 0x2c, 0x05, 0x05, 0x05, 0x06, 0x9e, 0xad,
776   0xfb, 0x9e, 0x4c, 0x31, 0x8c, 0x61, 0x15, 0x98, 0x8c, 0x82, 0x13, 0x2b,
777   0x25, 0x64, 0xfa, 0xbb, 0xe7, 0xa2, 0x08, 0x13, 0xb9, 0x93, 0xf5, 0x89,
778   0x74, 0xb4, 0x98, 0x91, 0xdc, 0x82, 0x3c, 0xca, 0xdc, 0xb9, 0x73, 0x45,
779   0x48, 0x48, 0x08, 0xbc, 0xc6, 0x73, 0x0a, 0x5e, 0x23, 0xbc, 0xcf, 0xd4,
780   0xb3, 0x67, 0xcf, 0x9a, 0xae, 0x5f, 0xbf, 0x1e, 0x64, 0xfb, 0xff, 0xbd,
781   0xd3, 0x4d, 0x54, 0xba, 0x42, 0x74, 0x04, 0x87, 0xb4, 0xf1, 0x89, 0x27,
782   0x9e, 0x70, 0xca, 0x3d, 0x8b, 0xcc, 0xb7, 0x99, 0x9d, 0xde, 0xc8, 0xbe,
783   0x90, 0x6e, 0xe1, 0x8a, 0x44, 0xc2, 0x31, 0x92, 0x92, 0xec, 0xec, 0x6c,
784   0xc1, 0xd3, 0xa1, 0xe7, 0x15, 0x1c, 0x18, 0x98, 0xb0, 0x78, 0xaa, 0x98,
785   0x21, 0x48, 0xf5, 0xb7, 0x9d, 0x4e, 0x5e, 0x3b, 0x80, 0x9e, 0x1e, 0x21,
786   0x53, 0xb4, 0x09, 0xc3, 0x67, 0x5a, 0x5a, 0x9a, 0x23, 0x31, 0x31, 0xd1,
787   0x63, 0x01, 0x72, 0x3e, 0x45, 0x59, 0x59, 0x99, 0xdb, 0x72, 0x8c, 0xea,
788   0x0b, 0xe9, 0x68, 0x29, 0x48, 0x67, 0x9a, 0xe5, 0x48, 0xe7, 0x96, 0x57,
789   0x2f, 0xeb, 0x2d, 0xf6, 0xb4, 0x60, 0x7c, 0xd4, 0x50, 0x39, 0xdf, 0xe0,
790   0x7e, 0x14, 0x22, 0x49, 0x90, 0x83, 0x86, 0x28, 0x2a, 0x45, 0x21, 0x8c,
791   0xbd, 0xc9, 0x1d, 0xcb, 0x8a, 0x90, 0x54, 0x4a, 0xd8, 0x5f, 0x8a, 0xe1,
792   0xa1, 0xd9, 0x6b, 0xd2, 0xd1, 0x52, 0x92, 0x8b, 0x8b, 0x8b, 0x0d, 0x88,
793   0x8b, 0x3d, 0xc9, 0x98, 0x31, 0x63, 0x44, 0x42, 0x42, 0x82, 0x9d, 0x4d,
794   0xcf, 0x0d, 0x99, 0x67, 0x45, 0x30, 0xe9, 0x61, 0x72, 0x0d, 0xe8, 0x7e,
795   0x14, 0x2c, 0xc2, 0xc8, 0xcf, 0xcf, 0xd7, 0x73, 0xb8, 0x97, 0x28, 0x53,
796   0xb4, 0x8b, 0x87, 0xd0, 0x56, 0x72, 0x9c, 0xe5, 0x48, 0x77, 0x27, 0xc0,
797   0xc6, 0x49, 0x85, 0xe4, 0x46, 0x05, 0x27, 0x2e, 0xe6, 0xc0, 0x81, 0x03,
798   0x3a, 0x19, 0xaf, 0xdd, 0xbd, 0xda, 0xa5, 0x4a, 0xf4, 0x2c, 0x5f, 0x92,
799   0x92, 0xe1, 0xbc, 0x12, 0xc6, 0x48, 0x63, 0xba, 0xac, 0x12, 0x28, 0xd6,
800   0x14, 0x7b, 0xf7, 0xee, 0x15, 0x3c, 0x54, 0x38, 0x65, 0x86, 0x0c, 0x28,
801   0x4a, 0xdf, 0xdc, 0xdc, 0xdc, 0xdd, 0xf8, 0x06, 0x4a, 0x96, 0x2c, 0x59,
802   0x22, 0x76, 0xee, 0xdc, 0xa9, 0xba, 0x7e, 0xc7, 0x8e, 0x1d, 0x13, 0x4b,
803   0x97, 0x2e, 0x95, 0x7d, 0x26, 0xe9, 0x18, 0x65, 0xa2, 0xd8, 0x87, 0xfa,
804   0x44, 0xa6, 0x28, 0x7a, 0xfa, 0x37, 0xa4, 0xf7, 0x58, 0xa9, 0x0e, 0x5b,
805   0x58, 0x58, 0x28, 0x28, 0xda, 0x32, 0x93, 0x83, 0x3d, 0x96, 0xad, 0x75,
806   0xb3, 0x5a, 0xd2, 0x8d, 0x3c, 0x9e, 0x3f, 0xc0, 0x2d, 0x47, 0x8e, 0xf4,
807   0x16, 0x26, 0xdd, 0x2e, 0x53, 0x59, 0xb4, 0xe0, 0xb8, 0xc3, 0x87, 0x0f,
808   0xeb, 0x95, 0x32, 0x80, 0x26, 0x93, 0x49, 0x44, 0x45, 0x45, 0x41, 0x99,
809   0xf0, 0xf6, 0xca, 0x25, 0x2c, 0x88, 0x89, 0xc7, 0xad, 0x74, 0xcc, 0x84,
810   0x0d, 0x64, 0xcf, 0x24, 0xa7, 0xd5, 0xab, 0xfa, 0x91, 0xf2, 0x15, 0xeb,
811   0x77, 0xe4, 0xc8, 0x11, 0xe1, 0x74, 0x3a, 0xc3, 0x29, 0x06, 0x1f, 0xc7,
812   0xbe, 0xd4, 0x6d, 0x89, 0xa2, 0x18, 0x46, 0x6b, 0x32, 0x32, 0x32, 0x1e,
813   0x32, 0x9b, 0xcd, 0x21, 0x56, 0xeb, 0x77, 0x73, 0x39, 0xf4, 0x1c, 0x51,
814   0x54, 0x54, 0x64, 0x9c, 0x37, 0x6f, 0xde, 0x18, 0xb6, 0xd6, 0xaa, 0x49,
815   0x47, 0xab, 0x9b, 0xd0, 0xd0, 0xd0, 0x10, 0xfa, 0xc5, 0x17, 0x5f, 0x48,
816   0x56, 0xf6, 0xb1, 0xc7, 0x1e, 0x73, 0x70, 0x6c, 0x5e, 0xa5, 0x10, 0x16,
817   0x4e, 0x20, 0x44, 0xbf, 0xff, 0xfe, 0xfb, 0x6a, 0x75, 0x8b, 0x68, 0xe0,
818   0x03, 0xc2, 0x6b, 0x12, 0x4a, 0x85, 0x8f, 0x90, 0x41, 0xd8, 0x4c, 0x98,
819   0xa6, 0x22, 0x0a, 0xf1, 0xb5, 0xb8, 0xeb, 0xf7, 0xba, 0xf0, 0xbc, 0x1a,
820   0x17, 0xf5, 0x7b, 0x98, 0xf0, 0x27, 0xc2, 0x74, 0xa1, 0xb0, 0x62, 0x96,
821   0xf4, 0x2c, 0xca, 0xcb, 0xcb, 0x43, 0x26, 0x4f, 0x9e, 0xfc, 0x20, 0x8f,
822   0xc7, 0x35, 0x32, 0xce, 0x5c, 0xb9, 0xc5, 0x62, 0x79, 0x3c, 0x33, 0x33,
823   0x33, 0xe4, 0xe4, 0xc9, 0x93, 0x52, 0x61, 0xa0, 0x9e, 0x48, 0x47, 0xf6,
824   0x13, 0xcf, 0xab, 0xbc, 0xdb, 0x1a, 0xe9, 0x65, 0xc6, 0xf3, 0x14, 0x78,
825   0xda, 0x52, 0x3d, 0x33, 0x22, 0x22, 0x42, 0x4c, 0x98, 0x30, 0xc1, 0xc6,
826   0xa4, 0x5f, 0x95, 0xf9, 0x26, 0xe4, 0xcb, 0xd3, 0x6d, 0x36, 0x9b, 0xe5,
827   0xe8, 0xd1, 0xa3, 0x6a, 0x95, 0xda, 0xc1, 0xa9, 0x44, 0xa4, 0x26, 0xaf,
828   0x79, 0x00, 0x72, 0xdc, 0x58, 0x5d, 0xfa, 0xb5, 0x08, 0xcc, 0x82, 0x43,
829   0x9f, 0xd7, 0x8f, 0x74, 0xad, 0x63, 0x8b, 0x38, 0x5a, 0xe1, 0xbd, 0x18,
830   0xd7, 0x5b, 0xe4, 0x9c, 0x39, 0x76, 0x96, 0xa3, 0xd8, 0x5a, 0x87, 0xaa,
831   0x71, 0xe4, 0xf4, 0x3c, 0xb6, 0xc4, 0x91, 0xd3, 0x25, 0xd9, 0x83, 0xf0,
832   0x52, 0x32, 0x47, 0x3a, 0x6e, 0xd5, 0x36, 0x6e, 0xcd, 0x77, 0x03, 0xbf,
833   0x9f, 0x84, 0xde, 0x78, 0xe6, 0xcc, 0x99, 0x90, 0x8e, 0x0e, 0xaf, 0x96,
834   0x7e, 0x3b, 0x14, 0xfe, 0xdf, 0x25, 0x02, 0xbb, 0xc2, 0xd4, 0xa7, 0xf5,
835   0x23, 0x5d, 0xeb, 0xb8, 0xb3, 0xcd, 0xe6, 0x10, 0x4e, 0x27, 0x01, 0xd8,
836   0x74, 0xa7, 0x9c, 0x33, 0x57, 0x52, 0x52, 0x02, 0x07, 0x31, 0x9c, 0x7b,
837   0xfa, 0x50, 0x35, 0xe6, 0x3d, 0x94, 0x5b, 0x48, 0x94, 0x5c, 0x78, 0xc5,
838   0x2d, 0xcd, 0xc8, 0x66, 0xf6, 0x77, 0x1c, 0xb2, 0xdd, 0x6d, 0x16, 0x86,
839   0x20, 0x7f, 0x03, 0xe2, 0xb9, 0x25, 0x07, 0x45, 0x42, 0x4e, 0x9f, 0x3e,
840   0xed, 0xee, 0x9d, 0xcb, 0x98, 0x83, 0x3a, 0x09, 0x7d, 0xce, 0xc3, 0x10,
841   0x20, 0x47, 0x3a, 0xa2, 0xad, 0xca, 0xca, 0x4a, 0x13, 0x59, 0xe2, 0x54,
842   0x1e, 0x2e, 0x1a, 0x94, 0x48, 0xc7, 0x83, 0x1f, 0x24, 0x73, 0x1c, 0x2e,
843   0x37, 0x9e, 0xf3, 0x4b, 0x0d, 0x6c, 0x15, 0x9e, 0x11, 0x9e, 0xa7, 0xf2,
844   0x0c, 0xec, 0x41, 0x86, 0x04, 0x43, 0x35, 0x79, 0xa9, 0xaa, 0xaa, 0x12,
845   0x14, 0x89, 0x98, 0xc8, 0x49, 0x84, 0xff, 0x93, 0xa4, 0xa4, 0xcf, 0xf8,
846   0xf8, 0xf8, 0xee, 0x79, 0xfc, 0x73, 0xe7, 0xce, 0x49, 0x99, 0x78, 0x03,
847   0x91, 0x1e, 0xcf, 0xd9, 0xb9, 0x33, 0x4a, 0xa4, 0xc3, 0xc4, 0xa4, 0x56,
848   0x54, 0x54, 0x98, 0xda, 0xda, 0xa4, 0xa3, 0xb0, 0xe3, 0xc7, 0x8f, 0x0b,
849   0x32, 0xd9, 0xee, 0x67, 0x44, 0x29, 0x7d, 0xd4, 0xc7, 0x1f, 0x7f, 0x1c,
850   0x64, 0x56, 0x46, 0xe0, 0x75, 0x6f, 0xd8, 0xb0, 0x41, 0x24, 0x25, 0x25,
851   0xa9, 0xd2, 0x67, 0xb7, 0xb3, 0x14, 0x1e, 0x2e, 0x3b, 0xae, 0xaf, 0x5e,
852   0xbd, 0xba, 0x77, 0x7a, 0xd7, 0x21, 0x47, 0x3a, 0x5a, 0x86, 0x62, 0xba,
853   0xf4, 0xd5, 0x57, 0x5f, 0xf5, 0xb7, 0x1e, 0xb4, 0x3e, 0x1c, 0xf8, 0xbc,
854   0x7e, 0x1c, 0xff, 0xfb, 0x44, 0x3c, 0xa4, 0x77, 0x5b, 0xa5, 0x1c, 0x39,
855   0x4c, 0xf3, 0x3d, 0x84, 0x74, 0x20, 0xcf, 0xd8, 0x04, 0x4a, 0x50, 0x8f,
856   0x78, 0xf6, 0xfc, 0x8d, 0x1e, 0x60, 0xe2, 0xb1, 0x6a, 0x58, 0x00, 0xc2,
857   0xb5, 0xde, 0xf5, 0x8b, 0x90, 0xa9, 0xdf, 0x48, 0xae, 0x5f, 0x40, 0x1a,
858   0x2f, 0x59, 0x6a, 0x51, 0x57, 0x57, 0x67, 0xe4, 0xe1, 0x77, 0xa4, 0x9c,
859   0x79, 0x0f, 0xe7, 0x42, 0xa1, 0x01, 0xce, 0x91, 0x43, 0x99, 0x39, 0xec,
860   0xc8, 0x78, 0x72, 0x68, 0xcc, 0xec, 0x40, 0x4e, 0x0c, 0x90, 0x52, 0xbd,
861   0xa9, 0x5f, 0x20, 0x1a, 0x65, 0xf7, 0x62, 0x89, 0x43, 0x87, 0x0e, 0xe9,
862   0x73, 0x73, 0x73, 0x63, 0xd9, 0x47, 0x38, 0x2f, 0x45, 0xfa, 0x08, 0xc4,
863   0x89, 0x17, 0x2f, 0x5e, 0x34, 0x5e, 0xbe, 0x7c, 0x39, 0x90, 0xa4, 0xa3,
864   0xa7, 0x8c, 0xe7, 0xb8, 0xb5, 0x4b, 0x22, 0xac, 0xb4, 0xf4, 0x0a, 0x6d,
865   0xb4, 0x5c, 0xbf, 0x80, 0x09, 0xb2, 0xa9, 0x44, 0xba, 0x3b, 0x5e, 0x3f,
866   0xee, 0x0e, 0x21, 0xef, 0x26, 0x1d, 0x2d, 0x22, 0x96, 0x0a, 0xeb, 0x03,
867   0xb5, 0x6f, 0x1d, 0xb3, 0x4e, 0x2b, 0x57, 0xae, 0x54, 0xed, 0x20, 0x7a,
868   0x4a, 0x45, 0xfa, 0x53, 0x30, 0x56, 0x7a, 0x53, 0xbf, 0xfa, 0xfa, 0xfa,
869   0x80, 0x91, 0x9e, 0x9f, 0x9f, 0x8f, 0x35, 0x84, 0x16, 0xa3, 0xd1, 0x38,
870   0x96, 0x1b, 0x60, 0xdb, 0x1d, 0x33, 0xd0, 0x6b, 0x1b, 0xd1, 0x1a, 0xac,
871   0xdf, 0x5e, 0xb4, 0x68, 0xd1, 0x7d, 0xbf, 0x7e, 0x6d, 0xb0, 0x00, 0x3b,
872   0x83, 0x88, 0xd3, 0x3c, 0x42, 0x8a, 0xa7, 0x35, 0x72, 0xdd, 0xe3, 0x39,
873   0xf5, 0x1c, 0x0b, 0x66, 0x86, 0x82, 0x32, 0x38, 0xa4, 0x57, 0x7a, 0x37,
874   0xd9, 0x93, 0xf7, 0x8e, 0xf1, 0x7c, 0xec, 0x97, 0x5f, 0x7e, 0x69, 0x1a,
875   0x68, 0x93, 0x19, 0x14, 0xbf, 0x93, 0x0e, 0x6e, 0x1f, 0x64, 0x5f, 0xe4,
876   0x5b, 0xa4, 0xa3, 0x25, 0x24, 0x06, 0xd3, 0xa5, 0x83, 0x4b, 0x38, 0x5e,
877   0x47, 0x16, 0x6f, 0x1c, 0x47, 0x1d, 0x77, 0x48, 0x0f, 0xe1, 0x7f, 0x1c,
878   0xc1, 0x89, 0xff, 0xa0, 0x0c, 0x12, 0xb9, 0x70, 0xe1, 0x82, 0x68, 0x6a,
879   0x6a, 0x32, 0x73, 0x92, 0x26, 0xba, 0x37, 0xe9, 0x58, 0x5e, 0x83, 0xd9,
880   0xb0, 0x21, 0x01, 0x4e, 0xca, 0x04, 0xc5, 0x0f, 0xf1, 0x3a, 0x71, 0xea,
881   0x5e, 0x83, 0x97, 0xd6, 0x3b, 0x64, 0x83, 0xcd, 0x4f, 0x6b, 0x68, 0x68,
882   0x08, 0xc1, 0xa6, 0x3c, 0xc0, 0x17, 0x52, 0x5d, 0x5d, 0xed, 0x71, 0xdb,
883   0x51, 0x50, 0x94, 0x25, 0x21, 0x21, 0x41, 0x36, 0xb7, 0xee, 0x8d, 0xd4,
884   0xd6, 0xd6, 0xc2, 0x7a, 0x0f, 0xe7, 0xde, 0x9e, 0x6f, 0xec, 0xd5, 0xd3,
885   0x43, 0x63, 0x63, 0x63, 0xad, 0x95, 0x95, 0x95, 0xb6, 0x7e, 0x3c, 0xdf,
886   0xc0, 0xce, 0x82, 0xae, 0xbd, 0xbd, 0x5d, 0x0c, 0x1b, 0x36, 0x2c, 0xc8,
887   0x5e, 0x1f, 0x65, 0xfd, 0xfa, 0xf5, 0xa2, 0xd7, 0x5e, 0x3a, 0x3b, 0xa3,
888   0x3f, 0xc9, 0x13, 0x3b, 0x27, 0x8c, 0xc2, 0xdd, 0xa4, 0x63, 0x85, 0x07,
889   0xd6, 0x32, 0x95, 0x89, 0xbe, 0xa7, 0x0d, 0xdd, 0x2b, 0x3a, 0x33, 0x61,
890   0x39, 0x28, 0x0a, 0xd0, 0x49, 0xed, 0x8a, 0x09, 0x8a, 0x2a, 0xaf, 0xdb,
891   0x4d, 0x3a, 0x56, 0x9e, 0x94, 0x8a, 0x9e, 0x15, 0x33, 0xfd, 0x51, 0x28,
892   0x96, 0x75, 0x15, 0x20, 0x9f, 0xe5, 0x26, 0x1d, 0x93, 0xec, 0xbb, 0x84,
893   0xfc, 0x56, 0x64, 0x35, 0x02, 0x2f, 0x71, 0x0b, 0x61, 0x31, 0x55, 0xda,
894   0x1c, 0xa4, 0xae, 0xdf, 0x5e, 0x37, 0x04, 0x29, 0xbd, 0x97, 0x09, 0xa7,
895   0x44, 0xff, 0x56, 0x0a, 0x61, 0x6a, 0x15, 0x5b, 0xb6, 0xed, 0x6e, 0xd2,
896   0xf1, 0xb0, 0x76, 0x1f, 0xd4, 0x15, 0x01, 0x3e, 0x26, 0x20, 0x1c, 0xc1,
897   0x4d, 0x0d, 0xfd, 0x13, 0xde, 0xdc, 0xe8, 0x4a, 0x4e, 0x4e, 0xc6, 0xca,
898   0x58, 0xac, 0x68, 0xbd, 0xe1, 0xab, 0x67, 0xfb, 0x7a, 0x06, 0x08, 0xb9,
899   0x68, 0xe4, 0xef, 0x4d, 0xc1, 0x95, 0x32, 0xfd, 0x17, 0xde, 0xad, 0x32,
900   0x84, 0x3d, 0x6f, 0xbd, 0x56, 0x49, 0xc7, 0x9a, 0xac, 0x31, 0xa5, 0xa5,
901   0xa5, 0xc6, 0xba, 0xba, 0xba, 0x20, 0x6b, 0xfd, 0x14, 0x5e, 0x32, 0x1e,
902   0xc5, 0xe1, 0x74, 0xa4, 0x16, 0x49, 0x87, 0xe7, 0x8e, 0x29, 0x3c, 0xd9,
903   0x5d, 0x31, 0x41, 0x51, 0x2f, 0x58, 0xd7, 0xde, 0xd1, 0xd1, 0x61, 0x66,
904   0xbd, 0x0e, 0xd3, 0x22, 0xe9, 0x48, 0xf1, 0x21, 0xab, 0x27, 0xbb, 0x2b,
905   0x26, 0x28, 0x5e, 0xc4, 0x58, 0x76, 0xbb, 0x28, 0x2e, 0x2e, 0x86, 0xdf,
906   0xd5, 0x9d, 0x22, 0xd7, 0x22, 0xe9, 0x08, 0xfe, 0xd3, 0x5b, 0x5b, 0x5b,
907   0xcd, 0x41, 0x27, 0xce, 0xa7, 0xa1, 0x9b, 0xfb, 0x0c, 0xfc, 0xb1, 0xbe,
908   0x7a, 0xa6, 0x2f, 0x8f, 0xfe, 0xc6, 0x9a, 0xb1, 0xa4, 0xc6, 0xc6, 0x46,
909   0xa3, 0xd2, 0x86, 0x3d, 0x35, 0x82, 0x6c, 0x1e, 0xc5, 0xfa, 0x8a, 0xe5,
910   0x90, 0x00, 0x9a, 0x37, 0x6f, 0xde, 0x80, 0x91, 0xd0, 0xd4, 0xd4, 0xd4,
911   0xbd, 0x12, 0x58, 0x49, 0xb0, 0xe7, 0x2d, 0x27, 0x27, 0x07, 0x1b, 0x42,
912   0xfa, 0xf5, 0xbe, 0xb0, 0xb0, 0x30, 0x77, 0x28, 0x0c, 0x13, 0x6f, 0x16,
913   0xd2, 0x87, 0x11, 0x05, 0x84, 0x74, 0x2c, 0xd0, 0xb7, 0xa7, 0xa7, 0xa7,
914   0xb7, 0xbd, 0xf7, 0xde, 0x7b, 0xde, 0x8c, 0xe9, 0x06, 0xfe, 0x98, 0x3b,
915   0xab, 0x6f, 0xb1, 0xb7, 0x0b, 0x47, 0x86, 0x29, 0x09, 0x36, 0x3b, 0xee,
916   0xdf, 0xbf, 0x5f, 0xcc, 0x9a, 0x35, 0xcb, 0xce, 0xca, 0xf0, 0xe7, 0x8e,
917   0x17, 0x7c, 0x93, 0x09, 0x27, 0x49, 0x2e, 0x5c, 0xb8, 0x50, 0xa7, 0xb4,
918   0x45, 0x0b, 0x89, 0x29, 0xec, 0x0d, 0x40, 0x66, 0xad, 0x97, 0x38, 0x39,
919   0xc1, 0xe2, 0x6d, 0x76, 0xcd, 0xca, 0xfa, 0xf5, 0x09, 0xe9, 0x3a, 0x1f,
920   0x2d, 0x8b, 0x82, 0x42, 0x90, 0x8d, 0x5b, 0xc0, 0x21, 0x9b, 0x37, 0xcd,
921   0x1b, 0xde, 0xe9, 0xe3, 0xa2, 0x67, 0xcd, 0x99, 0xbe, 0xb3, 0xb3, 0x53,
922   0xcc, 0x9f, 0x3f, 0x5f, 0x9c, 0x3a, 0x75, 0x4a, 0xf1, 0x87, 0xdb, 0xb7,
923   0x6f, 0x47, 0xd6, 0x0a, 0x4b, 0x80, 0x60, 0x12, 0x10, 0x23, 0x76, 0xf8,
924   0x99, 0x74, 0xf4, 0xb6, 0x39, 0x57, 0xae, 0x5c, 0x19, 0xf1, 0xe8, 0xa3,
925   0x8f, 0xea, 0xa5, 0xce, 0xb1, 0x73, 0x8b, 0xd9, 0x6c, 0xee, 0x5e, 0xb2,
926   0x94, 0x95, 0x95, 0xd5, 0x3b, 0xd1, 0xf2, 0x6f, 0xd1, 0xb3, 0xff, 0xcf,
927   0x1b, 0xc5, 0x77, 0xf2, 0xf7, 0x9d, 0x12, 0xfd, 0xcb, 0xca, 0xf5, 0x88,
928   0x8f, 0x0f, 0xc1, 0xd7, 0xf3, 0xb1, 0xdd, 0xde, 0x20, 0x94, 0xf0, 0x32,
929   0xc1, 0xea, 0x70, 0x38, 0xba, 0xcf, 0x59, 0x15, 0x2a, 0x96, 0x01, 0x6d,
930   0xde, 0xbc, 0x19, 0xc7, 0x71, 0xdd, 0x22, 0xec, 0x23, 0xcc, 0xe0, 0xa3,
931   0x3d, 0x0d, 0x7e, 0xc6, 0x28, 0x02, 0x5e, 0x7c, 0x91, 0x88, 0x77, 0xe0,
932   0x6c, 0x77, 0xa5, 0x7a, 0xd2, 0xf0, 0xe3, 0xa2, 0x71, 0x19, 0x75, 0xc5,
933   0xb2, 0xa5, 0x63, 0x7c, 0xe4, 0x69, 0x5f, 0xde, 0xad, 0x1b, 0x4c, 0x67,
934   0xc3, 0x4e, 0x25, 0x1c, 0x22, 0xd8, 0xd6, 0xac, 0x59, 0xa3, 0x8a, 0x70,
935   0x34, 0x0c, 0x12, 0xf7, 0x19, 0xef, 0x3f, 0xe4, 0xc6, 0x36, 0x50, 0xf5,
936   0x1d, 0x49, 0x78, 0x05, 0x67, 0xbf, 0xd3, 0xd8, 0xae, 0xea, 0x3c, 0x3c,
937   0x9c, 0x5e, 0x5d, 0x55, 0x55, 0x85, 0xb3, 0xf6, 0xaa, 0x09, 0xeb, 0xf8,
938   0x90, 0xe3, 0xfb, 0xf6, 0x40, 0xe0, 0xc9, 0x84, 0xbf, 0x13, 0xae, 0x6f,
939   0xdd, 0xba, 0x55, 0x15, 0xe1, 0xe8, 0x5d, 0x57, 0xaf, 0x5e, 0xb5, 0xf1,
940   0x6d, 0x0e, 0xab, 0x70, 0x26, 0x4e, 0x00, 0xea, 0x3d, 0x91, 0xf0, 0x0e,
941   0x16, 0x91, 0xbe, 0xf4, 0xd2, 0x4b, 0xaa, 0xea, 0x3d, 0x69, 0xd2, 0x24,
942   0x17, 0x45, 0x36, 0x68, 0xa8, 0x15, 0x5c, 0x6f, 0xd3, 0xfd, 0x48, 0x7a,
943   0x34, 0x1f, 0x86, 0x7f, 0x1d, 0xe7, 0xb8, 0xca, 0x9d, 0x84, 0x28, 0x7a,
944   0x9d, 0xf2, 0x5c, 0x50, 0x50, 0xe0, 0xe4, 0x7b, 0x5b, 0x36, 0x12, 0x86,
945   0x06, 0xa8, 0xee, 0x3a, 0x3e, 0x95, 0xf1, 0x90, 0xcd, 0x66, 0xb3, 0xce,
946   0x99, 0x33, 0x47, 0x15, 0xf1, 0xcb, 0x97, 0x2f, 0x77, 0x5b, 0xa8, 0x4f,
947   0xf8, 0xe6, 0x8a, 0xfb, 0x8a, 0x74, 0x98, 0xb7, 0xb5, 0x84, 0xda, 0x0b,
948   0x17, 0x2e, 0x38, 0x93, 0x92, 0x92, 0x14, 0x15, 0x66, 0x30, 0x18, 0x5c,
949   0x38, 0xf6, 0x9b, 0x8f, 0xd8, 0xde, 0x4e, 0x48, 0x0c, 0xb0, 0x95, 0x82,
950   0x0f, 0xf1, 0x13, 0x58, 0x9c, 0x96, 0x96, 0x16, 0x3b, 0x7a, 0xb2, 0x1a,
951   0xe2, 0x37, 0x6c, 0xd8, 0x80, 0x4b, 0x7d, 0xda, 0xe9, 0x77, 0x1f, 0x10,
952   0xa6, 0x05, 0xe2, 0xa8, 0xf0, 0x40, 0x28, 0x0b, 0x8e, 0xdb, 0x6f, 0x60,
953   0xe6, 0xc8, 0x4c, 0xdb, 0x13, 0x12, 0x12, 0x54, 0x29, 0xeb, 0xb9, 0xe7,
954   0x9e, 0x03, 0xe1, 0xb7, 0x79, 0xfc, 0x9f, 0xa0, 0x91, 0xb3, 0xd3, 0xc3,
955   0xd9, 0xb1, 0xab, 0x2b, 0x2b, 0x2b, 0xc3, 0xa9, 0xd3, 0xaa, 0xbe, 0xe5,
956   0x8d, 0x37, 0xde, 0x70, 0x5f, 0xfb, 0xf1, 0x16, 0x5f, 0x08, 0x34, 0xa8,
957   0x49, 0x47, 0xef, 0xf8, 0x31, 0xe1, 0x94, 0x95, 0x64, 0xee, 0xdc, 0xb9,
958   0xaa, 0xc7, 0x43, 0xbe, 0x9c, 0xa7, 0x98, 0x7b, 0x97, 0x41, 0x63, 0x17,
959   0x10, 0xbc, 0x0d, 0xc7, 0x6e, 0xdb, 0xb6, 0x6d, 0xaa, 0xbe, 0xc7, 0x62,
960   0xb1, 0xb8, 0x0a, 0x0b, 0x0b, 0xe1, 0xcd, 0x37, 0xf0, 0x85, 0x40, 0x89,
961   0x83, 0x95, 0x74, 0x5c, 0x8f, 0xf1, 0x53, 0xc2, 0x69, 0xdc, 0x59, 0xf6,
962   0xd4, 0x53, 0x4f, 0xa9, 0x52, 0x10, 0xee, 0x5f, 0xbb, 0x74, 0xe9, 0x12,
963   0x14, 0x74, 0x86, 0xf0, 0x2b, 0xb6, 0x14, 0x5a, 0xbb, 0x2d, 0x01, 0xbd,
964   0x75, 0x07, 0x42, 0xc8, 0x4d, 0x9b, 0x36, 0xa9, 0xfa, 0xae, 0xe8, 0xe8,
965   0x68, 0x57, 0x69, 0x69, 0x29, 0x7a, 0x7c, 0x23, 0x61, 0xcb, 0x40, 0x12,
966   0x3f, 0x90, 0x8a, 0x99, 0x43, 0xc8, 0x87, 0x89, 0x5e, 0xbf, 0x7e, 0xbd,
967   0x2a, 0xc5, 0xe0, 0x2a, 0xac, 0xf2, 0xf2, 0x72, 0x27, 0xf7, 0x08, 0xdc,
968   0xe3, 0x12, 0xa6, 0xe1, 0x6b, 0x32, 0x66, 0x72, 0x08, 0xd9, 0x85, 0xcb,
969   0xf8, 0xd4, 0x7c, 0x1f, 0x6e, 0x91, 0xa4, 0x06, 0x6d, 0xe7, 0x2b, 0xbd,
970   0x36, 0xf1, 0x51, 0xeb, 0x83, 0x86, 0xf4, 0x87, 0x08, 0xff, 0xc2, 0x6d,
971   0x46, 0x34, 0x9e, 0xa9, 0xde, 0x87, 0xc5, 0x37, 0x30, 0xb5, 0x70, 0x2f,
972   0x4a, 0xd0, 0xf8, 0xdd, 0x28, 0x26, 0x0e, 0xc5, 0xbe, 0x42, 0x48, 0x39,
973   0x7e, 0xfc, 0x78, 0x55, 0xdf, 0x38, 0x75, 0xea, 0x54, 0x17, 0x1c, 0x41,
974   0x5c, 0x10, 0x44, 0xf8, 0x25, 0x5b, 0xc4, 0x7b, 0x9e, 0xf4, 0x18, 0x4e,
975   0x66, 0xb4, 0x1c, 0x39, 0x72, 0x44, 0xb5, 0xb3, 0xb3, 0x6e, 0xdd, 0x3a,
976   0xf7, 0x01, 0xf8, 0x79, 0x4a, 0x87, 0xd6, 0x6b, 0x08, 0x43, 0x39, 0x94,
977   0xbc, 0x5c, 0x5f, 0x5f, 0xef, 0x80, 0x09, 0x57, 0xf3, 0xad, 0x33, 0x67,
978   0xce, 0xc4, 0x4d, 0x92, 0x5d, 0x9c, 0xb1, 0xcb, 0xf6, 0xf3, 0x4d, 0x92,
979   0x7e, 0x27, 0xfd, 0x4e, 0xda, 0xf2, 0xe4, 0xc9, 0x93, 0x92, 0x37, 0x17,
980   0xde, 0x8d, 0x9c, 0x9c, 0x1c, 0x97, 0xdd, 0x6e, 0x87, 0x12, 0x0a, 0x09,
981   0x4f, 0x06, 0x32, 0x91, 0xd1, 0x07, 0x24, 0x72, 0x48, 0x79, 0x2d, 0x2f,
982   0x2f, 0x4f, 0x55, 0xfe, 0x01, 0x58, 0xb1, 0x62, 0x05, 0x1a, 0x79, 0x3b,
983   0x0f, 0x81, 0x73, 0xfc, 0x19, 0xca, 0xf9, 0xf3, 0x56, 0x65, 0xac, 0xac,
984   0xfd, 0x19, 0xe1, 0xd7, 0x98, 0x84, 0xa9, 0xae, 0xae, 0xd6, 0x91, 0x93,
985   0xa3, 0xea, 0x87, 0xb9, 0xb9, 0xb9, 0x0e, 0x8a, 0xcb, 0x31, 0x29, 0x81,
986   0x4b, 0x68, 0x0e, 0x09, 0xf5, 0x17, 0xe1, 0x68, 0x41, 0x70, 0xb4, 0xda,
987   0x0e, 0xc2, 0xa8, 0xec, 0xec, 0xec, 0x45, 0x7b, 0xf6, 0xec, 0x89, 0xe4,
988   0xcd, 0x06, 0x8a, 0x42, 0xd6, 0xc1, 0x12, 0x17, 0x17, 0x37, 0x53, 0xf4,
989   0x5c, 0x94, 0x54, 0x23, 0xa4, 0x4f, 0x8e, 0xd4, 0xc4, 0x2c, 0x9b, 0x27,
990   0xc1, 0x4a, 0x8f, 0xbf, 0x62, 0x56, 0x4a, 0x78, 0xb7, 0xb4, 0x1a, 0x15,
991   0xc2, 0x89, 0x8b, 0xb8, 0x83, 0x0b, 0xb7, 0x18, 0x5d, 0xbd, 0x07, 0xd7,
992   0x3e, 0x60, 0x9a, 0x18, 0xf7, 0x6e, 0xac, 0x11, 0x3d, 0x87, 0x01, 0x86,
993   0x78, 0xf9, 0xfd, 0x58, 0x92, 0x8e, 0x2b, 0xc9, 0x3e, 0x12, 0xf2, 0x67,
994   0xee, 0x6a, 0x8e, 0x74, 0x2c, 0x9f, 0xc2, 0xfd, 0x66, 0xd1, 0xde, 0x4e,
995   0xfc, 0x89, 0x9e, 0xa3, 0x36, 0x71, 0x0c, 0xe7, 0xcd, 0x7b, 0x78, 0xd1,
996   0x0b, 0xa6, 0x97, 0x47, 0x8b, 0x9e, 0xad, 0x44, 0xde, 0xee, 0x01, 0x70,
997   0xdf, 0xb8, 0xe4, 0x97, 0x63, 0x50, 0xfd, 0x49, 0x7a, 0x50, 0x34, 0x2a,
998   0xff, 0x13, 0x60, 0x00, 0x43, 0x56, 0xa7, 0xa6, 0x51, 0x00, 0x63, 0x17,
999   0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
1000 };
1001 static unsigned int invader_png_len = 4008;
1002 
1003 static ui_window_qt_t ui_window = {0};
1004 
getInvader(void)1005 static const QPixmap getInvader(void)
1006 {
1007    QPixmap pix;
1008    pix.loadFromData(invader_png, invader_png_len, "PNG");
1009 
1010    return pix;
1011 }
1012 
1013 #ifdef HAVE_LIBRETRODB
scan_finished_handler(retro_task_t * task,void * task_data,void * user_data,const char * err)1014 static void scan_finished_handler(retro_task_t *task,
1015       void *task_data, void *user_data, const char *err)
1016 {
1017    bool dontAsk      = false;
1018    bool answer       = false;
1019 #ifdef HAVE_MENU
1020    menu_ctx_environment_t menu_environ;
1021    menu_environ.type = MENU_ENVIRON_RESET_HORIZONTAL_LIST;
1022    menu_environ.data = NULL;
1023 
1024    menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ);
1025 #endif
1026    if (!ui_window.qtWindow->settings()->value(
1027             "scan_finish_confirm", true).toBool())
1028       return;
1029 
1030    answer = ui_window.qtWindow->showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_SCAN_FINISHED), MainWindow::MSGBOX_TYPE_QUESTION_OKCANCEL, Qt::ApplicationModal, true, &dontAsk);
1031 
1032    if (answer && dontAsk)
1033       ui_window.qtWindow->settings()->setValue("scan_finish_confirm", false);
1034 }
1035 #endif
1036 
1037 /* https://stackoverflow.com/questions/7246622/how-to-create-a-slider-with-a-non-linear-scale */
expScale(double inputValue,double midValue,double maxValue)1038 static double expScale(double inputValue, double midValue, double maxValue)
1039 {
1040    double           M = maxValue / midValue;
1041    double           C = log(pow(M - 1, 2));
1042    double           B = maxValue / (exp(C) - 1);
1043    double           A = -1 * B;
1044    double returnValue = A + B * exp(C * inputValue);
1045    return returnValue;
1046 }
1047 
TreeView(QWidget * parent)1048 TreeView::TreeView(QWidget *parent) :
1049    QTreeView(parent)
1050 {
1051 }
1052 
columnCountChanged(int oldCount,int newCount)1053 void TreeView::columnCountChanged(int oldCount, int newCount)
1054 {
1055    QTreeView::columnCountChanged(oldCount, newCount);
1056 }
1057 
selectionChanged(const QItemSelection & selected,const QItemSelection & deselected)1058 void TreeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
1059 {
1060    QModelIndexList list = selected.indexes();
1061 
1062    QTreeView::selectionChanged(selected, deselected);
1063 
1064    emit itemsSelected(list);
1065 }
1066 
TableView(QWidget * parent)1067 TableView::TableView(QWidget *parent) :
1068    QTableView(parent)
1069 {
1070 }
1071 
isEditorOpen()1072 bool TableView::isEditorOpen()
1073 {
1074    return (state() == QAbstractItemView::EditingState);
1075 }
1076 
ListWidget(QWidget * parent)1077 ListWidget::ListWidget(QWidget *parent) :
1078    QListWidget(parent)
1079 {
1080 }
1081 
isEditorOpen()1082 bool ListWidget::isEditorOpen()
1083 {
1084    return (state() == QAbstractItemView::EditingState);
1085 }
1086 
keyPressEvent(QKeyEvent * event)1087 void ListWidget::keyPressEvent(QKeyEvent *event)
1088 {
1089    if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
1090       emit enterPressed();
1091    else if (event->key() == Qt::Key_Delete)
1092       emit deletePressed();
1093 
1094    QListWidget::keyPressEvent(event);
1095 }
1096 
CoreInfoLabel(QString text,QWidget * parent)1097 CoreInfoLabel::CoreInfoLabel(QString text, QWidget *parent) :
1098    QLabel(text, parent)
1099 {
1100    setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
1101 }
1102 
CoreInfoWidget(CoreInfoLabel * label,QWidget * parent)1103 CoreInfoWidget::CoreInfoWidget(CoreInfoLabel *label, QWidget *parent) :
1104    QWidget(parent)
1105    ,m_label(label)
1106    ,m_scrollArea(new QScrollArea(this))
1107 {
1108    m_scrollArea->setWidgetResizable(true);
1109    m_scrollArea->setWidget(m_label);
1110 }
1111 
sizeHint() const1112 QSize CoreInfoWidget::sizeHint() const
1113 {
1114    return QSize(256, 256);
1115 }
1116 
resizeEvent(QResizeEvent * event)1117 void CoreInfoWidget::resizeEvent(QResizeEvent *event)
1118 {
1119    QWidget::resizeEvent(event);
1120    m_scrollArea->resize(event->size());
1121 }
1122 
LogTextEdit(QWidget * parent)1123 LogTextEdit::LogTextEdit(QWidget *parent) :
1124    QPlainTextEdit(parent)
1125 {
1126 
1127 }
1128 
appendMessage(const QString & text)1129 void LogTextEdit::appendMessage(const QString& text)
1130 {
1131    if (text.isEmpty())
1132       return;
1133 
1134    appendPlainText(text);
1135    verticalScrollBar()->setValue(verticalScrollBar()->maximum());
1136 }
1137 
1138 /* only accept indexes from current path. https://www.qtcentre.org/threads/50700-QFileSystemModel-and-QSortFilterProxyModel-don-t-work-well-together */
filterAcceptsRow(int sourceRow,const QModelIndex & sourceParent) const1139 bool FileSystemProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
1140 {
1141    QFileSystemModel  *sm = qobject_cast<QFileSystemModel*>(sourceModel());
1142    QModelIndex rootIndex = sm->index(sm->rootPath());
1143 
1144    if (sourceParent == rootIndex)
1145       return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
1146    return true;
1147 }
1148 
sort(int column,Qt::SortOrder order)1149 void FileSystemProxyModel::sort(int column, Qt::SortOrder order)
1150 {
1151    /* sort the source (QFileSystemModel to keep directories before files) */
1152    sourceModel()->sort(column, order);
1153 }
1154 
MainWindow(QWidget * parent)1155 MainWindow::MainWindow(QWidget *parent) :
1156    QMainWindow(parent)
1157    ,m_loadCoreWindow(new LoadCoreWindow(this))
1158    ,m_timer(new QTimer(this))
1159    ,m_currentCore()
1160    ,m_currentCoreVersion()
1161    ,m_statusLabel(new QLabel(this))
1162    ,m_dirTree(new TreeView(this))
1163    ,m_dirModel(new QFileSystemModel(m_dirTree))
1164    ,m_fileModel(new QFileSystemModel(this))
1165    ,m_listWidget(new ListWidget(this))
1166    ,m_centralWidget(new QStackedWidget(this))
1167    ,m_tableView(new TableView(this))
1168    ,m_fileTableView(new QTableView(this))
1169    ,m_playlistViews(new FileDropWidget(this))
1170    ,m_searchWidget(new QWidget(this))
1171    ,m_searchLineEdit(new QLineEdit(this))
1172    ,m_searchDock(new QDockWidget(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SEARCH), this))
1173    ,m_playlistFiles()
1174    ,m_launchWithComboBox(new QComboBox(this))
1175    ,m_startCorePushButton(new QToolButton(this))
1176    ,m_coreInfoPushButton(new QToolButton(this))
1177    ,m_runPushButton(new QToolButton(this))
1178    ,m_stopPushButton(new QToolButton(this))
1179    ,m_browserAndPlaylistTabWidget(new QTabWidget(this))
1180    ,m_pendingRun(false)
1181    ,m_thumbnailPixmap(NULL)
1182    ,m_thumbnailPixmap2(NULL)
1183    ,m_thumbnailPixmap3(NULL)
1184    ,m_settings(NULL)
1185    ,m_viewOptionsDialog(NULL)
1186    ,m_coreInfoDialog(new CoreInfoDialog(this, NULL))
1187    ,m_defaultStyle(NULL)
1188    ,m_defaultPalette()
1189    ,m_currentTheme(THEME_SYSTEM_DEFAULT)
1190    ,m_coreInfoDock(new QDockWidget(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CORE_INFO), this))
1191    ,m_coreInfoLabel(new CoreInfoLabel(QString(), this))
1192    ,m_coreInfoWidget(new CoreInfoWidget(m_coreInfoLabel, this))
1193    ,m_logDock(new QDockWidget(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOG), this))
1194    ,m_logWidget(new QFrame(this))
1195    ,m_logTextEdit(new LogTextEdit(m_logWidget))
1196    ,m_historyPlaylistsItem(NULL)
1197    ,m_folderIcon()
1198    ,m_customThemeString()
1199    ,m_gridView(new GridView(this))
1200    ,m_playlistViewsAndFooter(new QWidget(this))
1201    ,m_zoomSlider(NULL)
1202    ,m_lastZoomSliderValue(0)
1203    ,m_viewType(VIEW_TYPE_LIST)
1204    ,m_thumbnailType(THUMBNAIL_TYPE_BOXART)
1205    ,m_gridProgressBar(NULL)
1206    ,m_gridProgressWidget(NULL)
1207    ,m_currentGridHash()
1208    ,m_currentGridWidget(NULL)
1209    ,m_allPlaylistsListMaxCount(0)
1210    ,m_allPlaylistsGridMaxCount(0)
1211    ,m_playlistEntryDialog(NULL)
1212    ,m_statusMessageElapsedTimer()
1213 #if defined(HAVE_MENU)
1214 #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
1215    ,m_shaderParamsDialog(new ShaderParamsDialog())
1216 #endif
1217 #endif
1218    ,m_coreOptionsDialog(new CoreOptionsDialog())
1219    ,m_networkManager(new QNetworkAccessManager(this))
1220    ,m_updateProgressDialog(new QProgressDialog())
1221    ,m_updateFile()
1222    ,m_updateReply()
1223    ,m_thumbnailDownloadProgressDialog(new QProgressDialog())
1224    ,m_thumbnailDownloadFile()
1225    ,m_thumbnailDownloadReply()
1226    ,m_pendingThumbnailDownloadTypes()
1227    ,m_thumbnailPackDownloadProgressDialog(new QProgressDialog())
1228    ,m_thumbnailPackDownloadFile()
1229    ,m_thumbnailPackDownloadReply()
1230    ,m_playlistThumbnailDownloadProgressDialog(new QProgressDialog())
1231    ,m_playlistThumbnailDownloadFile()
1232    ,m_playlistThumbnailDownloadReply()
1233    ,m_pendingPlaylistThumbnails()
1234    ,m_downloadedThumbnails(0)
1235    ,m_failedThumbnails(0)
1236    ,m_playlistThumbnailDownloadWasCanceled(false)
1237    ,m_pendingDirScrollPath()
1238    ,m_thumbnailTimer(new QTimer(this))
1239    ,m_gridItem(this)
1240    ,m_currentBrowser(BROWSER_TYPE_PLAYLISTS)
1241    ,m_searchRegExp()
1242    ,m_zoomWidget(new QWidget(this))
1243    ,m_itemsCountLiteral(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ITEMS_COUNT))
1244    ,m_itemsCountLabel(new QLabel(this))
1245 {
1246    settings_t                   *settings = config_get_ptr();
1247    const char *path_dir_playlist          = settings->paths.directory_playlist;
1248    const char *path_dir_assets            = settings->paths.directory_assets;
1249    const char *path_dir_menu_content      = settings->paths.directory_menu_content;
1250    QDir playlistDir(path_dir_playlist);
1251    QString                      configDir = QFileInfo(path_get(RARCH_PATH_CONFIG)).dir().absolutePath();
1252    QToolButton   *searchResetButton       = NULL;
1253    QHBoxLayout   *zoomLayout              = new QHBoxLayout();
1254    QLabel   *zoomLabel                    = new QLabel(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ZOOM), m_zoomWidget);
1255    QPushButton   *thumbnailTypePushButton = new QPushButton(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_TYPE), m_zoomWidget);
1256    QMenu               *thumbnailTypeMenu = new QMenu(thumbnailTypePushButton);
1257    QAction     *thumbnailTypeBoxartAction = NULL;
1258    QAction *thumbnailTypeScreenshotAction = NULL;
1259    QAction *thumbnailTypeTitleAction      = NULL;
1260    QPushButton *viewTypePushButton        = new QPushButton(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW), m_zoomWidget);
1261    QMenu                    *viewTypeMenu = new QMenu(viewTypePushButton);
1262    QAction           *viewTypeIconsAction = NULL;
1263    QAction            *viewTypeListAction = NULL;
1264    QHBoxLayout        *gridProgressLayout = new QHBoxLayout();
1265    QLabel              *gridProgressLabel = NULL;
1266    QHBoxLayout          *gridFooterLayout = NULL;
1267 
1268    qRegisterMetaType<QPointer<ThumbnailWidget> >("ThumbnailWidget");
1269    qRegisterMetaType<retro_task_callback_t>("retro_task_callback_t");
1270 
1271    /* Cancel all progress dialogs immediately since
1272     * they show as soon as they're constructed. */
1273    m_updateProgressDialog->cancel();
1274    m_thumbnailDownloadProgressDialog->cancel();
1275    m_thumbnailPackDownloadProgressDialog->cancel();
1276    m_playlistThumbnailDownloadProgressDialog->cancel();
1277 
1278    m_gridProgressWidget                   = new QWidget();
1279    gridProgressLabel                      = new QLabel(
1280          msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PROGRESS),
1281          m_gridProgressWidget);
1282 
1283    thumbnailTypePushButton->setObjectName("thumbnailTypePushButton");
1284    thumbnailTypePushButton->setFlat(true);
1285 
1286    thumbnailTypeBoxartAction              = thumbnailTypeMenu->addAction(
1287          msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_BOXART));
1288    thumbnailTypeScreenshotAction          = thumbnailTypeMenu->addAction(
1289          msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_SCREENSHOT));
1290    thumbnailTypeTitleAction               = thumbnailTypeMenu->addAction(
1291          msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_TITLE_SCREEN));
1292 
1293    thumbnailTypePushButton->setMenu(thumbnailTypeMenu);
1294 
1295    viewTypePushButton->setObjectName("viewTypePushButton");
1296    viewTypePushButton->setFlat(true);
1297 
1298    viewTypeIconsAction                    = viewTypeMenu->addAction(
1299          msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_ICONS));
1300    viewTypeListAction                     = viewTypeMenu->addAction(
1301          msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_LIST));
1302 
1303    viewTypePushButton->setMenu(viewTypeMenu);
1304 
1305    gridProgressLabel->setObjectName("gridProgressLabel");
1306 
1307    m_gridProgressBar                      = new QProgressBar(
1308          m_gridProgressWidget);
1309 
1310    m_gridProgressBar->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred));
1311 
1312    zoomLabel->setObjectName("zoomLabel");
1313 
1314    m_zoomSlider                           = new QSlider(
1315          Qt::Horizontal, m_zoomWidget);
1316 
1317    m_zoomSlider->setMinimum(0);
1318    m_zoomSlider->setMaximum(100);
1319    m_zoomSlider->setValue(50);
1320    m_zoomSlider->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred));
1321 
1322    m_lastZoomSliderValue = m_zoomSlider->value();
1323 
1324    m_playlistViewsAndFooter->setLayout(new QVBoxLayout());
1325 
1326    m_gridView->setSelectionMode(QAbstractItemView::SingleSelection);
1327    m_gridView->setEditTriggers(QAbstractItemView::NoEditTriggers);
1328 
1329    m_playlistViews->addWidget(m_gridView);
1330    m_playlistViews->addWidget(m_tableView);
1331    m_centralWidget->setObjectName("centralWidget");
1332 
1333    m_playlistViewsAndFooter->layout()->addWidget(m_playlistViews);
1334    m_playlistViewsAndFooter->layout()->setAlignment(Qt::AlignCenter);
1335    m_playlistViewsAndFooter->layout()->setContentsMargins(0, 0, 0, 0);
1336 
1337    m_gridProgressWidget->setLayout(gridProgressLayout);
1338    gridProgressLayout->setContentsMargins(0, 0, 0, 0);
1339    gridProgressLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Preferred));
1340    gridProgressLayout->addWidget(gridProgressLabel);
1341    gridProgressLayout->addWidget(m_gridProgressBar);
1342 
1343    m_playlistViewsAndFooter->layout()->addWidget(m_gridProgressWidget);
1344 
1345    m_zoomWidget->setLayout(zoomLayout);
1346    zoomLayout->setContentsMargins(0, 0, 0, 0);
1347    zoomLayout->addWidget(zoomLabel);
1348    zoomLayout->addWidget(m_zoomSlider);
1349 
1350    m_itemsCountLabel->setObjectName("itemsCountLabel");
1351 
1352    gridFooterLayout = new QHBoxLayout();
1353    gridFooterLayout->addWidget(m_itemsCountLabel);
1354    gridFooterLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Preferred));
1355    gridFooterLayout->addWidget(m_gridProgressWidget);
1356    gridFooterLayout->addWidget(m_zoomWidget);
1357    gridFooterLayout->addWidget(thumbnailTypePushButton);
1358    gridFooterLayout->addWidget(viewTypePushButton);
1359 
1360    static_cast<QVBoxLayout*>(m_playlistViewsAndFooter->layout())->addLayout(gridFooterLayout);
1361 
1362    m_gridProgressWidget->hide();
1363 
1364    m_playlistModel = new PlaylistModel(this);
1365    m_proxyModel    = new QSortFilterProxyModel(this);
1366    m_proxyModel->setSourceModel(m_playlistModel);
1367    m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
1368 
1369    m_proxyFileModel = new FileSystemProxyModel();
1370    m_proxyFileModel->setSourceModel(m_fileModel);
1371    m_proxyFileModel->setSortCaseSensitivity(Qt::CaseInsensitive);
1372 
1373    m_tableView->setAlternatingRowColors(true);
1374    m_tableView->setModel(m_proxyModel);
1375    m_tableView->setSortingEnabled(true);
1376    m_tableView->verticalHeader()->setVisible(false);
1377    m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
1378    m_tableView->setSelectionMode(QAbstractItemView::SingleSelection);
1379    m_tableView->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed);
1380    m_tableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
1381    m_tableView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
1382    m_tableView->horizontalHeader()->setStretchLastSection(true);
1383    m_tableView->setWordWrap(false);
1384 
1385    m_fileTableView->setModel(m_fileModel);
1386    m_fileTableView->sortByColumn(0, Qt::AscendingOrder);
1387    m_fileTableView->setSortingEnabled(true);
1388    m_fileTableView->setAlternatingRowColors(true);
1389    m_fileTableView->verticalHeader()->setVisible(false);
1390    m_fileTableView->setSelectionMode(QAbstractItemView::SingleSelection);
1391    m_fileTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
1392    m_fileTableView->horizontalHeader()->setStretchLastSection(true);
1393    m_fileTableView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
1394    m_fileTableView->setWordWrap(false);
1395 
1396    m_gridView->setItemDelegate(new ThumbnailDelegate(m_gridItem, this));
1397    m_gridView->setModel(m_proxyModel);
1398 
1399    m_gridView->setSelectionModel(m_tableView->selectionModel());
1400 
1401    m_logWidget->setObjectName("logWidget");
1402 
1403    m_folderIcon     = QIcon(QString(path_dir_assets) + GENERIC_FOLDER_ICON);
1404    m_imageFormats   = QVector<QByteArray>::fromList(QImageReader::supportedImageFormats());
1405    m_defaultStyle   = QApplication::style();
1406    m_defaultPalette = QApplication::palette();
1407 
1408    /* ViewOptionsDialog needs m_settings set before it's constructed */
1409    m_settings            = new QSettings(configDir + "/retroarch_qt.cfg", QSettings::IniFormat, this);
1410    m_viewOptionsDialog   = new ViewOptionsDialog(this, 0);
1411    m_playlistEntryDialog = new PlaylistEntryDialog(this, 0);
1412 
1413    /* default NULL parameter for parent wasn't added until 5.7 */
1414    m_startCorePushButton->setDefaultAction(new QAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_START_CORE), m_startCorePushButton));
1415    m_startCorePushButton->setFixedSize(m_startCorePushButton->sizeHint());
1416 
1417    m_runPushButton->setDefaultAction(new QAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_RUN), m_runPushButton));
1418    m_runPushButton->setFixedSize(m_runPushButton->sizeHint());
1419 
1420    m_stopPushButton->setDefaultAction(new QAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_STOP), m_stopPushButton));
1421    m_stopPushButton->setFixedSize(m_stopPushButton->sizeHint());
1422 
1423    m_coreInfoPushButton->setDefaultAction(new QAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_INFO), m_coreInfoPushButton));
1424    m_coreInfoPushButton->setFixedSize(m_coreInfoPushButton->sizeHint());
1425 
1426    searchResetButton = new QToolButton(m_searchWidget);
1427    searchResetButton->setDefaultAction(new QAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_SEARCH_CLEAR), searchResetButton));
1428    searchResetButton->setFixedSize(searchResetButton->sizeHint());
1429 
1430    connect(searchResetButton, SIGNAL(clicked()), this, SLOT(onSearchResetClicked()));
1431 
1432    m_dirModel->setFilter(QDir::NoDotAndDotDot |
1433                          QDir::AllDirs |
1434                          QDir::Drives |
1435                          (m_settings->value("show_hidden_files", true).toBool() ? (QDir::Hidden | QDir::System) : static_cast<QDir::Filter>(0)));
1436 
1437    m_fileModel->setFilter(QDir::NoDot |
1438                           QDir::AllEntries |
1439                           (m_settings->value("show_hidden_files", true).toBool() ? (QDir::Hidden | QDir::System) : static_cast<QDir::Filter>(0)));
1440 
1441 #if defined(Q_OS_WIN)
1442    m_dirModel->setRootPath("");
1443    m_fileModel->setRootPath("");
1444 #else
1445    m_dirModel->setRootPath("/");
1446    m_fileModel->setRootPath("/");
1447 #endif
1448 
1449    m_dirTree->setModel(m_dirModel);
1450    m_dirTree->setSelectionMode(QAbstractItemView::SingleSelection);
1451    m_dirTree->header()->setVisible(false);
1452 
1453    m_fileTableView->setModel(m_proxyFileModel);
1454 
1455    if (m_dirModel->columnCount() > 3)
1456    {
1457       /* size */
1458       m_dirTree->hideColumn(1);
1459       /* type */
1460       m_dirTree->hideColumn(2);
1461       /* date modified */
1462       m_dirTree->hideColumn(3);
1463    }
1464 
1465    reloadPlaylists();
1466 
1467    m_searchWidget->setLayout(new QHBoxLayout());
1468    m_searchWidget->layout()->addWidget(m_searchLineEdit);
1469    m_searchWidget->layout()->addWidget(searchResetButton);
1470 
1471    m_searchDock->setObjectName("searchDock");
1472    m_searchDock->setProperty("default_area", Qt::LeftDockWidgetArea);
1473    m_searchDock->setProperty("menu_text", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SEARCH));
1474    m_searchDock->setWidget(m_searchWidget);
1475    m_searchDock->setFixedHeight(m_searchDock->minimumSizeHint().height());
1476 
1477    addDockWidget(static_cast<Qt::DockWidgetArea>(m_searchDock->property("default_area").toInt()), m_searchDock);
1478 
1479    m_coreInfoLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
1480    m_coreInfoLabel->setTextFormat(Qt::RichText);
1481    m_coreInfoLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
1482    m_coreInfoLabel->setOpenExternalLinks(true);
1483 
1484    m_coreInfoDock->setObjectName("coreInfoDock");
1485    m_coreInfoDock->setProperty("default_area", Qt::RightDockWidgetArea);
1486    m_coreInfoDock->setProperty("menu_text", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CORE_INFO));
1487    m_coreInfoDock->setWidget(m_coreInfoWidget);
1488 
1489    addDockWidget(static_cast<Qt::DockWidgetArea>(m_coreInfoDock->property("default_area").toInt()), m_coreInfoDock);
1490 
1491    m_logWidget->setLayout(new QVBoxLayout());
1492    m_logWidget->layout()->addWidget(m_logTextEdit);
1493    m_logWidget->layout()->setContentsMargins(0, 0, 0, 0);
1494 
1495    m_logDock->setObjectName("logDock");
1496    m_logDock->setProperty("default_area", Qt::BottomDockWidgetArea);
1497    m_logDock->setProperty("menu_text", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOG));
1498    m_logDock->setWidget(m_logWidget);
1499 
1500    addDockWidget(static_cast<Qt::DockWidgetArea>(m_logDock->property("default_area").toInt()), m_logDock);
1501 
1502    /* Hide the log by default. If user has saved their dock positions with the log visible,
1503     * then this hide() call will be reversed later by restoreState().
1504     * FIXME: If user unchecks "save dock positions", the log will not be unhidden even if
1505     * it was previously saved in the config.
1506     */
1507    m_logDock->hide();
1508 
1509    m_dirTree->setContextMenuPolicy(Qt::CustomContextMenu);
1510    m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
1511 
1512    connect(m_searchLineEdit, SIGNAL(returnPressed()), this, SLOT(onSearchEnterPressed()));
1513    connect(m_searchLineEdit, SIGNAL(textEdited(const QString&)), this, SLOT(onSearchLineEditEdited(const QString&)));
1514    connect(m_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
1515    connect(m_loadCoreWindow, SIGNAL(coreLoaded()), this, SLOT(onCoreLoaded()));
1516    connect(m_loadCoreWindow, SIGNAL(windowClosed()), this, SLOT(onCoreLoadWindowClosed()));
1517    connect(m_listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(onCurrentListItemChanged(QListWidgetItem*, QListWidgetItem*)));
1518    connect(m_startCorePushButton, SIGNAL(clicked()), this, SLOT(onStartCoreClicked()));
1519    connect(m_coreInfoPushButton, SIGNAL(clicked()), m_coreInfoDialog, SLOT(showCoreInfo()));
1520    connect(m_runPushButton, SIGNAL(clicked()), this, SLOT(onRunClicked()));
1521    connect(m_stopPushButton, SIGNAL(clicked()), this, SLOT(onStopClicked()));
1522    connect(m_dirTree, SIGNAL(itemsSelected(QModelIndexList)), this, SLOT(onTreeViewItemsSelected(QModelIndexList)));
1523    connect(m_dirTree, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onFileBrowserTreeContextMenuRequested(const QPoint&)));
1524    connect(m_listWidget, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onPlaylistWidgetContextMenuRequested(const QPoint&)));
1525    connect(m_launchWithComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onLaunchWithComboBoxIndexChanged(int)));
1526    connect(m_zoomSlider, SIGNAL(valueChanged(int)), this, SLOT(onZoomValueChanged(int)));
1527    connect(thumbnailTypeBoxartAction, SIGNAL(triggered()), this, SLOT(onBoxartThumbnailClicked()));
1528    connect(thumbnailTypeScreenshotAction, SIGNAL(triggered()), this, SLOT(onScreenshotThumbnailClicked()));
1529    connect(thumbnailTypeTitleAction, SIGNAL(triggered()), this, SLOT(onTitleThumbnailClicked()));
1530    connect(viewTypeIconsAction, SIGNAL(triggered()), this, SLOT(onIconViewClicked()));
1531    connect(viewTypeListAction, SIGNAL(triggered()), this, SLOT(onListViewClicked()));
1532    connect(m_dirModel, SIGNAL(directoryLoaded(const QString&)), this, SLOT(onFileSystemDirLoaded(const QString&)));
1533    connect(m_fileModel, SIGNAL(directoryLoaded(const QString&)), this, SLOT(onFileBrowserTableDirLoaded(const QString&)));
1534 
1535    m_dirTree->setCurrentIndex(m_dirModel->index(path_dir_menu_content));
1536    m_dirTree->scrollTo(m_dirTree->currentIndex(), QAbstractItemView::PositionAtTop);
1537    m_dirTree->expand(m_dirTree->currentIndex());
1538 
1539    /* must use queued connection */
1540    connect(this, SIGNAL(scrollToDownloads(QString)), this, SLOT(onDownloadScroll(QString)), Qt::QueuedConnection);
1541    connect(this, SIGNAL(scrollToDownloadsAgain(QString)), this, SLOT(onDownloadScrollAgain(QString)), Qt::QueuedConnection);
1542 
1543    connect(m_playlistThumbnailDownloadProgressDialog, SIGNAL(canceled()), m_playlistThumbnailDownloadProgressDialog, SLOT(cancel()));
1544    connect(m_playlistThumbnailDownloadProgressDialog, SIGNAL(canceled()), this, SLOT(onPlaylistThumbnailDownloadCanceled()));
1545 
1546    connect(m_thumbnailDownloadProgressDialog, SIGNAL(canceled()), m_thumbnailDownloadProgressDialog, SLOT(cancel()));
1547    connect(m_thumbnailDownloadProgressDialog, SIGNAL(canceled()), this, SLOT(onThumbnailDownloadCanceled()));
1548 
1549    connect(m_thumbnailPackDownloadProgressDialog, SIGNAL(canceled()), m_thumbnailPackDownloadProgressDialog, SLOT(cancel()));
1550    connect(m_thumbnailPackDownloadProgressDialog, SIGNAL(canceled()), this, SLOT(onThumbnailPackDownloadCanceled()));
1551 
1552    connect(this, SIGNAL(itemChanged()), this, SLOT(onItemChanged()));
1553    connect(this, SIGNAL(gotThumbnailDownload(QString,QString)), this, SLOT(onDownloadThumbnail(QString,QString)));
1554 
1555    m_thumbnailTimer->setSingleShot(true);
1556    connect(m_thumbnailTimer, SIGNAL(timeout()), this, SLOT(updateVisibleItems()));
1557    connect(this, SIGNAL(updateThumbnails()), this, SLOT(updateVisibleItems()));
1558 
1559    /* TODO: Handle scroll and resize differently. */
1560    connect(m_gridView, SIGNAL(visibleItemsChangedMaybe()), this, SLOT(startTimer()));
1561 
1562    connect(m_tableView->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(onCurrentItemChanged(const QModelIndex&)));
1563    connect(m_fileTableView->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(onCurrentFileChanged(const QModelIndex&)));
1564 
1565    connect(m_gridView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onContentItemDoubleClicked(const QModelIndex&)));
1566    connect(m_tableView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onContentItemDoubleClicked(const QModelIndex&)));
1567    connect(m_fileTableView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onFileDoubleClicked(const QModelIndex&)));
1568 
1569    connect(m_playlistModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)), this, SLOT(onCurrentTableItemDataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
1570 
1571    /* make sure these use an auto connection so it will be queued if called from a different thread (some facilities in RA log messages from other threads) */
1572    connect(this, SIGNAL(gotLogMessage(const QString&)), this, SLOT(onGotLogMessage(const QString&)), Qt::AutoConnection);
1573    connect(this, SIGNAL(gotStatusMessage(QString,unsigned,unsigned,bool)), this, SLOT(onGotStatusMessage(QString,unsigned,unsigned,bool)), Qt::AutoConnection);
1574    connect(this, SIGNAL(gotReloadPlaylists()), this, SLOT(onGotReloadPlaylists()), Qt::AutoConnection);
1575 #if defined(HAVE_MENU)
1576 #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
1577    connect(this, SIGNAL(gotReloadShaderParams()), this, SLOT(onGotReloadShaderParams()), Qt::AutoConnection);
1578 #endif
1579 #endif
1580    connect(this, SIGNAL(gotReloadCoreOptions()), this, SLOT(onGotReloadCoreOptions()), Qt::AutoConnection);
1581 
1582    /* these are always queued */
1583    connect(this, SIGNAL(showErrorMessageDeferred(QString)), this, SLOT(onShowErrorMessage(QString)), Qt::QueuedConnection);
1584    connect(this, SIGNAL(showInfoMessageDeferred(QString)), this, SLOT(onShowInfoMessage(QString)), Qt::QueuedConnection);
1585    connect(this, SIGNAL(extractArchiveDeferred(QString,QString,QString,retro_task_callback_t)), this, SLOT(onExtractArchive(QString,QString,QString,retro_task_callback_t)), Qt::QueuedConnection);
1586 
1587    m_timer->start(TIMER_MSEC);
1588 
1589    statusBar()->addPermanentWidget(m_statusLabel);
1590 
1591    setCurrentCoreLabel();
1592    setCoreActions();
1593 
1594    /* both of these are necessary to get the folder to scroll to the top of the view */
1595    qApp->processEvents();
1596    QTimer::singleShot(0, this, SLOT(onBrowserStartClicked()));
1597 
1598    m_searchLineEdit->setFocus();
1599    m_loadCoreWindow->setWindowModality(Qt::ApplicationModal);
1600 
1601    m_statusMessageElapsedTimer.start();
1602 
1603 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
1604    resizeDocks(QList<QDockWidget*>() << m_searchDock, QList<int>() << 1, Qt::Vertical);
1605 #endif
1606 
1607 #ifdef HAVE_OPENSSL
1608    {
1609 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
1610       const SSL_METHOD *method = TLS_method();
1611       SSL_CTX *ctx = SSL_CTX_new(method);
1612 
1613       if (ctx)
1614          SSL_CTX_free(ctx);
1615 #else
1616       const SSL_METHOD *method = TLSv1_method();
1617       RARCH_LOG("[Qt]: TLS supports %d ciphers.\n", method->num_ciphers());
1618 #endif
1619       RARCH_LOG("[Qt]: Using %s\n", OPENSSL_VERSION_TEXT);
1620    }
1621 #endif
1622 }
1623 
~MainWindow()1624 MainWindow::~MainWindow()
1625 {
1626    if (m_thumbnailPixmap)
1627       delete m_thumbnailPixmap;
1628    if (m_thumbnailPixmap2)
1629       delete m_thumbnailPixmap2;
1630    if (m_thumbnailPixmap3)
1631       delete m_thumbnailPixmap3;
1632    if (m_proxyFileModel)
1633       delete m_proxyFileModel;
1634 }
1635 
startTimer()1636 void MainWindow::startTimer()
1637 {
1638    if (m_thumbnailTimer->isActive())
1639    {
1640       m_thumbnailTimer->stop();
1641       m_thumbnailTimer->start(50);
1642    }
1643    else
1644       m_thumbnailTimer->start(50);
1645 }
1646 
updateVisibleItems()1647 void MainWindow::updateVisibleItems()
1648 {
1649    if (m_currentBrowser == BROWSER_TYPE_PLAYLISTS && m_viewType == VIEW_TYPE_ICONS)
1650    {
1651       unsigned i;
1652       QVector<QModelIndex> indexes = m_gridView->visibleIndexes();
1653       size_t size                  = indexes.size();
1654 
1655       for (i = 0; i < size; i++)
1656          m_playlistModel->loadThumbnail(m_proxyModel->mapToSource(indexes.at(i)));
1657    }
1658 }
1659 
setThumbnailCacheLimit(int count)1660 void MainWindow::setThumbnailCacheLimit(int count)
1661 {
1662    if (count < 1)
1663       count = 0;
1664 
1665    m_playlistModel->setThumbnailCacheLimit(count);
1666 }
1667 
onFileSystemDirLoaded(const QString & path)1668 void MainWindow::onFileSystemDirLoaded(const QString &path)
1669 {
1670    if (path.isEmpty() || m_pendingDirScrollPath.isEmpty())
1671       return;
1672 
1673    if (QDir(path) == QDir(m_pendingDirScrollPath))
1674    {
1675       m_pendingDirScrollPath = QString();
1676 
1677       emit scrollToDownloads(path);
1678    }
1679 }
1680 
1681 /* workaround for columns being resized */
onFileBrowserTableDirLoaded(const QString & path)1682 void MainWindow::onFileBrowserTableDirLoaded(const QString &path)
1683 {
1684    if (path.isEmpty())
1685       return;
1686 
1687    m_fileTableView->horizontalHeader()->restoreState(m_fileTableHeaderState);
1688 }
1689 
getPlaylists()1690 QVector<QPair<QString, QString> > MainWindow::getPlaylists()
1691 {
1692    unsigned i;
1693    QVector<QPair<QString, QString> > playlists;
1694    size_t size  = m_listWidget->count();
1695 
1696    for (i = 0; i < size; i++)
1697    {
1698       QString label, path;
1699       QPair<QString, QString> pair;
1700       QListWidgetItem *item = m_listWidget->item(i);
1701 
1702       if (!item)
1703          continue;
1704 
1705       label       = item->text();
1706       path        = item->data(Qt::UserRole).toString();
1707 
1708       pair.first  = label;
1709       pair.second = path;
1710 
1711       playlists.append(pair);
1712    }
1713 
1714    return playlists;
1715 }
1716 
onItemChanged()1717 void MainWindow::onItemChanged()
1718 {
1719    QModelIndex index = getCurrentContentIndex();
1720    m_playlistModel->reloadThumbnail(index);
1721    onCurrentItemChanged(index);
1722 }
1723 
getSpecialPlaylistPath(SpecialPlaylist playlist)1724 QString MainWindow::getSpecialPlaylistPath(SpecialPlaylist playlist)
1725 {
1726    switch (playlist)
1727    {
1728       case SPECIAL_PLAYLIST_HISTORY:
1729          if (m_historyPlaylistsItem)
1730             return m_historyPlaylistsItem->data(Qt::UserRole).toString();
1731          break;
1732       default:
1733          break;
1734    }
1735 
1736    return QString();
1737 }
1738 
lerp(double x,double y,double a,double b,double d)1739 double MainWindow::lerp(double x, double y, double a, double b, double d)
1740 {
1741    return a + (b - a) * ((double)(d - x) / (double)(y - x));
1742 }
1743 
onIconViewClicked()1744 void MainWindow::onIconViewClicked()
1745 {
1746    setCurrentViewType(VIEW_TYPE_ICONS);
1747 }
1748 
onListViewClicked()1749 void MainWindow::onListViewClicked()
1750 {
1751    setCurrentViewType(VIEW_TYPE_LIST);
1752 }
1753 
onBoxartThumbnailClicked()1754 void MainWindow::onBoxartThumbnailClicked()
1755 {
1756    setCurrentThumbnailType(THUMBNAIL_TYPE_BOXART);
1757 }
1758 
onScreenshotThumbnailClicked()1759 void MainWindow::onScreenshotThumbnailClicked()
1760 {
1761    setCurrentThumbnailType(THUMBNAIL_TYPE_SCREENSHOT);
1762 }
1763 
onTitleThumbnailClicked()1764 void MainWindow::onTitleThumbnailClicked()
1765 {
1766    setCurrentThumbnailType(THUMBNAIL_TYPE_TITLE_SCREEN);
1767 }
1768 
setIconViewZoom(int zoomValue)1769 void MainWindow::setIconViewZoom(int zoomValue)
1770 {
1771    m_zoomSlider->setValue(zoomValue);
1772 }
1773 
onZoomValueChanged(int zoomValue)1774 void MainWindow::onZoomValueChanged(int zoomValue)
1775 {
1776    int newSize               = 0;
1777 
1778    if (zoomValue < 50)
1779       newSize                = expScale(
1780             lerp(0, 49, 25, 49, zoomValue) / 50.0, 102, 256);
1781    else
1782       newSize                = expScale(zoomValue / 100.0, 256, 1024);
1783 
1784    m_gridView->setGridSize(newSize);
1785 
1786    m_lastZoomSliderValue     = zoomValue;
1787 }
1788 
showWelcomeScreen()1789 void MainWindow::showWelcomeScreen()
1790 {
1791    bool dontAsk              = false;
1792    bool answer               = false;
1793    const QString welcomeText = QStringLiteral(""
1794       "Welcome to the RetroArch Desktop Menu!<br>\n"
1795       "<br>\n"
1796       "Many settings and actions are currently only available in the familiar Big Picture menu, "
1797       "but this Desktop Menu should be functional for launching content and managing playlists.<br>\n"
1798       "<br>\n"
1799       "Some useful hotkeys for interacting with the Big Picture menu include:\n"
1800       "<ul><li>F1 - Bring up the Big Picture menu</li>\n"
1801       "<li>F - Switch between fullscreen and windowed modes</li>\n"
1802       "<li>F5 - Bring the Desktop Menu back if closed</li>\n"
1803       "<li>Esc - Exit RetroArch</li></ul>\n"
1804       "\n"
1805       "For more hotkeys and their assignments, see:<br>\n"
1806       "Settings -> Input -> Input Hotkey Binds<br>\n"
1807       "<br>\n"
1808       "Documentation for RetroArch, libretro and cores:<br>\n"
1809       "<a href=\"https://docs.libretro.com/\">https://docs.libretro.com/</a>");
1810 
1811    if (!m_settings->value("show_welcome_screen", true).toBool())
1812       return;
1813 
1814    answer = showMessageBox(welcomeText, MainWindow::MSGBOX_TYPE_QUESTION_OKCANCEL, Qt::ApplicationModal, true, &dontAsk);
1815 
1816    if (answer && dontAsk)
1817       m_settings->setValue("show_welcome_screen", false);
1818 }
1819 
customThemeString() const1820 const QString& MainWindow::customThemeString() const
1821 {
1822    return m_customThemeString;
1823 }
1824 
setCustomThemeFile(QString filePath)1825 bool MainWindow::setCustomThemeFile(QString filePath)
1826 {
1827    if (filePath.isEmpty())
1828    {
1829       QMessageBox::critical(this, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CUSTOM_THEME), msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_PATH_IS_BLANK));
1830       return false;
1831    }
1832 
1833    QFile file(filePath);
1834 
1835    if (file.exists())
1836    {
1837       bool opened = file.open(QIODevice::ReadOnly);
1838 
1839       if (!opened)
1840       {
1841          QMessageBox::critical(this, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CUSTOM_THEME), msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_READ_OPEN_FAILED));
1842          return false;
1843       }
1844 
1845       {
1846          QByteArray fileArray = file.readAll();
1847          QString fileStr      = QString::fromUtf8(fileArray);
1848 
1849          file.close();
1850 
1851          if (fileStr.isEmpty())
1852          {
1853             QMessageBox::critical(this, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CUSTOM_THEME), msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_IS_EMPTY));
1854             return false;
1855          }
1856 
1857          setCustomThemeString(fileStr);
1858       }
1859    }
1860    else
1861    {
1862       QMessageBox::critical(this, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CUSTOM_THEME), msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST));
1863       return false;
1864    }
1865 
1866    return true;
1867 }
1868 
setCustomThemeString(QString qss)1869 void MainWindow::setCustomThemeString(QString qss)
1870 {
1871    m_customThemeString = qss;
1872 }
1873 
showMessageBox(QString msg,MessageBoxType msgType,Qt::WindowModality modality,bool showDontAsk,bool * dontAsk)1874 bool MainWindow::showMessageBox(QString msg, MessageBoxType msgType, Qt::WindowModality modality, bool showDontAsk, bool *dontAsk)
1875 {
1876    QCheckBox *checkBox             = NULL;
1877 
1878    QPointer<QMessageBox> msgBoxPtr = new QMessageBox(this);
1879    QMessageBox             *msgBox = msgBoxPtr.data();
1880 
1881    msgBox->setWindowModality(modality);
1882    msgBox->setTextFormat(Qt::RichText);
1883    msgBox->setTextInteractionFlags(Qt::TextBrowserInteraction);
1884 
1885    if (showDontAsk)
1886    {
1887       checkBox = new QCheckBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_DONT_SHOW_AGAIN), msgBox);
1888       /* QMessageBox::setCheckBox() is available since 5.2 */
1889       msgBox->setCheckBox(checkBox);
1890    }
1891 
1892    switch (msgType)
1893    {
1894       case MSGBOX_TYPE_INFO:
1895          msgBox->setIcon(QMessageBox::Information);
1896          msgBox->setWindowTitle(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_INFORMATION));
1897          break;
1898       case MSGBOX_TYPE_WARNING:
1899          msgBox->setIcon(QMessageBox::Warning);
1900          msgBox->setWindowTitle(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_WARNING));
1901          break;
1902       case MSGBOX_TYPE_ERROR:
1903          msgBox->setIcon(QMessageBox::Critical);
1904          msgBox->setWindowTitle(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ERROR));
1905          break;
1906       case MSGBOX_TYPE_QUESTION_YESNO:
1907          msgBox->setIcon(QMessageBox::Question);
1908          msgBox->setWindowTitle(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_QUESTION));
1909          msgBox->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
1910          break;
1911       case MSGBOX_TYPE_QUESTION_OKCANCEL:
1912          msgBox->setIcon(QMessageBox::Question);
1913          msgBox->setWindowTitle(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_QUESTION));
1914          msgBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
1915          break;
1916       default:
1917          break;
1918    }
1919 
1920    msgBox->setText(msg);
1921    msgBox->exec();
1922 
1923    if (!msgBoxPtr)
1924       return true;
1925 
1926    if (
1927             msgBox->result() != QMessageBox::Ok
1928          && msgBox->result() != QMessageBox::Yes)
1929       return false;
1930 
1931    if (checkBox)
1932       if (dontAsk)
1933          *dontAsk = checkBox->isChecked();
1934 
1935    return true;
1936 }
1937 
onFileBrowserTreeContextMenuRequested(const QPoint &)1938 void MainWindow::onFileBrowserTreeContextMenuRequested(const QPoint&)
1939 {
1940 #ifdef HAVE_LIBRETRODB
1941    QDir dir;
1942    QByteArray dirArray;
1943    QPointer<QAction> action;
1944    QList<QAction*> actions;
1945    QScopedPointer<QAction> scanAction;
1946    QString currentDirString      = QDir::toNativeSeparators(
1947          m_dirModel->filePath(m_dirTree->currentIndex()));
1948    settings_t *settings          = config_get_ptr();
1949    const char *fullpath          = NULL;
1950    const char *path_dir_playlist = settings->paths.directory_playlist;
1951    const char *path_content_db   = settings->paths.path_content_database;
1952 
1953    if (currentDirString.isEmpty())
1954       return;
1955 
1956    dir                           = currentDirString;
1957 
1958    if (!dir.exists())
1959       return;
1960 
1961    /* default NULL parameter for parent wasn't added until 5.7 */
1962    scanAction.reset(new QAction(
1963             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SCAN_DIRECTORY), 0));
1964 
1965    actions.append(scanAction.data());
1966 
1967    action                        = QMenu::exec(actions, QCursor::pos(), NULL, m_dirTree);
1968 
1969    if (!action)
1970       return;
1971 
1972    dirArray                      = currentDirString.toUtf8();
1973    fullpath                      = dirArray.constData();
1974 
1975    task_push_dbscan(
1976          path_dir_playlist,
1977          path_content_db,
1978          fullpath, true,
1979          m_settings->value("show_hidden_files", true).toBool(),
1980          scan_finished_handler);
1981 #endif
1982 }
1983 
showStatusMessage(QString msg,unsigned priority,unsigned duration,bool flush)1984 void MainWindow::showStatusMessage(QString msg,
1985       unsigned priority, unsigned duration, bool flush)
1986 {
1987    emit gotStatusMessage(msg, priority, duration, flush);
1988 }
1989 
onGotStatusMessage(QString msg,unsigned priority,unsigned duration,bool flush)1990 void MainWindow::onGotStatusMessage(
1991       QString msg, unsigned priority, unsigned duration, bool flush)
1992 {
1993    int msecDuration   = 0;
1994    QScreen *screen    = qApp->primaryScreen();
1995    QStatusBar *status = statusBar();
1996 
1997    Q_UNUSED(priority)
1998 
1999    if (msg.isEmpty())
2000       return;
2001 
2002    if (!status)
2003       return;
2004 
2005    if (screen)
2006       msecDuration    = (duration / screen->refreshRate()) * 1000;
2007 
2008    if (msecDuration <= 0)
2009       msecDuration    = 1000;
2010 
2011    if (status->currentMessage().isEmpty() || flush)
2012    {
2013       if (m_statusMessageElapsedTimer.elapsed() >= STATUS_MSG_THROTTLE_MSEC)
2014       {
2015          qint64 msgDuration = qMax(msecDuration, STATUS_MSG_THROTTLE_MSEC);
2016          m_statusMessageElapsedTimer.restart();
2017          status->showMessage(msg, msgDuration);
2018       }
2019    }
2020 }
2021 
deferReloadShaderParams()2022 void MainWindow::deferReloadShaderParams()
2023 {
2024 #if defined(HAVE_MENU)
2025 #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
2026    emit gotReloadShaderParams();
2027 #endif
2028 #endif
2029 }
2030 
onShaderParamsClicked()2031 void MainWindow::onShaderParamsClicked()
2032 {
2033 #if defined(HAVE_MENU)
2034 #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
2035    if (!m_shaderParamsDialog)
2036       return;
2037 
2038    m_shaderParamsDialog->show();
2039 
2040    onGotReloadShaderParams();
2041 #endif
2042 #endif
2043 }
2044 
onGotReloadShaderParams()2045 void MainWindow::onGotReloadShaderParams()
2046 {
2047 #if defined(HAVE_MENU)
2048 #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
2049    if (m_shaderParamsDialog && m_shaderParamsDialog->isVisible())
2050       m_shaderParamsDialog->reload();
2051 #endif
2052 #endif
2053 }
2054 
onCoreOptionsClicked()2055 void MainWindow::onCoreOptionsClicked()
2056 {
2057    if (!m_coreOptionsDialog)
2058       return;
2059 
2060    m_coreOptionsDialog->show();
2061 
2062    onGotReloadCoreOptions();
2063 }
2064 
onGotReloadCoreOptions()2065 void MainWindow::onGotReloadCoreOptions()
2066 {
2067    if (m_coreOptionsDialog && m_coreOptionsDialog->isVisible())
2068       m_coreOptionsDialog->reload();
2069 }
2070 
appendLogMessage(const QString & msg)2071 void MainWindow::appendLogMessage(const QString &msg)
2072 {
2073    emit gotLogMessage(msg);
2074 }
2075 
onGotLogMessage(const QString & msg)2076 void MainWindow::onGotLogMessage(const QString &msg)
2077 {
2078    QString newMsg = msg;
2079 
2080    if (newMsg.at(newMsg.size() - 1) == '\n')
2081       newMsg.chop(1);
2082 
2083    m_logTextEdit->appendMessage(newMsg);
2084 }
2085 
onLaunchWithComboBoxIndexChanged(int)2086 void MainWindow::onLaunchWithComboBoxIndexChanged(int)
2087 {
2088    QString coreInfoText;
2089    QVector<QHash<QString, QString> >
2090       infoList                  = getCoreInfo();
2091    QVariantMap          coreMap = m_launchWithComboBox->currentData(Qt::UserRole).value<QVariantMap>();
2092    core_selection coreSelection = static_cast<core_selection>(coreMap.value("core_selection").toInt());
2093    int i = 0;
2094 
2095    if (infoList.count() == 0)
2096       return;
2097 
2098    for (i = 0; i < infoList.count(); i++)
2099    {
2100       const QHash<QString, QString> &hash = infoList.at(i);
2101       const QString                  &key =
2102          hash.value("html_key", hash.value("key"));
2103       const QString                &value =
2104          hash.value("html_value", hash.value("value"));
2105 
2106       if (!key.isEmpty())
2107          coreInfoText                    += key;
2108 
2109       if (!value.isEmpty())
2110       {
2111          if (!key.isEmpty())
2112             coreInfoText                 += " ";
2113 
2114          coreInfoText                    += value;
2115       }
2116 
2117       if (i < infoList.count() - 1)
2118          coreInfoText                    += "<br>\n";
2119    }
2120 
2121    m_coreInfoLabel->setText(coreInfoText);
2122 
2123    if (coreSelection == CORE_SELECTION_LOAD_CORE)
2124       onLoadCoreClicked();
2125    else
2126       m_loadCoreWindow->setProperty("last_launch_with_index",
2127             m_launchWithComboBox->currentIndex());
2128 }
2129 
getThemeFromString(QString themeString)2130 MainWindow::Theme MainWindow::getThemeFromString(QString themeString)
2131 {
2132    if (themeString == "default")
2133       return THEME_SYSTEM_DEFAULT;
2134    else if (themeString == "dark")
2135       return THEME_DARK;
2136    else if (themeString == "custom")
2137       return THEME_CUSTOM;
2138 
2139    return THEME_SYSTEM_DEFAULT;
2140 }
2141 
getThemeString(Theme theme)2142 QString MainWindow::getThemeString(Theme theme)
2143 {
2144    switch (theme)
2145    {
2146       case THEME_SYSTEM_DEFAULT:
2147          return "default";
2148       case THEME_DARK:
2149          return "dark";
2150       case THEME_CUSTOM:
2151          return "custom";
2152       default:
2153          break;
2154    }
2155 
2156    return "default";
2157 }
2158 
theme()2159 MainWindow::Theme MainWindow::theme()
2160 {
2161    return m_currentTheme;
2162 }
2163 
setTheme(Theme theme)2164 void MainWindow::setTheme(Theme theme)
2165 {
2166    m_currentTheme = theme;
2167 
2168    setDefaultCustomProperties();
2169 
2170    switch(theme)
2171    {
2172       case THEME_SYSTEM_DEFAULT:
2173          qApp->setStyleSheet(qt_theme_default_stylesheet.arg(m_settings->value("highlight_color", "palette(highlight)").toString()));
2174          break;
2175       case THEME_DARK:
2176          qApp->setStyleSheet(qt_theme_dark_stylesheet.arg(m_settings->value("highlight_color", "palette(highlight)").toString()));
2177          break;
2178       case THEME_CUSTOM:
2179          qApp->setStyleSheet(m_customThemeString);
2180          break;
2181       default:
2182          break;
2183    }
2184 #ifdef HAVE_MENU
2185    m_viewOptionsDialog->repaintIcons();
2186 #endif
2187 }
2188 
setDefaultCustomProperties()2189 void MainWindow::setDefaultCustomProperties()
2190 {
2191    m_gridView->setLayout(QString(DEFAULT_GRID_LAYOUT));
2192    m_gridView->setSpacing(DEFAULT_GRID_SPACING);
2193    m_gridItem.setThumbnailVerticalAlign(QString(DEFAULT_GRID_ITEM_THUMBNAIL_ALIGNMENT));
2194    m_gridItem.setPadding(DEFAULT_GRID_ITEM_MARGIN);
2195 }
2196 
changeThumbnailType(ThumbnailType type)2197 void MainWindow::changeThumbnailType(ThumbnailType type)
2198 {
2199    m_playlistModel->setThumbnailType(type);
2200    updateVisibleItems();
2201    m_gridView->viewport()->update();
2202 }
2203 
changeThumbnail(const QImage & image,QString type)2204 QString MainWindow::changeThumbnail(const QImage &image, QString type)
2205 {
2206    QHash<QString, QString> hash = getCurrentContentHash();
2207    QString dirString            = m_playlistModel->getPlaylistThumbnailsDir(hash["db_name"]) + "/" + type;
2208    QString thumbPath            = dirString + "/" + m_playlistModel->getSanitizedThumbnailName(hash["label_noext"]);
2209    QByteArray   dirArray        = QDir::toNativeSeparators(dirString).toUtf8();
2210    const char   *dirData        = dirArray.constData();
2211    QByteArray thumbArray        = QDir::toNativeSeparators(thumbPath).toUtf8();
2212    const char *thumbData        = thumbArray.constData();
2213    int quality                  = -1;
2214    QDir dir(dirString);
2215    QImage scaledImage(image);
2216 
2217    if (!dir.exists())
2218    {
2219       if (dir.mkpath("."))
2220          RARCH_LOG("[Qt]: Created directory: %s\n", dirData);
2221       else
2222       {
2223          RARCH_ERR("[Qt]: Could not create directory: %s\n", dirData);
2224          return QString();
2225       }
2226    }
2227 
2228    if (m_settings->contains("thumbnail_max_size"))
2229    {
2230       int size = m_settings->value("thumbnail_max_size", 0).toInt();
2231 
2232       if (size != 0 && (image.height() > size ||  image.width() > size))
2233          scaledImage = image.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
2234    }
2235 
2236    if (m_settings->contains("thumbnail_quality"))
2237       quality = m_settings->value("thumbnail_quality", -1).toInt();
2238 
2239    if (scaledImage.save(thumbPath, "png", quality))
2240    {
2241       RARCH_LOG("[Qt]: Saved image: %s\n", thumbData);
2242       m_playlistModel->reloadThumbnailPath(thumbPath);
2243       updateVisibleItems();
2244 
2245       return thumbPath;
2246    }
2247 
2248    RARCH_ERR("[Qt]: Could not save image: %s\n", thumbData);
2249    return QString();
2250 }
2251 
onThumbnailDropped(const QImage & image,ThumbnailType thumbnailType)2252 void MainWindow::onThumbnailDropped(const QImage &image,
2253       ThumbnailType thumbnailType)
2254 {
2255    switch (thumbnailType)
2256    {
2257       case THUMBNAIL_TYPE_BOXART:
2258       {
2259          QString path = changeThumbnail(image, THUMBNAIL_BOXART);
2260 
2261          if (path.isNull())
2262             return;
2263 
2264          if (m_thumbnailPixmap)
2265             delete m_thumbnailPixmap;
2266 
2267          m_thumbnailPixmap = new QPixmap(path);
2268 
2269          onResizeThumbnailOne(*m_thumbnailPixmap, true);
2270          break;
2271       }
2272 
2273       case THUMBNAIL_TYPE_TITLE_SCREEN:
2274       {
2275          QString path = changeThumbnail(image, THUMBNAIL_TITLE);
2276 
2277          if (path.isNull())
2278             return;
2279 
2280          if (m_thumbnailPixmap2)
2281             delete m_thumbnailPixmap2;
2282 
2283          m_thumbnailPixmap2 = new QPixmap(path);
2284 
2285          onResizeThumbnailTwo(*m_thumbnailPixmap2, true);
2286          break;
2287       }
2288 
2289       case THUMBNAIL_TYPE_SCREENSHOT:
2290       {
2291          QString path = changeThumbnail(image, THUMBNAIL_SCREENSHOT);
2292 
2293          if (path.isNull())
2294             return;
2295 
2296          if (m_thumbnailPixmap3)
2297             delete m_thumbnailPixmap3;
2298 
2299          m_thumbnailPixmap3 = new QPixmap(path);
2300 
2301          onResizeThumbnailThree(*m_thumbnailPixmap3, true);
2302          break;
2303       }
2304    }
2305 }
2306 
getCoreInfo()2307 QVector<QHash<QString, QString> > MainWindow::getCoreInfo()
2308 {
2309    unsigned i;
2310    QVector<QHash<QString, QString> > infoList;
2311    QHash<QString, QString> currentCore = getSelectedCore();
2312    core_info_t *core_info              = NULL;
2313    QByteArray currentCorePathArray     = currentCore["core_path"].toUtf8();
2314    const char *current_core_path_data  = currentCorePathArray.constData();
2315 
2316    /* Search for current core */
2317    core_info_find(current_core_path_data, &core_info);
2318 
2319    if (     currentCore["core_path"].isEmpty()
2320          || !core_info
2321          || !core_info->has_info)
2322    {
2323       QHash<QString, QString> hash;
2324 
2325       hash["key"]   = msg_hash_to_str(
2326             MENU_ENUM_LABEL_VALUE_NO_CORE_INFORMATION_AVAILABLE);
2327       hash["value"] = "";
2328 
2329       infoList.append(hash);
2330 
2331       return infoList;
2332    }
2333 
2334    if (core_info->core_name)
2335    {
2336       QHash<QString, QString> hash;
2337 
2338       hash["key"]   = QString(
2339             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_CORE_NAME)) + ":";
2340       hash["value"] = core_info->core_name;
2341 
2342       infoList.append(hash);
2343    }
2344 
2345    if (core_info->display_name)
2346    {
2347       QHash<QString, QString> hash;
2348 
2349       hash["key"]   = QString(
2350             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_CORE_LABEL)) + ":";
2351       hash["value"] = core_info->display_name;
2352 
2353       infoList.append(hash);
2354    }
2355 
2356    if (core_info->systemname)
2357    {
2358       QHash<QString, QString> hash;
2359 
2360       hash["key"]   = QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_SYSTEM_NAME)) + ":";
2361       hash["value"] = core_info->systemname;
2362 
2363       infoList.append(hash);
2364    }
2365 
2366    if (core_info->system_manufacturer)
2367    {
2368       QHash<QString, QString> hash;
2369 
2370       hash["key"]   = QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_SYSTEM_MANUFACTURER)) + ":";
2371       hash["value"] = core_info->system_manufacturer;
2372 
2373       infoList.append(hash);
2374    }
2375 
2376    if (core_info->categories_list)
2377    {
2378       QHash<QString, QString> hash;
2379       QString categories;
2380 
2381       for (i = 0; i < core_info->categories_list->size; i++)
2382       {
2383          categories += core_info->categories_list->elems[i].data;
2384 
2385          if (i < core_info->categories_list->size - 1)
2386             categories += ", ";
2387       }
2388 
2389       hash["key"]   = QString(
2390             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_CATEGORIES)) + ":";
2391       hash["value"] = categories;
2392 
2393       infoList.append(hash);
2394    }
2395 
2396    if (core_info->authors_list)
2397    {
2398       QHash<QString, QString> hash;
2399       QString authors;
2400 
2401       for (i = 0; i < core_info->authors_list->size; i++)
2402       {
2403          authors += core_info->authors_list->elems[i].data;
2404 
2405          if (i < core_info->authors_list->size - 1)
2406             authors += ", ";
2407       }
2408 
2409       hash["key"]   = QString(
2410             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_AUTHORS)) + ":";
2411       hash["value"] = authors;
2412 
2413       infoList.append(hash);
2414    }
2415 
2416    if (core_info->permissions_list)
2417    {
2418       QHash<QString, QString> hash;
2419       QString permissions;
2420 
2421       for (i = 0; i < core_info->permissions_list->size; i++)
2422       {
2423          permissions += core_info->permissions_list->elems[i].data;
2424 
2425          if (i < core_info->permissions_list->size - 1)
2426             permissions += ", ";
2427       }
2428 
2429       hash["key"]   = QString(
2430             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_PERMISSIONS)) + ":";
2431       hash["value"] = permissions;
2432 
2433       infoList.append(hash);
2434    }
2435 
2436    if (core_info->licenses_list)
2437    {
2438       QHash<QString, QString> hash;
2439       QString licenses;
2440 
2441       for (i = 0; i < core_info->licenses_list->size; i++)
2442       {
2443          licenses += core_info->licenses_list->elems[i].data;
2444 
2445          if (i < core_info->licenses_list->size - 1)
2446             licenses += ", ";
2447       }
2448 
2449       hash["key"]   = QString(
2450             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_LICENSES)) + ":";
2451       hash["value"] = licenses;
2452 
2453       infoList.append(hash);
2454    }
2455 
2456    if (core_info->supported_extensions_list)
2457    {
2458       QHash<QString, QString> hash;
2459       QString supported_extensions;
2460 
2461       for (i = 0; i < core_info->supported_extensions_list->size; i++)
2462       {
2463          supported_extensions += core_info->supported_extensions_list->elems[i].data;
2464 
2465          if (i < core_info->supported_extensions_list->size - 1)
2466             supported_extensions += ", ";
2467       }
2468 
2469       hash["key"]   = QString(
2470             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_SUPPORTED_EXTENSIONS)) + ":";
2471       hash["value"] = supported_extensions;
2472 
2473       infoList.append(hash);
2474    }
2475 
2476    if (core_info->firmware_count > 0)
2477    {
2478       core_info_ctx_firmware_t firmware_info;
2479       bool update_missing_firmware   = false;
2480       bool set_missing_firmware      = false;
2481       settings_t *settings           = config_get_ptr();
2482 
2483       firmware_info.path             = core_info->path;
2484       firmware_info.directory.system = settings->paths.directory_system;
2485 
2486       rarch_ctl(RARCH_CTL_UNSET_MISSING_BIOS, NULL);
2487 
2488       update_missing_firmware        = core_info_list_update_missing_firmware(&firmware_info, &set_missing_firmware);
2489 
2490       if (set_missing_firmware)
2491          rarch_ctl(RARCH_CTL_SET_MISSING_BIOS, NULL);
2492 
2493       if (update_missing_firmware)
2494       {
2495          QHash<QString, QString> hash;
2496 
2497          hash["key"]   = QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_FIRMWARE)) + ":";
2498          hash["value"] = "";
2499 
2500          infoList.append(hash);
2501 
2502          /* FIXME: This looks hacky and probably
2503           * needs to be improved for good translation support. */
2504 
2505          for (i = 0; i < core_info->firmware_count; i++)
2506          {
2507             if (core_info->firmware[i].desc)
2508             {
2509                QString valueText;
2510                QHash<QString, QString> hash;
2511                QString labelText = "(!) ";
2512                bool missing      = false;
2513 
2514                if (core_info->firmware[i].missing)
2515                {
2516                   missing        = true;
2517                   labelText     += msg_hash_to_str(
2518                         MENU_ENUM_LABEL_VALUE_MISSING);
2519                }
2520                else
2521                   labelText     += msg_hash_to_str(
2522                         MENU_ENUM_LABEL_VALUE_PRESENT);
2523 
2524                labelText        += ", ";
2525 
2526                if (core_info->firmware[i].optional)
2527                   labelText     += msg_hash_to_str(
2528                         MENU_ENUM_LABEL_VALUE_OPTIONAL);
2529                else
2530                   labelText     += msg_hash_to_str(
2531                         MENU_ENUM_LABEL_VALUE_REQUIRED);
2532 
2533                labelText        += ":";
2534 
2535                if (core_info->firmware[i].desc)
2536                   valueText      = core_info->firmware[i].desc;
2537                else
2538                   valueText      = msg_hash_to_str(
2539                         MENU_ENUM_LABEL_VALUE_RDB_ENTRY_NAME);
2540 
2541                hash["key"]       = labelText;
2542                hash["value"]     = valueText;
2543 
2544                if (missing)
2545                {
2546                   QString style       = "font-weight: bold; color: #ff0000";
2547                   hash["label_style"] = style;
2548                   hash["value_style"] = style;
2549                   hash["html_key"]    = "<b><font color=\"#ff0000\">" + hash["key"] + "</font></b>";
2550                   hash["html_value"]  = "<b><font color=\"#ff0000\">" + hash["value"] + "</font></b>";
2551                }
2552                else
2553                {
2554                   QString style       = "font-weight: bold; color: rgb(0, 175, 0)";
2555                   hash["label_style"] = style;
2556                   hash["value_style"] = style;
2557                   hash["html_key"]    = "<b><font color=\"#00af00\">" + hash["key"] + "</font></b>";
2558                   hash["html_value"]  = "<b><font color=\"#00af00\">" + hash["value"] + "</font></b>";
2559                }
2560 
2561                infoList.append(hash);
2562             }
2563          }
2564       }
2565    }
2566 
2567    if (core_info->notes)
2568    {
2569       for (i = 0; i < core_info->note_list->size; i++)
2570       {
2571          QHash<QString, QString> hash;
2572 
2573          hash["key"]   = "";
2574          hash["value"] = core_info->note_list->elems[i].data;
2575 
2576          infoList.append(hash);
2577       }
2578    }
2579 
2580    return infoList;
2581 }
2582 
onSearchResetClicked()2583 void MainWindow::onSearchResetClicked()
2584 {
2585    m_searchLineEdit->clear();
2586    onSearchEnterPressed();
2587 }
2588 
coreInfoPushButton()2589 QToolButton* MainWindow::coreInfoPushButton()
2590 {
2591    return m_coreInfoPushButton;
2592 }
2593 
onTreeViewItemsSelected(QModelIndexList selectedIndexes)2594 void MainWindow::onTreeViewItemsSelected(QModelIndexList selectedIndexes)
2595 {
2596    QString dir;
2597 
2598    if (selectedIndexes.isEmpty())
2599       return;
2600 
2601    dir = m_dirModel->filePath(selectedIndexes.first());
2602 
2603    selectBrowserDir(dir);
2604 }
2605 
onFileDoubleClicked(const QModelIndex & proxyIndex)2606 void MainWindow::onFileDoubleClicked(const QModelIndex &proxyIndex)
2607 {
2608    const QModelIndex index = m_proxyFileModel->mapToSource(proxyIndex);
2609 
2610    if (m_fileModel->isDir(index))
2611       m_dirTree->setCurrentIndex(m_dirModel->index(m_fileModel->filePath(index)));
2612    else
2613       loadContent(getFileContentHash(index));
2614 }
2615 
selectBrowserDir(QString path)2616 void MainWindow::selectBrowserDir(QString path)
2617 {
2618    if (!path.isEmpty())
2619    {
2620       QModelIndex sourceIndex = m_fileModel->setRootPath(path);
2621       QModelIndex proxyIndex  = m_proxyFileModel->mapFromSource(sourceIndex);
2622       m_fileTableHeaderState  = m_fileTableView->horizontalHeader()->saveState();
2623 
2624       if (proxyIndex.isValid())
2625          m_fileTableView->setRootIndex(proxyIndex);
2626       else
2627       {
2628          /* the directory is filtered out. Remove the filter for a moment.
2629           * FIXME: Find a way to not have to do this
2630           * (not filtering dirs is one). */
2631          m_proxyFileModel->setFilterRegExp(QRegExp());
2632          m_fileTableView->setRootIndex(m_proxyFileModel->mapFromSource(sourceIndex));
2633          m_proxyFileModel->setFilterRegExp(m_searchRegExp);
2634       }
2635    }
2636    setCoreActions();
2637 }
2638 
browserAndPlaylistTabWidget()2639 QTabWidget* MainWindow::browserAndPlaylistTabWidget()
2640 {
2641    return m_browserAndPlaylistTabWidget;
2642 }
2643 
onDropWidgetEnterPressed()2644 void MainWindow::onDropWidgetEnterPressed()
2645 {
2646 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
2647    /* entry is being renamed, ignore this enter press */
2648    if (m_tableView->isPersistentEditorOpen(m_tableView->currentIndex()))
2649 #else
2650    /* we can only check if any editor at all is open */
2651    if (m_tableView->isEditorOpen())
2652 #endif
2653       return;
2654    onRunClicked();
2655 }
2656 
getCurrentContentIndex()2657 QModelIndex MainWindow::getCurrentContentIndex()
2658 {
2659    if (m_viewType == VIEW_TYPE_LIST)
2660       return m_tableView->currentIndex();
2661    else if (m_viewType == VIEW_TYPE_ICONS)
2662       return m_gridView->currentIndex();
2663    return QModelIndex();
2664 }
2665 
getCurrentContentHash()2666 QHash<QString, QString> MainWindow::getCurrentContentHash()
2667 {
2668    return getCurrentContentIndex().data(PlaylistModel::HASH).value<QHash<QString, QString> >();
2669 }
2670 
getFileContentHash(const QModelIndex & index)2671 QHash<QString, QString> MainWindow::getFileContentHash(const QModelIndex &index)
2672 {
2673    QHash<QString, QString> hash;
2674    QFileInfo fileInfo  = m_fileModel->fileInfo(index);
2675 
2676    hash["path"]        = QDir::toNativeSeparators(m_fileModel->filePath(index));
2677    hash["label"]       = hash["path"];
2678    hash["label_noext"] = fileInfo.completeBaseName();
2679    hash["db_name"]     = fileInfo.dir().dirName();
2680 
2681    return hash;
2682 }
2683 
onContentItemDoubleClicked(const QModelIndex & index)2684 void MainWindow::onContentItemDoubleClicked(const QModelIndex &index)
2685 {
2686    Q_UNUSED(index);
2687    onRunClicked();
2688 }
2689 
onStartCoreClicked()2690 void MainWindow::onStartCoreClicked()
2691 {
2692    content_ctx_info_t content_info;
2693 
2694    content_info.argc                   = 0;
2695    content_info.argv                   = NULL;
2696    content_info.args                   = NULL;
2697    content_info.environ_get            = NULL;
2698 
2699    path_clear(RARCH_PATH_BASENAME);
2700 
2701    if (!task_push_start_current_core(&content_info))
2702       QMessageBox::critical(this, msg_hash_to_str(MSG_ERROR), msg_hash_to_str(MSG_FAILED_TO_LOAD_CONTENT));
2703 }
2704 
getSelectedCore()2705 QHash<QString, QString> MainWindow::getSelectedCore()
2706 {
2707    QVariantMap coreMap = m_launchWithComboBox->currentData(Qt::UserRole).value<QVariantMap>();
2708    core_selection coreSelection = static_cast<core_selection>(coreMap.value("core_selection").toInt());
2709    QHash<QString, QString> coreHash;
2710    QHash<QString, QString> contentHash;
2711    ViewType viewType = getCurrentViewType();
2712 
2713    if (viewType == VIEW_TYPE_LIST)
2714       contentHash = m_tableView->currentIndex().data(PlaylistModel::HASH).value<QHash<QString, QString> >();
2715    else if (viewType == VIEW_TYPE_ICONS)
2716       contentHash = m_gridView->currentIndex().data(PlaylistModel::HASH).value<QHash<QString, QString> >();
2717    else
2718       return coreHash;
2719 
2720    switch(coreSelection)
2721    {
2722       case CORE_SELECTION_CURRENT:
2723          coreHash["core_path"] = path_get(RARCH_PATH_CORE);
2724          break;
2725       case CORE_SELECTION_PLAYLIST_SAVED:
2726          if (contentHash.isEmpty() || contentHash["core_path"].isEmpty())
2727             break;
2728 
2729          coreHash["core_path"] = contentHash["core_path"];
2730 
2731          break;
2732       case CORE_SELECTION_PLAYLIST_DEFAULT:
2733       {
2734          QString plName;
2735          QString defaultCorePath;
2736 
2737          if (contentHash.isEmpty())
2738             break;
2739 
2740          plName = contentHash["pl_name"].isEmpty() ?
2741                contentHash["db_name"] : contentHash["pl_name"];
2742 
2743          if (plName.isEmpty())
2744             break;
2745 
2746          defaultCorePath = getPlaylistDefaultCore(plName);
2747 
2748          if (!defaultCorePath.isEmpty())
2749             coreHash["core_path"] = defaultCorePath;
2750 
2751          break;
2752       }
2753       default:
2754          break;
2755    }
2756 
2757    return coreHash;
2758 }
2759 
2760 /* the hash typically has the following keys:
2761 path - absolute path to the content file
2762 core_path - absolute path to the core, or "DETECT" to ask the user
2763 db_name - the display name of the rdb database this content is from
2764 label - the display name of the content, usually comes from the database
2765 crc32 - an upper-case, 8 byte string representation of the hex CRC32 checksum (e.g. ABCDEF12) followed by "|crc"
2766 core_name - the display name of the core, or "DETECT" if unknown
2767 label_noext - the display name of the content that is guaranteed not to contain a file extension
2768 */
loadContent(const QHash<QString,QString> & contentHash)2769 void MainWindow::loadContent(const QHash<QString, QString> &contentHash)
2770 {
2771    content_ctx_info_t content_info;
2772    QByteArray corePathArray;
2773    QByteArray contentPathArray;
2774    QByteArray contentLabelArray;
2775    QByteArray contentDbNameArray;
2776    QByteArray contentCrc32Array;
2777    char contentDbNameFull[PATH_MAX_LENGTH];
2778    const char *corePath        = NULL;
2779    const char *contentPath     = NULL;
2780    const char *contentLabel    = NULL;
2781    const char *contentDbName   = NULL;
2782    const char *contentCrc32    = NULL;
2783    QVariantMap coreMap         = m_launchWithComboBox->currentData(Qt::UserRole).value<QVariantMap>();
2784    core_selection coreSelection = static_cast<core_selection>(coreMap.value("core_selection").toInt());
2785    core_info_t *coreInfo       = NULL;
2786 
2787    contentDbNameFull[0] = '\0';
2788 
2789    if (m_pendingRun)
2790       coreSelection = CORE_SELECTION_CURRENT;
2791 
2792    if (coreSelection == CORE_SELECTION_ASK)
2793    {
2794       QStringList extensionFilters;
2795 
2796       if (contentHash.contains("path"))
2797       {
2798          QString extensionStr;
2799          int lastIndex        = contentHash["path"].lastIndexOf('.');
2800          QByteArray pathArray = contentHash["path"].toUtf8();
2801          const char *pathData = pathArray.constData();
2802 
2803          if (lastIndex >= 0)
2804          {
2805             extensionStr = contentHash["path"].mid(lastIndex + 1);
2806 
2807             if (!extensionStr.isEmpty())
2808                extensionFilters.append(extensionStr.toLower());
2809          }
2810 
2811          if (path_is_compressed_file(pathData))
2812          {
2813             unsigned i = 0;
2814             struct string_list *list = file_archive_get_file_list(pathData, NULL);
2815 
2816             if (list)
2817             {
2818                if (list->size > 0)
2819                {
2820                   for (i = 0; i < list->size; i++)
2821                   {
2822                      const char *filePath = list->elems[i].data;
2823                      const char *extension = path_get_extension(filePath);
2824 
2825                      if (!extensionFilters.contains(extension, Qt::CaseInsensitive))
2826                         extensionFilters.append(extension);
2827                   }
2828                }
2829 
2830                string_list_free(list);
2831             }
2832          }
2833       }
2834 
2835       m_pendingRun = true;
2836       onLoadCoreClicked(extensionFilters);
2837 
2838       return;
2839    }
2840 
2841    switch (coreSelection)
2842    {
2843       case CORE_SELECTION_CURRENT:
2844          corePathArray     = path_get(RARCH_PATH_CORE);
2845          contentPathArray  = contentHash["path"].toUtf8();
2846          contentLabelArray = contentHash["label_noext"].toUtf8();
2847          break;
2848       case CORE_SELECTION_PLAYLIST_SAVED:
2849          corePathArray     = contentHash["core_path"].toUtf8();
2850          contentPathArray  = contentHash["path"].toUtf8();
2851          contentLabelArray = contentHash["label_noext"].toUtf8();
2852          break;
2853       case CORE_SELECTION_PLAYLIST_DEFAULT:
2854       {
2855          QString plName = contentHash["pl_name"].isEmpty() ?
2856                contentHash["db_name"] : contentHash["pl_name"];
2857 
2858          QString defaultCorePath = getPlaylistDefaultCore(plName);
2859 
2860          if (!defaultCorePath.isEmpty())
2861          {
2862             corePathArray = defaultCorePath.toUtf8();
2863             contentPathArray = contentHash["path"].toUtf8();
2864             contentLabelArray = contentHash["label_noext"].toUtf8();
2865          }
2866 
2867          break;
2868       }
2869       default:
2870          return;
2871    }
2872 
2873    contentDbNameArray                  = contentHash["db_name"].toUtf8();
2874    contentCrc32Array                   = contentHash["crc32"].toUtf8();
2875 
2876    corePath                            = corePathArray.constData();
2877    contentPath                         = contentPathArray.constData();
2878    contentLabel                        = contentLabelArray.constData();
2879    contentDbName                       = contentDbNameArray.constData();
2880    contentCrc32                        = contentCrc32Array.constData();
2881 
2882    /* Search for specified core - ensures path
2883     * is 'sanitised' */
2884    if (core_info_find(corePath, &coreInfo) &&
2885        !string_is_empty(coreInfo->path))
2886       corePath = coreInfo->path;
2887 
2888    /* Add lpl extension to db_name, if required */
2889    if (!string_is_empty(contentDbName))
2890    {
2891       const char *extension = NULL;
2892 
2893       strlcpy(contentDbNameFull, contentDbName, sizeof(contentDbNameFull));
2894       extension = path_get_extension(contentDbNameFull);
2895 
2896       if (      string_is_empty(extension)
2897             || !string_is_equal_noncase(
2898             extension, FILE_PATH_LPL_EXTENSION_NO_DOT))
2899          strlcat(
2900                contentDbNameFull, FILE_PATH_LPL_EXTENSION,
2901                      sizeof(contentDbNameFull));
2902    }
2903 
2904    content_info.argc                   = 0;
2905    content_info.argv                   = NULL;
2906    content_info.args                   = NULL;
2907    content_info.environ_get            = NULL;
2908 
2909 #ifdef HAVE_MENU
2910    menu_navigation_set_selection(0);
2911 #endif
2912 
2913    command_event(CMD_EVENT_UNLOAD_CORE, NULL);
2914 
2915    if (!task_push_load_content_with_new_core_from_companion_ui(
2916          corePath, contentPath, contentLabel, contentDbNameFull, contentCrc32,
2917          &content_info, NULL, NULL))
2918    {
2919       QMessageBox::critical(this, msg_hash_to_str(MSG_ERROR),
2920             msg_hash_to_str(MSG_FAILED_TO_LOAD_CONTENT));
2921       return;
2922    }
2923 
2924 #ifdef HAVE_MENU
2925    menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, NULL);
2926 #endif
2927 }
2928 
onRunClicked()2929 void MainWindow::onRunClicked()
2930 {
2931    QHash<QString, QString> contentHash;
2932 
2933    switch (m_currentBrowser)
2934    {
2935       case BROWSER_TYPE_FILES:
2936          contentHash = getFileContentHash(
2937                m_proxyFileModel->mapToSource(m_fileTableView->currentIndex()));
2938          break;
2939       case BROWSER_TYPE_PLAYLISTS:
2940          contentHash = getCurrentContentHash();
2941          break;
2942    }
2943 
2944    if (contentHash.isEmpty())
2945       return;
2946 
2947    loadContent(contentHash);
2948 }
2949 
isContentLessCore()2950 bool MainWindow::isContentLessCore()
2951 {
2952    rarch_system_info_t *system = runloop_get_system_info();
2953 
2954    return system->load_no_content;
2955 }
2956 
isCoreLoaded()2957 bool MainWindow::isCoreLoaded()
2958 {
2959    if (  m_currentCore.isEmpty() ||
2960          m_currentCore == msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_CORE))
2961       return false;
2962 
2963    return true;
2964 }
2965 
playlistEntryDialog()2966 PlaylistEntryDialog* MainWindow::playlistEntryDialog()
2967 {
2968    return m_playlistEntryDialog;
2969 }
2970 
viewOptionsDialog()2971 ViewOptionsDialog* MainWindow::viewOptionsDialog()
2972 {
2973    return m_viewOptionsDialog;
2974 }
2975 
setCoreActions()2976 void MainWindow::setCoreActions()
2977 {
2978    QListWidgetItem *currentPlaylistItem = m_listWidget->currentItem();
2979    ViewType                    viewType = getCurrentViewType();
2980    QHash<QString, QString>         hash = getCurrentContentHash();
2981    QString      currentPlaylistFileName = QString();
2982 
2983    m_launchWithComboBox->clear();
2984 
2985    if (isContentLessCore())
2986       m_startCorePushButton->show();
2987    else
2988       m_startCorePushButton->hide();
2989 
2990    if (     isCoreLoaded()
2991          && m_settings->value("suggest_loaded_core_first", false).toBool())
2992    {
2993       QVariantMap comboBoxMap;
2994       comboBoxMap["core_name"]      = m_currentCore;
2995       comboBoxMap["core_path"]      = path_get(RARCH_PATH_CORE);
2996       comboBoxMap["core_selection"] = CORE_SELECTION_CURRENT;
2997       m_launchWithComboBox->addItem(m_currentCore, QVariant::fromValue(comboBoxMap));
2998    }
2999 
3000    if (m_currentBrowser == BROWSER_TYPE_PLAYLISTS)
3001    {
3002       if (!hash.isEmpty())
3003       {
3004          QString coreName = hash["core_name"];
3005 
3006          if (coreName.isEmpty())
3007             coreName = "<n/a>";
3008          else
3009          {
3010             const char *detect_str = "DETECT";
3011 
3012             if (coreName != detect_str)
3013             {
3014                if (m_launchWithComboBox->findText(coreName) == -1)
3015                {
3016                   int i = 0;
3017                   bool found_existing = false;
3018 
3019                   for (i = 0; i < m_launchWithComboBox->count(); i++)
3020                   {
3021                      QVariantMap map = m_launchWithComboBox->itemData(i, Qt::UserRole).toMap();
3022 
3023                      if (map.value("core_path").toString() == hash["core_path"] || map.value("core_name").toString() == coreName)
3024                      {
3025                         found_existing = true;
3026                         break;
3027                      }
3028                   }
3029 
3030                   if (!found_existing)
3031                   {
3032                      QVariantMap comboBoxMap;
3033                      comboBoxMap["core_name"]      = coreName;
3034                      comboBoxMap["core_path"]      = hash["core_path"];
3035                      comboBoxMap["core_selection"] = CORE_SELECTION_PLAYLIST_SAVED;
3036                      m_launchWithComboBox->addItem(coreName, QVariant::fromValue(comboBoxMap));
3037                   }
3038                }
3039             }
3040          }
3041       }
3042    }
3043 
3044    switch(m_currentBrowser)
3045    {
3046       case BROWSER_TYPE_PLAYLISTS:
3047          currentPlaylistFileName = hash["pl_name"].isEmpty() ?
3048                hash["db_name"] : hash["pl_name"];
3049          break;
3050       case BROWSER_TYPE_FILES:
3051          currentPlaylistFileName = m_fileModel->rootDirectory().dirName();
3052          break;
3053    }
3054 
3055    if (!currentPlaylistFileName.isEmpty())
3056    {
3057       QString defaultCorePath = getPlaylistDefaultCore(currentPlaylistFileName);
3058 
3059       if (!defaultCorePath.isEmpty())
3060       {
3061          QString currentPlaylistItemDataString;
3062          bool allPlaylists                  = false;
3063          int row                            = 0;
3064          QByteArray defaultCorePathArray    = defaultCorePath.toUtf8();
3065          const char *default_core_path_data = defaultCorePathArray.constData();
3066 
3067          if (currentPlaylistItem)
3068          {
3069             currentPlaylistItemDataString   = currentPlaylistItem->data(Qt::UserRole).toString();
3070             allPlaylists                    = (
3071                   currentPlaylistItemDataString == ALL_PLAYLISTS_TOKEN);
3072          }
3073 
3074          for (row = 0; row < m_listWidget->count(); row++)
3075          {
3076             core_info_t *coreInfo = NULL;
3077 
3078             if (allPlaylists)
3079             {
3080                QFileInfo info;
3081                QListWidgetItem *listItem = m_listWidget->item(row);
3082                QString    listItemString = listItem->data(Qt::UserRole).toString();
3083 
3084                info.setFile(listItemString);
3085 
3086                if (listItemString == ALL_PLAYLISTS_TOKEN)
3087                   continue;
3088             }
3089 
3090             /* Search for default core */
3091             if (core_info_find(default_core_path_data, &coreInfo))
3092             {
3093                if (m_launchWithComboBox->findText(coreInfo->core_name) == -1)
3094                {
3095                   int i               = 0;
3096                   bool found_existing = false;
3097 
3098                   for (i = 0; i < m_launchWithComboBox->count(); i++)
3099                   {
3100                      QVariantMap map            =
3101                         m_launchWithComboBox->itemData(
3102                               i, Qt::UserRole).toMap();
3103                      QByteArray CorePathArray   =
3104                         map.value("core_path").toString().toUtf8();
3105                      const char *core_path_data = CorePathArray.constData();
3106 
3107                      if (string_starts_with(path_basename(core_path_data),
3108                               coreInfo->core_file_id.str) ||
3109                            map.value("core_name").toString() == coreInfo->core_name ||
3110                            map.value("core_name").toString() == coreInfo->display_name)
3111                      {
3112                         found_existing = true;
3113                         break;
3114                      }
3115                   }
3116 
3117                   if (!found_existing)
3118                   {
3119                      QVariantMap comboBoxMap;
3120                      comboBoxMap["core_name"] = coreInfo->core_name;
3121                      comboBoxMap["core_path"] = coreInfo->path;
3122                      comboBoxMap["core_selection"] = CORE_SELECTION_PLAYLIST_DEFAULT;
3123                      m_launchWithComboBox->addItem(coreInfo->core_name, QVariant::fromValue(comboBoxMap));
3124                   }
3125                }
3126             }
3127 
3128             if (!allPlaylists)
3129                break;
3130          }
3131       }
3132    }
3133 
3134    {
3135       QVariantMap comboBoxMap;
3136       comboBoxMap["core_selection"] = CORE_SELECTION_ASK;
3137       m_launchWithComboBox->addItem(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CORE_SELECTION_ASK), QVariant::fromValue(comboBoxMap));
3138       m_launchWithComboBox->insertSeparator(m_launchWithComboBox->count());
3139       comboBoxMap["core_selection"] = CORE_SELECTION_LOAD_CORE;
3140       m_launchWithComboBox->addItem(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOAD_CORE)) + "...", QVariant::fromValue(comboBoxMap));
3141    }
3142 }
3143 
onTabWidgetIndexChanged(int index)3144 void MainWindow::onTabWidgetIndexChanged(int index)
3145 {
3146    if (m_browserAndPlaylistTabWidget->tabText(index) == msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_TAB_FILE_BROWSER))
3147    {
3148       m_currentBrowser = BROWSER_TYPE_FILES;
3149 
3150       m_centralWidget->setCurrentWidget(m_fileTableView);
3151 
3152       onCurrentFileChanged(m_fileTableView->currentIndex());
3153    }
3154    else if (m_browserAndPlaylistTabWidget->tabText(index) == msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_TAB_PLAYLISTS))
3155    {
3156       m_currentBrowser = BROWSER_TYPE_PLAYLISTS;
3157 
3158       m_centralWidget->setCurrentWidget(m_playlistViewsAndFooter);
3159 
3160       onCurrentItemChanged(m_tableView->currentIndex());
3161    }
3162 
3163    applySearch();
3164 
3165    setCoreActions();
3166 }
3167 
runPushButton()3168 QToolButton* MainWindow::runPushButton()
3169 {
3170    return m_runPushButton;
3171 }
3172 
stopPushButton()3173 QToolButton* MainWindow::stopPushButton()
3174 {
3175    return m_stopPushButton;
3176 }
3177 
startCorePushButton()3178 QToolButton* MainWindow::startCorePushButton()
3179 {
3180    return m_startCorePushButton;
3181 }
3182 
launchWithComboBox()3183 QComboBox* MainWindow::launchWithComboBox()
3184 {
3185    return m_launchWithComboBox;
3186 }
3187 
onSearchLineEditEdited(const QString & text)3188 void MainWindow::onSearchLineEditEdited(const QString &text)
3189 {
3190    int i = 0;
3191    QVector<unsigned> textUnicode = text.toUcs4();
3192    QVector<unsigned> textHiraToKata;
3193    QVector<unsigned> textKataToHira;
3194    bool foundHira = false;
3195    bool foundKata = false;
3196 
3197    for (i = 0; i < textUnicode.size(); i++)
3198    {
3199       unsigned code = textUnicode.at(i);
3200 
3201       if (code >= HIRAGANA_START && code <= HIRAGANA_END)
3202       {
3203          foundHira       = true;
3204          textHiraToKata += code + HIRA_KATA_OFFSET;
3205       }
3206       else if (code >= KATAKANA_START && code <= KATAKANA_END)
3207       {
3208          foundKata       = true;
3209          textKataToHira += code - HIRA_KATA_OFFSET;
3210       }
3211       else
3212       {
3213          textHiraToKata += code;
3214          textKataToHira += code;
3215       }
3216    }
3217 
3218    if (!foundHira && !foundKata)
3219       m_searchRegExp = QRegExp(text, Qt::CaseInsensitive);
3220    else if (foundHira && !foundKata)
3221       m_searchRegExp = QRegExp(text + "|"
3222             + QString::fromUcs4(textHiraToKata.constData(),
3223                textHiraToKata.size()), Qt::CaseInsensitive);
3224    else if (!foundHira && foundKata)
3225       m_searchRegExp = QRegExp(text + "|"
3226             + QString::fromUcs4(textKataToHira.constData(),
3227                textKataToHira.size()), Qt::CaseInsensitive);
3228    else
3229       m_searchRegExp = QRegExp(text + "|"
3230             + QString::fromUcs4(textHiraToKata.constData(),
3231                textHiraToKata.size()) + "|" +
3232             QString::fromUcs4(textKataToHira.constData(),
3233                textKataToHira.size()), Qt::CaseInsensitive);
3234 
3235    applySearch();
3236 }
3237 
applySearch()3238 void MainWindow::applySearch()
3239 {
3240    switch (m_currentBrowser)
3241    {
3242       case BROWSER_TYPE_PLAYLISTS:
3243          if (m_proxyModel->filterRegExp() != m_searchRegExp)
3244          {
3245             m_proxyModel->setFilterRegExp(m_searchRegExp);
3246             updateItemsCount();
3247          }
3248          break;
3249       case BROWSER_TYPE_FILES:
3250          if (m_proxyFileModel->filterRegExp() != m_searchRegExp)
3251             m_proxyFileModel->setFilterRegExp(m_searchRegExp);
3252          break;
3253    }
3254 }
3255 
onViewClosedDocksAboutToShow()3256 void MainWindow::onViewClosedDocksAboutToShow()
3257 {
3258    QList<QDockWidget*> dockWidgets;
3259    int i               = 0;
3260    QMenu         *menu = qobject_cast<QMenu*>(sender());
3261    bool found          = false;
3262 
3263    if (!menu)
3264       return;
3265 
3266    dockWidgets         = findChildren<QDockWidget*>();
3267 
3268    menu->clear();
3269 
3270    if (dockWidgets.isEmpty())
3271    {
3272       menu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NONE));
3273       return;
3274    }
3275 
3276    for (i = 0; i < dockWidgets.count(); i++)
3277    {
3278       const QDockWidget *dock = dockWidgets.at(i);
3279 
3280       if (!dock->isVisible())
3281       {
3282          QAction *action = menu->addAction(
3283                dock->property("menu_text").toString(),
3284                this, SLOT(onShowHiddenDockWidgetAction()));
3285          action->setProperty("dock_name", dock->objectName());
3286          found = true;
3287       }
3288    }
3289 
3290    if (!found)
3291       menu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NONE));
3292 }
3293 
onShowHiddenDockWidgetAction()3294 void MainWindow::onShowHiddenDockWidgetAction()
3295 {
3296    QAction *action = qobject_cast<QAction*>(sender());
3297    QDockWidget *dock = NULL;
3298 
3299    if (!action)
3300       return;
3301 
3302    dock = findChild<QDockWidget*>(action->property("dock_name").toString());
3303 
3304    if (!dock)
3305       return;
3306 
3307    if (!dock->isVisible())
3308    {
3309       addDockWidget(static_cast<Qt::DockWidgetArea>(
3310                dock->property("default_area").toInt()), dock);
3311       dock->setVisible(true);
3312       dock->setFloating(false);
3313    }
3314 }
3315 
searchWidget()3316 QWidget* MainWindow::searchWidget()
3317 {
3318    return m_searchWidget;
3319 }
3320 
searchLineEdit()3321 QLineEdit* MainWindow::searchLineEdit()
3322 {
3323    return m_searchLineEdit;
3324 }
3325 
onSearchEnterPressed()3326 void MainWindow::onSearchEnterPressed()
3327 {
3328    onSearchLineEditEdited(m_searchLineEdit->text());
3329 }
3330 
onCurrentTableItemDataChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight,const QVector<int> & roles)3331 void MainWindow::onCurrentTableItemDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
3332 {
3333    QHash<QString, QString> hash;
3334 
3335    if (!roles.contains(Qt::EditRole))
3336       return;
3337 
3338    if (topLeft != bottomRight)
3339       return;
3340 
3341    hash = topLeft.data(PlaylistModel::HASH).value<QHash<QString, QString>>();
3342 
3343    updateCurrentPlaylistEntry(hash);
3344 
3345    onCurrentItemChanged(topLeft);
3346 }
3347 
onCurrentListItemDataChanged(QListWidgetItem * item)3348 void MainWindow::onCurrentListItemDataChanged(QListWidgetItem *item)
3349 {
3350    renamePlaylistItem(item, item->text());
3351 }
3352 
renamePlaylistItem(QListWidgetItem * item,QString newName)3353 void MainWindow::renamePlaylistItem(QListWidgetItem *item, QString newName)
3354 {
3355    QString oldPath;
3356    QString newPath;
3357    QString extension;
3358    QString oldName;
3359    QFile file;
3360    QFileInfo info;
3361    QFileInfo playlistInfo;
3362    QString playlistPath;
3363    bool specialPlaylist          = false;
3364    settings_t *settings          = config_get_ptr();
3365    const char *path_dir_playlist = settings->paths.directory_playlist;
3366    QDir playlistDir(path_dir_playlist);
3367 
3368    if (!item)
3369       return;
3370 
3371    playlistPath                  = item->data(Qt::UserRole).toString();
3372    playlistInfo                  = playlistPath;
3373    oldName                       = playlistInfo.completeBaseName();
3374 
3375    /* Don't just compare strings in case there are
3376     * case differences on Windows that should be ignored. */
3377    if (QDir(playlistInfo.absoluteDir()) != QDir(playlistDir))
3378    {
3379       /* special playlists like history etc. can't have an association */
3380       specialPlaylist = true;
3381    }
3382 
3383    if (specialPlaylist)
3384    {
3385       /* special playlists shouldn't be editable already,
3386        * but just in case, set the old name back and
3387        * early return if they rename it */
3388       item->setText(oldName);
3389       return;
3390    }
3391 
3392    /* block this signal because setData() would trigger
3393     * an infinite loop here */
3394    disconnect(m_listWidget, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(onCurrentListItemDataChanged(QListWidgetItem*)));
3395 
3396    oldPath   = item->data(Qt::UserRole).toString();
3397 
3398    file.setFileName(oldPath);
3399    info      = file;
3400 
3401    extension = info.suffix();
3402 
3403    newPath   = info.absolutePath();
3404 
3405    /* absolutePath() will always use / even on Windows */
3406    if (newPath.at(newPath.count() - 1) != '/')
3407       /* add trailing slash if the path doesn't have one */
3408       newPath += '/';
3409 
3410    newPath += newName + "." + extension;
3411 
3412    item->setData(Qt::UserRole, newPath);
3413 
3414    if (!file.rename(newPath))
3415    {
3416       RARCH_ERR("[Qt]: Could not rename playlist.\n");
3417       item->setText(oldName);
3418    }
3419 
3420    connect(m_listWidget, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(onCurrentListItemDataChanged(QListWidgetItem*)));
3421 }
3422 
onCurrentItemChanged(const QModelIndex & index)3423 void MainWindow::onCurrentItemChanged(const QModelIndex &index)
3424 {
3425    onCurrentItemChanged(index.data(PlaylistModel::HASH).value<QHash<QString, QString>>());
3426 }
3427 
onCurrentFileChanged(const QModelIndex & index)3428 void MainWindow::onCurrentFileChanged(const QModelIndex &index)
3429 {
3430    onCurrentItemChanged(getFileContentHash(m_proxyFileModel->mapToSource(index)));
3431 }
3432 
onCurrentItemChanged(const QHash<QString,QString> & hash)3433 void MainWindow::onCurrentItemChanged(const QHash<QString, QString> &hash)
3434 {
3435    QString    path = hash["path"];
3436    bool acceptDrop = false;
3437 
3438    if (m_thumbnailPixmap)
3439       delete m_thumbnailPixmap;
3440    if (m_thumbnailPixmap2)
3441       delete m_thumbnailPixmap2;
3442    if (m_thumbnailPixmap3)
3443       delete m_thumbnailPixmap3;
3444 
3445    if (m_playlistModel->isSupportedImage(path))
3446    {
3447       /* use thumbnail widgets to show regular image files */
3448       m_thumbnailPixmap = new QPixmap(path);
3449       m_thumbnailPixmap2 = new QPixmap(*m_thumbnailPixmap);
3450       m_thumbnailPixmap3 = new QPixmap(*m_thumbnailPixmap);
3451    }
3452    else
3453    {
3454       QString thumbnailsDir = m_playlistModel->getPlaylistThumbnailsDir(hash["db_name"]);
3455       QString thumbnailName = m_playlistModel->getSanitizedThumbnailName(hash["label_noext"]);
3456 
3457       m_thumbnailPixmap     = new QPixmap(thumbnailsDir + "/" + THUMBNAIL_BOXART + "/" + thumbnailName);
3458       m_thumbnailPixmap2    = new QPixmap(thumbnailsDir + "/" + THUMBNAIL_TITLE + "/" + thumbnailName);
3459       m_thumbnailPixmap3    = new QPixmap(thumbnailsDir + "/" + THUMBNAIL_SCREENSHOT + "/" + thumbnailName);
3460 
3461       if (      m_currentBrowser == BROWSER_TYPE_PLAYLISTS
3462             && !currentPlaylistIsSpecial())
3463          acceptDrop = true;
3464    }
3465 
3466    onResizeThumbnailOne(*m_thumbnailPixmap, acceptDrop);
3467    onResizeThumbnailTwo(*m_thumbnailPixmap2, acceptDrop);
3468    onResizeThumbnailThree(*m_thumbnailPixmap3, acceptDrop);
3469 
3470    setCoreActions();
3471 }
3472 
setThumbnail(QString widgetName,QPixmap & pixmap,bool acceptDrop)3473 void MainWindow::setThumbnail(QString widgetName,
3474       QPixmap &pixmap, bool acceptDrop)
3475 {
3476    ThumbnailWidget *thumbnail = findChild<ThumbnailWidget*>(widgetName);
3477 
3478    if (thumbnail)
3479       thumbnail->setPixmap(pixmap, acceptDrop);
3480 }
3481 
onResizeThumbnailOne(QPixmap & pixmap,bool acceptDrop)3482 void MainWindow::onResizeThumbnailOne(QPixmap &pixmap, bool acceptDrop)
3483 {
3484    setThumbnail("thumbnail", pixmap, acceptDrop);
3485 }
3486 
onResizeThumbnailTwo(QPixmap & pixmap,bool acceptDrop)3487 void MainWindow::onResizeThumbnailTwo(QPixmap &pixmap, bool acceptDrop)
3488 {
3489    setThumbnail("thumbnail2", pixmap, acceptDrop);
3490 }
3491 
onResizeThumbnailThree(QPixmap & pixmap,bool acceptDrop)3492 void MainWindow::onResizeThumbnailThree(QPixmap &pixmap, bool acceptDrop)
3493 {
3494    setThumbnail("thumbnail3", pixmap, acceptDrop);
3495 }
3496 
setCurrentViewType(ViewType viewType)3497 void MainWindow::setCurrentViewType(ViewType viewType)
3498 {
3499    m_viewType = viewType;
3500 
3501    switch (viewType)
3502    {
3503       case VIEW_TYPE_ICONS:
3504          m_playlistViews->setCurrentWidget(m_gridView);
3505          m_zoomWidget->show();
3506          break;
3507       case VIEW_TYPE_LIST:
3508       default:
3509          m_playlistViews->setCurrentWidget(m_tableView);
3510          m_zoomWidget->hide();
3511          break;
3512    }
3513 }
3514 
setCurrentThumbnailType(ThumbnailType thumbnailType)3515 void MainWindow::setCurrentThumbnailType(ThumbnailType thumbnailType)
3516 {
3517    m_thumbnailType = thumbnailType;
3518 
3519    m_playlistModel->setThumbnailType(thumbnailType);
3520    updateVisibleItems();
3521    m_gridView->viewport()->update();
3522 }
3523 
getCurrentViewType()3524 MainWindow::ViewType MainWindow::getCurrentViewType()
3525 {
3526    return m_viewType;
3527 }
3528 
getCurrentThumbnailType()3529 ThumbnailType MainWindow::getCurrentThumbnailType()
3530 {
3531    return m_thumbnailType;
3532 }
3533 
onCurrentListItemChanged(QListWidgetItem * current,QListWidgetItem * previous)3534 void MainWindow::onCurrentListItemChanged(
3535       QListWidgetItem *current, QListWidgetItem *previous)
3536 {
3537    Q_UNUSED(current)
3538    Q_UNUSED(previous)
3539 
3540    initContentTableWidget();
3541 
3542    setCoreActions();
3543 }
3544 
contentTableView()3545 TableView* MainWindow::contentTableView()
3546 {
3547    return m_tableView;
3548 }
3549 
fileTableView()3550 QTableView* MainWindow::fileTableView()
3551 {
3552    return m_fileTableView;
3553 }
3554 
centralWidget()3555 QStackedWidget* MainWindow::centralWidget()
3556 {
3557    return m_centralWidget;
3558 }
3559 
playlistViews()3560 FileDropWidget* MainWindow::playlistViews()
3561 {
3562    return m_playlistViews;
3563 }
3564 
playlistViewsAndFooter()3565 QWidget* MainWindow::playlistViewsAndFooter()
3566 {
3567    return m_playlistViewsAndFooter;
3568 }
3569 
contentGridView()3570 GridView* MainWindow::contentGridView()
3571 {
3572    return m_gridView;
3573 }
3574 
onBrowserDownloadsClicked()3575 void MainWindow::onBrowserDownloadsClicked()
3576 {
3577    settings_t *settings = config_get_ptr();
3578    QDir dir(settings->paths.directory_core_assets);
3579    QString path = dir.absolutePath();
3580    QModelIndex index;
3581 
3582    m_pendingDirScrollPath = path;
3583 
3584    index = m_dirModel->index(path);
3585 
3586    m_dirTree->setCurrentIndex(index);
3587 
3588    onDownloadScroll(path);
3589 }
3590 
onDownloadScroll(QString path)3591 void MainWindow::onDownloadScroll(QString path)
3592 {
3593    QModelIndex index = m_dirModel->index(path);
3594    m_dirTree->scrollTo(index, QAbstractItemView::PositionAtTop);
3595    m_dirTree->expand(index);
3596 
3597    /* FIXME: Find a way to make this unnecessary */
3598    emit scrollToDownloadsAgain(path);
3599 }
3600 
onDownloadScrollAgain(QString path)3601 void MainWindow::onDownloadScrollAgain(QString path)
3602 {
3603    QModelIndex index = m_dirModel->index(path);
3604    m_dirTree->scrollTo(index, QAbstractItemView::PositionAtTop);
3605    m_dirTree->expand(index);
3606 }
3607 
onBrowserUpClicked()3608 void MainWindow::onBrowserUpClicked()
3609 {
3610    QDir dir(m_dirModel->filePath(m_dirTree->currentIndex()));
3611 
3612    dir.cdUp();
3613 
3614    m_dirTree->setCurrentIndex(m_dirModel->index(dir.absolutePath()));
3615    m_dirTree->scrollTo(m_dirTree->currentIndex(),
3616          QAbstractItemView::EnsureVisible);
3617 }
3618 
onBrowserStartClicked()3619 void MainWindow::onBrowserStartClicked()
3620 {
3621    settings_t *settings = config_get_ptr();
3622 
3623    m_dirTree->setCurrentIndex(
3624          m_dirModel->index(settings->paths.directory_menu_content));
3625    m_dirTree->scrollTo(m_dirTree->currentIndex(), QAbstractItemView::PositionAtTop);
3626 }
3627 
playlistListWidget()3628 ListWidget* MainWindow::playlistListWidget()
3629 {
3630    return m_listWidget;
3631 }
3632 
dirTreeView()3633 TreeView* MainWindow::dirTreeView()
3634 {
3635    return m_dirTree;
3636 }
3637 
onTimeout()3638 void MainWindow::onTimeout()
3639 {
3640    bool contentless = false;
3641    bool is_inited   = false;
3642 
3643    content_get_status(&contentless, &is_inited);
3644 
3645    if (is_inited)
3646    {
3647       if (m_runPushButton->isVisible())
3648          m_runPushButton->hide();
3649       if (!m_stopPushButton->isVisible())
3650          m_stopPushButton->show();
3651    }
3652    else
3653    {
3654       if (!m_runPushButton->isVisible())
3655          m_runPushButton->show();
3656       if (m_stopPushButton->isVisible())
3657          m_stopPushButton->hide();
3658    }
3659 
3660    setCurrentCoreLabel();
3661 }
3662 
onStopClicked()3663 void MainWindow::onStopClicked()
3664 {
3665 #ifdef HAVE_MENU
3666    menu_navigation_set_selection(0);
3667 #endif
3668    command_event(CMD_EVENT_UNLOAD_CORE, NULL);
3669    setCurrentCoreLabel();
3670    activateWindow();
3671    raise();
3672 }
3673 
setCurrentCoreLabel()3674 void MainWindow::setCurrentCoreLabel()
3675 {
3676    bool update                      = false;
3677    struct retro_system_info *system = runloop_get_libretro_system_info();
3678    QString libraryName              = system->library_name;
3679    const char *no_core_str          = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_CORE);
3680 
3681    if (     (m_statusLabel->text().isEmpty())
3682          || (m_currentCore != no_core_str && libraryName.isEmpty())
3683       )
3684    {
3685       m_currentCore           = no_core_str;
3686       m_currentCoreVersion    = "";
3687       update                  = true;
3688    }
3689    else
3690    {
3691       if (m_currentCore != libraryName && !libraryName.isEmpty())
3692       {
3693          m_currentCore        = system->library_name;
3694          m_currentCoreVersion = (string_is_empty(system->library_version) ? "" : system->library_version);
3695          update = true;
3696       }
3697    }
3698 
3699    if (update)
3700    {
3701       QAction *unloadCoreAction = findChild<QAction*>("unloadCoreAction");
3702       QString text              = QString(PACKAGE_VERSION) + " - " + m_currentCore + " " + m_currentCoreVersion;
3703       m_statusLabel->setText(text);
3704       m_loadCoreWindow->setStatusLabel(text);
3705       setCoreActions();
3706 
3707       if (unloadCoreAction)
3708       {
3709          if (libraryName.isEmpty())
3710             unloadCoreAction->setEnabled(false);
3711          else
3712             unloadCoreAction->setEnabled(true);
3713       }
3714    }
3715 }
3716 
onCoreLoadWindowClosed()3717 void MainWindow::onCoreLoadWindowClosed()
3718 {
3719    QVariant lastLaunchWithVariant = m_loadCoreWindow->property("last_launch_with_index");
3720    int lastLaunchWithIndex        = lastLaunchWithVariant.toInt();
3721 
3722    m_pendingRun                   = false;
3723 
3724    if (lastLaunchWithVariant.isValid() && lastLaunchWithIndex >= 0)
3725    {
3726       m_launchWithComboBox->setCurrentIndex(lastLaunchWithIndex);
3727       m_loadCoreWindow->setProperty("last_launch_with_index", -1);
3728    }
3729 }
3730 
onCoreLoaded()3731 void MainWindow::onCoreLoaded()
3732 {
3733    QAction *unloadAction = findChild<QAction*>("unloadCoreAction");
3734 
3735    activateWindow();
3736    raise();
3737    setCurrentCoreLabel();
3738    setCoreActions();
3739 
3740    if (unloadAction)
3741       unloadAction->setEnabled(true);
3742 
3743    m_loadCoreWindow->hide();
3744 
3745    if (m_pendingRun)
3746    {
3747       onRunClicked();
3748       m_pendingRun = false;
3749    }
3750 }
3751 
onUnloadCoreMenuAction()3752 void MainWindow::onUnloadCoreMenuAction()
3753 {
3754    QAction *action = qobject_cast<QAction*>(sender());
3755 
3756 #ifdef HAVE_MENU
3757    menu_navigation_set_selection(0);
3758 #endif
3759 
3760    /* TODO */
3761    if (!command_event(CMD_EVENT_UNLOAD_CORE, NULL))
3762       return;
3763 
3764    setCurrentCoreLabel();
3765    setCoreActions();
3766 
3767    if (!action)
3768       return;
3769 
3770    action->setEnabled(false);
3771    activateWindow();
3772    raise();
3773 }
3774 
onLoadCoreClicked(const QStringList & extensionFilters)3775 void MainWindow::onLoadCoreClicked(const QStringList &extensionFilters)
3776 {
3777    m_loadCoreWindow->show();
3778    m_loadCoreWindow->resize(width() / 2, height());
3779    m_loadCoreWindow->setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, m_loadCoreWindow->size(), geometry()));
3780    m_loadCoreWindow->initCoreList(extensionFilters);
3781 }
3782 
initContentTableWidget()3783 void MainWindow::initContentTableWidget()
3784 {
3785    QString path;
3786    QListWidgetItem *item = m_listWidget->currentItem();
3787 
3788    if (!item)
3789       return;
3790 
3791    m_currentGridHash.clear();
3792 
3793    if (m_currentGridWidget)
3794    {
3795       m_currentGridWidget->setObjectName("thumbnailWidget");
3796       m_currentGridWidget->style()->unpolish(m_currentGridWidget);
3797       m_currentGridWidget->style()->polish(m_currentGridWidget);
3798    }
3799 
3800    m_currentGridWidget = NULL;
3801 
3802    path = item->data(Qt::UserRole).toString();
3803 
3804    if (path == ALL_PLAYLISTS_TOKEN)
3805    {
3806       unsigned i;
3807       settings_t *settings = config_get_ptr();
3808       QDir playlistDir(settings->paths.directory_playlist);
3809       QStringList playlists;
3810       size_t list_size = (size_t)m_playlistFiles.count();
3811 
3812       for (i = 0; i < list_size; i++)
3813       {
3814          const QString &playlist = m_playlistFiles.at(i);
3815          playlists.append(playlistDir.absoluteFilePath(playlist));
3816       }
3817 
3818       m_playlistModel->addPlaylistItems(playlists, true);
3819    }
3820    else
3821       m_playlistModel->addPlaylistItems(QStringList() << path);
3822 
3823    if (item != m_historyPlaylistsItem)
3824       m_tableView->sortByColumn(0, Qt::AscendingOrder);
3825    else
3826       m_proxyModel->sort(-1);
3827 
3828    updateItemsCount();
3829 
3830    m_gridView->scrollToTop();
3831    m_gridView->setCurrentIndex(m_proxyModel->index(0, 0));
3832 }
3833 
updateItemsCount()3834 void MainWindow::updateItemsCount()
3835 {
3836    m_itemsCountLabel->setText(
3837          m_itemsCountLiteral.arg(m_proxyModel->rowCount()));
3838 }
3839 
keyPressEvent(QKeyEvent * event)3840 void MainWindow::keyPressEvent(QKeyEvent *event)
3841 {
3842    QMainWindow::keyPressEvent(event);
3843 }
3844 
settings()3845 QSettings* MainWindow::settings()
3846 {
3847    return m_settings;
3848 }
3849 
getCurrentViewTypeString()3850 QString MainWindow::getCurrentViewTypeString()
3851 {
3852    switch (m_viewType)
3853    {
3854       case VIEW_TYPE_ICONS:
3855          return QStringLiteral("icons");
3856       case VIEW_TYPE_LIST:
3857       default:
3858          break;
3859    }
3860 
3861    return QStringLiteral("list");
3862 }
3863 
getCurrentThumbnailTypeString()3864 QString MainWindow::getCurrentThumbnailTypeString()
3865 {
3866    switch (m_thumbnailType)
3867    {
3868       case THUMBNAIL_TYPE_SCREENSHOT:
3869          return QStringLiteral("screenshot");
3870       case THUMBNAIL_TYPE_TITLE_SCREEN:
3871          return QStringLiteral("title");
3872       case THUMBNAIL_TYPE_BOXART:
3873       default:
3874          return QStringLiteral("boxart");
3875    }
3876 
3877    return QStringLiteral("list");
3878 }
3879 
getThumbnailTypeFromString(QString thumbnailType)3880 ThumbnailType MainWindow::getThumbnailTypeFromString(QString thumbnailType)
3881 {
3882    if (thumbnailType == "boxart")
3883       return THUMBNAIL_TYPE_BOXART;
3884    else if (thumbnailType == "screenshot")
3885       return THUMBNAIL_TYPE_SCREENSHOT;
3886    else if (thumbnailType == "title")
3887       return THUMBNAIL_TYPE_TITLE_SCREEN;
3888 
3889    return THUMBNAIL_TYPE_BOXART;
3890 }
3891 
closeEvent(QCloseEvent * event)3892 void MainWindow::closeEvent(QCloseEvent *event)
3893 {
3894    if (m_settings->value("save_geometry", false).toBool())
3895       m_settings->setValue("geometry", saveGeometry());
3896    if (m_settings->value("save_dock_positions", false).toBool())
3897       m_settings->setValue("dock_positions", saveState());
3898    if (m_settings->value("save_last_tab", false).toBool())
3899       m_settings->setValue("last_tab", m_browserAndPlaylistTabWidget->currentIndex());
3900 
3901    m_settings->setValue("view_type", getCurrentViewTypeString());
3902    m_settings->setValue("file_browser_table_headers", m_fileTableView->horizontalHeader()->saveState());
3903    m_settings->setValue("icon_view_zoom", m_lastZoomSliderValue);
3904    m_settings->setValue("icon_view_thumbnail_type", getCurrentThumbnailTypeString());
3905    m_settings->setValue("options_dialog_geometry", m_viewOptionsDialog->saveGeometry());
3906 
3907    QMainWindow::closeEvent(event);
3908 }
3909 
onContributorsClicked()3910 void MainWindow::onContributorsClicked()
3911 {
3912    QScopedPointer<QDialog> dialog(new QDialog());
3913    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
3914    QTextEdit *textEdit = new QTextEdit(dialog.data());
3915 
3916    connect(buttonBox, SIGNAL(accepted()), dialog.data(), SLOT(accept()));
3917    connect(buttonBox, SIGNAL(rejected()), dialog.data(), SLOT(reject()));
3918 
3919    dialog->setWindowTitle(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_ABOUT_CONTRIBUTORS));
3920    dialog->setLayout(new QVBoxLayout());
3921 
3922    dialog->layout()->addWidget(textEdit);
3923 
3924    dialog->layout()->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum));
3925    dialog->layout()->addWidget(buttonBox);
3926 
3927    textEdit->setReadOnly(true);
3928    textEdit->setHtml(QString("<pre>") + retroarch_contributors_list + "</pre>");
3929 
3930    dialog->resize(480, 640);
3931    dialog->exec();
3932 }
3933 
showAbout()3934 void MainWindow::showAbout()
3935 {
3936    QScopedPointer<QDialog> dialog(new QDialog());
3937    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
3938    QString text = QString("RetroArch ") + PACKAGE_VERSION +
3939          "<br><br>" + "<a href=\"https://www.libretro.com/\">www.libretro.com</a>"
3940          "<br><br>" + "<a href=\"https://www.retroarch.com/\">www.retroarch.com</a>"
3941 #ifdef HAVE_GIT_VERSION
3942          "<br><br>" + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_GIT_VERSION) + ": " + retroarch_git_version +
3943 #endif
3944          "<br>" + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_BUILD_DATE) + ": " + __DATE__;
3945    QLabel *label = new QLabel(text, dialog.data());
3946    QPixmap pix = getInvader();
3947    QLabel *pixLabel = new QLabel(dialog.data());
3948    QPushButton *contributorsPushButton = new QPushButton(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_ABOUT_CONTRIBUTORS), dialog.data());
3949 
3950    connect(contributorsPushButton, SIGNAL(clicked()), this, SLOT(onContributorsClicked()));
3951    connect(buttonBox, SIGNAL(accepted()), dialog.data(), SLOT(accept()));
3952    connect(buttonBox, SIGNAL(rejected()), dialog.data(), SLOT(reject()));
3953 
3954    label->setTextFormat(Qt::RichText);
3955    label->setAlignment(Qt::AlignCenter);
3956    label->setTextInteractionFlags(Qt::TextBrowserInteraction);
3957    label->setOpenExternalLinks(true);
3958 
3959    pixLabel->setAlignment(Qt::AlignCenter);
3960    pixLabel->setPixmap(pix);
3961 
3962    dialog->setWindowTitle(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_ABOUT));
3963    dialog->setLayout(new QVBoxLayout());
3964 
3965    dialog->layout()->addWidget(pixLabel);
3966    dialog->layout()->addWidget(label);
3967    dialog->layout()->addWidget(contributorsPushButton);
3968 
3969    dialog->layout()->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding));
3970    dialog->layout()->addWidget(buttonBox);
3971 
3972    dialog->exec();
3973 }
3974 
showDocs()3975 void MainWindow::showDocs()
3976 {
3977    QDesktopServices::openUrl(QUrl(DOCS_URL));
3978 }
3979 
onShowErrorMessage(QString msg)3980 void MainWindow::onShowErrorMessage(QString msg)
3981 {
3982    showMessageBox(msg, MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false);
3983 }
3984 
onShowInfoMessage(QString msg)3985 void MainWindow::onShowInfoMessage(QString msg)
3986 {
3987    showMessageBox(msg, MainWindow::MSGBOX_TYPE_INFO, Qt::ApplicationModal, false);
3988 }
3989 
onExtractArchive(QString path,QString extractionDir,QString tempExtension,retro_task_callback_t cb)3990 int MainWindow::onExtractArchive(QString path, QString extractionDir, QString tempExtension, retro_task_callback_t cb)
3991 {
3992    unsigned i;
3993    file_archive_transfer_t state;
3994    struct archive_extract_userdata userdata;
3995    QByteArray pathArray          = path.toUtf8();
3996    QByteArray dirArray           = extractionDir.toUtf8();
3997    const char *file              = pathArray.constData();
3998    const char *dir               = dirArray.constData();
3999    struct string_list *file_list = file_archive_get_file_list(file, NULL);
4000    bool returnerr                = true;
4001    retro_task_t *decompress_task = NULL;
4002 
4003    if (!file_list || file_list->size == 0)
4004    {
4005       showMessageBox("Error: Archive is empty.", MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false);
4006       RARCH_ERR("[Qt]: Downloaded archive is empty?\n");
4007       return -1;
4008    }
4009 
4010    for (i = 0; i < file_list->size; i++)
4011    {
4012       QFile fileObj(file_list->elems[i].data);
4013 
4014       if (fileObj.exists())
4015       {
4016          if (!fileObj.remove())
4017          {
4018             /* if we cannot delete the existing file to update it, rename it for now and delete later */
4019             QFile fileTemp(fileObj.fileName() + tempExtension);
4020 
4021             if (fileTemp.exists())
4022             {
4023                if (!fileTemp.remove())
4024                {
4025                   showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_DELETE_FILE), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false);
4026                   RARCH_ERR("[Qt]: Could not delete file: %s\n", file_list->elems[i].data);
4027                   return -1;
4028                }
4029             }
4030 
4031             if (!fileObj.rename(fileTemp.fileName()))
4032             {
4033                showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_RENAME_FILE), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false);
4034                RARCH_ERR("[Qt]: Could not rename file: %s\n", file_list->elems[i].data);
4035                return -1;
4036             }
4037          }
4038       }
4039    }
4040 
4041    string_list_free(file_list);
4042 
4043    memset(&state,    0, sizeof(state));
4044    memset(&userdata, 0, sizeof(userdata));
4045 
4046    state.type = ARCHIVE_TRANSFER_INIT;
4047 
4048    m_updateProgressDialog->setWindowModality(Qt::NonModal);
4049    m_updateProgressDialog->setMinimumDuration(0);
4050    m_updateProgressDialog->setRange(0, 0);
4051    m_updateProgressDialog->setAutoClose(true);
4052    m_updateProgressDialog->setAutoReset(true);
4053    m_updateProgressDialog->setValue(0);
4054    m_updateProgressDialog->setLabelText(QString(msg_hash_to_str(MSG_EXTRACTING)) + "...");
4055    m_updateProgressDialog->setCancelButtonText(QString());
4056    m_updateProgressDialog->show();
4057 
4058    decompress_task = (retro_task_t*)task_push_decompress(
4059          file, dir,
4060          NULL, NULL, NULL,
4061          cb, this, NULL, false);
4062 
4063    if (!decompress_task)
4064    {
4065       m_updateProgressDialog->cancel();
4066       return -1;
4067    }
4068 
4069    return returnerr;
4070 }
4071 
getScrubbedString(QString str)4072 QString MainWindow::getScrubbedString(QString str)
4073 {
4074    const QString chars("&*/:`\"<>?\\|");
4075    int i;
4076 
4077    for (i = 0; i < chars.count(); i++)
4078       str.replace(chars.at(i), '_');
4079 
4080    return str;
4081 }
4082 
ui_window_qt_init(void)4083 static void* ui_window_qt_init(void)
4084 {
4085    ui_window.qtWindow = new MainWindow();
4086 
4087    return &ui_window;
4088 }
4089 
ui_window_qt_destroy(void * data)4090 static void ui_window_qt_destroy(void *data)
4091 {
4092 #if 0
4093    ui_window_qt_t *window = (ui_window_qt_t*)data;
4094 
4095    delete window->qtWindow;
4096 #endif
4097 }
4098 
ui_window_qt_set_focused(void * data)4099 static void ui_window_qt_set_focused(void *data)
4100 {
4101 #if 0
4102    ui_window_qt_t *window = (ui_window_qt_t*)data;
4103 
4104    window->qtWindow->raise();
4105    window->qtWindow->activateWindow();
4106 #endif
4107 }
4108 
ui_window_qt_set_visible(void * data,bool set_visible)4109 static void ui_window_qt_set_visible(void *data,
4110         bool set_visible)
4111 {
4112    (void)data;
4113    (void)set_visible;
4114    /* TODO/FIXME */
4115 }
4116 
ui_window_qt_set_title(void * data,char * buf)4117 static void ui_window_qt_set_title(void *data, char *buf)
4118 {
4119 #if 0
4120    ui_window_qt_t *window = (ui_window_qt_t*)data;
4121 
4122    window->qtWindow->setWindowTitle(QString::fromUtf8(buf));
4123 #endif
4124 }
4125 
ui_window_qt_set_droppable(void * data,bool droppable)4126 static void ui_window_qt_set_droppable(void *data, bool droppable)
4127 {
4128 #if 0
4129    ui_window_qt_t *window = (ui_window_qt_t*)data;
4130 
4131    window->qtWindow->setAcceptDrops(droppable);
4132 #endif
4133 }
4134 
ui_window_qt_focused(void * data)4135 static bool ui_window_qt_focused(void *data)
4136 {
4137 #if 0
4138    ui_window_qt_t *window = (ui_window_qt_t*)data;
4139    return window->qtWindow->isActiveWindow() && !window->qtWindow->isMinimized();
4140 #else
4141    return true;
4142 #endif
4143 }
4144 
4145 static ui_window_t ui_window_qt = {
4146    ui_window_qt_init,
4147    ui_window_qt_destroy,
4148    ui_window_qt_set_focused,
4149    ui_window_qt_set_visible,
4150    ui_window_qt_set_title,
4151    ui_window_qt_set_droppable,
4152    ui_window_qt_focused,
4153    "qt"
4154 };
4155 
ui_msg_window_qt_response(ui_msg_window_state * state,QMessageBox::StandardButtons response)4156 static enum ui_msg_window_response ui_msg_window_qt_response(ui_msg_window_state *state, QMessageBox::StandardButtons response)
4157 {
4158 	switch (response)
4159    {
4160       case QMessageBox::Ok:
4161          return UI_MSG_RESPONSE_OK;
4162       case QMessageBox::Cancel:
4163          return UI_MSG_RESPONSE_CANCEL;
4164       case QMessageBox::Yes:
4165          return UI_MSG_RESPONSE_YES;
4166       case QMessageBox::No:
4167          return UI_MSG_RESPONSE_NO;
4168       default:
4169          break;
4170    }
4171 
4172 	switch (state->buttons)
4173 	{
4174 	   case UI_MSG_WINDOW_OK:
4175 		   return UI_MSG_RESPONSE_OK;
4176 	   case UI_MSG_WINDOW_OKCANCEL:
4177 		   return UI_MSG_RESPONSE_CANCEL;
4178 	   case UI_MSG_WINDOW_YESNO:
4179 		   return UI_MSG_RESPONSE_NO;
4180 	   case UI_MSG_WINDOW_YESNOCANCEL:
4181 		   return UI_MSG_RESPONSE_CANCEL;
4182 	   default:
4183 		   break;
4184 	}
4185 
4186 	return UI_MSG_RESPONSE_NA;
4187 }
4188 
4189 static QFlags<QMessageBox::StandardButton>
ui_msg_window_qt_buttons(ui_msg_window_state * state)4190 ui_msg_window_qt_buttons(ui_msg_window_state *state)
4191 {
4192 	switch (state->buttons)
4193 	{
4194 	   case UI_MSG_WINDOW_OK:
4195 		   return QMessageBox::Ok;
4196 	   case UI_MSG_WINDOW_OKCANCEL:
4197 		   return QMessageBox::Cancel;
4198 	   case UI_MSG_WINDOW_YESNO:
4199 		   return (QMessageBox::Yes | QMessageBox::No);
4200 	   case UI_MSG_WINDOW_YESNOCANCEL:
4201 		   return (QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
4202 	}
4203 
4204 	return QMessageBox::NoButton;
4205 }
4206 
4207 static enum ui_msg_window_response
ui_msg_window_qt_error(ui_msg_window_state * state)4208 ui_msg_window_qt_error(ui_msg_window_state *state)
4209 {
4210    QFlags<QMessageBox::StandardButton> flags = ui_msg_window_qt_buttons(state);
4211    return ui_msg_window_qt_response(state, QMessageBox::critical((QWidget*)state->window, state->title, state->text, flags));
4212 }
4213 
ui_msg_window_qt_information(ui_msg_window_state * state)4214 static enum ui_msg_window_response ui_msg_window_qt_information(ui_msg_window_state *state)
4215 {
4216    QFlags<QMessageBox::StandardButton> flags = ui_msg_window_qt_buttons(state);
4217    return ui_msg_window_qt_response(state, QMessageBox::information((QWidget*)state->window, state->title, state->text, flags));
4218 }
4219 
ui_msg_window_qt_question(ui_msg_window_state * state)4220 static enum ui_msg_window_response ui_msg_window_qt_question(ui_msg_window_state *state)
4221 {
4222    QFlags<QMessageBox::StandardButton> flags = ui_msg_window_qt_buttons(state);
4223    return ui_msg_window_qt_response(state, QMessageBox::question((QWidget*)state->window, state->title, state->text, flags));
4224 }
4225 
ui_msg_window_qt_warning(ui_msg_window_state * state)4226 static enum ui_msg_window_response ui_msg_window_qt_warning(ui_msg_window_state *state)
4227 {
4228    QFlags<QMessageBox::StandardButton> flags = ui_msg_window_qt_buttons(state);
4229    return ui_msg_window_qt_response(state, QMessageBox::warning((QWidget*)state->window, state->title, state->text, flags));
4230 }
4231 
4232 static ui_msg_window_t ui_msg_window_qt = {
4233    ui_msg_window_qt_error,
4234    ui_msg_window_qt_information,
4235    ui_msg_window_qt_question,
4236    ui_msg_window_qt_warning,
4237    "qt"
4238 };
4239 
ui_browser_window_qt_open(ui_browser_window_state_t * state)4240 static bool ui_browser_window_qt_open(ui_browser_window_state_t *state)
4241 {
4242    return true;
4243 }
4244 
ui_browser_window_qt_save(ui_browser_window_state_t * state)4245 static bool ui_browser_window_qt_save(ui_browser_window_state_t *state)
4246 {
4247    return false;
4248 }
4249 
4250 static ui_browser_window_t ui_browser_window_qt = {
4251    ui_browser_window_qt_open,
4252    ui_browser_window_qt_save,
4253    "qt"
4254 };
4255 
ui_application_qt_initialize(void)4256 static void* ui_application_qt_initialize(void)
4257 {
4258    /* These must last for the lifetime of the QApplication */
4259    static int app_argc     = 1;
4260    static char app_name[]  = "retroarch";
4261    static char *app_argv[] = { app_name, NULL };
4262 
4263    app_handler             = new AppHandler();
4264 
4265 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
4266    /* HiDpi supported since Qt 5.6 */
4267    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
4268 #endif
4269 
4270    /* Create QApplication() before calling QApplication::setStyle()
4271     * to ensure that plugin path is determined correctly */
4272    ui_application.app = new QApplication(app_argc, app_argv);
4273    QApplication::setStyle("fusion");
4274    ui_application.app->setOrganizationName("libretro");
4275    ui_application.app->setApplicationName("RetroArch");
4276    ui_application.app->setApplicationVersion(PACKAGE_VERSION);
4277    ui_application.app->connect(ui_application.app, SIGNAL(lastWindowClosed()),
4278          app_handler, SLOT(onLastWindowClosed()));
4279 
4280 #ifdef Q_OS_UNIX
4281    setlocale(LC_NUMERIC, "C");
4282 #endif
4283    {
4284       /* Can't declare the pixmap at the top, because: "QPixmap: Must construct a QGuiApplication before a QPixmap" */
4285       QImage iconImage(16, 16, QImage::Format_ARGB32);
4286       QPixmap iconPixmap;
4287       unsigned char *bits = iconImage.bits();
4288 
4289       memcpy(bits, retroarch_qt_icon_data, 16 * 16 * sizeof(unsigned));
4290 
4291       iconPixmap = QPixmap::fromImage(iconImage);
4292 
4293       ui_application.app->setWindowIcon(QIcon(iconPixmap));
4294    }
4295 
4296    return &ui_application;
4297 }
4298 
ui_application_qt_process_events(void)4299 static void ui_application_qt_process_events(void)
4300 {
4301    QAbstractEventDispatcher *dispatcher = QApplication::eventDispatcher();
4302    if (dispatcher && dispatcher->hasPendingEvents())
4303       QApplication::processEvents();
4304 }
4305 
ui_application_qt_quit(void)4306 static void ui_application_qt_quit(void)
4307 {
4308    if (app_handler)
4309       app_handler->exit();
4310 }
4311 
4312 #ifdef HAVE_MAIN
4313 #if defined(__cplusplus) && !defined(CXX_BUILD)
4314 extern "C"
4315 #endif
main(int argc,char * argv[])4316 int main(int argc, char *argv[])
4317 {
4318    return rarch_main(argc, argv, NULL);
4319 }
4320 #endif
4321 
4322 static ui_application_t ui_application_qt = {
4323    ui_application_qt_initialize,
4324    ui_application_qt_process_events,
4325    ui_application_qt_quit,
4326    false,
4327    "qt"
4328 };
4329 
4330 
AppHandler(QObject * parent)4331 AppHandler::AppHandler(QObject *parent) :
4332    QObject(parent)
4333 {
4334 }
4335 
~AppHandler()4336 AppHandler::~AppHandler()
4337 {
4338 }
4339 
exit()4340 void AppHandler::exit()
4341 {
4342    ui_application_qt.exiting = true;
4343 
4344    if (qApp)
4345       qApp->closeAllWindows();
4346 }
4347 
isExiting() const4348 bool AppHandler::isExiting() const
4349 {
4350    return ui_application_qt.exiting;
4351 }
4352 
onLastWindowClosed()4353 void AppHandler::onLastWindowClosed()
4354 {
4355 }
4356 
4357 typedef struct ui_companion_qt
4358 {
4359    ui_application_qt_t *app;
4360    ui_window_qt_t *window;
4361 } ui_companion_qt_t;
4362 
ThumbnailWidget(ThumbnailType type,QWidget * parent)4363 ThumbnailWidget::ThumbnailWidget(ThumbnailType type, QWidget *parent) :
4364    QStackedWidget(parent)
4365    ,m_thumbnailType(type)
4366    ,m_thumbnailLabel(new ThumbnailLabel(this))
4367    ,m_dropIndicator(new QLabel(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_DROP_IMAGE_HERE), this))
4368 {
4369    m_dropIndicator->setObjectName("dropIndicator");
4370    m_dropIndicator->setAlignment(Qt::AlignCenter);
4371    addWidget(m_dropIndicator);
4372    addWidget(m_thumbnailLabel);
4373 }
4374 
setPixmap(const QPixmap & pixmap,bool acceptDrops)4375 void ThumbnailWidget::setPixmap(const QPixmap &pixmap, bool acceptDrops)
4376 {
4377    m_thumbnailLabel->setPixmap(pixmap);
4378 
4379    if (acceptDrops && pixmap.isNull())
4380       setCurrentWidget(m_dropIndicator);
4381    else
4382       setCurrentWidget(m_thumbnailLabel);
4383 
4384    m_thumbnailLabel->update();
4385 
4386    QWidget::setAcceptDrops(acceptDrops);
4387 }
4388 
dragEnterEvent(QDragEnterEvent * event)4389 void ThumbnailWidget::dragEnterEvent(QDragEnterEvent *event)
4390 {
4391    const QMimeData *data = event->mimeData();
4392 
4393    if (data->hasUrls())
4394       event->acceptProposedAction();
4395 }
4396 
4397 /* Workaround for QTBUG-72844. Without it, you can't
4398  * drop on this if you first drag over another
4399  * widget that doesn't accept drops. */
dragMoveEvent(QDragMoveEvent * event)4400 void ThumbnailWidget::dragMoveEvent(QDragMoveEvent *event)
4401 {
4402    event->acceptProposedAction();
4403 }
4404 
dropEvent(QDropEvent * event)4405 void ThumbnailWidget::dropEvent(QDropEvent *event)
4406 {
4407    const QMimeData *data = event->mimeData();
4408 
4409    if (data->hasUrls())
4410    {
4411       const QString imageString = data->urls().at(0).toLocalFile();
4412       const QImage image(imageString);
4413 
4414       if (!image.isNull())
4415          emit(filesDropped(image, m_thumbnailType));
4416       else
4417       {
4418          const char *string_data = QDir::toNativeSeparators(imageString).toUtf8().constData();
4419          RARCH_ERR("[Qt]: Could not read image: %s\n", string_data);
4420       }
4421    }
4422 }
4423 
ThumbnailLabel(QWidget * parent)4424 ThumbnailLabel::ThumbnailLabel(QWidget *parent) :
4425    QWidget(parent)
4426    ,m_pixmap(NULL)
4427    ,m_pixmapWidth(0)
4428    ,m_pixmapHeight(0)
4429 {
4430 }
4431 
~ThumbnailLabel()4432 ThumbnailLabel::~ThumbnailLabel()
4433 {
4434    if (m_pixmap)
4435       delete m_pixmap;
4436 }
4437 
setPixmap(const QPixmap & pixmap)4438 void ThumbnailLabel::setPixmap(const QPixmap &pixmap)
4439 {
4440    m_pixmapWidth = pixmap.width();
4441    m_pixmapHeight = pixmap.height();
4442 
4443    if (m_pixmap)
4444       delete m_pixmap;
4445 
4446    m_pixmap = new QPixmap(pixmap);
4447 }
4448 
sizeHint() const4449 QSize ThumbnailLabel::sizeHint() const
4450 {
4451    return QSize(256, 256);
4452 }
4453 
paintEvent(QPaintEvent * event)4454 void ThumbnailLabel::paintEvent(QPaintEvent *event)
4455 {
4456    QStyleOption o;
4457    QPainter p;
4458    int w = width();
4459    int h = height();
4460 
4461    event->accept();
4462 
4463    o.initFrom(this);
4464    p.begin(this);
4465    style()->drawPrimitive(
4466      QStyle::PE_Widget, &o, &p, this);
4467    p.end();
4468 
4469    if (!m_pixmap || m_pixmap->isNull())
4470    {
4471       if (m_pixmap)
4472          delete m_pixmap;
4473       m_pixmap = new QPixmap(sizeHint());
4474       m_pixmap->fill(QColor(0, 0, 0, 0));
4475    }
4476 
4477    if (w > 0 && h > 0 && m_pixmap && !m_pixmap->isNull())
4478    {
4479       int newHeight = (m_pixmap->height() / static_cast<float>(m_pixmap->width())) * width();
4480       QPixmap pixmapScaled = *m_pixmap;
4481       QPixmap pixmap;
4482       QPainter pScale;
4483       int pw = 0;
4484       int ph = 0;
4485       unsigned *buf = new unsigned[w * h];
4486 
4487       if (newHeight > h)
4488          pixmapScaled = pixmapScaled.scaledToHeight(h, Qt::SmoothTransformation);
4489       else
4490          pixmapScaled = pixmapScaled.scaledToWidth(w, Qt::SmoothTransformation);
4491 
4492       pw = pixmapScaled.width();
4493       ph = pixmapScaled.height();
4494 
4495       pixmap = QPixmap(w, h);
4496       pixmap.fill(QColor(0, 0, 0, 0));
4497 
4498       pScale.begin(&pixmap);
4499       pScale.drawPixmap(QRect((w - pw) / 2, (h - ph) / 2, pw, ph), pixmapScaled, pixmapScaled.rect());
4500       pScale.end();
4501 
4502       if (!pixmap.isNull())
4503       {
4504          p.begin(this);
4505          p.drawPixmap(rect(), pixmap, pixmap.rect());
4506          p.end();
4507       }
4508 
4509       delete []buf;
4510    }
4511    else
4512       QWidget::paintEvent(event);
4513 }
4514 
resizeEvent(QResizeEvent * event)4515 void ThumbnailLabel::resizeEvent(QResizeEvent *event)
4516 {
4517    QWidget::resizeEvent(event);
4518 }
4519 
ui_companion_qt_deinit(void * data)4520 static void ui_companion_qt_deinit(void *data)
4521 {
4522    ui_companion_qt_t *handle = (ui_companion_qt_t*)data;
4523 
4524    if (!handle)
4525       return;
4526 
4527    /* why won't deleteLater() here call the destructor? */
4528    delete handle->window->qtWindow;
4529 
4530    free(handle);
4531 }
4532 
ui_companion_qt_init(void)4533 static void* ui_companion_qt_init(void)
4534 {
4535    int i = 0;
4536    QString initialPlaylist;
4537    QRect desktopRect;
4538    ui_companion_qt_t               *handle = (ui_companion_qt_t*)
4539       calloc(1, sizeof(*handle));
4540    MainWindow                  *mainwindow = NULL;
4541    QHBoxLayout   *browserButtonsHBoxLayout = NULL;
4542    QVBoxLayout                     *layout = NULL;
4543    QVBoxLayout     *launchWithWidgetLayout = NULL;
4544    QHBoxLayout         *coreComboBoxLayout = NULL;
4545    QMenuBar                          *menu = NULL;
4546    QDesktopWidget                 *desktop = NULL;
4547    QMenu                         *fileMenu = NULL;
4548    QMenu                         *editMenu = NULL;
4549    QMenu                         *viewMenu = NULL;
4550    QMenu              *viewClosedDocksMenu = NULL;
4551    QMenu                         *helpMenu = NULL;
4552    QDockWidget              *thumbnailDock = NULL;
4553    QDockWidget             *thumbnail2Dock = NULL;
4554    QDockWidget             *thumbnail3Dock = NULL;
4555    QDockWidget  *browserAndPlaylistTabDock = NULL;
4556    QDockWidget          *coreSelectionDock = NULL;
4557    QTabWidget *browserAndPlaylistTabWidget = NULL;
4558    QStackedWidget           *centralWidget = NULL;
4559    QStackedWidget                  *widget = NULL;
4560    QFrame                   *browserWidget = NULL;
4561    QFrame                  *playlistWidget = NULL;
4562    QWidget            *coreSelectionWidget = NULL;
4563    QWidget               *launchWithWidget = NULL;
4564    ThumbnailWidget        *thumbnailWidget = NULL;
4565    ThumbnailWidget       *thumbnail2Widget = NULL;
4566    ThumbnailWidget       *thumbnail3Widget = NULL;
4567    QPushButton     *browserDownloadsButton = NULL;
4568    QPushButton            *browserUpButton = NULL;
4569    QPushButton         *browserStartButton = NULL;
4570    ThumbnailLabel               *thumbnail = NULL;
4571    ThumbnailLabel              *thumbnail2 = NULL;
4572    ThumbnailLabel              *thumbnail3 = NULL;
4573    QAction               *editSearchAction = NULL;
4574    QAction                 *loadCoreAction = NULL;
4575    QAction               *unloadCoreAction = NULL;
4576    QAction                     *exitAction = NULL;
4577    QComboBox           *launchWithComboBox = NULL;
4578    QSettings                    *qsettings = NULL;
4579    QListWidget                 *listWidget = NULL;
4580    bool                      foundPlaylist = false;
4581 
4582    if (!handle)
4583       return NULL;
4584 
4585    handle->app     = static_cast<ui_application_qt_t*>
4586       (ui_application_qt.initialize());
4587    handle->window  = static_cast<ui_window_qt_t*>(ui_window_qt.init());
4588 
4589    desktop         = qApp->desktop();
4590    desktopRect     = desktop->availableGeometry();
4591 
4592    mainwindow      = handle->window->qtWindow;
4593 
4594    qsettings       = mainwindow->settings();
4595 
4596    initialPlaylist = qsettings->value("initial_playlist", mainwindow->getSpecialPlaylistPath(SPECIAL_PLAYLIST_HISTORY)).toString();
4597 
4598    mainwindow->resize(qMin(desktopRect.width(), INITIAL_WIDTH), qMin(desktopRect.height(), INITIAL_HEIGHT));
4599    mainwindow->setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, mainwindow->size(), desktopRect));
4600 
4601    mainwindow->setWindowTitle("RetroArch");
4602    mainwindow->setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks | GROUPED_DRAGGING);
4603 
4604    listWidget      = mainwindow->playlistListWidget();
4605 
4606    widget          = mainwindow->playlistViews();
4607    widget->setContextMenuPolicy(Qt::CustomContextMenu);
4608 
4609    QObject::connect(widget, SIGNAL(filesDropped(QStringList)), mainwindow, SLOT(onPlaylistFilesDropped(QStringList)));
4610    QObject::connect(widget, SIGNAL(enterPressed()), mainwindow, SLOT(onDropWidgetEnterPressed()));
4611    QObject::connect(widget, SIGNAL(deletePressed()), mainwindow, SLOT(deleteCurrentPlaylistItem()));
4612    QObject::connect(widget, SIGNAL(customContextMenuRequested(const QPoint&)), mainwindow, SLOT(onFileDropWidgetContextMenuRequested(const QPoint&)));
4613 
4614    centralWidget = mainwindow->centralWidget();
4615 
4616    centralWidget->addWidget(mainwindow->playlistViewsAndFooter());
4617    centralWidget->addWidget(mainwindow->fileTableView());
4618 
4619    mainwindow->setCentralWidget(centralWidget);
4620 
4621    menu = mainwindow->menuBar();
4622 
4623    fileMenu = menu->addMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_FILE));
4624 
4625    loadCoreAction = fileMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_FILE_LOAD_CORE), mainwindow, SLOT(onLoadCoreClicked()));
4626    loadCoreAction->setShortcut(QKeySequence("Ctrl+L"));
4627 
4628    unloadCoreAction = fileMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_FILE_UNLOAD_CORE), mainwindow, SLOT(onUnloadCoreMenuAction()));
4629    unloadCoreAction->setObjectName("unloadCoreAction");
4630    unloadCoreAction->setEnabled(false);
4631    unloadCoreAction->setShortcut(QKeySequence("Ctrl+U"));
4632 
4633    exitAction = fileMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_FILE_EXIT), mainwindow, SLOT(close()));
4634    exitAction->setShortcut(QKeySequence::Quit);
4635 
4636    editMenu = menu->addMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_EDIT));
4637    editSearchAction = editMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_EDIT_SEARCH), mainwindow->searchLineEdit(), SLOT(setFocus()));
4638    editSearchAction->setShortcut(QKeySequence::Find);
4639 
4640    viewMenu = menu->addMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW));
4641    viewClosedDocksMenu = viewMenu->addMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_CLOSED_DOCKS));
4642    viewClosedDocksMenu->setObjectName("viewClosedDocksMenu");
4643 
4644    QObject::connect(viewClosedDocksMenu, SIGNAL(aboutToShow()), mainwindow, SLOT(onViewClosedDocksAboutToShow()));
4645 
4646    viewMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CORE_OPTIONS), mainwindow, SLOT(onCoreOptionsClicked()));
4647 #if defined(HAVE_MENU)
4648 #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
4649    viewMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SHADER_OPTIONS), mainwindow, SLOT(onShaderParamsClicked()));
4650 #endif
4651 #endif
4652 
4653    viewMenu->addSeparator();
4654    viewMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_ICONS), mainwindow, SLOT(onIconViewClicked()));
4655    viewMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_LIST), mainwindow, SLOT(onListViewClicked()));
4656    viewMenu->addSeparator();
4657    viewMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS), mainwindow->viewOptionsDialog(), SLOT(showDialog()));
4658 
4659    helpMenu = menu->addMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP));
4660    helpMenu->addAction(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_DOCUMENTATION)), mainwindow, SLOT(showDocs()));
4661    helpMenu->addAction(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_ABOUT)) + "...", mainwindow, SLOT(showAbout()));
4662    helpMenu->addAction("About Qt...", qApp, SLOT(aboutQt()));
4663 
4664    playlistWidget = new QFrame();
4665    playlistWidget->setLayout(new QVBoxLayout());
4666    playlistWidget->setObjectName("playlistWidget");
4667    playlistWidget->layout()->setContentsMargins(0, 0, 0, 0);
4668 
4669    playlistWidget->layout()->addWidget(mainwindow->playlistListWidget());
4670 
4671    browserWidget = new QFrame();
4672    browserWidget->setLayout(new QVBoxLayout());
4673    browserWidget->setObjectName("browserWidget");
4674    browserWidget->layout()->setContentsMargins(0, 0, 0, 0);
4675 
4676    browserDownloadsButton = new QPushButton(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_ASSETS_DIRECTORY));
4677    browserUpButton = new QPushButton(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_TAB_FILE_BROWSER_UP));
4678    browserStartButton = new QPushButton(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FAVORITES));
4679 
4680    QObject::connect(browserDownloadsButton, SIGNAL(clicked()), mainwindow, SLOT(onBrowserDownloadsClicked()));
4681    QObject::connect(browserUpButton, SIGNAL(clicked()), mainwindow, SLOT(onBrowserUpClicked()));
4682    QObject::connect(browserStartButton, SIGNAL(clicked()), mainwindow, SLOT(onBrowserStartClicked()));
4683 
4684    browserButtonsHBoxLayout = new QHBoxLayout();
4685    browserButtonsHBoxLayout->addWidget(browserUpButton);
4686    browserButtonsHBoxLayout->addWidget(browserStartButton);
4687    browserButtonsHBoxLayout->addWidget(browserDownloadsButton);
4688 
4689    qobject_cast<QVBoxLayout*>(browserWidget->layout())->addLayout(browserButtonsHBoxLayout);
4690    browserWidget->layout()->addWidget(mainwindow->dirTreeView());
4691 
4692    browserAndPlaylistTabWidget = mainwindow->browserAndPlaylistTabWidget();
4693    browserAndPlaylistTabWidget->setObjectName("browserAndPlaylistTabWidget");
4694 
4695    /* Several functions depend on the same tab title strings here, so if you change these, make sure to change those too
4696     * setCoreActions()
4697     * onTabWidgetIndexChanged()
4698     * onCurrentListItemChanged()
4699     */
4700    browserAndPlaylistTabWidget->addTab(playlistWidget, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_TAB_PLAYLISTS));
4701    browserAndPlaylistTabWidget->addTab(browserWidget, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_TAB_FILE_BROWSER));
4702 
4703    browserAndPlaylistTabDock = new QDockWidget(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_DOCK_CONTENT_BROWSER), mainwindow);
4704    browserAndPlaylistTabDock->setObjectName("browserAndPlaylistTabDock");
4705    browserAndPlaylistTabDock->setProperty("default_area", Qt::LeftDockWidgetArea);
4706    browserAndPlaylistTabDock->setProperty("menu_text", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_DOCK_CONTENT_BROWSER));
4707    browserAndPlaylistTabDock->setWidget(browserAndPlaylistTabWidget);
4708 
4709    mainwindow->addDockWidget(static_cast<Qt::DockWidgetArea>(browserAndPlaylistTabDock->property("default_area").toInt()), browserAndPlaylistTabDock);
4710 
4711    browserButtonsHBoxLayout->addItem(new QSpacerItem(browserAndPlaylistTabWidget->tabBar()->width(), 20, QSizePolicy::Expanding, QSizePolicy::Minimum));
4712 
4713    thumbnailWidget = new ThumbnailWidget(THUMBNAIL_TYPE_BOXART);
4714    thumbnailWidget->setObjectName("thumbnail");
4715 
4716    thumbnail2Widget = new ThumbnailWidget(THUMBNAIL_TYPE_TITLE_SCREEN);
4717    thumbnail2Widget->setObjectName("thumbnail2");
4718 
4719    thumbnail3Widget = new ThumbnailWidget(THUMBNAIL_TYPE_SCREENSHOT);
4720    thumbnail3Widget->setObjectName("thumbnail3");
4721 
4722    QObject::connect(thumbnailWidget, SIGNAL(filesDropped(const QImage&, ThumbnailType)), mainwindow, SLOT(onThumbnailDropped(const QImage&, ThumbnailType)));
4723    QObject::connect(thumbnail2Widget, SIGNAL(filesDropped(const QImage&, ThumbnailType)), mainwindow, SLOT(onThumbnailDropped(const QImage&, ThumbnailType)));
4724    QObject::connect(thumbnail3Widget, SIGNAL(filesDropped(const QImage&, ThumbnailType)), mainwindow, SLOT(onThumbnailDropped(const QImage&, ThumbnailType)));
4725 
4726    thumbnailDock = new QDockWidget(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_BOXART), mainwindow);
4727    thumbnailDock->setObjectName("thumbnailDock");
4728    thumbnailDock->setProperty("default_area", Qt::RightDockWidgetArea);
4729    thumbnailDock->setProperty("menu_text", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_BOXART));
4730    thumbnailDock->setWidget(thumbnailWidget);
4731 
4732    mainwindow->addDockWidget(static_cast<Qt::DockWidgetArea>(thumbnailDock->property("default_area").toInt()), thumbnailDock);
4733 
4734    thumbnail2Dock = new QDockWidget(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_TITLE_SCREEN), mainwindow);
4735    thumbnail2Dock->setObjectName("thumbnail2Dock");
4736    thumbnail2Dock->setProperty("default_area", Qt::RightDockWidgetArea);
4737    thumbnail2Dock->setProperty("menu_text", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_TITLE_SCREEN));
4738    thumbnail2Dock->setWidget(thumbnail2Widget);
4739 
4740    mainwindow->addDockWidget(static_cast<Qt::DockWidgetArea>(thumbnail2Dock->property("default_area").toInt()), thumbnail2Dock);
4741 
4742    thumbnail3Dock = new QDockWidget(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_SCREENSHOT), mainwindow);
4743    thumbnail3Dock->setObjectName("thumbnail3Dock");
4744    thumbnail3Dock->setProperty("default_area", Qt::RightDockWidgetArea);
4745    thumbnail3Dock->setProperty("menu_text", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_SCREENSHOT));
4746    thumbnail3Dock->setWidget(thumbnail3Widget);
4747 
4748    mainwindow->addDockWidget(static_cast<Qt::DockWidgetArea>(thumbnail3Dock->property("default_area").toInt()), thumbnail3Dock);
4749 
4750    mainwindow->tabifyDockWidget(thumbnailDock, thumbnail2Dock);
4751    mainwindow->tabifyDockWidget(thumbnailDock, thumbnail3Dock);
4752 
4753    /* when tabifying the dock widgets, the last tab added is selected by default, so we need to re-select the first tab */
4754    thumbnailDock->raise();
4755 
4756    coreSelectionWidget = new QWidget();
4757    coreSelectionWidget->setLayout(new QVBoxLayout());
4758 
4759    launchWithComboBox = mainwindow->launchWithComboBox();
4760 
4761    launchWithWidgetLayout = new QVBoxLayout();
4762 
4763    launchWithWidget = new QWidget();
4764    launchWithWidget->setLayout(launchWithWidgetLayout);
4765 
4766    coreComboBoxLayout = new QHBoxLayout();
4767 
4768    mainwindow->runPushButton()->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
4769    mainwindow->stopPushButton()->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
4770    mainwindow->startCorePushButton()->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
4771 
4772    coreComboBoxLayout->addWidget(launchWithComboBox);
4773    coreComboBoxLayout->addWidget(mainwindow->startCorePushButton());
4774    coreComboBoxLayout->addWidget(mainwindow->coreInfoPushButton());
4775    coreComboBoxLayout->addWidget(mainwindow->runPushButton());
4776    coreComboBoxLayout->addWidget(mainwindow->stopPushButton());
4777 
4778    mainwindow->stopPushButton()->hide();
4779 
4780    coreComboBoxLayout->setStretchFactor(launchWithComboBox, 1);
4781 
4782    launchWithWidgetLayout->addLayout(coreComboBoxLayout);
4783 
4784    coreSelectionWidget->layout()->addWidget(launchWithWidget);
4785 
4786    coreSelectionWidget->layout()->addItem(new QSpacerItem(20, browserAndPlaylistTabWidget->height(), QSizePolicy::Minimum, QSizePolicy::Expanding));
4787 
4788    coreSelectionDock = new QDockWidget(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CORE), mainwindow);
4789    coreSelectionDock->setObjectName("coreSelectionDock");
4790    coreSelectionDock->setProperty("default_area", Qt::LeftDockWidgetArea);
4791    coreSelectionDock->setProperty("menu_text", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CORE));
4792    coreSelectionDock->setWidget(coreSelectionWidget);
4793    coreSelectionDock->setFixedHeight(coreSelectionDock->minimumSizeHint().height());
4794 
4795    mainwindow->addDockWidget(static_cast<Qt::DockWidgetArea>(coreSelectionDock->property("default_area").toInt()), coreSelectionDock);
4796 
4797    mainwindow->splitDockWidget(browserAndPlaylistTabDock, coreSelectionDock, Qt::Vertical);
4798 
4799 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
4800    mainwindow->resizeDocks(QList<QDockWidget*>() << coreSelectionDock, QList<int>() << 1, Qt::Vertical);
4801 #endif
4802 
4803    if (qsettings->contains("all_playlists_list_max_count"))
4804       mainwindow->setAllPlaylistsListMaxCount(qsettings->value("all_playlists_list_max_count", 0).toInt());
4805 
4806    if (qsettings->contains("all_playlists_grid_max_count"))
4807       mainwindow->setAllPlaylistsGridMaxCount(qsettings->value("all_playlists_grid_max_count", 5000).toInt());
4808 
4809    if (qsettings->contains("thumbnail_cache_limit"))
4810       mainwindow->setThumbnailCacheLimit(qsettings->value("thumbnail_cache_limit", 500).toInt());
4811    else
4812       mainwindow->setThumbnailCacheLimit(500);
4813 
4814    if (qsettings->contains("geometry"))
4815       if (qsettings->contains("save_geometry"))
4816          mainwindow->restoreGeometry(qsettings->value("geometry").toByteArray());
4817 
4818    if (qsettings->contains("options_dialog_geometry"))
4819       mainwindow->viewOptionsDialog()->restoreGeometry(qsettings->value("options_dialog_geometry").toByteArray());
4820 
4821    if (qsettings->contains("save_dock_positions"))
4822       if (qsettings->contains("dock_positions"))
4823          mainwindow->restoreState(qsettings->value("dock_positions").toByteArray());
4824 
4825    if (qsettings->contains("file_browser_table_headers"))
4826       mainwindow->fileTableView()->horizontalHeader()->restoreState(qsettings->value("file_browser_table_headers").toByteArray());
4827    else
4828       mainwindow->fileTableView()->horizontalHeader()->resizeSection(0, 300);
4829 
4830    if (qsettings->contains("icon_view_zoom"))
4831       mainwindow->setIconViewZoom(qsettings->value("icon_view_zoom", 50).toInt());
4832 
4833    if (qsettings->contains("theme"))
4834    {
4835       QString themeStr = qsettings->value("theme").toString();
4836       MainWindow::Theme theme = mainwindow->getThemeFromString(themeStr);
4837 
4838       if (qsettings->contains("custom_theme") && theme == MainWindow::THEME_CUSTOM)
4839       {
4840          QString customThemeFilePath = qsettings->value("custom_theme").toString();
4841 
4842          mainwindow->setCustomThemeFile(customThemeFilePath);
4843       }
4844 
4845       mainwindow->setTheme(theme);
4846    }
4847    else
4848       mainwindow->setTheme();
4849 
4850    if (qsettings->contains("view_type"))
4851    {
4852       QString viewType = qsettings->value("view_type", "list").toString();
4853 
4854       if (viewType == "list")
4855          mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_LIST);
4856       else if (viewType == "icons")
4857          mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_ICONS);
4858       else
4859          mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_LIST);
4860    }
4861    else
4862       mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_LIST);
4863 
4864    if (qsettings->contains("icon_view_thumbnail_type"))
4865    {
4866       QString thumbnailType = qsettings->value("icon_view_thumbnail_type", "boxart").toString();
4867 
4868       if (thumbnailType == "boxart")
4869          mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_BOXART);
4870       else if (thumbnailType == "screenshot")
4871          mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_SCREENSHOT);
4872       else if (thumbnailType == "title")
4873          mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_TITLE_SCREEN);
4874       else
4875          mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_BOXART);
4876    }
4877 
4878    /* We make sure to hook up the tab widget callback only after the tabs themselves have been added,
4879     * but before changing to a specific one, to avoid the callback firing before the view type is set.
4880     */
4881    QObject::connect(browserAndPlaylistTabWidget, SIGNAL(currentChanged(int)), mainwindow, SLOT(onTabWidgetIndexChanged(int)));
4882 
4883    /* setting the last tab must come after setting the view type */
4884    if (qsettings->contains("save_last_tab"))
4885    {
4886       int lastTabIndex = qsettings->value("last_tab", 0).toInt();
4887 
4888       if (lastTabIndex >= 0 && browserAndPlaylistTabWidget->count() > lastTabIndex)
4889       {
4890          browserAndPlaylistTabWidget->setCurrentIndex(lastTabIndex);
4891          mainwindow->onTabWidgetIndexChanged(lastTabIndex);
4892       }
4893    }
4894    else
4895    {
4896       browserAndPlaylistTabWidget->setCurrentIndex(0);
4897       mainwindow->onTabWidgetIndexChanged(0);
4898    }
4899 
4900    /* the initial playlist that is selected is based on the user's setting (initialPlaylist) */
4901    for (i = 0; listWidget->count() && i < listWidget->count(); i++)
4902    {
4903       QString path;
4904       QListWidgetItem *item = listWidget->item(i);
4905 
4906       if (!item)
4907          continue;
4908 
4909       path = item->data(Qt::UserRole).toString();
4910 
4911       if (path == initialPlaylist)
4912       {
4913          foundPlaylist = true;
4914          listWidget->setRowHidden(i, false);
4915          listWidget->setCurrentRow(i);
4916          break;
4917       }
4918    }
4919 
4920    /* couldn't find the user's initial playlist, just find anything */
4921    if (!foundPlaylist)
4922    {
4923       for (i = 0; listWidget->count() && i < listWidget->count(); i++)
4924       {
4925          /* select the first non-hidden row */
4926          if (!listWidget->isRowHidden(i))
4927          {
4928             listWidget->setCurrentRow(i);
4929             break;
4930          }
4931       }
4932    }
4933 
4934    mainwindow->initContentTableWidget();
4935 
4936    return handle;
4937 }
4938 
ui_companion_qt_notify_content_loaded(void * data)4939 static void ui_companion_qt_notify_content_loaded(void *data) { }
4940 
ui_companion_qt_toggle(void * data,bool force)4941 static void ui_companion_qt_toggle(void *data, bool force)
4942 {
4943    static bool already_started = false;
4944    ui_companion_qt_t *handle   = (ui_companion_qt_t*)data;
4945    ui_window_qt_t *win_handle  = (ui_window_qt_t*)handle->window;
4946    settings_t *settings        = config_get_ptr();
4947    bool ui_companion_toggle    = settings->bools.ui_companion_toggle;
4948    bool video_fullscreen       = settings->bools.video_fullscreen;
4949    bool mouse_grabbed          = input_mouse_grabbed();
4950 
4951    if (ui_companion_toggle || force)
4952    {
4953       if (mouse_grabbed)
4954          command_event(CMD_EVENT_GRAB_MOUSE_TOGGLE, NULL);
4955       video_driver_show_mouse();
4956 
4957       if (video_fullscreen)
4958          command_event(CMD_EVENT_FULLSCREEN_TOGGLE, NULL);
4959 
4960       win_handle->qtWindow->activateWindow();
4961       win_handle->qtWindow->raise();
4962       win_handle->qtWindow->show();
4963 
4964       if (video_driver_started_fullscreen())
4965          win_handle->qtWindow->lower();
4966 
4967       if (!already_started)
4968       {
4969          already_started = true;
4970 
4971          if (win_handle->qtWindow->settings()->value(
4972                   "show_welcome_screen", true).toBool())
4973             win_handle->qtWindow->showWelcomeScreen();
4974       }
4975    }
4976 }
4977 
ui_companion_qt_event_command(void * data,enum event_command cmd)4978 static void ui_companion_qt_event_command(void *data, enum event_command cmd)
4979 {
4980    ui_companion_qt_t *handle  = (ui_companion_qt_t*)data;
4981    ui_window_qt_t *win_handle = (ui_window_qt_t*)handle->window;
4982 
4983    if (!handle)
4984       return;
4985 
4986    switch (cmd)
4987    {
4988       case CMD_EVENT_SHADERS_APPLY_CHANGES:
4989       case CMD_EVENT_SHADER_PRESET_LOADED:
4990 #if defined(HAVE_MENU)
4991 #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
4992          RARCH_LOG("[Qt]: Reloading shader parameters.\n");
4993          win_handle->qtWindow->deferReloadShaderParams();
4994 #endif
4995 #endif
4996          break;
4997       default:
4998          break;
4999    }
5000 }
5001 
ui_companion_qt_notify_list_pushed(void * data,file_list_t * list,file_list_t * menu_list)5002 static void ui_companion_qt_notify_list_pushed(void *data, file_list_t *list,
5003    file_list_t *menu_list) { }
5004 
ui_companion_qt_notify_refresh(void * data)5005 static void ui_companion_qt_notify_refresh(void *data)
5006 {
5007    ui_companion_qt_t *handle  = (ui_companion_qt_t*)data;
5008    ui_window_qt_t *win_handle = (ui_window_qt_t*)handle->window;
5009 
5010    win_handle->qtWindow->deferReloadPlaylists();
5011 }
5012 
ui_companion_qt_log_msg(void * data,const char * msg)5013 static void ui_companion_qt_log_msg(void *data, const char *msg)
5014 {
5015    ui_companion_qt_t *handle  = (ui_companion_qt_t*)data;
5016    ui_window_qt_t *win_handle = (ui_window_qt_t*)handle->window;
5017 
5018    win_handle->qtWindow->appendLogMessage(msg);
5019 }
5020 
ui_companion_qt_is_active(void * data)5021 static bool ui_companion_qt_is_active(void *data)
5022 {
5023    ui_companion_qt_t *handle  = (ui_companion_qt_t*)data;
5024    ui_window_qt_t *win_handle = (ui_window_qt_t*)handle->window;
5025 
5026    return win_handle->qtWindow->isVisible();
5027 }
5028 
ui_companion_qt_msg_queue_push(void * data,const char * msg,unsigned priority,unsigned duration,bool flush)5029 void ui_companion_qt_msg_queue_push(void *data,
5030       const char *msg, unsigned priority, unsigned duration, bool flush)
5031 {
5032    ui_companion_qt_t *handle  = (ui_companion_qt_t*)data;
5033    ui_window_qt_t *win_handle = NULL;
5034 
5035    if (!handle)
5036       return;
5037 
5038    win_handle                 = (ui_window_qt_t*)handle->window;
5039 
5040    if (win_handle)
5041       win_handle->qtWindow->showStatusMessage(msg, priority, duration, flush);
5042 }
5043 
5044 ui_companion_driver_t ui_companion_qt = {
5045    ui_companion_qt_init,
5046    ui_companion_qt_deinit,
5047    ui_companion_qt_toggle,
5048    ui_companion_qt_event_command,
5049    ui_companion_qt_notify_content_loaded,
5050    ui_companion_qt_notify_list_pushed,
5051    ui_companion_qt_notify_refresh,
5052    ui_companion_qt_msg_queue_push,
5053    NULL,
5054    NULL,
5055    ui_companion_qt_log_msg,
5056    ui_companion_qt_is_active,
5057    &ui_browser_window_qt,
5058    &ui_msg_window_qt,
5059    &ui_window_qt,
5060    &ui_application_qt,
5061    "qt",
5062 };
5063 
string_split_to_qt(QString str,char delim)5064 QStringList string_split_to_qt(QString str, char delim)
5065 {
5066    int at;
5067    QStringList list = QStringList();
5068 
5069    for (at = 0;;)
5070    {
5071       /* Find next split */
5072       int spl = str.indexOf(delim, at);
5073 
5074       /* Store split into list of extensions */
5075       list << str.mid(at, (spl < 0 ? -1 : spl - at));
5076 
5077       /* No more splits */
5078       if (spl < 0)
5079          break;
5080 
5081       at = spl + 1;
5082    }
5083 
5084    return list;
5085 }
5086 
5087 #define CORE_NAME_COLUMN    0
5088 #define CORE_VERSION_COLUMN 1
5089 
LoadCoreTableWidget(QWidget * parent)5090 LoadCoreTableWidget::LoadCoreTableWidget(QWidget *parent) :
5091    QTableWidget(parent)
5092 {
5093 }
5094 
keyPressEvent(QKeyEvent * event)5095 void LoadCoreTableWidget::keyPressEvent(QKeyEvent *event)
5096 {
5097    if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
5098    {
5099       event->accept();
5100       emit enterPressed();
5101    }
5102    else
5103       QTableWidget::keyPressEvent(event);
5104 }
5105 
LoadCoreWindow(QWidget * parent)5106 LoadCoreWindow::LoadCoreWindow(QWidget *parent) :
5107    QMainWindow(parent)
5108    ,m_layout()
5109    ,m_table(new LoadCoreTableWidget())
5110    ,m_statusLabel(new QLabel())
5111 {
5112    QHBoxLayout             *hbox = new QHBoxLayout();
5113    QPushButton *customCoreButton = new QPushButton(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOAD_CUSTOM_CORE));
5114 
5115    connect(customCoreButton, SIGNAL(clicked()), this, SLOT(onLoadCustomCoreClicked()));
5116    connect(m_table, SIGNAL(enterPressed()), this, SLOT(onCoreEnterPressed()));
5117    connect(m_table, SIGNAL(cellDoubleClicked(int,int)), this, SLOT(onCellDoubleClicked(int,int)));
5118 
5119    setWindowTitle(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOAD_CORE));
5120 
5121    setCentralWidget(new QWidget());
5122 
5123    centralWidget()->setLayout(&m_layout);
5124 
5125    hbox->addWidget(customCoreButton);
5126    hbox->addItem(new QSpacerItem(width(),
5127             20, QSizePolicy::Expanding, QSizePolicy::Minimum));
5128 
5129    m_layout.addWidget(m_table);
5130    m_layout.addLayout(hbox);
5131 
5132    statusBar()->addPermanentWidget(m_statusLabel);
5133 }
5134 
closeEvent(QCloseEvent * event)5135 void LoadCoreWindow::closeEvent(QCloseEvent *event)
5136 {
5137    emit windowClosed();
5138 
5139    QWidget::closeEvent(event);
5140 }
5141 
keyPressEvent(QKeyEvent * event)5142 void LoadCoreWindow::keyPressEvent(QKeyEvent *event)
5143 {
5144    if (event->key() == Qt::Key_Escape)
5145    {
5146       event->accept();
5147       close();
5148    }
5149    else
5150       QMainWindow::keyPressEvent(event);
5151 }
5152 
setStatusLabel(QString label)5153 void LoadCoreWindow::setStatusLabel(QString label)
5154 {
5155    m_statusLabel->setText(label);
5156 }
5157 
onCellDoubleClicked(int,int)5158 void LoadCoreWindow::onCellDoubleClicked(int, int)
5159 {
5160    onCoreEnterPressed();
5161 }
5162 
loadCore(const char * path)5163 void LoadCoreWindow::loadCore(const char *path)
5164 {
5165    QProgressDialog progress(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOADING_CORE), QString(), 0, 0, this);
5166    progress.setWindowTitle(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOAD_CORE));
5167    progress.setMinimumDuration(0);
5168    progress.setValue(progress.minimum());
5169    progress.show();
5170 
5171    /* Because core loading will block, we need to go ahead and process pending events that would allow the progress dialog to fully show its contents before actually starting the core loading process. Must call processEvents() twice. */
5172    qApp->processEvents();
5173    qApp->processEvents();
5174 
5175 #ifdef HAVE_DYNAMIC
5176    path_set(RARCH_PATH_CORE, path);
5177 
5178    command_event(CMD_EVENT_CORE_INFO_DEINIT, NULL);
5179    command_event(CMD_EVENT_CORE_INFO_INIT, NULL);
5180 
5181    core_info_init_current_core();
5182 
5183    if (!command_event(CMD_EVENT_LOAD_CORE, NULL))
5184    {
5185       QMessageBox::critical(this, msg_hash_to_str(MSG_ERROR), msg_hash_to_str(MSG_FAILED_TO_OPEN_LIBRETRO_CORE));
5186       return;
5187    }
5188 
5189    setProperty("last_launch_with_index", -1);
5190 
5191    emit coreLoaded();
5192 #endif
5193 }
5194 
onCoreEnterPressed()5195 void LoadCoreWindow::onCoreEnterPressed()
5196 {
5197    QByteArray pathArray;
5198    const char               *pathData = NULL;
5199    QTableWidgetItem *selectedCoreItem =
5200       m_table->item(m_table->currentRow(), CORE_NAME_COLUMN);
5201    QVariantHash                  hash = selectedCoreItem->data(
5202          Qt::UserRole).toHash();
5203    QString                       path = hash["path"].toString();
5204 
5205    pathArray.append(path);
5206    pathData                           = pathArray.constData();
5207 
5208    loadCore(pathData);
5209 }
5210 
onLoadCustomCoreClicked()5211 void LoadCoreWindow::onLoadCustomCoreClicked()
5212 {
5213    QString path;
5214    QByteArray pathArray;
5215    char core_ext[255]            = {0};
5216    char filters[PATH_MAX_LENGTH] = {0};
5217    const char *pathData          = NULL;
5218    settings_t *settings          = config_get_ptr();
5219    const char *path_dir_libretro = settings->paths.directory_libretro;
5220 
5221    frontend_driver_get_core_extension(core_ext, sizeof(core_ext));
5222 
5223    strlcpy(filters, "Cores (*.", sizeof(filters));
5224    strlcat(filters, core_ext, sizeof(filters));
5225    strlcat(filters, ");;All Files (*.*)", sizeof(filters));
5226 
5227    path                          = QFileDialog::getOpenFileName(
5228          this, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOAD_CORE),
5229          path_dir_libretro, filters, NULL);
5230 
5231    if (path.isEmpty())
5232       return;
5233 
5234    pathArray.append(path);
5235    pathData                      = pathArray.constData();
5236 
5237    loadCore(pathData);
5238 }
5239 
initCoreList(const QStringList & extensionFilters)5240 void LoadCoreWindow::initCoreList(const QStringList &extensionFilters)
5241 {
5242    int j;
5243    unsigned i;
5244    QStringList horizontal_header_labels;
5245    core_info_list_t *cores = NULL;
5246    QDesktopWidget *desktop = qApp->desktop();
5247    QRect desktopRect       = desktop->availableGeometry();
5248 
5249    horizontal_header_labels << msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NAME);
5250    horizontal_header_labels << msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CORE_VERSION);
5251 
5252    core_info_get_list(&cores);
5253 
5254    m_table->clear();
5255    m_table->setColumnCount(0);
5256    m_table->setRowCount(0);
5257    m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
5258    m_table->setSelectionMode(QAbstractItemView::SingleSelection);
5259    m_table->setSortingEnabled(false);
5260    m_table->setColumnCount(2);
5261    m_table->setHorizontalHeaderLabels(horizontal_header_labels);
5262 
5263    if (cores)
5264    {
5265       m_table->setRowCount(cores->count);
5266 
5267       for (i = 0; i < cores->count; i++)
5268       {
5269          QVariantHash hash;
5270          core_info_t              *core = core_info_get(cores, i);
5271          QTableWidgetItem    *name_item = NULL;
5272          QTableWidgetItem *version_item = new QTableWidgetItem(core->display_version);
5273          const char               *name = core->display_name;
5274 
5275          if (string_is_empty(name))
5276             name                        = path_basename(core->path);
5277 
5278          name_item                      = new QTableWidgetItem(name);
5279 
5280          hash["path"]                   = core->path;
5281          hash["extensions"]             = string_split_to_qt(QString(core->supported_extensions), '|');
5282 
5283          name_item->setData(Qt::UserRole, hash);
5284          name_item->setFlags(name_item->flags() & ~Qt::ItemIsEditable);
5285          version_item->setFlags(version_item->flags() & ~Qt::ItemIsEditable);
5286 
5287          m_table->setItem(i, CORE_NAME_COLUMN, name_item);
5288          m_table->setItem(i, CORE_VERSION_COLUMN, version_item);
5289       }
5290    }
5291 
5292    if (!extensionFilters.isEmpty())
5293    {
5294       QVector<int> rowsToHide;
5295 
5296       for (j = 0; j < m_table->rowCount(); j++)
5297       {
5298          int k;
5299          QVariantHash hash;
5300          QStringList extensions;
5301          bool             found = false;
5302          QTableWidgetItem *item = m_table->item(j, CORE_NAME_COLUMN);
5303 
5304          if (!item)
5305             continue;
5306 
5307          hash       = item->data(Qt::UserRole).toHash();
5308          extensions = hash["extensions"].toStringList();
5309 
5310          if (!extensions.isEmpty())
5311          {
5312             for (k = 0; k < extensions.size(); k++)
5313             {
5314                QString ext = extensions.at(k).toLower();
5315 
5316                if (extensionFilters.contains(ext, Qt::CaseInsensitive))
5317                {
5318                   found = true;
5319                   break;
5320                }
5321             }
5322 
5323             if (!found)
5324                rowsToHide.append(j);
5325          }
5326       }
5327 
5328       if (rowsToHide.size() != m_table->rowCount())
5329       {
5330          int i = 0;
5331 
5332          for (i = 0; i < rowsToHide.count() && rowsToHide.count() > 0; i++)
5333          {
5334             const int &row = rowsToHide.at(i);
5335             m_table->setRowHidden(row, true);
5336          }
5337       }
5338    }
5339 
5340    m_table->setSortingEnabled(true);
5341    m_table->resizeColumnsToContents();
5342    m_table->sortByColumn(0, Qt::AscendingOrder);
5343    m_table->selectRow(0);
5344    m_table->setAlternatingRowColors(true);
5345 
5346    resize(qMin(desktopRect.width(), contentsMargins().left() + m_table->horizontalHeader()->length() + contentsMargins().right()), height());
5347 }
5348