/* RetroArch - A frontend for libretro. * Copyright (C) 2011-2017 - Daniel De Matteis * Copyright (C) 2016-2019 - Brad Parker * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_qt.h" #include "qt/gridview.h" #include "qt/ui_qt_load_core_window.h" #include "qt/qt_dialogs.h" #ifndef CXX_BUILD extern "C" { #endif #include #include #include #include #include #ifdef Q_OS_UNIX #include #endif #ifdef HAVE_CONFIG_H #include "../../config.h" #endif #ifdef HAVE_MENU #include "../../menu/menu_driver.h" #endif #include "../../core_info.h" #include "../../command.h" #include "../ui_companion_driver.h" #include "../../configuration.h" #include "../../frontend/frontend.h" #include "../../frontend/frontend_driver.h" #include "../../file_path_special.h" #include "../../paths.h" #include "../../retroarch.h" #include "../../verbosity.h" #include "../../version.h" #include "../../msg_hash.h" #include "../../tasks/task_content.h" #include "../../tasks/tasks_internal.h" #include "../../AUTHORS.h" #ifdef HAVE_GIT_VERSION #include "../../version_git.h" #endif #ifdef HAVE_WAYLAND #include "../../gfx/common/wayland_common.h" #endif #ifndef CXX_BUILD } #endif #define INITIAL_WIDTH 1280 #define INITIAL_HEIGHT 720 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) #define GROUPED_DRAGGING QMainWindow::GroupedDragging #else #define GROUPED_DRAGGING static_cast(0) #endif #define TIMER_MSEC 1000 /* periodic timer for gathering statistics */ #define STATUS_MSG_THROTTLE_MSEC 250 #define GENERIC_FOLDER_ICON "/xmb/dot-art/png/folder.png" #define HIRAGANA_START 0x3041U #define HIRAGANA_END 0x3096U #define KATAKANA_START 0x30A1U #define KATAKANA_END 0x30F6U #define HIRA_KATA_OFFSET (KATAKANA_START - HIRAGANA_START) #define DOCS_URL "http://docs.libretro.com/" enum core_selection { CORE_SELECTION_CURRENT = 0, CORE_SELECTION_PLAYLIST_SAVED, CORE_SELECTION_PLAYLIST_DEFAULT, CORE_SELECTION_ASK, CORE_SELECTION_LOAD_CORE }; static AppHandler *app_handler; static ui_application_qt_t ui_application; /* %1 is a placeholder for palette(highlight) or the equivalent chosen by the user */ static const QString qt_theme_default_stylesheet = QStringLiteral(R"( QPushButton[flat="true"] { min-height:20px; min-width:80px; padding:1px 3px 1px 3px; background-color: transparent; border: 1px solid #ddd; } ThumbnailWidget#thumbnailWidget, ThumbnailLabel#thumbnailGridLabel, QLabel#thumbnailQLabel { background-color:#d4d4d4; } QLabel#dropIndicator { font-size: 9pt; color: darkgrey; border: 2px dashed lightgrey; border-radius: 5px; margin: 20px; } ThumbnailWidget#thumbnailWidgetSelected { background-color:#d4d4d4; border:3px solid %1; } QFrame#playlistWidget, QFrame#browserWidget, QFrame#logWidget { padding: 8px; } QListWidget { icon-size: 32px; } /* color of the icons on the settings dialog */ /* QLabel#iconColor { color: black; } */ )"); static const QString qt_theme_dark_stylesheet = QStringLiteral(R"( QWidget { color:white; background-color:rgb(53,53,53); selection-background-color:%1; } QWidget:disabled { color:rgb(127,127,127); } QFrame#playlistWidget, QFrame#browserWidget, QStackedWidget#centralWidget, QFrame#logWidget { padding: 8px; background-color:rgb(66,66,66); border-top:1px solid rgba(175,175,175,50%); border-left:1px solid rgba(125,125,125,50%); border-right:1px solid rgba(125,125,125,50%); border-bottom:1px solid rgba(25,25,25,75%); } QListWidget { icon-size: 32px; } QLabel#dropIndicator { font-size: 9pt; color: #575757; border: 2px dashed #575757; border-radius: 5px; margin: 20px; } QTextEdit, LogTextEdit { background-color:rgb(25,25,25); } QSpinBox, QDoubleSpinBox, QCheckBox, QRadioButton { background-color:rgb(25,25,25); } QCheckBox:checked, QCheckBox:unchecked, QRadioButton:checked, QRadioButton:unchecked { background-color:transparent; } /* Groupboxes for the settings window, can be restricted later with ViewOptionsDialog QGroupBox */ QGroupBox { background-color:rgba(80,80,80,50%); margin-top:27px; border:1px solid rgba(25,25,25,127); border-top-left-radius:0px; border-top-right-radius:4px; } QGroupBox::title { min-height:28px; subcontrol-origin:margin; subcontrol-position:left top; padding:4px 6px 5px 6px; margin-left:0px; 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)); border:1px solid rgba(25,25,25,127); border-bottom:1px solid rgb(65,65,65); border-top-left-radius:4px; border-top-right-radius:4px; } QGroupBox::indicator:checked { background-color:%1; border:4px solid rgb(45,45,45); } QGroupBox::indicator:unchecked { background-color:rgba(25,25,25,50%); } QGroupBox::indicator { width:16px; height:16px; } QWidget#shaderParamsWidget { background-color:rgb(25,25,25); } QDialog#shaderParamsDialog QGroupBox { background-color:rgb(53,53,53); border-top-left-radius:0px; } QDialog#shaderParamsDialog QGroupBox::title { margin-left:0px; min-height:28px; padding:4px 10px; background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgb(53,53,53),stop:1 rgba(125,125,125,127)); border:1px solid rgba(25,25,25,75); border-top:1px solid rgba(175,175,175,50%); border-bottom:none transparent; } QToolTip { color:white; background-color:rgb(53,53,53); border:1px solid rgb(80,80,80); border-radius:4px; } QMenuBar { 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)); border-bottom:2px solid rgba(25,25,25,75); } QMenuBar::item { spacing:2px; padding:3px 4px; background-color:transparent; } QMenuBar::item:selected { 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)); border:1px solid %1; } QMenuBar::item:pressed { background-color:%1; border-left:1px solid rgba(25,25,25,127); border-right:1px solid rgba(25,25,25,127); } QMenu { background-color:rgb(45,45,45); border:1px solid palette(shadow); } QMenu::item { padding:3px 25px 3px 25px; border:1px solid transparent; } QMenu::item:disabled { color:rgb(127,127,127); } QMenu::item:selected { border-color:rgba(200,200,200,127); background-color:%1; } QMenu::icon:checked { 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)); border:1px solid %1; border-radius:2px; } QMenu::separator { height:1px; background-color:rgb(100,100,100); margin-left:5px; margin-right:5px; } QMenu::indicator { width:18px; height:18px; } QToolBar::top { 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)); 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)); } QToolBar::bottom { 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)); 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)); } QToolBar::left { 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)); 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)); } QToolBar::right { 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)); 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)); } QMainWindow { background-color:rgb(53,53,53); } QMainWindow::separator { width:6px; height:5px; padding:2px; background-color:rgba(25,25,25,50%); } QLineEdit { color:white; background-color:rgb(25,25,25); } QLineEdit::focus { border:1px solid %1; border-radius:3px; color:white; background-color:rgb(25,25,25); } QSplitter::handle:horizontal { width:10px; } QSplitter::handle:vertical { height:10px; } QMainWindow::separator:hover, QSplitter::handle:hover { } QDockWidget { font-family:"Segoe UI"; font-size:9pt; } QDockWidget::title { padding:3px 4px; 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)); border:1px solid rgba(25,25,25,75); border-top:1px solid rgba(175,175,175,50%); border-bottom:1px solid rgba(25,25,25,127); } QDockWidget::close-button, QDockWidget::float-button { subcontrol-position:top right; subcontrol-origin:margin; position:absolute; top:3px; bottom:0px; width:20px; height:20px; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { border:1px solid %1; border-radius:4px; } QDockWidget::close-button { right:3px; } QDockWidget::float-button { right:25px; } QTabWidget::pane { background-color:rgba(66,66,66,50%); } QTabWidget::tab-bar { } QTabBar { background-color:transparent; qproperty-drawBase:0; border-bottom:1px solid rgba(25,25,25,50%); } QTabBar::tab { padding:4px 6px; background-color:rgba(25,25,25,127); border:1px solid rgba(25,25,25,75); } QTabBar::tab:selected { background-color:rgb(66,66,66); border-bottom-color:rgba(66,66,66,75%); } QTabBar::tab:!selected { color:rgb(175,175,175); } QComboBox { min-height:20px; padding:1px 6px 1px 6px; } QComboBox::focus { background:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgba(255,255,255,50), stop: 1 rgba(100,100,100,25)); border:1px solid %1; border-radius:4px; } QComboBox::hover { 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)); border:1px solid %1; border-radius:4px; } QComboBox::drop-down { background-color:transparent; width:0px; } QComboBox::selected:on, QComboBox::selected:off { background-color:%1; } QTabBar::tab:hover { color:white; background-color:%1; } QComboBox::separator { background-color:rgb(100,100,100); height:1px; margin-left:4px; margin-right:4px; } QCheckBox::indicator { width:18px; height:18px; } QPushButton { min-height:20px; min-width:80px; padding:1px 3px 1px 3px; outline:none; } QPushButton::disabled, QToolButton::disabled { color:grey; background-color:rgb(25,25,25); } QPushButton::focus, QToolButton::focus { background:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgba(255,255,255,50), stop: 1 rgba(100,100,100,25)); border:1px solid %1; border-radius:4px; } QPushButton::hover, QToolButton::hover { 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)); border:1px solid %1; border-radius:4px; } QPushButton::pressed, QToolButton::pressed { background-color:transparent; border:1px solid %1; border-radius:4px; } QPushButton[flat="true"] { background-color:transparent; } QPushButton[flat="true"]::menu-indicator { position:relative; bottom:4px; right:4px; } QRadioButton::indicator { width:18px; height:18px; } QListWidget::item:selected, QTreeView::item:selected, QTableView::item:selected { color:white; background-color:%1; } QTreeView { background-color:rgb(25,25,25); selection-background-color:%1; } QTreeView::branch:selected { background-color:%1; } QTreeView::item:selected:disabled, QTableView::item:selected:disabled { background-color:rgb(80,80,80); } QTreeView::branch:open, QTreeView::branch:closed { background-color:solid; } QTableView, QListWidget { background-color:rgb(25,25,25); } QTreeView QHeaderView::section, QTableView QHeaderView::section { /*height:24px;*/ 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)); border-style:none; border-bottom:1px solid rgb(65,65,65); padding-left:5px; padding-right:5px; } QTableView { background-color:rgb(25,25,25); alternate-background-color:rgb(40,40,40); } QScrollBar:vertical, QScrollBar:horizontal { background-color:rgb(35,35,35); } QScrollBar::handle:vertical, QScrollBar::handle:horizontal { background-color:rgb(65,65,65); border-right:1px solid rgba(175,175,175,50%); border-top:1px solid rgba(175,175,175,50%); border-bottom:1px solid rgba(25,25,25,75); border-radius:2px; } QScrollBar::handle:horizontal:hover, QScrollBar::handle:vertical:hover { border:1px solid %1; 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)); } QScrollBar:vertical { border-top-right-radius:2px; border-bottom-right-radius:2px; width:16px; margin:0px; } QScrollBar::handle:vertical { min-height:20px; margin:2px 4px 2px 4px; } QScrollBar::add-line:vertical { background:none; height:0px; subcontrol-position:right; subcontrol-origin:margin; } QScrollBar::sub-line:vertical { background:none; height:0px; subcontrol-position:left; subcontrol-origin:margin; } QScrollBar:horizontal { height:16px; margin:0px; } QScrollBar::handle:horizontal { min-width:20px; margin:4px 2px 4px 2px; } QScrollBar::add-line:horizontal { background:none; width:0px; subcontrol-position:bottom; subcontrol-origin:margin; } QScrollBar::sub-line:horizontal { background:none; width:0px; subcontrol-position:top; subcontrol-origin:margin; } QSlider { background:transparent; } QSlider::sub-page { background:%1; } QSlider::groove:vertical { width:3px; background:rgb(25,25,25); } QSlider::handle:vertical { background:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgb(175,175,175), stop: 1 rgb(75,75,75)); border:1px solid rgb(35,35,35); border-radius:2px; height:16px; margin:0 -4px; } QSlider::handle:vertical:hover { background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgb(200,200,200), stop: 1 rgba(100,100,100)); border:1px solid %1; border-radius:2px; height:16px; margin:0 -4px; } QSlider::groove:horizontal { height:3px; background:rgb(25,25,25); } QSlider::handle:horizontal { background:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgb(175,175,175), stop: 1 rgb(75,75,75)); border:1px solid rgb(35,35,35); border-radius:2px; width:16px; margin:-4px 0; } QSlider::handle:horizontal:hover { background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 rgb(200,200,200), stop: 1 rgba(100,100,100)); border:1px solid %1; border-radius:2px; width:16px; margin:-4px 0; } QStatusBar { color:white; 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)); } QStatusBar QLabel { background-color:transparent; } QLabel { background-color:transparent; } QSizeGrip { background-color:solid; } GridView::item { background-color:rgb(40,40,40); } GridView::item:selected { border:3px solid %1; } GridView { background-color:rgb(25,25,25); selection-color: white; qproperty-layout: "fixed"; } GridItem { qproperty-thumbnailvalign: "center"; } QLabel#itemsCountLabel { padding-left: 5px; } )"); /* ARGB 16x16 */ static const unsigned retroarch_qt_icon_data[] = { 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, 0x00000000,0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,0x00000000, 0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000, 0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000, 0x00000000,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000, 0x00000000,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0x00000000,0x00000000, 0x00000000,0xff333333,0xfff2f2f2,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0xfff2f2f2,0xff333333,0x00000000,0x00000000, 0x00000000,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0x00000000,0x00000000, 0x00000000,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0x00000000,0x00000000, 0x00000000,0xff333333,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0xff333333,0x00000000,0x00000000, 0x00000000,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000, 0x00000000,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0x00000000,0x00000000, 0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000, 0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000, 0x00000000,0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,0x00000000, 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 }; static unsigned char invader_png[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x44, 0x08, 0x06, 0x00, 0x00, 0x00, 0xac, 0xf5, 0x3a, 0x40, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x71, 0xc9, 0x65, 0x3c, 0x00, 0x00, 0x0f, 0x4a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x5d, 0x79, 0x50, 0x95, 0xd7, 0x15, 0xbf, 0x6f, 0xe3, 0xf1, 0x58, 0xa2, 0x28, 0x8a, 0xec, 0x82, 0x58, 0x8c, 0xc6, 0x25, 0xa8, 0xc4, 0xa5, 0x21, 0x1a, 0x97, 0xd6, 0x50, 0x26, 0xd5, 0x6a, 0x3b, 0x6d, 0x4d, 0xeb, 0x42, 0x27, 0x13, 0xd3, 0x24, 0xda, 0x31, 0xa6, 0x63, 0xa7, 0xda, 0x3f, 0xda, 0xce, 0x44, 0x27, 0xc6, 0x24, 0xd3, 0x4c, 0x26, 0xea, 0x38, 0x4d, 0xc6, 0x4e, 0x4d, 0x62, 0x27, 0x13, 0x71, 0xa3, 0x46, 0x31, 0x46, 0x4c, 0x80, 0x04, 0x0b, 0xc4, 0x22, 0x8a, 0x82, 0x86, 0x25, 0x8a, 0x02, 0x0a, 0xc8, 0xe3, 0x6d, 0x3d, 0x3f, 0x38, 0xcf, 0x12, 0xf3, 0xbe, 0xe5, 0xc1, 0x7b, 0xbc, 0x4f, 0x7c, 0x67, 0xe6, 0x37, 0xe3, 0x72, 0xdf, 0xf7, 0xdd, 0xef, 0xfc, 0xee, 0x3d, 0xf7, 0x9c, 0x73, 0x37, 0x9d, 0xcb, 0xe5, 0x12, 0x1a, 0x12, 0x3d, 0x61, 0x24, 0x21, 0x96, 0x60, 0x50, 0x28, 0xdb, 0x45, 0xa8, 0x23, 0x5c, 0x0f, 0x40, 0x3d, 0x43, 0x08, 0xc9, 0x84, 0x07, 0x08, 0x3a, 0x85, 0xb2, 0x6d, 0x84, 0xcb, 0x84, 0x0e, 0xad, 0x28, 0xd9, 0x28, 0xb4, 0x25, 0xe1, 0x84, 0x25, 0x84, 0xe5, 0x84, 0x30, 0x15, 0xca, 0x2c, 0x20, 0xec, 0x26, 0x54, 0x0f, 0x30, 0xe1, 0x8b, 0x08, 0x2b, 0x98, 0x78, 0xbd, 0x4c, 0x59, 0xf4, 0xa8, 0x72, 0xc2, 0x36, 0x42, 0x19, 0xc1, 0x19, 0x24, 0xfd, 0xbb, 0x82, 0xde, 0xd0, 0xcc, 0xbd, 0x3d, 0xa5, 0xa9, 0xa9, 0x49, 0x77, 0xfb, 0xf6, 0x6d, 0x8f, 0x05, 0x87, 0x0e, 0x1d, 0xea, 0x8a, 0x8c, 0x8c, 0x4c, 0xe5, 0x86, 0xf2, 0x0a, 0xe1, 0xeb, 0x01, 0xd2, 0xd7, 0x42, 0xc2, 0xf3, 0x84, 0x99, 0xf5, 0xf5, 0xf5, 0xa1, 0x0e, 0x87, 0xc3, 0x63, 0xc1, 0x51, 0xa3, 0x46, 0x09, 0x93, 0xc9, 0xd4, 0x4e, 0x7f, 0x2c, 0x21, 0xdc, 0xd0, 0x0a, 0xe1, 0x3d, 0x4d, 0x91, 0xcc, 0xbb, 0xc6, 0x30, 0x9c, 0xb0, 0x8d, 0x70, 0x35, 0x3f, 0x3f, 0xdf, 0x49, 0x8a, 0x73, 0x71, 0x8f, 0xf9, 0x16, 0xe2, 0xe3, 0xe3, 0x5d, 0xe7, 0xcf, 0x9f, 0x77, 0x50, 0xb9, 0xcb, 0x84, 0xdf, 0x13, 0x22, 0xfc, 0x5c, 0x2f, 0x1d, 0x61, 0x21, 0xe1, 0xa8, 0xd3, 0xe9, 0x6c, 0xdf, 0xb8, 0x71, 0xa3, 0xc7, 0x7a, 0x01, 0x4b, 0x97, 0x2e, 0x75, 0x51, 0x19, 0x2b, 0x95, 0x3d, 0x41, 0x98, 0xc9, 0xbf, 0xd5, 0x8c, 0x8e, 0xb5, 0x48, 0x3a, 0x30, 0x9e, 0xf0, 0x4f, 0x42, 0xeb, 0x8e, 0x1d, 0x3b, 0x9c, 0x52, 0xca, 0x9d, 0x32, 0x65, 0x8a, 0xab, 0xb3, 0xb3, 0xd3, 0x4e, 0xe5, 0xbe, 0x22, 0x2c, 0x27, 0x18, 0xfd, 0x58, 0xa7, 0x34, 0xae, 0x53, 0xfb, 0x9b, 0x6f, 0xbe, 0x29, 0x59, 0xa7, 0xd1, 0xa3, 0x47, 0xbb, 0x9a, 0x9b, 0x9b, 0x51, 0xa7, 0xb3, 0x84, 0x95, 0x04, 0x83, 0xd6, 0xf4, 0xab, 0x55, 0xd2, 0xa1, 0xa8, 0x2c, 0xc2, 0x01, 0xc2, 0xed, 0x67, 0x9f, 0x7d, 0x56, 0xb2, 0x57, 0xcd, 0x9f, 0x3f, 0xdf, 0xd5, 0xd6, 0xd6, 0xd6, 0xc5, 0xbd, 0xea, 0x07, 0x7e, 0x22, 0x3e, 0x81, 0xad, 0x4f, 0xd3, 0xc1, 0x83, 0x07, 0x9d, 0x61, 0x61, 0x61, 0x1e, 0xeb, 0x12, 0x17, 0x17, 0xe7, 0xaa, 0xae, 0xae, 0x76, 0x52, 0xb9, 0x1a, 0xc2, 0x1f, 0xd9, 0x6a, 0x89, 0x20, 0xe9, 0xea, 0xa1, 0x27, 0x2c, 0x25, 0x94, 0x50, 0x6f, 0xee, 0x4a, 0x4f, 0x4f, 0x97, 0x24, 0x3e, 0x37, 0x37, 0x97, 0x8a, 0xb9, 0xda, 0x08, 0x1f, 0x11, 0xbe, 0xef, 0xe3, 0x7a, 0x0c, 0x23, 0xfc, 0x01, 0xc3, 0x48, 0x71, 0x71, 0xb1, 0xe4, 0x70, 0x03, 0x1c, 0x38, 0x70, 0x00, 0x84, 0xdf, 0x20, 0xfc, 0x8d, 0x10, 0xa7, 0x55, 0xdd, 0x6a, 0x99, 0x74, 0x20, 0x92, 0xf0, 0x5b, 0x42, 0x55, 0x49, 0x49, 0x89, 0x23, 0x22, 0x22, 0xc2, 0xa3, 0xb2, 0x75, 0x3a, 0x9d, 0x6b, 0xf7, 0xee, 0xdd, 0x50, 0xf8, 0x4d, 0xc2, 0x5e, 0xc2, 0x18, 0x1f, 0xbd, 0xdf, 0x42, 0x78, 0x1e, 0xa6, 0xba, 0xb1, 0xb1, 0xd1, 0x96, 0x9a, 0x9a, 0x2a, 0x49, 0x38, 0xac, 0x11, 0xbf, 0xff, 0x1f, 0x84, 0x87, 0xb5, 0xac, 0x57, 0xad, 0x93, 0x0e, 0x3c, 0xc0, 0x8e, 0xda, 0xd5, 0xa2, 0xa2, 0x22, 0xd7, 0x90, 0x21, 0x43, 0x3c, 0x2a, 0x1d, 0x3d, 0xf0, 0xdd, 0x77, 0xdf, 0x85, 0xe2, 0x9b, 0x09, 0xaf, 0x13, 0x52, 0xfb, 0xf9, 0xde, 0x10, 0xc2, 0x2f, 0x08, 0xe7, 0x5a, 0x5b, 0x5b, 0xed, 0x99, 0x99, 0x99, 0x92, 0x84, 0x2f, 0x5b, 0xb6, 0xcc, 0xd5, 0xd5, 0xd5, 0x65, 0xe3, 0xe1, 0x28, 0x53, 0x6b, 0x8e, 0xdb, 0xbd, 0x48, 0xba, 0x60, 0x02, 0x3f, 0x21, 0x38, 0x76, 0xee, 0xdc, 0x29, 0xa9, 0x7c, 0xf4, 0x78, 0x78, 0xfc, 0x54, 0xae, 0x9e, 0xb0, 0x95, 0x10, 0xdb, 0xc7, 0xf7, 0xc1, 0x2f, 0xc8, 0x26, 0x1c, 0xb3, 0x5a, 0xad, 0x5d, 0xb3, 0x67, 0xcf, 0x96, 0x7c, 0xe7, 0xb4, 0x69, 0xd3, 0x5c, 0x36, 0x9b, 0xcd, 0xdd, 0xd8, 0x9e, 0xd1, 0x3a, 0xe1, 0x80, 0x4e, 0x45, 0x46, 0x0e, 0xb1, 0x69, 0x34, 0x61, 0x98, 0x42, 0x96, 0x0c, 0x31, 0x69, 0x23, 0xe1, 0x36, 0x2b, 0x44, 0x4a, 0x90, 0xc5, 0x1a, 0x45, 0x30, 0x7b, 0x11, 0x59, 0xc6, 0x11, 0xfe, 0x4c, 0xc8, 0xc0, 0x58, 0x4f, 0x3d, 0x4b, 0xec, 0xdb, 0xb7, 0x4f, 0x32, 0x3e, 0xfe, 0xec, 0xb3, 0xcf, 0x9c, 0xc9, 0xc9, 0xc9, 0xc8, 0x82, 0xed, 0x21, 0xec, 0xf7, 0x32, 0x1b, 0x86, 0x6f, 0x9c, 0xcc, 0xc9, 0x97, 0x4c, 0xf2, 0x17, 0xc2, 0x76, 0xed, 0xda, 0xe5, 0xb1, 0x20, 0x59, 0x1d, 0xf1, 0xf9, 0xe7, 0x9f, 0x0b, 0xf2, 0x37, 0xf0, 0xd7, 0x5b, 0x9c, 0x84, 0xd9, 0xcf, 0xd9, 0x42, 0x35, 0x82, 0x28, 0xa0, 0x95, 0xf0, 0x0d, 0xc1, 0xa6, 0xc0, 0x41, 0x0c, 0x21, 0x4a, 0x21, 0x03, 0xe8, 0xe6, 0x40, 0xf6, 0x7b, 0xd5, 0x90, 0x8e, 0x04, 0xc8, 0x6a, 0xc2, 0x0c, 0x82, 0x49, 0xa6, 0x5c, 0x0d, 0xe1, 0x2d, 0x42, 0x11, 0xc1, 0x2e, 0x51, 0x26, 0x94, 0xf0, 0x24, 0xe1, 0xe7, 0xdc, 0x88, 0xd4, 0x88, 0x8e, 0x1b, 0xca, 0x18, 0x4e, 0xc4, 0x88, 0x96, 0x96, 0x16, 0x41, 0xe1, 0x9a, 0xa8, 0xad, 0xad, 0xf5, 0xf8, 0x83, 0x47, 0x1e, 0x79, 0x44, 0x7c, 0xfa, 0xe9, 0xa7, 0x4e, 0xa3, 0xd1, 0x88, 0xa4, 0xc8, 0x25, 0x42, 0xa7, 0x97, 0x09, 0x18, 0xa4, 0x81, 0xe3, 0xc8, 0x4f, 0x30, 0xad, 0x5a, 0xb5, 0x4a, 0x52, 0xc9, 0x68, 0x0c, 0xf4, 0xff, 0xee, 0xbf, 0x3a, 0x58, 0xe1, 0x57, 0x14, 0x08, 0xbc, 0x9b, 0xf4, 0x33, 0x84, 0xb7, 0x09, 0x67, 0x65, 0xca, 0xa1, 0x3e, 0xcf, 0x10, 0xb2, 0x14, 0x32, 0x80, 0x48, 0x50, 0xbd, 0x43, 0x38, 0x26, 0xd7, 0xf0, 0x8c, 0x2a, 0x5a, 0x3d, 0xc8, 0x5e, 0x56, 0x51, 0x51, 0x91, 0x52, 0x53, 0x53, 0xe3, 0x51, 0x01, 0xd4, 0xab, 0xc4, 0xc4, 0x89, 0x13, 0x13, 0xe8, 0x8f, 0x87, 0x99, 0x74, 0x29, 0x49, 0x02, 0xe1, 0xed, 0xed, 0xed, 0x0b, 0x8f, 0x1f, 0x3f, 0x1e, 0xe2, 0x05, 0x11, 0xba, 0xbb, 0x3f, 0x76, 0xed, 0xda, 0xb5, 0xe2, 0xc5, 0x17, 0x5f, 0x14, 0x76, 0xfb, 0x77, 0xdb, 0x17, 0x7a, 0xdf, 0xe2, 0xc5, 0x8b, 0xf5, 0x4f, 0x3f, 0xfd, 0xb4, 0xdb, 0x42, 0xb9, 0xbc, 0x7c, 0x97, 0xee, 0xda, 0xb5, 0x6b, 0xba, 0x17, 0x5e, 0x78, 0x41, 0xb2, 0x50, 0x46, 0x46, 0x86, 0x18, 0x31, 0x62, 0x84, 0xc8, 0xcb, 0xcb, 0xeb, 0xad, 0xab, 0x38, 0x26, 0x48, 0xd5, 0xfb, 0x62, 0x62, 0x62, 0xc4, 0xf4, 0xe9, 0xd3, 0x53, 0x39, 0x8d, 0x0c, 0x58, 0x25, 0x8a, 0x5a, 0x08, 0x53, 0x6e, 0xdd, 0xba, 0x35, 0xeb, 0xc4, 0x89, 0x13, 0x92, 0xa4, 0x2f, 0x58, 0xb0, 0xa0, 0xdd, 0x6c, 0x36, 0x83, 0xec, 0xaf, 0xb8, 0xf1, 0xf5, 0x29, 0x23, 0x07, 0xef, 0xf9, 0x2f, 0x84, 0x96, 0xac, 0xac, 0x2c, 0xc9, 0x71, 0x6d, 0xcb, 0x96, 0x2d, 0x18, 0x47, 0xff, 0x43, 0x98, 0xab, 0xf0, 0xbc, 0x1f, 0x11, 0x2a, 0xc9, 0x34, 0x4b, 0x26, 0x37, 0xee, 0x27, 0xa4, 0xa4, 0xa4, 0xc0, 0x17, 0xe8, 0x20, 0xec, 0x24, 0x8c, 0x90, 0xd1, 0x5b, 0x14, 0xe1, 0xed, 0x9b, 0x37, 0x6f, 0x5a, 0xf5, 0x7a, 0xbd, 0xe4, 0xf3, 0xc8, 0xba, 0x21, 0x43, 0x59, 0xc2, 0x59, 0x40, 0x49, 0x1e, 0xf4, 0x0a, 0x8d, 0x11, 0xbd, 0x64, 0x1c, 0xb5, 0x30, 0x0b, 0x8d, 0x93, 0x92, 0x85, 0x66, 0xcc, 0x98, 0xe1, 0xe2, 0x96, 0x55, 0x2b, 0xf3, 0x2c, 0x98, 0xf6, 0x71, 0x84, 0xe1, 0x07, 0x0f, 0x1e, 0xd4, 0x89, 0xa0, 0x88, 0x4b, 0x97, 0x2e, 0x61, 0x88, 0x82, 0xc5, 0x4b, 0x63, 0x3f, 0x47, 0x4a, 0xe0, 0x2f, 0x9c, 0x8f, 0x8c, 0x8c, 0xb4, 0xce, 0x9a, 0x35, 0x4b, 0xb2, 0x10, 0x71, 0xa4, 0xe7, 0xe7, 0x8c, 0x55, 0x9a, 0xca, 0x94, 0x93, 0xd1, 0xa8, 0x10, 0xb5, 0x20, 0x23, 0x85, 0x24, 0x1e, 0x0b, 0x50, 0x45, 0x30, 0x86, 0xe2, 0x3f, 0xff, 0xcb, 0x0e, 0x89, 0x94, 0xc0, 0x09, 0x99, 0xe4, 0x70, 0x38, 0x22, 0x3e, 0xfc, 0xf0, 0xc3, 0x20, 0xe3, 0x2c, 0x05, 0x05, 0x05, 0x06, 0x9e, 0xad, 0xfb, 0x9e, 0x4c, 0x31, 0x8c, 0x61, 0x15, 0x98, 0x8c, 0x82, 0x13, 0x2b, 0x25, 0x64, 0xfa, 0xbb, 0xe7, 0xa2, 0x08, 0x13, 0xb9, 0x93, 0xf5, 0x89, 0x74, 0xb4, 0x98, 0x91, 0xdc, 0x82, 0x3c, 0xca, 0xdc, 0xb9, 0x73, 0x45, 0x48, 0x48, 0x08, 0xbc, 0xc6, 0x73, 0x0a, 0x5e, 0x23, 0xbc, 0xcf, 0xd4, 0xb3, 0x67, 0xcf, 0x9a, 0xae, 0x5f, 0xbf, 0x1e, 0x64, 0xfb, 0xff, 0xbd, 0xd3, 0x4d, 0x54, 0xba, 0x42, 0x74, 0x04, 0x87, 0xb4, 0xf1, 0x89, 0x27, 0x9e, 0x70, 0xca, 0x3d, 0x8b, 0xcc, 0xb7, 0x99, 0x9d, 0xde, 0xc8, 0xbe, 0x90, 0x6e, 0xe1, 0x8a, 0x44, 0xc2, 0x31, 0x92, 0x92, 0xec, 0xec, 0x6c, 0xc1, 0xd3, 0xa1, 0xe7, 0x15, 0x1c, 0x18, 0x98, 0xb0, 0x78, 0xaa, 0x98, 0x21, 0x48, 0xf5, 0xb7, 0x9d, 0x4e, 0x5e, 0x3b, 0x80, 0x9e, 0x1e, 0x21, 0x53, 0xb4, 0x09, 0xc3, 0x67, 0x5a, 0x5a, 0x9a, 0x23, 0x31, 0x31, 0xd1, 0x63, 0x01, 0x72, 0x3e, 0x45, 0x59, 0x59, 0x99, 0xdb, 0x72, 0x8c, 0xea, 0x0b, 0xe9, 0x68, 0x29, 0x48, 0x67, 0x9a, 0xe5, 0x48, 0xe7, 0x96, 0x57, 0x2f, 0xeb, 0x2d, 0xf6, 0xb4, 0x60, 0x7c, 0xd4, 0x50, 0x39, 0xdf, 0xe0, 0x7e, 0x14, 0x22, 0x49, 0x90, 0x83, 0x86, 0x28, 0x2a, 0x45, 0x21, 0x8c, 0xbd, 0xc9, 0x1d, 0xcb, 0x8a, 0x90, 0x54, 0x4a, 0xd8, 0x5f, 0x8a, 0xe1, 0xa1, 0xd9, 0x6b, 0xd2, 0xd1, 0x52, 0x92, 0x8b, 0x8b, 0x8b, 0x0d, 0x88, 0x8b, 0x3d, 0xc9, 0x98, 0x31, 0x63, 0x44, 0x42, 0x42, 0x82, 0x9d, 0x4d, 0xcf, 0x0d, 0x99, 0x67, 0x45, 0x30, 0xe9, 0x61, 0x72, 0x0d, 0xe8, 0x7e, 0x14, 0x2c, 0xc2, 0xc8, 0xcf, 0xcf, 0xd7, 0x73, 0xb8, 0x97, 0x28, 0x53, 0xb4, 0x8b, 0x87, 0xd0, 0x56, 0x72, 0x9c, 0xe5, 0x48, 0x77, 0x27, 0xc0, 0xc6, 0x49, 0x85, 0xe4, 0x46, 0x05, 0x27, 0x2e, 0xe6, 0xc0, 0x81, 0x03, 0x3a, 0x19, 0xaf, 0xdd, 0xbd, 0xda, 0xa5, 0x4a, 0xf4, 0x2c, 0x5f, 0x92, 0x92, 0xe1, 0xbc, 0x12, 0xc6, 0x48, 0x63, 0xba, 0xac, 0x12, 0x28, 0xd6, 0x14, 0x7b, 0xf7, 0xee, 0x15, 0x3c, 0x54, 0x38, 0x65, 0x86, 0x0c, 0x28, 0x4a, 0xdf, 0xdc, 0xdc, 0xdc, 0xdd, 0xf8, 0x06, 0x4a, 0x96, 0x2c, 0x59, 0x22, 0x76, 0xee, 0xdc, 0xa9, 0xba, 0x7e, 0xc7, 0x8e, 0x1d, 0x13, 0x4b, 0x97, 0x2e, 0x95, 0x7d, 0x26, 0xe9, 0x18, 0x65, 0xa2, 0xd8, 0x87, 0xfa, 0x44, 0xa6, 0x28, 0x7a, 0xfa, 0x37, 0xa4, 0xf7, 0x58, 0xa9, 0x0e, 0x5b, 0x58, 0x58, 0x28, 0x28, 0xda, 0x32, 0x93, 0x83, 0x3d, 0x96, 0xad, 0x75, 0xb3, 0x5a, 0xd2, 0x8d, 0x3c, 0x9e, 0x3f, 0xc0, 0x2d, 0x47, 0x8e, 0xf4, 0x16, 0x26, 0xdd, 0x2e, 0x53, 0x59, 0xb4, 0xe0, 0xb8, 0xc3, 0x87, 0x0f, 0xeb, 0x95, 0x32, 0x80, 0x26, 0x93, 0x49, 0x44, 0x45, 0x45, 0x41, 0x99, 0xf0, 0xf6, 0xca, 0x25, 0x2c, 0x88, 0x89, 0xc7, 0xad, 0x74, 0xcc, 0x84, 0x0d, 0x64, 0xcf, 0x24, 0xa7, 0xd5, 0xab, 0xfa, 0x91, 0xf2, 0x15, 0xeb, 0x77, 0xe4, 0xc8, 0x11, 0xe1, 0x74, 0x3a, 0xc3, 0x29, 0x06, 0x1f, 0xc7, 0xbe, 0xd4, 0x6d, 0x89, 0xa2, 0x18, 0x46, 0x6b, 0x32, 0x32, 0x32, 0x1e, 0x32, 0x9b, 0xcd, 0x21, 0x56, 0xeb, 0x77, 0x73, 0x39, 0xf4, 0x1c, 0x51, 0x54, 0x54, 0x64, 0x9c, 0x37, 0x6f, 0xde, 0x18, 0xb6, 0xd6, 0xaa, 0x49, 0x47, 0xab, 0x9b, 0xd0, 0xd0, 0xd0, 0x10, 0xfa, 0xc5, 0x17, 0x5f, 0x48, 0x56, 0xf6, 0xb1, 0xc7, 0x1e, 0x73, 0x70, 0x6c, 0x5e, 0xa5, 0x10, 0x16, 0x4e, 0x20, 0x44, 0xbf, 0xff, 0xfe, 0xfb, 0x6a, 0x75, 0x8b, 0x68, 0xe0, 0x03, 0xc2, 0x6b, 0x12, 0x4a, 0x85, 0x8f, 0x90, 0x41, 0xd8, 0x4c, 0x98, 0xa6, 0x22, 0x0a, 0xf1, 0xb5, 0xb8, 0xeb, 0xf7, 0xba, 0xf0, 0xbc, 0x1a, 0x17, 0xf5, 0x7b, 0x98, 0xf0, 0x27, 0xc2, 0x74, 0xa1, 0xb0, 0x62, 0x96, 0xf4, 0x2c, 0xca, 0xcb, 0xcb, 0x43, 0x26, 0x4f, 0x9e, 0xfc, 0x20, 0x8f, 0xc7, 0x35, 0x32, 0xce, 0x5c, 0xb9, 0xc5, 0x62, 0x79, 0x3c, 0x33, 0x33, 0x33, 0xe4, 0xe4, 0xc9, 0x93, 0x52, 0x61, 0xa0, 0x9e, 0x48, 0x47, 0xf6, 0x13, 0xcf, 0xab, 0xbc, 0xdb, 0x1a, 0xe9, 0x65, 0xc6, 0xf3, 0x14, 0x78, 0xda, 0x52, 0x3d, 0x33, 0x22, 0x22, 0x42, 0x4c, 0x98, 0x30, 0xc1, 0xc6, 0xa4, 0x5f, 0x95, 0xf9, 0x26, 0xe4, 0xcb, 0xd3, 0x6d, 0x36, 0x9b, 0xe5, 0xe8, 0xd1, 0xa3, 0x6a, 0x95, 0xda, 0xc1, 0xa9, 0x44, 0xa4, 0x26, 0xaf, 0x79, 0x00, 0x72, 0xdc, 0x58, 0x5d, 0xfa, 0xb5, 0x08, 0xcc, 0x82, 0x43, 0x9f, 0xd7, 0x8f, 0x74, 0xad, 0x63, 0x8b, 0x38, 0x5a, 0xe1, 0xbd, 0x18, 0xd7, 0x5b, 0xe4, 0x9c, 0x39, 0x76, 0x96, 0xa3, 0xd8, 0x5a, 0x87, 0xaa, 0x71, 0xe4, 0xf4, 0x3c, 0xb6, 0xc4, 0x91, 0xd3, 0x25, 0xd9, 0x83, 0xf0, 0x52, 0x32, 0x47, 0x3a, 0x6e, 0xd5, 0x36, 0x6e, 0xcd, 0x77, 0x03, 0xbf, 0x9f, 0x84, 0xde, 0x78, 0xe6, 0xcc, 0x99, 0x90, 0x8e, 0x0e, 0xaf, 0x96, 0x7e, 0x3b, 0x14, 0xfe, 0xdf, 0x25, 0x02, 0xbb, 0xc2, 0xd4, 0xa7, 0xf5, 0x23, 0x5d, 0xeb, 0xb8, 0xb3, 0xcd, 0xe6, 0x10, 0x4e, 0x27, 0x01, 0xd8, 0x74, 0xa7, 0x9c, 0x33, 0x57, 0x52, 0x52, 0x02, 0x07, 0x31, 0x9c, 0x7b, 0xfa, 0x50, 0x35, 0xe6, 0x3d, 0x94, 0x5b, 0x48, 0x94, 0x5c, 0x78, 0xc5, 0x2d, 0xcd, 0xc8, 0x66, 0xf6, 0x77, 0x1c, 0xb2, 0xdd, 0x6d, 0x16, 0x86, 0x20, 0x7f, 0x03, 0xe2, 0xb9, 0x25, 0x07, 0x45, 0x42, 0x4e, 0x9f, 0x3e, 0xed, 0xee, 0x9d, 0xcb, 0x98, 0x83, 0x3a, 0x09, 0x7d, 0xce, 0xc3, 0x10, 0x20, 0x47, 0x3a, 0xa2, 0xad, 0xca, 0xca, 0x4a, 0x13, 0x59, 0xe2, 0x54, 0x1e, 0x2e, 0x1a, 0x94, 0x48, 0xc7, 0x83, 0x1f, 0x24, 0x73, 0x1c, 0x2e, 0x37, 0x9e, 0xf3, 0x4b, 0x0d, 0x6c, 0x15, 0x9e, 0x11, 0x9e, 0xa7, 0xf2, 0x0c, 0xec, 0x41, 0x86, 0x04, 0x43, 0x35, 0x79, 0xa9, 0xaa, 0xaa, 0x12, 0x14, 0x89, 0x98, 0xc8, 0x49, 0x84, 0xff, 0x93, 0xa4, 0xa4, 0xcf, 0xf8, 0xf8, 0xf8, 0xee, 0x79, 0xfc, 0x73, 0xe7, 0xce, 0x49, 0x99, 0x78, 0x03, 0x91, 0x1e, 0xcf, 0xd9, 0xb9, 0x33, 0x4a, 0xa4, 0xc3, 0xc4, 0xa4, 0x56, 0x54, 0x54, 0x98, 0xda, 0xda, 0xa4, 0xa3, 0xb0, 0xe3, 0xc7, 0x8f, 0x0b, 0x32, 0xd9, 0xee, 0x67, 0x44, 0x29, 0x7d, 0xd4, 0xc7, 0x1f, 0x7f, 0x1c, 0x64, 0x56, 0x46, 0xe0, 0x75, 0x6f, 0xd8, 0xb0, 0x41, 0x24, 0x25, 0x25, 0xa9, 0xd2, 0x67, 0xb7, 0xb3, 0x14, 0x1e, 0x2e, 0x3b, 0xae, 0xaf, 0x5e, 0xbd, 0xba, 0x77, 0x7a, 0xd7, 0x21, 0x47, 0x3a, 0x5a, 0x86, 0x62, 0xba, 0xf4, 0xd5, 0x57, 0x5f, 0xf5, 0xb7, 0x1e, 0xb4, 0x3e, 0x1c, 0xf8, 0xbc, 0x7e, 0x1c, 0xff, 0xfb, 0x44, 0x3c, 0xa4, 0x77, 0x5b, 0xa5, 0x1c, 0x39, 0x4c, 0xf3, 0x3d, 0x84, 0x74, 0x20, 0xcf, 0xd8, 0x04, 0x4a, 0x50, 0x8f, 0x78, 0xf6, 0xfc, 0x8d, 0x1e, 0x60, 0xe2, 0xb1, 0x6a, 0x58, 0x00, 0xc2, 0xb5, 0xde, 0xf5, 0x8b, 0x90, 0xa9, 0xdf, 0x48, 0xae, 0x5f, 0x40, 0x1a, 0x2f, 0x59, 0x6a, 0x51, 0x57, 0x57, 0x67, 0xe4, 0xe1, 0x77, 0xa4, 0x9c, 0x79, 0x0f, 0xe7, 0x42, 0xa1, 0x01, 0xce, 0x91, 0x43, 0x99, 0x39, 0xec, 0xc8, 0x78, 0x72, 0x68, 0xcc, 0xec, 0x40, 0x4e, 0x0c, 0x90, 0x52, 0xbd, 0xa9, 0x5f, 0x20, 0x1a, 0x65, 0xf7, 0x62, 0x89, 0x43, 0x87, 0x0e, 0xe9, 0x73, 0x73, 0x73, 0x63, 0xd9, 0x47, 0x38, 0x2f, 0x45, 0xfa, 0x08, 0xc4, 0x89, 0x17, 0x2f, 0x5e, 0x34, 0x5e, 0xbe, 0x7c, 0x39, 0x90, 0xa4, 0xa3, 0xa7, 0x8c, 0xe7, 0xb8, 0xb5, 0x4b, 0x22, 0xac, 0xb4, 0xf4, 0x0a, 0x6d, 0xb4, 0x5c, 0xbf, 0x80, 0x09, 0xb2, 0xa9, 0x44, 0xba, 0x3b, 0x5e, 0x3f, 0xee, 0x0e, 0x21, 0xef, 0x26, 0x1d, 0x2d, 0x22, 0x96, 0x0a, 0xeb, 0x03, 0xb5, 0x6f, 0x1d, 0xb3, 0x4e, 0x2b, 0x57, 0xae, 0x54, 0xed, 0x20, 0x7a, 0x4a, 0x45, 0xfa, 0x53, 0x30, 0x56, 0x7a, 0x53, 0xbf, 0xfa, 0xfa, 0xfa, 0x80, 0x91, 0x9e, 0x9f, 0x9f, 0x8f, 0x35, 0x84, 0x16, 0xa3, 0xd1, 0x38, 0x96, 0x1b, 0x60, 0xdb, 0x1d, 0x33, 0xd0, 0x6b, 0x1b, 0xd1, 0x1a, 0xac, 0xdf, 0x5e, 0xb4, 0x68, 0xd1, 0x7d, 0xbf, 0x7e, 0x6d, 0xb0, 0x00, 0x3b, 0x83, 0x88, 0xd3, 0x3c, 0x42, 0x8a, 0xa7, 0x35, 0x72, 0xdd, 0xe3, 0x39, 0xf5, 0x1c, 0x0b, 0x66, 0x86, 0x82, 0x32, 0x38, 0xa4, 0x57, 0x7a, 0x37, 0xd9, 0x93, 0xf7, 0x8e, 0xf1, 0x7c, 0xec, 0x97, 0x5f, 0x7e, 0x69, 0x1a, 0x68, 0x93, 0x19, 0x14, 0xbf, 0x93, 0x0e, 0x6e, 0x1f, 0x64, 0x5f, 0xe4, 0x5b, 0xa4, 0xa3, 0x25, 0x24, 0x06, 0xd3, 0xa5, 0x83, 0x4b, 0x38, 0x5e, 0x47, 0x16, 0x6f, 0x1c, 0x47, 0x1d, 0x77, 0x48, 0x0f, 0xe1, 0x7f, 0x1c, 0xc1, 0x89, 0xff, 0xa0, 0x0c, 0x12, 0xb9, 0x70, 0xe1, 0x82, 0x68, 0x6a, 0x6a, 0x32, 0x73, 0x92, 0x26, 0xba, 0x37, 0xe9, 0x58, 0x5e, 0x83, 0xd9, 0xb0, 0x21, 0x01, 0x4e, 0xca, 0x04, 0xc5, 0x0f, 0xf1, 0x3a, 0x71, 0xea, 0x5e, 0x83, 0x97, 0xd6, 0x3b, 0x64, 0x83, 0xcd, 0x4f, 0x6b, 0x68, 0x68, 0x08, 0xc1, 0xa6, 0x3c, 0xc0, 0x17, 0x52, 0x5d, 0x5d, 0xed, 0x71, 0xdb, 0x51, 0x50, 0x94, 0x25, 0x21, 0x21, 0x41, 0x36, 0xb7, 0xee, 0x8d, 0xd4, 0xd6, 0xd6, 0xc2, 0x7a, 0x0f, 0xe7, 0xde, 0x9e, 0x6f, 0xec, 0xd5, 0xd3, 0x43, 0x63, 0x63, 0x63, 0xad, 0x95, 0x95, 0x95, 0xb6, 0x7e, 0x3c, 0xdf, 0xc0, 0xce, 0x82, 0xae, 0xbd, 0xbd, 0x5d, 0x0c, 0x1b, 0x36, 0x2c, 0xc8, 0x5e, 0x1f, 0x65, 0xfd, 0xfa, 0xf5, 0xa2, 0xd7, 0x5e, 0x3a, 0x3b, 0xa3, 0x3f, 0xc9, 0x13, 0x3b, 0x27, 0x8c, 0xc2, 0xdd, 0xa4, 0x63, 0x85, 0x07, 0xd6, 0x32, 0x95, 0x89, 0xbe, 0xa7, 0x0d, 0xdd, 0x2b, 0x3a, 0x33, 0x61, 0x39, 0x28, 0x0a, 0xd0, 0x49, 0xed, 0x8a, 0x09, 0x8a, 0x2a, 0xaf, 0xdb, 0x4d, 0x3a, 0x56, 0x9e, 0x94, 0x8a, 0x9e, 0x15, 0x33, 0xfd, 0x51, 0x28, 0x96, 0x75, 0x15, 0x20, 0x9f, 0xe5, 0x26, 0x1d, 0x93, 0xec, 0xbb, 0x84, 0xfc, 0x56, 0x64, 0x35, 0x02, 0x2f, 0x71, 0x0b, 0x61, 0x31, 0x55, 0xda, 0x1c, 0xa4, 0xae, 0xdf, 0x5e, 0x37, 0x04, 0x29, 0xbd, 0x97, 0x09, 0xa7, 0x44, 0xff, 0x56, 0x0a, 0x61, 0x6a, 0x15, 0x5b, 0xb6, 0xed, 0x6e, 0xd2, 0xf1, 0xb0, 0x76, 0x1f, 0xd4, 0x15, 0x01, 0x3e, 0x26, 0x20, 0x1c, 0xc1, 0x4d, 0x0d, 0xfd, 0x13, 0xde, 0xdc, 0xe8, 0x4a, 0x4e, 0x4e, 0xc6, 0xca, 0x58, 0xac, 0x68, 0xbd, 0xe1, 0xab, 0x67, 0xfb, 0x7a, 0x06, 0x08, 0xb9, 0x68, 0xe4, 0xef, 0x4d, 0xc1, 0x95, 0x32, 0xfd, 0x17, 0xde, 0xad, 0x32, 0x84, 0x3d, 0x6f, 0xbd, 0x56, 0x49, 0xc7, 0x9a, 0xac, 0x31, 0xa5, 0xa5, 0xa5, 0xc6, 0xba, 0xba, 0xba, 0x20, 0x6b, 0xfd, 0x14, 0x5e, 0x32, 0x1e, 0xc5, 0xe1, 0x74, 0xa4, 0x16, 0x49, 0x87, 0xe7, 0x8e, 0x29, 0x3c, 0xd9, 0x5d, 0x31, 0x41, 0x51, 0x2f, 0x58, 0xd7, 0xde, 0xd1, 0xd1, 0x61, 0x66, 0xbd, 0x0e, 0xd3, 0x22, 0xe9, 0x48, 0xf1, 0x21, 0xab, 0x27, 0xbb, 0x2b, 0x26, 0x28, 0x5e, 0xc4, 0x58, 0x76, 0xbb, 0x28, 0x2e, 0x2e, 0x86, 0xdf, 0xd5, 0x9d, 0x22, 0xd7, 0x22, 0xe9, 0x08, 0xfe, 0xd3, 0x5b, 0x5b, 0x5b, 0xcd, 0x41, 0x27, 0xce, 0xa7, 0xa1, 0x9b, 0xfb, 0x0c, 0xfc, 0xb1, 0xbe, 0x7a, 0xa6, 0x2f, 0x8f, 0xfe, 0xc6, 0x9a, 0xb1, 0xa4, 0xc6, 0xc6, 0x46, 0xa3, 0xd2, 0x86, 0x3d, 0x35, 0x82, 0x6c, 0x1e, 0xc5, 0xfa, 0x8a, 0xe5, 0x90, 0x00, 0x9a, 0x37, 0x6f, 0xde, 0x80, 0x91, 0xd0, 0xd4, 0xd4, 0xd4, 0xbd, 0x12, 0x58, 0x49, 0xb0, 0xe7, 0x2d, 0x27, 0x27, 0x07, 0x1b, 0x42, 0xfa, 0xf5, 0xbe, 0xb0, 0xb0, 0x30, 0x77, 0x28, 0x0c, 0x13, 0x6f, 0x16, 0xd2, 0x87, 0x11, 0x05, 0x84, 0x74, 0x2c, 0xd0, 0xb7, 0xa7, 0xa7, 0xa7, 0xb7, 0xbd, 0xf7, 0xde, 0x7b, 0xde, 0x8c, 0xe9, 0x06, 0xfe, 0x98, 0x3b, 0xab, 0x6f, 0xb1, 0xb7, 0x0b, 0x47, 0x86, 0x29, 0x09, 0x36, 0x3b, 0xee, 0xdf, 0xbf, 0x5f, 0xcc, 0x9a, 0x35, 0xcb, 0xce, 0xca, 0xf0, 0xe7, 0x8e, 0x17, 0x7c, 0x93, 0x09, 0x27, 0x49, 0x2e, 0x5c, 0xb8, 0x50, 0xa7, 0xb4, 0x45, 0x0b, 0x89, 0x29, 0xec, 0x0d, 0x40, 0x66, 0xad, 0x97, 0x38, 0x39, 0xc1, 0xe2, 0x6d, 0x76, 0xcd, 0xca, 0xfa, 0xf5, 0x09, 0xe9, 0x3a, 0x1f, 0x2d, 0x8b, 0x82, 0x42, 0x90, 0x8d, 0x5b, 0xc0, 0x21, 0x9b, 0x37, 0xcd, 0x1b, 0xde, 0xe9, 0xe3, 0xa2, 0x67, 0xcd, 0x99, 0xbe, 0xb3, 0xb3, 0x53, 0xcc, 0x9f, 0x3f, 0x5f, 0x9c, 0x3a, 0x75, 0x4a, 0xf1, 0x87, 0xdb, 0xb7, 0x6f, 0x47, 0xd6, 0x0a, 0x4b, 0x80, 0x60, 0x12, 0x10, 0x23, 0x76, 0xf8, 0x99, 0x74, 0xf4, 0xb6, 0x39, 0x57, 0xae, 0x5c, 0x19, 0xf1, 0xe8, 0xa3, 0x8f, 0xea, 0xa5, 0xce, 0xb1, 0x73, 0x8b, 0xd9, 0x6c, 0xee, 0x5e, 0xb2, 0x94, 0x95, 0x95, 0xd5, 0x3b, 0xd1, 0xf2, 0x6f, 0xd1, 0xb3, 0xff, 0xcf, 0x1b, 0xc5, 0x77, 0xf2, 0xf7, 0x9d, 0x12, 0xfd, 0xcb, 0xca, 0xf5, 0x88, 0x8f, 0x0f, 0xc1, 0xd7, 0xf3, 0xb1, 0xdd, 0xde, 0x20, 0x94, 0xf0, 0x32, 0xc1, 0xea, 0x70, 0x38, 0xba, 0xcf, 0x59, 0x15, 0x2a, 0x96, 0x01, 0x6d, 0xde, 0xbc, 0x19, 0xc7, 0x71, 0xdd, 0x22, 0xec, 0x23, 0xcc, 0xe0, 0xa3, 0x3d, 0x0d, 0x7e, 0xc6, 0x28, 0x02, 0x5e, 0x7c, 0x91, 0x88, 0x77, 0xe0, 0x6c, 0x77, 0xa5, 0x7a, 0xd2, 0xf0, 0xe3, 0xa2, 0x71, 0x19, 0x75, 0xc5, 0xb2, 0xa5, 0x63, 0x7c, 0xe4, 0x69, 0x5f, 0xde, 0xad, 0x1b, 0x4c, 0x67, 0xc3, 0x4e, 0x25, 0x1c, 0x22, 0xd8, 0xd6, 0xac, 0x59, 0xa3, 0x8a, 0x70, 0x34, 0x0c, 0x12, 0xf7, 0x19, 0xef, 0x3f, 0xe4, 0xc6, 0x36, 0x50, 0xf5, 0x1d, 0x49, 0x78, 0x05, 0x67, 0xbf, 0xd3, 0xd8, 0xae, 0xea, 0x3c, 0x3c, 0x9c, 0x5e, 0x5d, 0x55, 0x55, 0x85, 0xb3, 0xf6, 0xaa, 0x09, 0xeb, 0xf8, 0x90, 0xe3, 0xfb, 0xf6, 0x40, 0xe0, 0xc9, 0x84, 0xbf, 0x13, 0xae, 0x6f, 0xdd, 0xba, 0x55, 0x15, 0xe1, 0xe8, 0x5d, 0x57, 0xaf, 0x5e, 0xb5, 0xf1, 0x6d, 0x0e, 0xab, 0x70, 0x26, 0x4e, 0x00, 0xea, 0x3d, 0x91, 0xf0, 0x0e, 0x16, 0x91, 0xbe, 0xf4, 0xd2, 0x4b, 0xaa, 0xea, 0x3d, 0x69, 0xd2, 0x24, 0x17, 0x45, 0x36, 0x68, 0xa8, 0x15, 0x5c, 0x6f, 0xd3, 0xfd, 0x48, 0x7a, 0x34, 0x1f, 0x86, 0x7f, 0x1d, 0xe7, 0xb8, 0xca, 0x9d, 0x84, 0x28, 0x7a, 0x9d, 0xf2, 0x5c, 0x50, 0x50, 0xe0, 0xe4, 0x7b, 0x5b, 0x36, 0x12, 0x86, 0x06, 0xa8, 0xee, 0x3a, 0x3e, 0x95, 0xf1, 0x90, 0xcd, 0x66, 0xb3, 0xce, 0x99, 0x33, 0x47, 0x15, 0xf1, 0xcb, 0x97, 0x2f, 0x77, 0x5b, 0xa8, 0x4f, 0xf8, 0xe6, 0x8a, 0xfb, 0x8a, 0x74, 0x98, 0xb7, 0xb5, 0x84, 0xda, 0x0b, 0x17, 0x2e, 0x38, 0x93, 0x92, 0x92, 0x14, 0x15, 0x66, 0x30, 0x18, 0x5c, 0x38, 0xf6, 0x9b, 0x8f, 0xd8, 0xde, 0x4e, 0x48, 0x0c, 0xb0, 0x95, 0x82, 0x0f, 0xf1, 0x13, 0x58, 0x9c, 0x96, 0x96, 0x16, 0x3b, 0x7a, 0xb2, 0x1a, 0xe2, 0x37, 0x6c, 0xd8, 0x80, 0x4b, 0x7d, 0xda, 0xe9, 0x77, 0x1f, 0x10, 0xa6, 0x05, 0xe2, 0xa8, 0xf0, 0x40, 0x28, 0x0b, 0x8e, 0xdb, 0x6f, 0x60, 0xe6, 0xc8, 0x4c, 0xdb, 0x13, 0x12, 0x12, 0x54, 0x29, 0xeb, 0xb9, 0xe7, 0x9e, 0x03, 0xe1, 0xb7, 0x79, 0xfc, 0x9f, 0xa0, 0x91, 0xb3, 0xd3, 0xc3, 0xd9, 0xb1, 0xab, 0x2b, 0x2b, 0x2b, 0xc3, 0xa9, 0xd3, 0xaa, 0xbe, 0xe5, 0x8d, 0x37, 0xde, 0x70, 0x5f, 0xfb, 0xf1, 0x16, 0x5f, 0x08, 0x34, 0xa8, 0x49, 0x47, 0xef, 0xf8, 0x31, 0xe1, 0x94, 0x95, 0x64, 0xee, 0xdc, 0xb9, 0xaa, 0xc7, 0x43, 0xbe, 0x9c, 0xa7, 0x98, 0x7b, 0x97, 0x41, 0x63, 0x17, 0x10, 0xbc, 0x0d, 0xc7, 0x6e, 0xdb, 0xb6, 0x6d, 0xaa, 0xbe, 0xc7, 0x62, 0xb1, 0xb8, 0x0a, 0x0b, 0x0b, 0xe1, 0xcd, 0x37, 0xf0, 0x85, 0x40, 0x89, 0x83, 0x95, 0x74, 0x5c, 0x8f, 0xf1, 0x53, 0xc2, 0x69, 0xdc, 0x59, 0xf6, 0xd4, 0x53, 0x4f, 0xa9, 0x52, 0x10, 0xee, 0x5f, 0xbb, 0x74, 0xe9, 0x12, 0x14, 0x74, 0x86, 0xf0, 0x2b, 0xb6, 0x14, 0x5a, 0xbb, 0x2d, 0x01, 0xbd, 0x75, 0x07, 0x42, 0xc8, 0x4d, 0x9b, 0x36, 0xa9, 0xfa, 0xae, 0xe8, 0xe8, 0x68, 0x57, 0x69, 0x69, 0x29, 0x7a, 0x7c, 0x23, 0x61, 0xcb, 0x40, 0x12, 0x3f, 0x90, 0x8a, 0x99, 0x43, 0xc8, 0x87, 0x89, 0x5e, 0xbf, 0x7e, 0xbd, 0x2a, 0xc5, 0xe0, 0x2a, 0xac, 0xf2, 0xf2, 0x72, 0x27, 0xf7, 0x08, 0xdc, 0xe3, 0x12, 0xa6, 0xe1, 0x6b, 0x32, 0x66, 0x72, 0x08, 0xd9, 0x85, 0xcb, 0xf8, 0xd4, 0x7c, 0x1f, 0x6e, 0x91, 0xa4, 0x06, 0x6d, 0xe7, 0x2b, 0xbd, 0x36, 0xf1, 0x51, 0xeb, 0x83, 0x86, 0xf4, 0x87, 0x08, 0xff, 0xc2, 0x6d, 0x46, 0x34, 0x9e, 0xa9, 0xde, 0x87, 0xc5, 0x37, 0x30, 0xb5, 0x70, 0x2f, 0x4a, 0xd0, 0xf8, 0xdd, 0x28, 0x26, 0x0e, 0xc5, 0xbe, 0x42, 0x48, 0x39, 0x7e, 0xfc, 0x78, 0x55, 0xdf, 0x38, 0x75, 0xea, 0x54, 0x17, 0x1c, 0x41, 0x5c, 0x10, 0x44, 0xf8, 0x25, 0x5b, 0xc4, 0x7b, 0x9e, 0xf4, 0x18, 0x4e, 0x66, 0xb4, 0x1c, 0x39, 0x72, 0x44, 0xb5, 0xb3, 0xb3, 0x6e, 0xdd, 0x3a, 0xf7, 0x01, 0xf8, 0x79, 0x4a, 0x87, 0xd6, 0x6b, 0x08, 0x43, 0x39, 0x94, 0xbc, 0x5c, 0x5f, 0x5f, 0xef, 0x80, 0x09, 0x57, 0xf3, 0xad, 0x33, 0x67, 0xce, 0xc4, 0x4d, 0x92, 0x5d, 0x9c, 0xb1, 0xcb, 0xf6, 0xf3, 0x4d, 0x92, 0x7e, 0x27, 0xfd, 0x4e, 0xda, 0xf2, 0xe4, 0xc9, 0x93, 0x92, 0x37, 0x17, 0xde, 0x8d, 0x9c, 0x9c, 0x1c, 0x97, 0xdd, 0x6e, 0x87, 0x12, 0x0a, 0x09, 0x4f, 0x06, 0x32, 0x91, 0xd1, 0x07, 0x24, 0x72, 0x48, 0x79, 0x2d, 0x2f, 0x2f, 0x4f, 0x55, 0xfe, 0x01, 0x58, 0xb1, 0x62, 0x05, 0x1a, 0x79, 0x3b, 0x0f, 0x81, 0x73, 0xfc, 0x19, 0xca, 0xf9, 0xf3, 0x56, 0x65, 0xac, 0xac, 0xfd, 0x19, 0xe1, 0xd7, 0x98, 0x84, 0xa9, 0xae, 0xae, 0xd6, 0x91, 0x93, 0xa3, 0xea, 0x87, 0xb9, 0xb9, 0xb9, 0x0e, 0x8a, 0xcb, 0x31, 0x29, 0x81, 0x4b, 0x68, 0x0e, 0x09, 0xf5, 0x17, 0xe1, 0x68, 0x41, 0x70, 0xb4, 0xda, 0x0e, 0xc2, 0xa8, 0xec, 0xec, 0xec, 0x45, 0x7b, 0xf6, 0xec, 0x89, 0xe4, 0xcd, 0x06, 0x8a, 0x42, 0xd6, 0xc1, 0x12, 0x17, 0x17, 0x37, 0x53, 0xf4, 0x5c, 0x94, 0x54, 0x23, 0xa4, 0x4f, 0x8e, 0xd4, 0xc4, 0x2c, 0x9b, 0x27, 0xc1, 0x4a, 0x8f, 0xbf, 0x62, 0x56, 0x4a, 0x78, 0xb7, 0xb4, 0x1a, 0x15, 0xc2, 0x89, 0x8b, 0xb8, 0x83, 0x0b, 0xb7, 0x18, 0x5d, 0xbd, 0x07, 0xd7, 0x3e, 0x60, 0x9a, 0x18, 0xf7, 0x6e, 0xac, 0x11, 0x3d, 0x87, 0x01, 0x86, 0x78, 0xf9, 0xfd, 0x58, 0x92, 0x8e, 0x2b, 0xc9, 0x3e, 0x12, 0xf2, 0x67, 0xee, 0x6a, 0x8e, 0x74, 0x2c, 0x9f, 0xc2, 0xfd, 0x66, 0xd1, 0xde, 0x4e, 0xfc, 0x89, 0x9e, 0xa3, 0x36, 0x71, 0x0c, 0xe7, 0xcd, 0x7b, 0x78, 0xd1, 0x0b, 0xa6, 0x97, 0x47, 0x8b, 0x9e, 0xad, 0x44, 0xde, 0xee, 0x01, 0x70, 0xdf, 0xb8, 0xe4, 0x97, 0x63, 0x50, 0xfd, 0x49, 0x7a, 0x50, 0x34, 0x2a, 0xff, 0x13, 0x60, 0x00, 0x43, 0x56, 0xa7, 0xa6, 0x51, 0x00, 0x63, 0x17, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; static unsigned int invader_png_len = 4008; static ui_window_qt_t ui_window = {0}; static const QPixmap getInvader(void) { QPixmap pix; pix.loadFromData(invader_png, invader_png_len, "PNG"); return pix; } #ifdef HAVE_LIBRETRODB static void scan_finished_handler(retro_task_t *task, void *task_data, void *user_data, const char *err) { bool dont_ask = false; bool answer = false; #ifdef HAVE_MENU struct menu_state *menu_st = menu_state_get_ptr(); if (menu_st->driver_ctx->environ_cb) menu_st->driver_ctx->environ_cb(MENU_ENVIRON_RESET_HORIZONTAL_LIST, NULL, menu_st->userdata); #endif if (!ui_window.qtWindow->settings()->value( "scan_finish_confirm", true).toBool()) return; answer = ui_window.qtWindow->showMessageBox(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_SCAN_FINISHED), MainWindow::MSGBOX_TYPE_QUESTION_OKCANCEL, Qt::ApplicationModal, true, &dont_ask); if (answer && dont_ask) ui_window.qtWindow->settings()->setValue("scan_finish_confirm", false); } #endif /* https://stackoverflow.com/questions/7246622/how-to-create-a-slider-with-a-non-linear-scale */ static double exp_scale(double input_val, double mid_val, double max_val) { double M = max_val / mid_val; double base = M - 1; double C = log(base * base); double B = max_val / (exp(C) - 1); double A = -1 * B; double ret = A + B * exp(C * input_val); return ret; } TreeView::TreeView(QWidget *parent) : QTreeView(parent) { } void TreeView::columnCountChanged(int oldCount, int newCount) { QTreeView::columnCountChanged(oldCount, newCount); } void TreeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndexList list = selected.indexes(); QTreeView::selectionChanged(selected, deselected); emit itemsSelected(list); } TableView::TableView(QWidget *parent) : QTableView(parent) { } bool TableView::isEditorOpen() { return (state() == QAbstractItemView::EditingState); } ListWidget::ListWidget(QWidget *parent) : QListWidget(parent) { } bool ListWidget::isEditorOpen() { return (state() == QAbstractItemView::EditingState); } void ListWidget::keyPressEvent(QKeyEvent *event) { int key = event->key(); if ( key == Qt::Key_Return || key == Qt::Key_Enter) emit enterPressed(); else if (key == Qt::Key_Delete) emit deletePressed(); QListWidget::keyPressEvent(event); } CoreInfoLabel::CoreInfoLabel(QString text, QWidget *parent) : QLabel(text, parent) { setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); } CoreInfoWidget::CoreInfoWidget(CoreInfoLabel *label, QWidget *parent) : QWidget(parent) ,m_label(label) ,m_scrollArea(new QScrollArea(this)) { m_scrollArea->setWidgetResizable(true); m_scrollArea->setWidget(m_label); } QSize CoreInfoWidget::sizeHint() const { return QSize(256, 256); } void CoreInfoWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); m_scrollArea->resize(event->size()); } LogTextEdit::LogTextEdit(QWidget *parent) : QPlainTextEdit(parent) { } void LogTextEdit::appendMessage(const QString& text) { if (text.isEmpty()) return; appendPlainText(text); verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } /* Only accept indexes from current path. * https://www.qtcentre.org/threads/50700-QFileSystemModel-and-QSortFilterProxyModel-don-t-work-well-together */ bool FileSystemProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QFileSystemModel *sm = qobject_cast(sourceModel()); QModelIndex rootIndex = sm->index(sm->rootPath()); if (sourceParent == rootIndex) return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); return true; } /* sort the source (QFileSystemModel to keep directories before files) */ void FileSystemProxyModel::sort(int column, Qt::SortOrder order) { sourceModel()->sort(column, order); } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) ,m_loadCoreWindow(new LoadCoreWindow(this)) ,m_timer(new QTimer(this)) ,m_currentCore() ,m_currentCoreVersion() ,m_statusLabel(new QLabel(this)) ,m_dirTree(new TreeView(this)) ,m_dirModel(new QFileSystemModel(m_dirTree)) ,m_fileModel(new QFileSystemModel(this)) ,m_listWidget(new ListWidget(this)) ,m_centralWidget(new QStackedWidget(this)) ,m_tableView(new TableView(this)) ,m_fileTableView(new QTableView(this)) ,m_playlistViews(new FileDropWidget(this)) ,m_searchWidget(new QWidget(this)) ,m_searchLineEdit(new QLineEdit(this)) ,m_searchDock(new QDockWidget(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SEARCH), this)) ,m_playlistFiles() ,m_launchWithComboBox(new QComboBox(this)) ,m_startCorePushButton(new QToolButton(this)) ,m_coreInfoPushButton(new QToolButton(this)) ,m_runPushButton(new QToolButton(this)) ,m_stopPushButton(new QToolButton(this)) ,m_browserAndPlaylistTabWidget(new QTabWidget(this)) ,m_pendingRun(false) ,m_thumbnailPixmap(NULL) ,m_thumbnailPixmap2(NULL) ,m_thumbnailPixmap3(NULL) ,m_thumbnailPixmap4(NULL) ,m_settings(NULL) ,m_viewOptionsDialog(NULL) ,m_coreInfoDialog(new CoreInfoDialog(this, NULL)) ,m_defaultStyle(NULL) ,m_defaultPalette() ,m_currentTheme(THEME_SYSTEM_DEFAULT) ,m_coreInfoDock(new QDockWidget(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CORE_INFO), this)) ,m_coreInfoLabel(new CoreInfoLabel(QString(), this)) ,m_coreInfoWidget(new CoreInfoWidget(m_coreInfoLabel, this)) ,m_logDock(new QDockWidget(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOG), this)) ,m_logWidget(new QFrame(this)) ,m_logTextEdit(new LogTextEdit(m_logWidget)) ,m_historyPlaylistsItem(NULL) ,m_folderIcon() ,m_customThemeString() ,m_gridView(new GridView(this)) ,m_playlistViewsAndFooter(new QWidget(this)) ,m_zoomSlider(NULL) ,m_lastZoomSliderValue(0) ,m_viewType(VIEW_TYPE_LIST) ,m_thumbnailType(THUMBNAIL_TYPE_BOXART) ,m_gridProgressBar(NULL) ,m_gridProgressWidget(NULL) ,m_currentGridHash() ,m_currentGridWidget(NULL) ,m_allPlaylistsListMaxCount(0) ,m_allPlaylistsGridMaxCount(0) ,m_playlistEntryDialog(NULL) ,m_statusMessageElapsedTimer() #if defined(HAVE_MENU) #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) ,m_shaderParamsDialog(new ShaderParamsDialog()) #endif #endif ,m_coreOptionsDialog(new CoreOptionsDialog()) ,m_networkManager(new QNetworkAccessManager(this)) ,m_updateProgressDialog(new QProgressDialog()) ,m_updateFile() ,m_updateReply() ,m_thumbnailDownloadProgressDialog(new QProgressDialog()) ,m_thumbnailDownloadFile() ,m_thumbnailDownloadReply() ,m_pendingThumbnailDownloadTypes() ,m_thumbnailPackDownloadProgressDialog(new QProgressDialog()) ,m_thumbnailPackDownloadFile() ,m_thumbnailPackDownloadReply() ,m_playlistThumbnailDownloadProgressDialog(new QProgressDialog()) ,m_playlistThumbnailDownloadFile() ,m_playlistThumbnailDownloadReply() ,m_pendingPlaylistThumbnails() ,m_downloadedThumbnails(0) ,m_failedThumbnails(0) ,m_playlistThumbnailDownloadWasCanceled(false) ,m_pendingDirScrollPath() ,m_thumbnailTimer(new QTimer(this)) ,m_gridItem(this) ,m_currentBrowser(BROWSER_TYPE_PLAYLISTS) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) ,m_searchRegularExpression() #else ,m_searchRegExp() #endif ,m_zoomWidget(new QWidget(this)) ,m_itemsCountLiteral(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ITEMS_COUNT)) ,m_itemsCountLabel(new QLabel(this)) { settings_t *settings = config_get_ptr(); const char *path_dir_playlist = settings->paths.directory_playlist; const char *path_dir_assets = settings->paths.directory_assets; const char *path_dir_menu_content = settings->paths.directory_menu_content; QDir playlistDir(path_dir_playlist); QString configDir = QFileInfo(path_get(RARCH_PATH_CONFIG)).dir().absolutePath(); QToolButton *searchResetButton = NULL; QHBoxLayout *zoomLayout = new QHBoxLayout(); QLabel *zoomLabel = new QLabel(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ZOOM), m_zoomWidget); QPushButton *thumbnailTypePushButton = new QPushButton(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_TYPE), m_zoomWidget); QMenu *thumbnailTypeMenu = new QMenu(thumbnailTypePushButton); QAction *thumbnailTypeBoxartAction = NULL; QAction *thumbnailTypeScreenshotAction = NULL; QAction *thumbnailTypeTitleAction = NULL; QAction *thumbnailTypeLogoAction = NULL; QPushButton *viewTypePushButton = new QPushButton(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW), m_zoomWidget); QMenu *viewTypeMenu = new QMenu(viewTypePushButton); QAction *viewTypeIconsAction = NULL; QAction *viewTypeListAction = NULL; QHBoxLayout *gridProgressLayout = new QHBoxLayout(); QLabel *gridProgressLabel = NULL; QHBoxLayout *gridFooterLayout = NULL; qRegisterMetaType >("ThumbnailWidget"); qRegisterMetaType("retro_task_callback_t"); /* Cancel all progress dialogs immediately since * they show as soon as they're constructed. */ m_updateProgressDialog->cancel(); m_thumbnailDownloadProgressDialog->cancel(); m_thumbnailPackDownloadProgressDialog->cancel(); m_playlistThumbnailDownloadProgressDialog->cancel(); m_gridProgressWidget = new QWidget(); gridProgressLabel = new QLabel( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PROGRESS), m_gridProgressWidget); thumbnailTypePushButton->setObjectName("thumbnailTypePushButton"); thumbnailTypePushButton->setFlat(true); thumbnailTypeBoxartAction = thumbnailTypeMenu->addAction( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_BOXART)); thumbnailTypeScreenshotAction = thumbnailTypeMenu->addAction( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_SCREENSHOT)); thumbnailTypeTitleAction = thumbnailTypeMenu->addAction( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_TITLE_SCREEN)); thumbnailTypeLogoAction = thumbnailTypeMenu->addAction( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_LOGO)); thumbnailTypePushButton->setMenu(thumbnailTypeMenu); viewTypePushButton->setObjectName("viewTypePushButton"); viewTypePushButton->setFlat(true); viewTypeIconsAction = viewTypeMenu->addAction( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_ICONS)); viewTypeListAction = viewTypeMenu->addAction( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_LIST)); viewTypePushButton->setMenu(viewTypeMenu); gridProgressLabel->setObjectName("gridProgressLabel"); m_gridProgressBar = new QProgressBar( m_gridProgressWidget); m_gridProgressBar->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred)); zoomLabel->setObjectName("zoomLabel"); m_zoomSlider = new QSlider( Qt::Horizontal, m_zoomWidget); m_zoomSlider->setMinimum(0); m_zoomSlider->setMaximum(100); m_zoomSlider->setValue(50); m_zoomSlider->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred)); m_lastZoomSliderValue = m_zoomSlider->value(); m_playlistViewsAndFooter->setLayout(new QVBoxLayout()); m_gridView->setSelectionMode(QAbstractItemView::SingleSelection); m_gridView->setEditTriggers(QAbstractItemView::NoEditTriggers); m_playlistViews->addWidget(m_gridView); m_playlistViews->addWidget(m_tableView); m_centralWidget->setObjectName("centralWidget"); m_playlistViewsAndFooter->layout()->addWidget(m_playlistViews); m_playlistViewsAndFooter->layout()->setAlignment(Qt::AlignCenter); m_playlistViewsAndFooter->layout()->setContentsMargins(0, 0, 0, 0); m_gridProgressWidget->setLayout(gridProgressLayout); gridProgressLayout->setContentsMargins(0, 0, 0, 0); gridProgressLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Preferred)); gridProgressLayout->addWidget(gridProgressLabel); gridProgressLayout->addWidget(m_gridProgressBar); m_playlistViewsAndFooter->layout()->addWidget(m_gridProgressWidget); m_zoomWidget->setLayout(zoomLayout); zoomLayout->setContentsMargins(0, 0, 0, 0); zoomLayout->addWidget(zoomLabel); zoomLayout->addWidget(m_zoomSlider); m_itemsCountLabel->setObjectName("itemsCountLabel"); gridFooterLayout = new QHBoxLayout(); gridFooterLayout->addWidget(m_itemsCountLabel); gridFooterLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Preferred)); gridFooterLayout->addWidget(m_gridProgressWidget); gridFooterLayout->addWidget(m_zoomWidget); gridFooterLayout->addWidget(thumbnailTypePushButton); gridFooterLayout->addWidget(viewTypePushButton); static_cast(m_playlistViewsAndFooter->layout())->addLayout(gridFooterLayout); m_gridProgressWidget->hide(); m_playlistModel = new PlaylistModel(this); m_proxyModel = new QSortFilterProxyModel(this); m_proxyModel->setSourceModel(m_playlistModel); m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); m_proxyFileModel = new FileSystemProxyModel(); m_proxyFileModel->setSourceModel(m_fileModel); m_proxyFileModel->setSortCaseSensitivity(Qt::CaseInsensitive); m_tableView->setAlternatingRowColors(true); m_tableView->setModel(m_proxyModel); m_tableView->setSortingEnabled(true); m_tableView->verticalHeader()->setVisible(false); m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows); m_tableView->setSelectionMode(QAbstractItemView::SingleSelection); m_tableView->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed); m_tableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_tableView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); m_tableView->horizontalHeader()->setStretchLastSection(true); m_tableView->setWordWrap(false); m_fileTableView->setModel(m_fileModel); m_fileTableView->sortByColumn(0, Qt::AscendingOrder); m_fileTableView->setSortingEnabled(true); m_fileTableView->setAlternatingRowColors(true); m_fileTableView->verticalHeader()->setVisible(false); m_fileTableView->setSelectionMode(QAbstractItemView::SingleSelection); m_fileTableView->setSelectionBehavior(QAbstractItemView::SelectRows); m_fileTableView->horizontalHeader()->setStretchLastSection(true); m_fileTableView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); m_fileTableView->setWordWrap(false); m_gridView->setItemDelegate(new ThumbnailDelegate(m_gridItem, this)); m_gridView->setModel(m_proxyModel); m_gridView->setSelectionModel(m_tableView->selectionModel()); m_logWidget->setObjectName("logWidget"); m_folderIcon = QIcon(QString(path_dir_assets) + GENERIC_FOLDER_ICON); m_imageFormats = QVector::fromList(QImageReader::supportedImageFormats()); m_defaultStyle = QApplication::style(); m_defaultPalette = QApplication::palette(); /* ViewOptionsDialog needs m_settings set before it's constructed */ m_settings = new QSettings(configDir + QStringLiteral("/retroarch_qt.cfg"), QSettings::IniFormat, this); m_viewOptionsDialog = new ViewOptionsDialog(this, 0); m_playlistEntryDialog = new PlaylistEntryDialog(this, 0); /* default NULL parameter for parent wasn't added until 5.7 */ m_startCorePushButton->setDefaultAction(new QAction(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_START_CORE), m_startCorePushButton)); m_startCorePushButton->setFixedSize(m_startCorePushButton->sizeHint()); m_runPushButton->setDefaultAction(new QAction(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_RUN), m_runPushButton)); m_runPushButton->setFixedSize(m_runPushButton->sizeHint()); m_stopPushButton->setDefaultAction(new QAction(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_STOP), m_stopPushButton)); m_stopPushButton->setFixedSize(m_stopPushButton->sizeHint()); m_coreInfoPushButton->setDefaultAction(new QAction(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_INFO), m_coreInfoPushButton)); m_coreInfoPushButton->setFixedSize(m_coreInfoPushButton->sizeHint()); searchResetButton = new QToolButton(m_searchWidget); searchResetButton->setDefaultAction(new QAction(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_SEARCH_CLEAR), searchResetButton)); searchResetButton->setFixedSize(searchResetButton->sizeHint()); connect(searchResetButton, SIGNAL(clicked()), this, SLOT(onSearchResetClicked())); m_dirModel->setFilter( QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Drives | (m_settings->value("show_hidden_files", true).toBool() ? (QDir::Hidden | QDir::System) : static_cast(0))); m_fileModel->setFilter( QDir::NoDot | QDir::AllEntries | (m_settings->value("show_hidden_files", true).toBool() ? (QDir::Hidden | QDir::System) : static_cast(0))); #if defined(Q_OS_WIN) m_dirModel->setRootPath(""); m_fileModel->setRootPath(""); #else m_dirModel->setRootPath("/"); m_fileModel->setRootPath("/"); #endif m_dirTree->setModel(m_dirModel); m_dirTree->setSelectionMode(QAbstractItemView::SingleSelection); m_dirTree->header()->setVisible(false); m_fileTableView->setModel(m_proxyFileModel); if (m_dirModel->columnCount() > 3) { m_dirTree->hideColumn(1); /* size */ m_dirTree->hideColumn(2); /* type */ m_dirTree->hideColumn(3); /* date modified */ } reloadPlaylists(); m_searchWidget->setLayout(new QHBoxLayout()); m_searchWidget->layout()->addWidget(m_searchLineEdit); m_searchWidget->layout()->addWidget(searchResetButton); m_searchDock->setObjectName("searchDock"); m_searchDock->setProperty("default_area", Qt::LeftDockWidgetArea); m_searchDock->setProperty("menu_text", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SEARCH)); m_searchDock->setWidget(m_searchWidget); m_searchDock->setFixedHeight(m_searchDock->minimumSizeHint().height()); addDockWidget(static_cast( m_searchDock->property("default_area").toInt()), m_searchDock); m_coreInfoLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); m_coreInfoLabel->setTextFormat(Qt::RichText); m_coreInfoLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); m_coreInfoLabel->setOpenExternalLinks(true); m_coreInfoDock->setObjectName("coreInfoDock"); m_coreInfoDock->setProperty("default_area", Qt::RightDockWidgetArea); m_coreInfoDock->setProperty("menu_text", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CORE_INFO)); m_coreInfoDock->setWidget(m_coreInfoWidget); addDockWidget(static_cast( m_coreInfoDock->property("default_area").toInt()), m_coreInfoDock); m_logWidget->setLayout(new QVBoxLayout()); m_logWidget->layout()->addWidget(m_logTextEdit); m_logWidget->layout()->setContentsMargins(0, 0, 0, 0); m_logDock->setObjectName("logDock"); m_logDock->setProperty("default_area", Qt::BottomDockWidgetArea); m_logDock->setProperty("menu_text", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOG)); m_logDock->setWidget(m_logWidget); addDockWidget(static_cast( m_logDock->property("default_area").toInt()), m_logDock); /* Hide the log by default. If user has saved their dock positions * with the log visible, then this hide() call will be reversed * later by restoreState(). * * FIXME: If user unchecks "save dock positions", the log will * not be unhidden even if it was previously saved in the config. */ m_logDock->hide(); m_dirTree->setContextMenuPolicy(Qt::CustomContextMenu); m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_searchLineEdit, SIGNAL(returnPressed()), this, SLOT(onSearchEnterPressed())); connect(m_searchLineEdit, SIGNAL(textEdited(const QString&)), this, SLOT(onSearchLineEditEdited(const QString&))); connect(m_timer, SIGNAL(timeout()), this, SLOT(onTimeout())); connect(m_loadCoreWindow, SIGNAL(coreLoaded()), this, SLOT(onCoreLoaded())); connect(m_loadCoreWindow, SIGNAL(windowClosed()), this, SLOT(onCoreLoadWindowClosed())); connect(m_listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(onCurrentListItemChanged(QListWidgetItem*, QListWidgetItem*))); connect(m_startCorePushButton, SIGNAL(clicked()), this, SLOT(onStartCoreClicked())); connect(m_coreInfoPushButton, SIGNAL(clicked()), m_coreInfoDialog, SLOT(showCoreInfo())); connect(m_runPushButton, SIGNAL(clicked()), this, SLOT(onRunClicked())); connect(m_stopPushButton, SIGNAL(clicked()), this, SLOT(onStopClicked())); connect(m_dirTree, SIGNAL(itemsSelected(QModelIndexList)), this, SLOT(onTreeViewItemsSelected(QModelIndexList))); connect(m_dirTree, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onFileBrowserTreeContextMenuRequested(const QPoint&))); connect(m_listWidget, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onPlaylistWidgetContextMenuRequested(const QPoint&))); connect(m_launchWithComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onLaunchWithComboBoxIndexChanged(int))); connect(m_zoomSlider, SIGNAL(valueChanged(int)), this, SLOT(onZoomValueChanged(int))); connect(thumbnailTypeBoxartAction, SIGNAL(triggered()), this, SLOT(onBoxartThumbnailClicked())); connect(thumbnailTypeScreenshotAction, SIGNAL(triggered()), this, SLOT(onScreenshotThumbnailClicked())); connect(thumbnailTypeTitleAction, SIGNAL(triggered()), this, SLOT(onTitleThumbnailClicked())); connect(thumbnailTypeLogoAction, SIGNAL(triggered()), this, SLOT(onLogoThumbnailClicked())); connect(viewTypeIconsAction, SIGNAL(triggered()), this, SLOT(onIconViewClicked())); connect(viewTypeListAction, SIGNAL(triggered()), this, SLOT(onListViewClicked())); connect(m_dirModel, SIGNAL(directoryLoaded(const QString&)), this, SLOT(onFileSystemDirLoaded(const QString&))); connect(m_fileModel, SIGNAL(directoryLoaded(const QString&)), this, SLOT(onFileBrowserTableDirLoaded(const QString&))); m_dirTree->setCurrentIndex(m_dirModel->index(path_dir_menu_content)); m_dirTree->scrollTo(m_dirTree->currentIndex(), QAbstractItemView::PositionAtTop); m_dirTree->expand(m_dirTree->currentIndex()); /* must use queued connection */ connect(this, SIGNAL(scrollToDownloads(QString)), this, SLOT(onDownloadScroll(QString)), Qt::QueuedConnection); connect(this, SIGNAL(scrollToDownloadsAgain(QString)), this, SLOT(onDownloadScrollAgain(QString)), Qt::QueuedConnection); connect(m_playlistThumbnailDownloadProgressDialog, SIGNAL(canceled()), m_playlistThumbnailDownloadProgressDialog, SLOT(cancel())); connect(m_playlistThumbnailDownloadProgressDialog, SIGNAL(canceled()), this,SLOT(onPlaylistThumbnailDownloadCanceled())); connect(m_thumbnailDownloadProgressDialog, SIGNAL(canceled()), m_thumbnailDownloadProgressDialog, SLOT(cancel())); connect(m_thumbnailDownloadProgressDialog, SIGNAL(canceled()), this, SLOT(onThumbnailDownloadCanceled())); connect(m_thumbnailPackDownloadProgressDialog, SIGNAL(canceled()), m_thumbnailPackDownloadProgressDialog, SLOT(cancel())); connect(m_thumbnailPackDownloadProgressDialog, SIGNAL(canceled()), this, SLOT(onThumbnailPackDownloadCanceled())); connect(this, SIGNAL(itemChanged()), this, SLOT(onItemChanged())); connect(this, SIGNAL(gotThumbnailDownload(QString,QString)), this, SLOT(onDownloadThumbnail(QString,QString))); m_thumbnailTimer->setSingleShot(true); connect(m_thumbnailTimer, SIGNAL(timeout()), this, SLOT(updateVisibleItems())); connect(this, SIGNAL(updateThumbnails()), this, SLOT(updateVisibleItems())); /* TODO: Handle scroll and resize differently. */ connect(m_gridView, SIGNAL(visibleItemsChangedMaybe()), this, SLOT(startTimer())); connect(m_tableView->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(onCurrentItemChanged(const QModelIndex&))); connect(m_fileTableView->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(onCurrentFileChanged(const QModelIndex&))); connect(m_gridView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onContentItemDoubleClicked(const QModelIndex&))); connect(m_tableView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onContentItemDoubleClicked(const QModelIndex&))); connect(m_fileTableView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onFileDoubleClicked(const QModelIndex&))); connect(m_playlistModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&)), this, SLOT(onCurrentTableItemDataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); /* 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) */ connect(this, SIGNAL(gotLogMessage(const QString&)), this, SLOT(onGotLogMessage(const QString&)), Qt::AutoConnection); connect(this, SIGNAL(gotStatusMessage(QString,unsigned,unsigned,bool)), this, SLOT(onGotStatusMessage(QString,unsigned,unsigned,bool)), Qt::AutoConnection); connect(this, SIGNAL(gotReloadPlaylists()), this, SLOT(onGotReloadPlaylists()), Qt::AutoConnection); #if defined(HAVE_MENU) #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) connect(this, SIGNAL(gotReloadShaderParams()), this, SLOT(onGotReloadShaderParams()), Qt::AutoConnection); #endif #endif connect(this, SIGNAL(gotReloadCoreOptions()), this, SLOT(onGotReloadCoreOptions()), Qt::AutoConnection); /* These are always queued */ connect(this, SIGNAL(showErrorMessageDeferred(QString)), this, SLOT(onShowErrorMessage(QString)), Qt::QueuedConnection); connect(this, SIGNAL(showInfoMessageDeferred(QString)), this, SLOT(onShowInfoMessage(QString)), Qt::QueuedConnection); connect(this, SIGNAL(extractArchiveDeferred(QString,QString, QString,retro_task_callback_t)), this, SLOT(onExtractArchive(QString,QString,QString,retro_task_callback_t)), Qt::QueuedConnection); m_timer->start(TIMER_MSEC); statusBar()->addPermanentWidget(m_statusLabel); setCurrentCoreLabel(); setCoreActions(); /* Both of these are necessary to get the folder to scroll * to the top of the view */ qApp->processEvents(); QTimer::singleShot(0, this, SLOT(onBrowserStartClicked())); m_searchLineEdit->setFocus(); m_loadCoreWindow->setWindowModality(Qt::ApplicationModal); m_statusMessageElapsedTimer.start(); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) resizeDocks(QList() << m_searchDock, QList() << 1, Qt::Vertical); #endif } MainWindow::~MainWindow() { if (m_thumbnailPixmap) delete m_thumbnailPixmap; if (m_thumbnailPixmap2) delete m_thumbnailPixmap2; if (m_thumbnailPixmap3) delete m_thumbnailPixmap3; if (m_thumbnailPixmap4) delete m_thumbnailPixmap4; if (m_proxyFileModel) delete m_proxyFileModel; } void MainWindow::startTimer() { if (m_thumbnailTimer->isActive()) { m_thumbnailTimer->stop(); m_thumbnailTimer->start(50); } else m_thumbnailTimer->start(50); } void MainWindow::updateVisibleItems() { if ( m_currentBrowser == BROWSER_TYPE_PLAYLISTS && m_viewType == VIEW_TYPE_ICONS) { size_t i; QVector indexes = m_gridView->visibleIndexes(); size_t _len = indexes.size(); for (i = 0; i < _len; i++) m_playlistModel->loadThumbnail( m_proxyModel->mapToSource(indexes.at(i))); } } void MainWindow::setThumbnailCacheLimit(int count) { if (count < 1) count = 0; m_playlistModel->setThumbnailCacheLimit(count); } void MainWindow::onFileSystemDirLoaded(const QString &path) { if (path.isEmpty() || m_pendingDirScrollPath.isEmpty()) return; if (QDir(path) == QDir(m_pendingDirScrollPath)) { m_pendingDirScrollPath = QString(); emit scrollToDownloads(path); } } /* workaround for columns being resized */ void MainWindow::onFileBrowserTableDirLoaded(const QString &path) { if (!path.isEmpty()) m_fileTableView->horizontalHeader()->restoreState(m_fileTableHeaderState); } QVector > MainWindow::getPlaylists() { size_t i; QVector > playlists; size_t _len = m_listWidget->count(); for (i = 0; i < _len; i++) { QString label, path; QPair pair; QListWidgetItem *item = m_listWidget->item(i); if (!item) continue; label = item->text(); path = item->data(Qt::UserRole).toString(); pair.first = label; pair.second = path; playlists.append(pair); } return playlists; } void MainWindow::onItemChanged() { QModelIndex index = getCurrentContentIndex(); m_playlistModel->reloadThumbnail(index); onCurrentItemChanged(index); } QString MainWindow::getSpecialPlaylistPath(SpecialPlaylist playlist) { switch (playlist) { case SPECIAL_PLAYLIST_HISTORY: if (m_historyPlaylistsItem) return m_historyPlaylistsItem->data(Qt::UserRole).toString(); break; default: break; } return QString(); } void MainWindow::onIconViewClicked() { setCurrentViewType(VIEW_TYPE_ICONS); } void MainWindow::onListViewClicked() { setCurrentViewType(VIEW_TYPE_LIST); } void MainWindow::onBoxartThumbnailClicked() { setCurrentThumbnailType(THUMBNAIL_TYPE_BOXART); } void MainWindow::onScreenshotThumbnailClicked() { setCurrentThumbnailType(THUMBNAIL_TYPE_SCREENSHOT); } void MainWindow::onTitleThumbnailClicked() { setCurrentThumbnailType(THUMBNAIL_TYPE_TITLE_SCREEN); } void MainWindow::onLogoThumbnailClicked() { setCurrentThumbnailType(THUMBNAIL_TYPE_LOGO); } void MainWindow::setIconViewZoom(int zoom_val) { m_zoomSlider->setValue(zoom_val); } void MainWindow::onZoomValueChanged(int zoom_val) { int new_size = 0; if (zoom_val < 50) new_size = exp_scale(lerp(0, 49, 25, 49, zoom_val) / 50.0, 102, 256); else new_size = exp_scale(zoom_val / 100.0, 256, 1024); m_gridView->setGridSize(new_size); m_lastZoomSliderValue = zoom_val; } void MainWindow::showWelcomeScreen() { bool dont_ask = false; bool answer = false; if (!m_settings->value("show_welcome_screen", true).toBool()) return; const QString welcome_txt = QStringLiteral("" "Welcome to the RetroArch Desktop Menu!
\n" "
\n" "Many settings and actions are currently only available in the familiar Big Picture menu, " "but this Desktop Menu should be functional for launching content and managing playlists.
\n" "
\n" "Some useful hotkeys for interacting with the Big Picture menu include:\n" "
    \n" "
  • F1 - Bring up the Big Picture menu
  • \n" "
  • F5 - Bring the Desktop Menu back if closed
  • \n" "
  • F - Switch between fullscreen and windowed modes
  • \n" "
  • Esc - Exit RetroArch
  • \n" "
\n" "\n" "For more hotkeys and their assignments, see:
\n" "Settings -> Input -> Hotkeys
\n" "
\n" "Documentation for RetroArch, libretro and cores:
\n" "https://docs.libretro.com/"); answer = showMessageBox(welcome_txt, MainWindow::MSGBOX_TYPE_QUESTION_OKCANCEL, Qt::ApplicationModal, true, &dont_ask); if (answer && dont_ask) m_settings->setValue("show_welcome_screen", false); } const QString& MainWindow::customThemeString() const { return m_customThemeString; } bool MainWindow::setCustomThemeFile(QString filePath) { if (filePath.isEmpty()) { 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)); return false; } QFile file(filePath); if (file.exists()) { bool opened = file.open(QIODevice::ReadOnly); if (!opened) { 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)); return false; } { QByteArray fileArray = file.readAll(); QString fileStr = QString::fromUtf8(fileArray); file.close(); if (fileStr.isEmpty()) { 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)); return false; } setCustomThemeString(fileStr); } } else { 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)); return false; } return true; } void MainWindow::setCustomThemeString(QString qss) { m_customThemeString = qss;} bool MainWindow::showMessageBox(QString msg, MessageBoxType msgType, Qt::WindowModality modality, bool show_dont_ask, bool *dont_ask) { QCheckBox *checkbox = NULL; QPointer msg_box_ptr = new QMessageBox(this); QMessageBox *msg_box = msg_box_ptr.data(); msg_box->setWindowModality(modality); msg_box->setTextFormat(Qt::RichText); msg_box->setTextInteractionFlags(Qt::TextBrowserInteraction); if (show_dont_ask) { checkbox = new QCheckBox( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_DONT_SHOW_AGAIN), msg_box); /* QMessageBox::setCheckBox() is available since 5.2 */ msg_box->setCheckBox(checkbox); } switch (msgType) { case MSGBOX_TYPE_INFO: msg_box->setIcon(QMessageBox::Information); msg_box->setWindowTitle(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_INFORMATION)); break; case MSGBOX_TYPE_WARNING: msg_box->setIcon(QMessageBox::Warning); msg_box->setWindowTitle(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_WARNING)); break; case MSGBOX_TYPE_ERROR: msg_box->setIcon(QMessageBox::Critical); msg_box->setWindowTitle(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_ERROR)); break; case MSGBOX_TYPE_QUESTION_YESNO: msg_box->setIcon(QMessageBox::Question); msg_box->setWindowTitle(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_QUESTION)); msg_box->setStandardButtons(QMessageBox::Yes | QMessageBox::No); break; case MSGBOX_TYPE_QUESTION_OKCANCEL: msg_box->setIcon(QMessageBox::Question); msg_box->setWindowTitle(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_QUESTION)); msg_box->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); break; default: break; } msg_box->setText(msg); msg_box->exec(); if (!msg_box_ptr) return true; int key = msg_box->result(); if ( key != QMessageBox::Ok && key != QMessageBox::Yes) return false; if (checkbox && dont_ask) *dont_ask = checkbox->isChecked(); return true; } void MainWindow::onFileBrowserTreeContextMenuRequested(const QPoint&) { #ifdef HAVE_LIBRETRODB QDir dir; QByteArray dirArray; QPointer action; QList actions; QScopedPointer scanAction; QString currentDirString = QDir::toNativeSeparators( m_dirModel->filePath(m_dirTree->currentIndex())); settings_t *settings = config_get_ptr(); const char *fullpath = NULL; const char *path_dir_playlist = settings->paths.directory_playlist; const char *path_content_db = settings->paths.path_content_database; if (currentDirString.isEmpty()) return; #if (QT_VERSION > QT_VERSION_CHECK(6, 0, 0)) dir.setPath(currentDirString); #else dir = currentDirString; #endif if (!dir.exists()) return; /* Default NULL parameter for parent wasn't added until 5.7 */ scanAction.reset(new QAction( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SCAN_DIRECTORY), 0)); actions.append(scanAction.data()); if (!(action = QMenu::exec(actions, QCursor::pos(), NULL, m_dirTree))) return; dirArray = currentDirString.toUtf8(); fullpath = dirArray.constData(); task_push_dbscan( path_dir_playlist, path_content_db, fullpath, true, m_settings->value("show_hidden_files", true).toBool(), scan_finished_handler); #endif } void MainWindow::showStatusMessage(QString msg, unsigned priority, unsigned duration, bool flush) { emit gotStatusMessage(msg, priority, duration, flush); } void MainWindow::onGotStatusMessage( QString msg, unsigned priority, unsigned duration, bool flush) { QStatusBar *status = statusBar(); if (msg.isEmpty() || !status) return; if (status->currentMessage().isEmpty() || flush) { if (m_statusMessageElapsedTimer.elapsed() >= STATUS_MSG_THROTTLE_MSEC) { qint64 msg_duration; QScreen *screen = qApp->primaryScreen(); int msec_duration = 0; if (screen) msec_duration = (duration / screen->refreshRate()) * 1000; if (msec_duration <= 0) msec_duration = 1000; msg_duration = qMax(msec_duration, STATUS_MSG_THROTTLE_MSEC); m_statusMessageElapsedTimer.restart(); status->showMessage(msg, msg_duration); } } } void MainWindow::deferReloadShaderParams() { #if defined(HAVE_MENU) #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) emit gotReloadShaderParams(); #endif #endif } void MainWindow::onShaderParamsClicked() { #if defined(HAVE_MENU) #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) if (!m_shaderParamsDialog) return; m_shaderParamsDialog->show(); onGotReloadShaderParams(); #endif #endif } void MainWindow::onGotReloadShaderParams() { #if defined(HAVE_MENU) #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) if (m_shaderParamsDialog && m_shaderParamsDialog->isVisible()) m_shaderParamsDialog->reload(); #endif #endif } void MainWindow::onCoreOptionsClicked() { if (!m_coreOptionsDialog) return; m_coreOptionsDialog->show(); onGotReloadCoreOptions(); } void MainWindow::onGotReloadCoreOptions() { if (m_coreOptionsDialog && m_coreOptionsDialog->isVisible()) m_coreOptionsDialog->reload(); } void MainWindow::appendLogMessage(const QString &msg) { emit gotLogMessage(msg); } void MainWindow::onGotLogMessage(const QString &msg) { QString newMsg = msg; if (newMsg.at(newMsg.size() - 1) == '\n') newMsg.chop(1); m_logTextEdit->appendMessage(newMsg); } void MainWindow::onLaunchWithComboBoxIndexChanged(int) { int i; QString core_info_txt; QVector > infoList = getCoreInfo(); QVariantMap coreMap = m_launchWithComboBox->currentData( Qt::UserRole).value(); core_selection coreSelection = static_cast( coreMap.value("core_selection").toInt()); if (infoList.count() == 0) return; for (i = 0; i < infoList.count(); i++) { const QHash &hash = infoList.at(i); const QString &key = hash.value("html_key", hash.value("key")); const QString &value = hash.value("html_value", hash.value("value")); if (!key.isEmpty()) core_info_txt += key; if (!value.isEmpty()) { if (!key.isEmpty()) core_info_txt += QStringLiteral(" "); core_info_txt += value; } if (i < infoList.count() - 1) core_info_txt += QStringLiteral("
\n"); } m_coreInfoLabel->setText(core_info_txt); if (coreSelection == CORE_SELECTION_LOAD_CORE) onLoadCoreClicked(); else m_loadCoreWindow->setProperty("last_launch_with_index", m_launchWithComboBox->currentIndex()); } MainWindow::Theme MainWindow::getThemeFromString(QString themeString) { if (themeString == QLatin1String("default")) return THEME_SYSTEM_DEFAULT; else if (themeString == QLatin1String("dark")) return THEME_DARK; else if (themeString == QLatin1String("custom")) return THEME_CUSTOM; return THEME_SYSTEM_DEFAULT; } QString MainWindow::getThemeString(Theme theme) { switch (theme) { case THEME_SYSTEM_DEFAULT: return QStringLiteral("default"); case THEME_DARK: return QStringLiteral("dark"); case THEME_CUSTOM: return QStringLiteral("custom"); default: break; } return QStringLiteral("default"); } MainWindow::Theme MainWindow::theme() { return m_currentTheme; } void MainWindow::setTheme(Theme theme) { m_currentTheme = theme; setDefaultCustomProperties(); switch(theme) { case THEME_SYSTEM_DEFAULT: qApp->setStyleSheet(qt_theme_default_stylesheet.arg( m_settings->value("highlight_color", "palette(highlight)").toString())); break; case THEME_DARK: qApp->setStyleSheet(qt_theme_dark_stylesheet.arg( m_settings->value("highlight_color", "palette(highlight)").toString())); break; case THEME_CUSTOM: qApp->setStyleSheet(m_customThemeString); break; default: break; } #ifdef HAVE_MENU m_viewOptionsDialog->repaintIcons(); #endif } void MainWindow::setDefaultCustomProperties() { m_gridView->setLayout(QString(DEFAULT_GRID_LAYOUT)); m_gridView->setSpacing(DEFAULT_GRID_SPACING); m_gridItem.setThumbnailVerticalAlign(QString(DEFAULT_GRID_ITEM_THUMBNAIL_ALIGNMENT)); m_gridItem.setPadding(DEFAULT_GRID_ITEM_MARGIN); } void MainWindow::changeThumbnailType(ThumbnailType type) { m_playlistModel->setThumbnailType(type); updateVisibleItems(); m_gridView->viewport()->update(); } QString MainWindow::changeThumbnail(const QImage &image, QString type) { QHash hash = getCurrentContentHash(); QString dirString = m_playlistModel->getPlaylistThumbnailsDir( hash["db_name"]) + QStringLiteral("/") + type; QString thumbPath = m_playlistModel->getSanitizedThumbnailName( dirString + QStringLiteral("/"), hash["label_noext"]); QByteArray dirArray = QDir::toNativeSeparators(dirString).toUtf8(); const char *dirData = dirArray.constData(); QByteArray thumbArray = QDir::toNativeSeparators(thumbPath).toUtf8(); const char *thumbData = thumbArray.constData(); int quality = -1; QDir dir(dirString); QImage scaledImage(image); if (!dir.exists()) { if (!dir.mkpath(".")) { RARCH_ERR("[Qt]: Could not create directory: %s\n", dirData); return QString(); } RARCH_LOG("[Qt]: Created directory: %s\n", dirData); } if (m_settings->contains("thumbnail_max_size")) { int size = m_settings->value("thumbnail_max_size", 0).toInt(); if (size != 0 && (image.height() > size || image.width() > size)) scaledImage = image.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation); } if (m_settings->contains("thumbnail_quality")) quality = m_settings->value("thumbnail_quality", -1).toInt(); if (scaledImage.save(thumbPath, "png", quality)) { RARCH_LOG("[Qt]: Saved image: %s\n", thumbData); m_playlistModel->reloadThumbnailPath(thumbPath); updateVisibleItems(); return thumbPath; } RARCH_ERR("[Qt]: Could not save image: %s\n", thumbData); return QString(); } void MainWindow::onThumbnailDropped(const QImage &image, ThumbnailType thumbnailType) { switch (thumbnailType) { case THUMBNAIL_TYPE_BOXART: { QString path = changeThumbnail(image, THUMBNAIL_BOXART); if (path.isNull()) return; if (m_thumbnailPixmap) delete m_thumbnailPixmap; m_thumbnailPixmap = new QPixmap(path); onResizeThumbnailOne(*m_thumbnailPixmap, true); break; } case THUMBNAIL_TYPE_TITLE_SCREEN: { QString path = changeThumbnail(image, THUMBNAIL_TITLE); if (path.isNull()) return; if (m_thumbnailPixmap2) delete m_thumbnailPixmap2; m_thumbnailPixmap2 = new QPixmap(path); onResizeThumbnailTwo(*m_thumbnailPixmap2, true); break; } case THUMBNAIL_TYPE_SCREENSHOT: { QString path = changeThumbnail(image, THUMBNAIL_SCREENSHOT); if (path.isNull()) return; if (m_thumbnailPixmap3) delete m_thumbnailPixmap3; m_thumbnailPixmap3 = new QPixmap(path); onResizeThumbnailThree(*m_thumbnailPixmap3, true); break; } case THUMBNAIL_TYPE_LOGO: { QString path = changeThumbnail(image, THUMBNAIL_LOGO); if (path.isNull()) return; if (m_thumbnailPixmap4) delete m_thumbnailPixmap4; m_thumbnailPixmap4 = new QPixmap(path); onResizeThumbnailFour(*m_thumbnailPixmap4, true); break; } } } QVector > MainWindow::getCoreInfo() { size_t i; QVector > infoList; runloop_state_t *runloop_st = runloop_state_get_ptr(); QHash currentCore = getSelectedCore(); core_info_t *core_info = NULL; QByteArray currentCorePathArray = currentCore["core_path"].toUtf8(); const char *current_core_path_data = currentCorePathArray.constData(); /* Search for current core */ core_info_find(current_core_path_data, &core_info); if ( currentCore["core_path"].isEmpty() || !core_info || !core_info->has_info) { QHash hash; hash["key"] = msg_hash_to_str( MENU_ENUM_LABEL_VALUE_NO_CORE_INFORMATION_AVAILABLE); hash["value"] = QLatin1String(""); infoList.append(hash); return infoList; } if (core_info->core_name) { QHash hash; hash["key"] = QString( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_CORE_NAME)) + QStringLiteral(":"); hash["value"] = core_info->core_name; infoList.append(hash); } if (core_info->display_name) { QHash hash; hash["key"] = QString( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_CORE_LABEL)) + QStringLiteral(":"); hash["value"] = core_info->display_name; infoList.append(hash); } if (core_info->systemname) { QHash hash; hash["key"] = QString(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_CORE_INFO_SYSTEM_NAME)) + QStringLiteral(":"); hash["value"] = core_info->systemname; infoList.append(hash); } if (core_info->system_manufacturer) { QHash hash; hash["key"] = QString(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_CORE_INFO_SYSTEM_MANUFACTURER)) + QStringLiteral(":"); hash["value"] = core_info->system_manufacturer; infoList.append(hash); } if (core_info->categories_list) { QString categories; QHash hash; for (i = 0; i < core_info->categories_list->size; i++) { categories += core_info->categories_list->elems[i].data; if (i < core_info->categories_list->size - 1) categories += QStringLiteral(", "); } hash["key"] = QString( msg_hash_to_str( MENU_ENUM_LABEL_VALUE_CORE_INFO_CATEGORIES)) + QStringLiteral(":"); hash["value"] = categories; infoList.append(hash); } if (core_info->authors_list) { QString authors; QHash hash; for (i = 0; i < core_info->authors_list->size; i++) { authors += core_info->authors_list->elems[i].data; if (i < core_info->authors_list->size - 1) authors += QStringLiteral(", "); } hash["key"] = QString( msg_hash_to_str( MENU_ENUM_LABEL_VALUE_CORE_INFO_AUTHORS)) + QStringLiteral(":"); hash["value"] = authors; infoList.append(hash); } if (core_info->permissions_list) { QString permissions; QHash hash; for (i = 0; i < core_info->permissions_list->size; i++) { permissions += core_info->permissions_list->elems[i].data; if (i < core_info->permissions_list->size - 1) permissions += QStringLiteral(", "); } hash["key"] = QString( msg_hash_to_str( MENU_ENUM_LABEL_VALUE_CORE_INFO_PERMISSIONS)) + QStringLiteral(":"); hash["value"] = permissions; infoList.append(hash); } if (core_info->licenses_list) { QString licenses; QHash hash; for (i = 0; i < core_info->licenses_list->size; i++) { licenses += core_info->licenses_list->elems[i].data; if (i < core_info->licenses_list->size - 1) licenses += QStringLiteral(", "); } hash["key"] = QString( msg_hash_to_str( MENU_ENUM_LABEL_VALUE_CORE_INFO_LICENSES)) + QStringLiteral(":"); hash["value"] = licenses; infoList.append(hash); } if (core_info->supported_extensions_list) { QString supported_extensions; QHash hash; for (i = 0; i < core_info->supported_extensions_list->size; i++) { supported_extensions += core_info->supported_extensions_list->elems[i].data; if (i < core_info->supported_extensions_list->size - 1) supported_extensions += QStringLiteral(", "); } hash["key"] = QString( msg_hash_to_str( MENU_ENUM_LABEL_VALUE_CORE_INFO_SUPPORTED_EXTENSIONS)) + QStringLiteral(":"); hash["value"] = supported_extensions; infoList.append(hash); } if (core_info->firmware_count > 0) { char tmp_path[PATH_MAX_LENGTH]; core_info_ctx_firmware_t firmware_info; bool update_missing_firmware = false; bool set_missing_firmware = false; settings_t *settings = config_get_ptr(); uint8_t flags = content_get_flags(); bool systemfiles_in_content_dir = settings->bools.systemfiles_in_content_dir; bool content_is_inited = flags & CONTENT_ST_FLAG_IS_INITED; firmware_info.path = core_info->path; /* If 'System Files are in Content Directory' is enabled and content is inited, * adjust the path to check for firmware files */ if (systemfiles_in_content_dir && content_is_inited) { fill_pathname_basedir(tmp_path, path_get(RARCH_PATH_CONTENT), sizeof(tmp_path)); /* If content path is empty, fall back to global system dir path */ if (string_is_empty(tmp_path)) firmware_info.directory.system = settings->paths.directory_system; else { size_t _len = strlen(tmp_path); /* Removes trailing slash (unless root dir), doesn't really matter * but it's more consistent with how the path is stored and * displayed without 'System Files are in Content Directory' */ if ( string_count_occurrences_single_character(tmp_path, PATH_DEFAULT_SLASH_C()) > 1 && tmp_path[_len - 1] == PATH_DEFAULT_SLASH_C()) tmp_path[_len - 1] = '\0'; firmware_info.directory.system = tmp_path; } } else firmware_info.directory.system = settings->paths.directory_system; update_missing_firmware = core_info_list_update_missing_firmware( &firmware_info, &set_missing_firmware); if (set_missing_firmware) runloop_st->missing_bios = true; else runloop_st->missing_bios = false; if (update_missing_firmware) { char tmp[PATH_MAX_LENGTH]; QHash hash; hash["key"] = QString(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_CORE_INFO_FIRMWARE)) + QStringLiteral(":"); hash["value"] = QLatin1String(""); infoList.append(hash); /* If 'System Files are in Content Directory' is enabled, * let's add a note about it. */ if (systemfiles_in_content_dir) { hash["key"] = QString(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_CORE_INFO_FIRMWARE_IN_CONTENT_DIRECTORY)); hash["value"] = QLatin1String(""); infoList.append(hash); } /* Show the path that was checked */ snprintf(tmp, sizeof(tmp), msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_FIRMWARE_PATH), firmware_info.directory.system); hash["key"] = QString(tmp); hash["value"] = QLatin1String(""); infoList.append(hash); /* FIXME: This looks hacky and probably * needs to be improved for good translation support. */ for (i = 0; i < core_info->firmware_count; i++) { if (core_info->firmware[i].desc) { QString val_txt; QHash hash; QString lbl_txt = QStringLiteral("(!) "); bool missing = false; if (core_info->firmware[i].missing) { missing = true; if (core_info->firmware[i].optional) lbl_txt += msg_hash_to_str( MENU_ENUM_LABEL_VALUE_MISSING_OPTIONAL); else lbl_txt += msg_hash_to_str( MENU_ENUM_LABEL_VALUE_MISSING_REQUIRED); } else if (core_info->firmware[i].optional) lbl_txt += msg_hash_to_str( MENU_ENUM_LABEL_VALUE_PRESENT_OPTIONAL); else lbl_txt += msg_hash_to_str( MENU_ENUM_LABEL_VALUE_PRESENT_REQUIRED); if (core_info->firmware[i].desc) val_txt = core_info->firmware[i].desc; else val_txt = msg_hash_to_str( MENU_ENUM_LABEL_VALUE_RDB_ENTRY_NAME); hash["key"] = lbl_txt; hash["value"] = val_txt; if (missing) { QString style = QStringLiteral("font-weight: bold; color: #ff0000"); hash["label_style"] = style; hash["value_style"] = style; hash["html_key"] = "" + hash["key"] + QStringLiteral(""); hash["html_value"] = "" + hash["value"] + QStringLiteral(""); } else { QString style = QStringLiteral("font-weight: bold; color: rgb(0, 175, 0)"); hash["label_style"] = style; hash["value_style"] = style; hash["html_key"] = "" + hash["key"] + QStringLiteral(""); hash["html_value"] = "" + hash["value"] + QStringLiteral(""); } infoList.append(hash); } } } } if (core_info->notes) { for (i = 0; i < core_info->note_list->size; i++) { QHash hash; hash["key"] = QLatin1String(""); hash["value"] = core_info->note_list->elems[i].data; infoList.append(hash); } } return infoList; } void MainWindow::onSearchResetClicked() { m_searchLineEdit->clear(); onSearchEnterPressed(); } QToolButton* MainWindow::coreInfoPushButton() { return m_coreInfoPushButton; } void MainWindow::onTreeViewItemsSelected(QModelIndexList selectedIndexes) { QString dir; if (selectedIndexes.isEmpty()) return; dir = m_dirModel->filePath(selectedIndexes.first()); selectBrowserDir(dir); } void MainWindow::onFileDoubleClicked(const QModelIndex &proxyIndex) { const QModelIndex index = m_proxyFileModel->mapToSource(proxyIndex); if (m_fileModel->isDir(index)) m_dirTree->setCurrentIndex(m_dirModel->index(m_fileModel->filePath(index))); else loadContent(getFileContentHash(index)); } void MainWindow::selectBrowserDir(QString path) { if (!path.isEmpty()) { QModelIndex sourceIndex = m_fileModel->setRootPath(path); QModelIndex proxyIndex = m_proxyFileModel->mapFromSource(sourceIndex); m_fileTableHeaderState = m_fileTableView->horizontalHeader()->saveState(); if (proxyIndex.isValid()) m_fileTableView->setRootIndex(proxyIndex); else { /* the directory is filtered out. Remove the filter for a moment. * FIXME: Find a way to not have to do this * (not filtering dirs is one). */ #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) m_proxyFileModel->setFilterRegularExpression(QRegularExpression()); #else m_proxyFileModel->setFilterRegExp(QRegExp()); #endif m_fileTableView->setRootIndex(m_proxyFileModel->mapFromSource(sourceIndex)); #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) m_proxyFileModel->setFilterRegularExpression(m_searchRegularExpression); #else m_proxyFileModel->setFilterRegExp(m_searchRegExp); #endif } } setCoreActions(); } QTabWidget* MainWindow::browserAndPlaylistTabWidget() { return m_browserAndPlaylistTabWidget; } void MainWindow::onDropWidgetEnterPressed() { #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) /* Entry is being renamed, ignore this enter press */ if (m_tableView->isPersistentEditorOpen(m_tableView->currentIndex())) return; #else /* We can only check if any editor at all is open */ if (m_tableView->isEditorOpen()) return; #endif onRunClicked(); } QModelIndex MainWindow::getCurrentContentIndex() { if (m_viewType == VIEW_TYPE_LIST) return m_tableView->currentIndex(); else if (m_viewType == VIEW_TYPE_ICONS) return m_gridView->currentIndex(); return QModelIndex(); } QHash MainWindow::getCurrentContentHash() { return getCurrentContentIndex().data(PlaylistModel::HASH).value >(); } QHash MainWindow::getFileContentHash(const QModelIndex &index) { QHash hash; QFileInfo fileInfo = m_fileModel->fileInfo(index); hash["path"] = QDir::toNativeSeparators(m_fileModel->filePath(index)); hash["label"] = hash["path"]; hash["label_noext"] = fileInfo.completeBaseName(); hash["db_name"] = fileInfo.dir().dirName(); return hash; } void MainWindow::onContentItemDoubleClicked(const QModelIndex &index) { Q_UNUSED(index); onRunClicked(); } void MainWindow::onStartCoreClicked() { content_ctx_info_t content_info; content_info.argc = 0; content_info.argv = NULL; content_info.args = NULL; content_info.environ_get = NULL; path_clear(RARCH_PATH_BASENAME); if (!task_push_start_current_core(&content_info)) QMessageBox::critical(this, msg_hash_to_str(MSG_ERROR), msg_hash_to_str(MSG_FAILED_TO_LOAD_CONTENT)); } QHash MainWindow::getSelectedCore() { QHash coreHash; QHash contentHash; QVariantMap coreMap = m_launchWithComboBox->currentData( Qt::UserRole).value(); core_selection coreSelection = static_cast( coreMap.value("core_selection").toInt()); ViewType viewType = getCurrentViewType(); if (viewType == VIEW_TYPE_LIST) contentHash = m_tableView->currentIndex().data( PlaylistModel::HASH).value >(); else if (viewType == VIEW_TYPE_ICONS) contentHash = m_gridView->currentIndex().data( PlaylistModel::HASH).value >(); else return coreHash; switch(coreSelection) { case CORE_SELECTION_CURRENT: coreHash["core_path"] = path_get(RARCH_PATH_CORE); break; case CORE_SELECTION_PLAYLIST_SAVED: if ( contentHash.isEmpty() || contentHash["core_path"].isEmpty()) break; coreHash["core_path"] = contentHash["core_path"]; break; case CORE_SELECTION_PLAYLIST_DEFAULT: { QString plName; QString defaultCorePath; if (contentHash.isEmpty()) break; plName = contentHash["pl_name"].isEmpty() ? contentHash["db_name"] : contentHash["pl_name"]; if (plName.isEmpty()) break; defaultCorePath = getPlaylistDefaultCore(plName); if (!defaultCorePath.isEmpty()) coreHash["core_path"] = defaultCorePath; break; } default: break; } return coreHash; } /* the hash typically has the following keys: path - absolute path to the content file core_path - absolute path to the core, or "DETECT" to ask the user db_name - the display name of the rdb database this content is from label - the display name of the content, usually comes from the database crc32 - an upper-case, 8 byte string representation of the hex CRC32 checksum (e.g. ABCDEF12) followed by "|crc" core_name - The display name of the core, or "DETECT" if unknown label_noext - The display name of the content that is guaranteed not to contain a file extension */ void MainWindow::loadContent(const QHash &contentHash) { content_ctx_info_t content_info; QByteArray corePathArray; QByteArray contentPathArray; QByteArray contentLabelArray; QByteArray contentDbNameArray; QByteArray contentCrc32Array; char content_db_name_full[PATH_MAX_LENGTH]; char core_path_cached[PATH_MAX_LENGTH]; const char *core_path = NULL; const char *content_path = NULL; const char *content_label = NULL; const char *content_db_name = NULL; const char *content_crc32 = NULL; #ifdef HAVE_MENU struct menu_state *menu_st = menu_state_get_ptr(); #endif QVariantMap coreMap = m_launchWithComboBox->currentData( Qt::UserRole).value(); core_selection coreSelection = static_cast( coreMap.value("core_selection").toInt()); core_info_t *coreInfo = NULL; content_db_name_full[0] = '\0'; core_path_cached[0] = '\0'; if (m_pendingRun) coreSelection = CORE_SELECTION_CURRENT; else if (coreSelection == CORE_SELECTION_ASK) { QStringList extensionFilters; if (contentHash.contains("path")) { int last_index = contentHash["path"].lastIndexOf('.'); QByteArray pathArray = contentHash["path"].toUtf8(); const char *pathData = pathArray.constData(); if (last_index >= 0) { QString ext_str = contentHash["path"].mid(last_index + 1); if (!ext_str.isEmpty()) extensionFilters.append(ext_str.toLower()); } if (path_is_compressed_file(pathData)) { struct string_list *list = file_archive_get_file_list(pathData, NULL); if (list) { if (list->size > 0) { size_t i; for (i = 0; i < list->size; i++) { const char *filePath = list->elems[i].data; const char *extension = path_get_extension(filePath); if (!extensionFilters.contains(extension, Qt::CaseInsensitive)) extensionFilters.append(extension); } } string_list_free(list); } } } m_pendingRun = true; onLoadCoreClicked(extensionFilters); return; } switch (coreSelection) { case CORE_SELECTION_CURRENT: corePathArray = path_get(RARCH_PATH_CORE); contentPathArray = contentHash["path"].toUtf8(); contentLabelArray = contentHash["label_noext"].toUtf8(); break; case CORE_SELECTION_PLAYLIST_SAVED: corePathArray = contentHash["core_path"].toUtf8(); contentPathArray = contentHash["path"].toUtf8(); contentLabelArray = contentHash["label_noext"].toUtf8(); break; case CORE_SELECTION_PLAYLIST_DEFAULT: { QString plName = contentHash["pl_name"].isEmpty() ? contentHash["db_name"] : contentHash["pl_name"]; QString defaultCorePath = getPlaylistDefaultCore(plName); if (!defaultCorePath.isEmpty()) { corePathArray = defaultCorePath.toUtf8(); contentPathArray = contentHash["path"].toUtf8(); contentLabelArray = contentHash["label_noext"].toUtf8(); } break; } default: return; } contentDbNameArray = contentHash["db_name"].toUtf8(); contentCrc32Array = contentHash["crc32"].toUtf8(); core_path = corePathArray.constData(); content_path = contentPathArray.constData(); content_label = contentLabelArray.constData(); content_db_name = contentDbNameArray.constData(); content_crc32 = contentCrc32Array.constData(); /* Search for specified core - ensures path * is 'sanitised' */ if ( core_info_find(core_path, &coreInfo) && !string_is_empty(coreInfo->path)) core_path = coreInfo->path; /* If a core is currently running, the following * call of 'command_event(CMD_EVENT_UNLOAD_CORE, NULL)' * will free the global core_info struct, which will * in turn free the pointer referenced by coreInfo->path. * This will invalidate core_path, so we have to cache * its current value here. */ if (!string_is_empty(core_path)) strlcpy(core_path_cached, core_path, sizeof(core_path_cached)); /* Add lpl extension to db_name, if required */ if (!string_is_empty(content_db_name)) fill_pathname(content_db_name_full, content_db_name, ".lpl", sizeof(content_db_name_full)); content_info.argc = 0; content_info.argv = NULL; content_info.args = NULL; content_info.environ_get = NULL; #ifdef HAVE_MENU menu_st->selection_ptr = 0; #endif command_event(CMD_EVENT_UNLOAD_CORE, NULL); if (!task_push_load_content_with_new_core_from_companion_ui( core_path_cached, content_path, content_label, content_db_name_full, content_crc32, &content_info, NULL, NULL)) { QMessageBox::critical(this, msg_hash_to_str(MSG_ERROR), msg_hash_to_str(MSG_FAILED_TO_LOAD_CONTENT)); return; } #ifdef HAVE_MENU menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, NULL); #endif } void MainWindow::onRunClicked() { QHash contentHash; switch (m_currentBrowser) { case BROWSER_TYPE_FILES: contentHash = getFileContentHash( m_proxyFileModel->mapToSource(m_fileTableView->currentIndex())); break; case BROWSER_TYPE_PLAYLISTS: contentHash = getCurrentContentHash(); break; } if (!contentHash.isEmpty()) loadContent(contentHash); } PlaylistEntryDialog* MainWindow::playlistEntryDialog() { return m_playlistEntryDialog; } ViewOptionsDialog* MainWindow::viewOptionsDialog() {return m_viewOptionsDialog;} void MainWindow::setCoreActions() { QListWidgetItem *currentPlaylistItem = m_listWidget->currentItem(); ViewType viewType = getCurrentViewType(); QHash hash = getCurrentContentHash(); QString currentPlaylistFileName = QString(); rarch_system_info_t *sys_info = &runloop_state_get_ptr()->system; m_launchWithComboBox->clear(); /* Is contentless core? */ if (sys_info->load_no_content) m_startCorePushButton->show(); else m_startCorePushButton->hide(); /* Is core loaded? */ if ( !m_currentCore.isEmpty() && m_currentCore != msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_CORE) && m_settings->value("suggest_loaded_core_first", false).toBool()) { QVariantMap comboBoxMap; comboBoxMap["core_name"] = m_currentCore; comboBoxMap["core_path"] = path_get(RARCH_PATH_CORE); comboBoxMap["core_selection"] = CORE_SELECTION_CURRENT; m_launchWithComboBox->addItem(m_currentCore, QVariant::fromValue(comboBoxMap)); } if (m_currentBrowser == BROWSER_TYPE_PLAYLISTS) { if (!hash.isEmpty()) { QString coreName = hash["core_name"]; if (coreName.isEmpty()) coreName = QStringLiteral(""); else { const char *detect_str = "DETECT"; if (coreName != detect_str) { if (m_launchWithComboBox->findText(coreName) == -1) { int i; bool found_existing = false; for (i = 0; i < m_launchWithComboBox->count(); i++) { QVariantMap map = m_launchWithComboBox->itemData( i, Qt::UserRole).toMap(); if ( map.value("core_path").toString() == hash["core_path"] || map.value("core_name").toString() == coreName) { found_existing = true; break; } } if (!found_existing) { QVariantMap comboBoxMap; comboBoxMap["core_name"] = coreName; comboBoxMap["core_path"] = hash["core_path"]; comboBoxMap["core_selection"] = CORE_SELECTION_PLAYLIST_SAVED; m_launchWithComboBox->addItem(coreName, QVariant::fromValue(comboBoxMap)); } } } } } } switch(m_currentBrowser) { case BROWSER_TYPE_PLAYLISTS: currentPlaylistFileName = hash["pl_name"].isEmpty() ? hash["db_name"] : hash["pl_name"]; break; case BROWSER_TYPE_FILES: currentPlaylistFileName = m_fileModel->rootDirectory().dirName(); break; } if (!currentPlaylistFileName.isEmpty()) { QString defaultCorePath = getPlaylistDefaultCore(currentPlaylistFileName); if (!defaultCorePath.isEmpty()) { QString currentPlaylistItemDataString; bool allPlaylists = false; int row = 0; QByteArray defaultCorePathArray = defaultCorePath.toUtf8(); const char *default_core_path_data = defaultCorePathArray.constData(); if (currentPlaylistItem) { currentPlaylistItemDataString = currentPlaylistItem->data( Qt::UserRole).toString(); allPlaylists = ( currentPlaylistItemDataString == ALL_PLAYLISTS_TOKEN); } for (row = 0; row < m_listWidget->count(); row++) { core_info_t *coreInfo = NULL; if (allPlaylists) { QFileInfo info; QListWidgetItem *listItem = m_listWidget->item(row); QString listItemString = listItem->data( Qt::UserRole).toString(); info.setFile(listItemString); if (listItemString == ALL_PLAYLISTS_TOKEN) continue; } /* Search for default core */ if (core_info_find(default_core_path_data, &coreInfo)) { if (m_launchWithComboBox->findText(coreInfo->core_name) == -1) { int i; bool found_existing = false; for (i = 0; i < m_launchWithComboBox->count(); i++) { QVariantMap map = m_launchWithComboBox->itemData( i, Qt::UserRole).toMap(); QByteArray CorePathArray = map.value("core_path").toString().toUtf8(); const char *core_path_data = CorePathArray.constData(); if ( string_starts_with(path_basename(core_path_data), coreInfo->core_file_id.str) || map.value("core_name").toString() == coreInfo->core_name || map.value("core_name").toString() == coreInfo->display_name) { found_existing = true; break; } } if (!found_existing) { QVariantMap comboBoxMap; comboBoxMap["core_name"] = QVariant::fromValue( QString(coreInfo->core_name)); comboBoxMap["core_path"] = QVariant::fromValue( QString(coreInfo->path)); comboBoxMap["core_selection"] = CORE_SELECTION_PLAYLIST_DEFAULT; m_launchWithComboBox->addItem(coreInfo->core_name, QVariant::fromValue(comboBoxMap)); } } } if (!allPlaylists) break; } } } { QVariantMap comboBoxMap; comboBoxMap["core_selection"] = CORE_SELECTION_ASK; m_launchWithComboBox->addItem(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_CORE_SELECTION_ASK), QVariant::fromValue(comboBoxMap)); m_launchWithComboBox->insertSeparator(m_launchWithComboBox->count()); comboBoxMap["core_selection"] = CORE_SELECTION_LOAD_CORE; m_launchWithComboBox->addItem(QString(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_LOAD_CORE)) + QStringLiteral("..."), QVariant::fromValue(comboBoxMap)); } } void MainWindow::onTabWidgetIndexChanged(int index) { QString str = m_browserAndPlaylistTabWidget->tabText(index); if (str == msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_TAB_FILE_BROWSER)) { m_currentBrowser = BROWSER_TYPE_FILES; m_centralWidget->setCurrentWidget(m_fileTableView); onCurrentFileChanged(m_fileTableView->currentIndex()); } else if (str == msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_TAB_PLAYLISTS)) { m_currentBrowser = BROWSER_TYPE_PLAYLISTS; m_centralWidget->setCurrentWidget(m_playlistViewsAndFooter); onCurrentItemChanged(m_tableView->currentIndex()); } applySearch(); setCoreActions(); } QToolButton* MainWindow::runPushButton() { return m_runPushButton; } QToolButton* MainWindow::stopPushButton() { return m_stopPushButton; } QToolButton* MainWindow::startCorePushButton() { return m_startCorePushButton;} QComboBox* MainWindow::launchWithComboBox() { return m_launchWithComboBox; } void MainWindow::onSearchLineEditEdited(const QString &text) { int i; QVector textHiraToKata; QVector textKataToHira; QVector textUnicode = text.toUcs4(); bool found_hiragana = false; bool found_katakana = false; for (i = 0; i < textUnicode.size(); i++) { unsigned code = textUnicode.at(i); if (code >= HIRAGANA_START && code <= HIRAGANA_END) { found_hiragana = true; textHiraToKata += code + HIRA_KATA_OFFSET; } else if (code >= KATAKANA_START && code <= KATAKANA_END) { found_katakana = true; textKataToHira += code - HIRA_KATA_OFFSET; } else { textHiraToKata += code; textKataToHira += code; } } #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) if (!found_hiragana && !found_katakana) m_searchRegularExpression = QRegularExpression(text, QRegularExpression::CaseInsensitiveOption); else if (found_hiragana && !found_katakana) m_searchRegularExpression = QRegularExpression(text + QStringLiteral("|") + QString::fromUcs4(textHiraToKata.constData(), textHiraToKata.size()), QRegularExpression::CaseInsensitiveOption); else if (!found_hiragana && found_katakana) m_searchRegularExpression = QRegularExpression(text + QStringLiteral("|") + QString::fromUcs4(textKataToHira.constData(), textKataToHira.size()), QRegularExpression::CaseInsensitiveOption); else m_searchRegularExpression = QRegularExpression(text + QStringLiteral("|") + QString::fromUcs4(textHiraToKata.constData(), textHiraToKata.size()) + QStringLiteral("|") + QString::fromUcs4(textKataToHira.constData(), textKataToHira.size()), QRegularExpression::CaseInsensitiveOption); #else if (!found_hiragana && !found_katakana) m_searchRegExp = QRegExp(text, Qt::CaseInsensitive); else if (found_hiragana && !found_katakana) m_searchRegExp = QRegExp(text + QStringLiteral("|") + QString::fromUcs4(textHiraToKata.constData(), textHiraToKata.size()), Qt::CaseInsensitive); else if (!found_hiragana && found_katakana) m_searchRegExp = QRegExp(text + QStringLiteral("|") + QString::fromUcs4(textKataToHira.constData(), textKataToHira.size()), Qt::CaseInsensitive); else m_searchRegExp = QRegExp(text + QStringLiteral("|") + QString::fromUcs4(textHiraToKata.constData(), textHiraToKata.size()) + QStringLiteral("|") + QString::fromUcs4(textKataToHira.constData(), textKataToHira.size()), Qt::CaseInsensitive); #endif applySearch(); } void MainWindow::applySearch() { switch (m_currentBrowser) { case BROWSER_TYPE_PLAYLISTS: #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) if ( m_proxyModel->filterRegularExpression() != m_searchRegularExpression) { m_proxyModel->setFilterRegularExpression(m_searchRegularExpression); updateItemsCount(); } #else if (m_proxyModel->filterRegExp() != m_searchRegExp) { m_proxyModel->setFilterRegExp(m_searchRegExp); updateItemsCount(); } #endif break; case BROWSER_TYPE_FILES: #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) if ( m_proxyFileModel->filterRegularExpression() != m_searchRegularExpression) m_proxyFileModel->setFilterRegularExpression( m_searchRegularExpression); #else if (m_proxyFileModel->filterRegExp() != m_searchRegExp) m_proxyFileModel->setFilterRegExp(m_searchRegExp); #endif break; } } void MainWindow::onViewClosedDocksAboutToShow() { int i; QList dockWidgets; QMenu *menu = qobject_cast(sender()); bool found = false; if (!menu) return; dockWidgets = findChildren(); menu->clear(); if (dockWidgets.isEmpty()) { menu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NONE)); return; } for (i = 0; i < dockWidgets.count(); i++) { const QDockWidget *dock = dockWidgets.at(i); if (!dock->isVisible()) { QAction *action = menu->addAction( dock->property("menu_text").toString(), this, SLOT(onShowHiddenDockWidgetAction())); action->setProperty("dock_name", dock->objectName()); found = true; } } if (!found) menu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NONE)); } void MainWindow::onShowHiddenDockWidgetAction() { QDockWidget *dock = NULL; QAction *action = qobject_cast(sender()); if (!action) return; if (!(dock = findChild(action->property( "dock_name").toString()))) return; if (!dock->isVisible()) { addDockWidget(static_cast( dock->property("default_area").toInt()), dock); dock->setVisible(true); dock->setFloating(false); } } QWidget* MainWindow::searchWidget() { return m_searchWidget; } QLineEdit* MainWindow::searchLineEdit() { return m_searchLineEdit; } void MainWindow::onSearchEnterPressed() { onSearchLineEditEdited(m_searchLineEdit->text()); } void MainWindow::onCurrentTableItemDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { QHash hash; if (!roles.contains(Qt::EditRole)) return; if (topLeft != bottomRight) return; hash = topLeft.data(PlaylistModel::HASH).value>(); updateCurrentPlaylistEntry(hash); onCurrentItemChanged(topLeft); } void MainWindow::onCurrentListItemDataChanged(QListWidgetItem *item) { renamePlaylistItem(item, item->text()); } void MainWindow::renamePlaylistItem(QListWidgetItem *item, QString newName) { QString oldPath; QString newPath; QString extension; QString oldName; QFile file; QFileInfo info; QFileInfo playlistInfo; QString playlistPath; settings_t *settings = config_get_ptr(); const char *path_dir_playlist = settings->paths.directory_playlist; QDir playlistDir(path_dir_playlist); if (!item) return; playlistPath = item->data(Qt::UserRole).toString(); playlistInfo = QFileInfo(playlistPath); oldName = playlistInfo.completeBaseName(); /* Don't just compare strings in case there are * case differences on Windows that should be ignored. */ /* special playlists like history etc. can't have an association */ if (QDir(playlistInfo.absoluteDir()) != QDir(playlistDir)) { /* Special playlists shouldn't be editable already, * but just in case, set the old name back and * early return if they rename it */ item->setText(oldName); return; } /* Block this signal because setData() would trigger * an infinite loop here */ disconnect(m_listWidget, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(onCurrentListItemDataChanged(QListWidgetItem*))); oldPath = item->data(Qt::UserRole).toString(); file.setFileName(oldPath); info = QFileInfo(file); extension = info.suffix(); newPath = info.absolutePath(); /* absolutePath() will always use / even on Windows */ if (newPath.at(newPath.count() - 1) != '/') /* add trailing slash if the path doesn't have one */ newPath += '/'; newPath += newName + QStringLiteral(".") + extension; item->setData(Qt::UserRole, newPath); if (!file.rename(newPath)) { RARCH_ERR("[Qt]: Could not rename playlist.\n"); item->setText(oldName); } connect(m_listWidget, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(onCurrentListItemDataChanged(QListWidgetItem*))); } void MainWindow::onCurrentItemChanged(const QModelIndex &index) { onCurrentItemChanged(index.data( PlaylistModel::HASH).value>()); } void MainWindow::onCurrentFileChanged(const QModelIndex &index) { onCurrentItemChanged(getFileContentHash( m_proxyFileModel->mapToSource(index))); } void MainWindow::onCurrentItemChanged(const QHash &hash) { QString path = hash["path"]; bool acceptDrop = false; if (m_thumbnailPixmap) delete m_thumbnailPixmap; if (m_thumbnailPixmap2) delete m_thumbnailPixmap2; if (m_thumbnailPixmap3) delete m_thumbnailPixmap3; if (m_thumbnailPixmap4) delete m_thumbnailPixmap4; if (m_playlistModel->isSupportedImage(path)) { /* use thumbnail widgets to show regular image files */ m_thumbnailPixmap = new QPixmap(path); m_thumbnailPixmap2 = new QPixmap(*m_thumbnailPixmap); m_thumbnailPixmap3 = new QPixmap(*m_thumbnailPixmap); m_thumbnailPixmap4 = new QPixmap(*m_thumbnailPixmap); } else { QString thumbnailsDir = m_playlistModel->getPlaylistThumbnailsDir( hash["db_name"]); QString thumbnailName1 = m_playlistModel->getSanitizedThumbnailName( thumbnailsDir + QStringLiteral("/") + THUMBNAIL_BOXART + QStringLiteral("/"), hash["label_noext"]); QString thumbnailName2 = m_playlistModel->getSanitizedThumbnailName( thumbnailsDir + QStringLiteral("/") + THUMBNAIL_TITLE + QStringLiteral("/"), hash["label_noext"]); QString thumbnailName3 = m_playlistModel->getSanitizedThumbnailName( thumbnailsDir + QStringLiteral("/") + THUMBNAIL_SCREENSHOT + QStringLiteral("/"), hash["label_noext"]); QString thumbnailName4 = m_playlistModel->getSanitizedThumbnailName( thumbnailsDir + QStringLiteral("/") + THUMBNAIL_LOGO + QStringLiteral("/"), hash["label_noext"]); m_thumbnailPixmap = new QPixmap(thumbnailName1); m_thumbnailPixmap2 = new QPixmap(thumbnailName2); m_thumbnailPixmap3 = new QPixmap(thumbnailName3); m_thumbnailPixmap4 = new QPixmap(thumbnailName4); if ( m_currentBrowser == BROWSER_TYPE_PLAYLISTS && !currentPlaylistIsSpecial()) acceptDrop = true; } onResizeThumbnailOne(*m_thumbnailPixmap, acceptDrop); onResizeThumbnailTwo(*m_thumbnailPixmap2, acceptDrop); onResizeThumbnailThree(*m_thumbnailPixmap3, acceptDrop); onResizeThumbnailFour(*m_thumbnailPixmap4, acceptDrop); setCoreActions(); } void MainWindow::setThumbnail(QString widgetName, QPixmap &pixmap, bool acceptDrop) { ThumbnailWidget *thumbnail = findChild(widgetName); if (thumbnail) thumbnail->setPixmap(pixmap, acceptDrop); } void MainWindow::onResizeThumbnailOne(QPixmap &pixmap, bool acceptDrop) { setThumbnail("thumbnail", pixmap, acceptDrop); } void MainWindow::onResizeThumbnailTwo(QPixmap &pixmap, bool acceptDrop) { setThumbnail("thumbnail2", pixmap, acceptDrop); } void MainWindow::onResizeThumbnailThree(QPixmap &pixmap, bool acceptDrop) { setThumbnail("thumbnail3", pixmap, acceptDrop); } void MainWindow::onResizeThumbnailFour(QPixmap &pixmap, bool acceptDrop) { setThumbnail("thumbnail4", pixmap, acceptDrop); } void MainWindow::setCurrentViewType(ViewType viewType) { m_viewType = viewType; switch (viewType) { case VIEW_TYPE_ICONS: m_playlistViews->setCurrentWidget(m_gridView); m_zoomWidget->show(); break; case VIEW_TYPE_LIST: default: m_playlistViews->setCurrentWidget(m_tableView); m_zoomWidget->hide(); break; } } void MainWindow::setCurrentThumbnailType(ThumbnailType thumbnailType) { m_thumbnailType = thumbnailType; m_playlistModel->setThumbnailType(thumbnailType); updateVisibleItems(); m_gridView->viewport()->update(); } MainWindow::ViewType MainWindow::getCurrentViewType() { return m_viewType; } ThumbnailType MainWindow::getCurrentThumbnailType() { return m_thumbnailType;} void MainWindow::onCurrentListItemChanged( QListWidgetItem *current, QListWidgetItem *previous) { Q_UNUSED(current) Q_UNUSED(previous) initContentTableWidget(); setCoreActions(); } TableView* MainWindow::contentTableView() { return m_tableView; } QTableView* MainWindow::fileTableView() { return m_fileTableView; } QStackedWidget* MainWindow::centralWidget() { return m_centralWidget; } FileDropWidget* MainWindow::playlistViews() { return m_playlistViews; } QWidget* MainWindow::playlistViewsAndFooter() {return m_playlistViewsAndFooter;} GridView* MainWindow::contentGridView() { return m_gridView; } void MainWindow::onBrowserDownloadsClicked() { QModelIndex index; QDir dir(config_get_ptr()->paths.directory_core_assets); QString path = dir.absolutePath(); m_pendingDirScrollPath = path; index = m_dirModel->index(path); m_dirTree->setCurrentIndex(index); onDownloadScroll(path); } void MainWindow::onDownloadScroll(QString path) { QModelIndex index = m_dirModel->index(path); m_dirTree->scrollTo(index, QAbstractItemView::PositionAtTop); m_dirTree->expand(index); /* FIXME: Find a way to make this unnecessary */ emit scrollToDownloadsAgain(path); } void MainWindow::onDownloadScrollAgain(QString path) { QModelIndex index = m_dirModel->index(path); m_dirTree->scrollTo(index, QAbstractItemView::PositionAtTop); m_dirTree->expand(index); } void MainWindow::onBrowserUpClicked() { QDir dir(m_dirModel->filePath(m_dirTree->currentIndex())); dir.cdUp(); m_dirTree->setCurrentIndex(m_dirModel->index(dir.absolutePath())); m_dirTree->scrollTo(m_dirTree->currentIndex(), QAbstractItemView::EnsureVisible); } void MainWindow::onBrowserStartClicked() { m_dirTree->setCurrentIndex( m_dirModel->index(config_get_ptr()->paths.directory_menu_content)); m_dirTree->scrollTo(m_dirTree->currentIndex(), QAbstractItemView::PositionAtTop); } ListWidget* MainWindow::playlistListWidget() { return m_listWidget; } TreeView* MainWindow::dirTreeView() { return m_dirTree; } void MainWindow::onTimeout() { uint8_t flags = content_get_flags(); if (flags & CONTENT_ST_FLAG_IS_INITED) { if (m_runPushButton->isVisible()) m_runPushButton->hide(); if (!m_stopPushButton->isVisible()) m_stopPushButton->show(); } else { if (!m_runPushButton->isVisible()) m_runPushButton->show(); if (m_stopPushButton->isVisible()) m_stopPushButton->hide(); } setCurrentCoreLabel(); } void MainWindow::onStopClicked() { #ifdef HAVE_MENU struct menu_state *menu_st = menu_state_get_ptr(); menu_st->selection_ptr = 0; #endif command_event(CMD_EVENT_UNLOAD_CORE, NULL); setCurrentCoreLabel(); activateWindow(); raise(); } void MainWindow::setCurrentCoreLabel() { bool update = false; struct retro_system_info *sysinfo = &runloop_state_get_ptr()->system.info; QString libraryName = sysinfo->library_name; const char *no_core_str = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_CORE); if ( (m_statusLabel->text().isEmpty()) || (m_currentCore != no_core_str && libraryName.isEmpty()) ) { m_currentCore = no_core_str; m_currentCoreVersion = QLatin1String(""); update = true; } else { if ( m_currentCore != libraryName && !libraryName.isEmpty()) { m_currentCore = sysinfo->library_name; m_currentCoreVersion = (string_is_empty(sysinfo->library_version) ? "" : sysinfo->library_version); update = true; } } if (update) { QAction *unloadCoreAction = findChild("unloadCoreAction"); QString text = QString(PACKAGE_VERSION) + QStringLiteral(" - ") + m_currentCore + QStringLiteral(" ") + m_currentCoreVersion; m_statusLabel->setText(text); m_loadCoreWindow->setStatusLabel(text); setCoreActions(); if (unloadCoreAction) { if (libraryName.isEmpty()) unloadCoreAction->setEnabled(false); else unloadCoreAction->setEnabled(true); } } } void MainWindow::onCoreLoadWindowClosed() { QVariant lastLaunchWithVariant = m_loadCoreWindow->property("last_launch_with_index"); int lastLaunchWithIndex = lastLaunchWithVariant.toInt(); m_pendingRun = false; if (lastLaunchWithVariant.isValid() && lastLaunchWithIndex >= 0) { m_launchWithComboBox->setCurrentIndex(lastLaunchWithIndex); m_loadCoreWindow->setProperty("last_launch_with_index", -1); } } void MainWindow::onCoreLoaded() { QAction *unloadAction = findChild("unloadCoreAction"); activateWindow(); raise(); setCurrentCoreLabel(); setCoreActions(); if (unloadAction) unloadAction->setEnabled(true); m_loadCoreWindow->hide(); if (m_pendingRun) { onRunClicked(); m_pendingRun = false; } } void MainWindow::onUnloadCoreMenuAction() { QAction *action = qobject_cast(sender()); #ifdef HAVE_MENU struct menu_state *menu_st = menu_state_get_ptr(); menu_st->selection_ptr = 0; #endif /* TODO */ if (!command_event(CMD_EVENT_UNLOAD_CORE, NULL)) return; setCurrentCoreLabel(); setCoreActions(); if (!action) return; action->setEnabled(false); activateWindow(); raise(); } void MainWindow::onLoadCoreClicked(const QStringList &extensionFilters) { m_loadCoreWindow->show(); m_loadCoreWindow->resize(width() / 2, height()); m_loadCoreWindow->setGeometry(QStyle::alignedRect( Qt::LeftToRight, Qt::AlignCenter, m_loadCoreWindow->size(), geometry())); m_loadCoreWindow->initCoreList(extensionFilters); } void MainWindow::initContentTableWidget() { QString path; QListWidgetItem *item = m_listWidget->currentItem(); if (!item) return; m_currentGridHash.clear(); if (m_currentGridWidget) { m_currentGridWidget->setObjectName("thumbnailWidget"); m_currentGridWidget->style()->unpolish(m_currentGridWidget); m_currentGridWidget->style()->polish(m_currentGridWidget); } m_currentGridWidget = NULL; path = item->data(Qt::UserRole).toString(); if (path == ALL_PLAYLISTS_TOKEN) { size_t i; QStringList playlists; settings_t *settings = config_get_ptr(); QDir playlistDir(settings->paths.directory_playlist); size_t list_size = (size_t)m_playlistFiles.count(); for (i = 0; i < list_size; i++) { const QString &playlist = m_playlistFiles.at(i); playlists.append(playlistDir.absoluteFilePath(playlist)); } m_playlistModel->addPlaylistItems(playlists, true); } else m_playlistModel->addPlaylistItems(QStringList() << path); if (item != m_historyPlaylistsItem) m_tableView->sortByColumn(0, Qt::AscendingOrder); else m_proxyModel->sort(-1); updateItemsCount(); m_gridView->scrollToTop(); m_gridView->setCurrentIndex(m_proxyModel->index(0, 0)); } void MainWindow::updateItemsCount() { m_itemsCountLabel->setText( m_itemsCountLiteral.arg(m_proxyModel->rowCount())); } void MainWindow::keyPressEvent(QKeyEvent *event) { QMainWindow::keyPressEvent(event); } QSettings* MainWindow::settings() { return m_settings; } QString MainWindow::getCurrentViewTypeString() { switch (m_viewType) { case VIEW_TYPE_ICONS: return QStringLiteral("icons"); case VIEW_TYPE_LIST: default: break; } return QStringLiteral("list"); } QString MainWindow::getCurrentThumbnailTypeString() { switch (m_thumbnailType) { case THUMBNAIL_TYPE_SCREENSHOT: return QStringLiteral("screenshot"); case THUMBNAIL_TYPE_TITLE_SCREEN: return QStringLiteral("title"); case THUMBNAIL_TYPE_LOGO: return QStringLiteral("logo"); case THUMBNAIL_TYPE_BOXART: default: return QStringLiteral("boxart"); } return QStringLiteral("list"); } ThumbnailType MainWindow::getThumbnailTypeFromString(QString thumbnailType) { if (thumbnailType == QLatin1String("boxart")) return THUMBNAIL_TYPE_BOXART; else if (thumbnailType == QLatin1String("screenshot")) return THUMBNAIL_TYPE_SCREENSHOT; else if (thumbnailType == QLatin1String("title")) return THUMBNAIL_TYPE_TITLE_SCREEN; else if (thumbnailType == QLatin1String("logo")) return THUMBNAIL_TYPE_LOGO; return THUMBNAIL_TYPE_BOXART; } void MainWindow::closeEvent(QCloseEvent *event) { if (m_settings->value("save_geometry", false).toBool()) m_settings->setValue("geometry", saveGeometry()); if (m_settings->value("save_dock_positions", false).toBool()) m_settings->setValue("dock_positions", saveState()); if (m_settings->value("save_last_tab", false).toBool()) m_settings->setValue("last_tab", m_browserAndPlaylistTabWidget->currentIndex()); m_settings->setValue("view_type", getCurrentViewTypeString()); m_settings->setValue("file_browser_table_headers", m_fileTableView->horizontalHeader()->saveState()); m_settings->setValue("icon_view_zoom", m_lastZoomSliderValue); m_settings->setValue("icon_view_thumbnail_type", getCurrentThumbnailTypeString()); m_settings->setValue("options_dialog_geometry", m_viewOptionsDialog->saveGeometry()); QMainWindow::closeEvent(event); } void MainWindow::onContributorsClicked() { QScopedPointer dialog(new QDialog()); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); QTextEdit *textEdit = new QTextEdit(dialog.data()); connect(buttonBox, SIGNAL(accepted()), dialog.data(), SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), dialog.data(), SLOT(reject())); dialog->setWindowTitle(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_ABOUT_CONTRIBUTORS)); dialog->setLayout(new QVBoxLayout()); dialog->layout()->addWidget(textEdit); dialog->layout()->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum)); dialog->layout()->addWidget(buttonBox); textEdit->setReadOnly(true); textEdit->setHtml(QStringLiteral("
") + retroarch_contributors_list + QStringLiteral("
")); dialog->resize(480, 640); dialog->exec(); } void MainWindow::showAbout() { QScopedPointer dialog(new QDialog()); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); QString text = QStringLiteral("RetroArch ") + QStringLiteral(PACKAGE_VERSION) + QStringLiteral("

") + "www.libretro.com" "

" + "www.retroarch.com" #ifdef HAVE_GIT_VERSION "

" + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_GIT_VERSION) + QStringLiteral(": ") + retroarch_git_version #endif + QStringLiteral("
") + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_BUILD_DATE) + QStringLiteral(": ") + __DATE__; QLabel *label = new QLabel(text, dialog.data()); QPixmap pix = getInvader(); QLabel *pixLabel = new QLabel(dialog.data()); QPushButton *contributorsPushButton = new QPushButton(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_ABOUT_CONTRIBUTORS), dialog.data()); connect(contributorsPushButton, SIGNAL(clicked()), this, SLOT(onContributorsClicked())); connect(buttonBox, SIGNAL(accepted()), dialog.data(), SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), dialog.data(), SLOT(reject())); label->setTextFormat(Qt::RichText); label->setAlignment(Qt::AlignCenter); label->setTextInteractionFlags(Qt::TextBrowserInteraction); label->setOpenExternalLinks(true); pixLabel->setAlignment(Qt::AlignCenter); pixLabel->setPixmap(pix); dialog->setWindowTitle(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_ABOUT)); dialog->setLayout(new QVBoxLayout()); dialog->layout()->addWidget(pixLabel); dialog->layout()->addWidget(label); dialog->layout()->addWidget(contributorsPushButton); dialog->layout()->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); dialog->layout()->addWidget(buttonBox); dialog->exec(); } void MainWindow::showDocs() { QDesktopServices::openUrl(QUrl(DOCS_URL)); } void MainWindow::onShowErrorMessage(QString msg) { showMessageBox(msg, MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); } void MainWindow::onShowInfoMessage(QString msg) { showMessageBox(msg, MainWindow::MSGBOX_TYPE_INFO, Qt::ApplicationModal, false); } int MainWindow::onExtractArchive(QString path, QString extractionDir, QString tempExtension, retro_task_callback_t cb) { size_t i; file_archive_transfer_t state; struct archive_extract_userdata userdata; QByteArray pathArray = path.toUtf8(); QByteArray dirArray = extractionDir.toUtf8(); const char *file = pathArray.constData(); const char *dir = dirArray.constData(); struct string_list *file_list = file_archive_get_file_list(file, NULL); retro_task_t *decompress_task = NULL; if (!file_list || file_list->size == 0) { showMessageBox("Error: Archive is empty.", MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); RARCH_ERR("[Qt]: Downloaded archive is empty?\n"); return -1; } for (i = 0; i < file_list->size; i++) { QFile fileObj(file_list->elems[i].data); if (fileObj.exists()) { if (!fileObj.remove()) { /* If we cannot delete the existing file to update it, * rename it for now and delete later */ QFile fileTemp(fileObj.fileName() + tempExtension); if (fileTemp.exists()) { if (!fileTemp.remove()) { showMessageBox(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_DELETE_FILE), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); RARCH_ERR("[Qt]: Could not delete file: %s\n", file_list->elems[i].data); return -1; } } if (!fileObj.rename(fileTemp.fileName())) { showMessageBox(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_RENAME_FILE), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); RARCH_ERR("[Qt]: Could not rename file: %s\n", file_list->elems[i].data); return -1; } } } } string_list_free(file_list); memset(&state, 0, sizeof(state)); memset(&userdata, 0, sizeof(userdata)); state.type = ARCHIVE_TRANSFER_INIT; m_updateProgressDialog->setWindowModality(Qt::NonModal); m_updateProgressDialog->setMinimumDuration(0); m_updateProgressDialog->setRange(0, 0); m_updateProgressDialog->setAutoClose(true); m_updateProgressDialog->setAutoReset(true); m_updateProgressDialog->setValue(0); m_updateProgressDialog->setLabelText(QString(msg_hash_to_str(MSG_EXTRACTING)) + QStringLiteral("...")); m_updateProgressDialog->setCancelButtonText(QString()); m_updateProgressDialog->show(); if (!(decompress_task = (retro_task_t*)task_push_decompress( file, dir, NULL, NULL, NULL, cb, this, NULL, false))) { m_updateProgressDialog->cancel(); return -1; } return 1; } QString MainWindow::getScrubbedString(QString str) { const QString chars("&*/:`\"<>?\\|"); int i; for (i = 0; i < chars.count(); i++) str.replace(chars.at(i), '_'); return str; } static void* ui_window_qt_init(void) { ui_window.qtWindow = new MainWindow(); return &ui_window; } static void ui_window_qt_destroy(void *data) { /* TODO/FIXME - implement? */ } static void ui_window_qt_set_focused(void *data) { /* TODO/FIXME - implement */ } static void ui_window_qt_set_visible(void *data, bool set_visible) { /* TODO/FIXME - implement */ } static void ui_window_qt_set_title(void *data, char *buf) { /* TODO/FIXME - implement? */ } static void ui_window_qt_set_droppable(void *data, bool droppable) { /* TODO/FIXME - implement */ } static bool ui_window_qt_focused(void *data) { /* TODO/FIXME - implement? */ return true; } static ui_window_t ui_window_qt = { ui_window_qt_init, ui_window_qt_destroy, ui_window_qt_set_focused, ui_window_qt_set_visible, ui_window_qt_set_title, ui_window_qt_set_droppable, ui_window_qt_focused, "qt" }; static enum ui_msg_window_response ui_msg_window_qt_response( ui_msg_window_state *state, QMessageBox::StandardButtons response) { switch (response) { case QMessageBox::Ok: return UI_MSG_RESPONSE_OK; case QMessageBox::Cancel: return UI_MSG_RESPONSE_CANCEL; case QMessageBox::Yes: return UI_MSG_RESPONSE_YES; case QMessageBox::No: return UI_MSG_RESPONSE_NO; default: break; } switch (state->buttons) { case UI_MSG_WINDOW_OK: return UI_MSG_RESPONSE_OK; case UI_MSG_WINDOW_OKCANCEL: return UI_MSG_RESPONSE_CANCEL; case UI_MSG_WINDOW_YESNO: return UI_MSG_RESPONSE_NO; case UI_MSG_WINDOW_YESNOCANCEL: return UI_MSG_RESPONSE_CANCEL; default: break; } return UI_MSG_RESPONSE_NA; } static QFlags ui_msg_window_qt_buttons(ui_msg_window_state *state) { switch (state->buttons) { case UI_MSG_WINDOW_OK: return QMessageBox::Ok; case UI_MSG_WINDOW_OKCANCEL: return QMessageBox::Cancel; case UI_MSG_WINDOW_YESNO: return (QMessageBox::Yes | QMessageBox::No); case UI_MSG_WINDOW_YESNOCANCEL: return (QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); } return QMessageBox::NoButton; } static enum ui_msg_window_response ui_msg_window_qt_error(ui_msg_window_state *state) { QFlags flags = ui_msg_window_qt_buttons(state); return ui_msg_window_qt_response(state, QMessageBox::critical( (QWidget*)state->window, state->title, state->text, flags)); } static enum ui_msg_window_response ui_msg_window_qt_information( ui_msg_window_state *state) { QFlags flags = ui_msg_window_qt_buttons(state); return ui_msg_window_qt_response(state, QMessageBox::information( (QWidget*)state->window, state->title, state->text, flags)); } static enum ui_msg_window_response ui_msg_window_qt_question( ui_msg_window_state *state) { QFlags flags = ui_msg_window_qt_buttons(state); return ui_msg_window_qt_response(state, QMessageBox::question( (QWidget*)state->window, state->title, state->text, flags)); } static enum ui_msg_window_response ui_msg_window_qt_warning( ui_msg_window_state *state) { QFlags flags = ui_msg_window_qt_buttons(state); return ui_msg_window_qt_response(state, QMessageBox::warning( (QWidget*)state->window, state->title, state->text, flags)); } static ui_msg_window_t ui_msg_window_qt = { ui_msg_window_qt_error, ui_msg_window_qt_information, ui_msg_window_qt_question, ui_msg_window_qt_warning, "qt" }; static bool ui_browser_window_qt_open(ui_browser_window_state_t *state) { return true; } static bool ui_browser_window_qt_save(ui_browser_window_state_t *state) { return false; } static ui_browser_window_t ui_browser_window_qt = { ui_browser_window_qt_open, ui_browser_window_qt_save, "qt" }; static void* ui_application_qt_initialize(void) { /* These must last for the lifetime of the QApplication */ static int app_argc = 1; static char app_name[] = "retroarch"; static char *app_argv[] = { app_name, NULL }; app_handler = new AppHandler(); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) /* HiDpi supported since Qt 5.6 */ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif /* Create QApplication() before calling QApplication::setStyle() * to ensure that plugin path is determined correctly */ ui_application.app = new QApplication(app_argc, app_argv); QApplication::setStyle("fusion"); ui_application.app->setOrganizationName("libretro"); ui_application.app->setApplicationName("RetroArch"); ui_application.app->setApplicationVersion(PACKAGE_VERSION); ui_application.app->connect(ui_application.app, SIGNAL(lastWindowClosed()), app_handler, SLOT(onLastWindowClosed())); #ifdef Q_OS_UNIX setlocale(LC_NUMERIC, "C"); #ifdef HAVE_WAYLAND #if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)) /* This needs to match the name of the .desktop file in order for * Windows to be correctly associated on Wayland */ ui_application.app->setDesktopFileName(WAYLAND_APP_ID); #endif #endif #endif { QPixmap iconPixmap; /* Can't declare the pixmap at the top, because: * "QPixmap: Must construct a QGuiApplication before a QPixmap" */ QImage iconImage(16, 16, QImage::Format_ARGB32); unsigned char *bits = iconImage.bits(); memcpy(bits, retroarch_qt_icon_data, 16 * 16 * sizeof(unsigned)); iconPixmap = QPixmap::fromImage(iconImage); ui_application.app->setWindowIcon(QIcon(iconPixmap)); } return &ui_application; } static void ui_application_qt_process_events(void) { #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) QAbstractEventDispatcher *dispatcher = QApplication::eventDispatcher(); if (dispatcher && dispatcher->hasPendingEvents()) #endif QApplication::processEvents(); } static void ui_application_qt_quit(void) { if (app_handler) app_handler->exit(); } #ifdef HAVE_MAIN #if defined(__cplusplus) && !defined(CXX_BUILD) extern "C" #endif int main(int argc, char *argv[]) { return rarch_main(argc, argv, NULL); } #endif static ui_application_t ui_application_qt = { ui_application_qt_initialize, ui_application_qt_process_events, ui_application_qt_quit, false, "qt" }; AppHandler::AppHandler(QObject *parent) : QObject(parent) { } AppHandler::~AppHandler() { } void AppHandler::onLastWindowClosed() { } void AppHandler::exit() { ui_application_qt.exiting = true; if (qApp) qApp->closeAllWindows(); } typedef struct ui_companion_qt { ui_application_qt_t *app; ui_window_qt_t *window; } ui_companion_qt_t; ThumbnailWidget::ThumbnailWidget(QWidget *parent) { } ThumbnailWidget::ThumbnailWidget(ThumbnailType type, QWidget *parent) : QStackedWidget(parent) ,m_thumbnailType(type) ,m_thumbnailLabel(new ThumbnailLabel(this)) ,m_dropIndicator(new QLabel(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_DROP_IMAGE_HERE), this)) { m_dropIndicator->setObjectName("dropIndicator"); m_dropIndicator->setAlignment(Qt::AlignCenter); addWidget(m_dropIndicator); addWidget(m_thumbnailLabel); } void ThumbnailWidget::setPixmap(const QPixmap &pixmap, bool acceptDrops) { m_thumbnailLabel->setPixmap(pixmap); if (acceptDrops && pixmap.isNull()) setCurrentWidget(m_dropIndicator); else setCurrentWidget(m_thumbnailLabel); m_thumbnailLabel->update(); QWidget::setAcceptDrops(acceptDrops); } void ThumbnailWidget::dragEnterEvent(QDragEnterEvent *event) { const QMimeData *data = event->mimeData(); if (data->hasUrls()) event->acceptProposedAction(); } /* Workaround for QTBUG-72844. Without it, you can't * drop on this if you first drag over another * widget that doesn't accept drops. */ void ThumbnailWidget::dragMoveEvent(QDragMoveEvent *event) { event->acceptProposedAction(); } void ThumbnailWidget::dropEvent(QDropEvent *event) { const QMimeData *data = event->mimeData(); if (data->hasUrls()) { const QString imageString = data->urls().at(0).toLocalFile(); const QImage image(imageString); if (!image.isNull()) emit(filesDropped(image, m_thumbnailType)); else { const char *string_data = QDir::toNativeSeparators( imageString).toUtf8().constData(); RARCH_ERR("[Qt]: Could not read image: %s\n", string_data); } } } ThumbnailLabel::ThumbnailLabel(QWidget *parent) : QWidget(parent) ,m_pixmap(NULL) ,m_pixmapWidth(0) ,m_pixmapHeight(0) { } ThumbnailLabel::~ThumbnailLabel() { if (m_pixmap) delete m_pixmap; } void ThumbnailLabel::setPixmap(const QPixmap &pixmap) { m_pixmapWidth = pixmap.width(); m_pixmapHeight = pixmap.height(); if (m_pixmap) delete m_pixmap; m_pixmap = new QPixmap(pixmap); } QSize ThumbnailLabel::sizeHint() const { return QSize(256, 256); } void ThumbnailLabel::paintEvent(QPaintEvent *event) { QStyleOption o; QPainter p; int w = width(); int h = height(); event->accept(); o.initFrom(this); p.begin(this); style()->drawPrimitive( QStyle::PE_Widget, &o, &p, this); p.end(); if ( !m_pixmap || m_pixmap->isNull()) { if (m_pixmap) delete m_pixmap; m_pixmap = new QPixmap(sizeHint()); m_pixmap->fill(QColor(0, 0, 0, 0)); } if (w > 0 && h > 0 && m_pixmap && !m_pixmap->isNull()) { QPixmap pixmap; QPainter pScale; int pw = 0; int ph = 0; int newHeight = (m_pixmap->height() / static_cast(m_pixmap->width())) * width(); QPixmap pixmapScaled = *m_pixmap; unsigned *buf = new unsigned[w * h]; if (newHeight > h) pixmapScaled = pixmapScaled.scaledToHeight(h, Qt::SmoothTransformation); else pixmapScaled = pixmapScaled.scaledToWidth(w, Qt::SmoothTransformation); pw = pixmapScaled.width(); ph = pixmapScaled.height(); pixmap = QPixmap(w, h); pixmap.fill(QColor(0, 0, 0, 0)); pScale.begin(&pixmap); pScale.drawPixmap(QRect((w - pw) / 2, (h - ph) / 2, pw, ph), pixmapScaled, pixmapScaled.rect()); pScale.end(); if (!pixmap.isNull()) { p.begin(this); p.drawPixmap(rect(), pixmap, pixmap.rect()); p.end(); } delete []buf; } else QWidget::paintEvent(event); } void ThumbnailLabel::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); } static void ui_companion_qt_deinit(void *data) { ui_companion_qt_t *handle = (ui_companion_qt_t*)data; if (!handle) return; /* why won't deleteLater() here call the destructor? */ delete handle->window->qtWindow; free(handle); } static void* ui_companion_qt_init(void) { int i = 0; QString initialPlaylist; QRect desktopRect; ui_companion_qt_t *handle = (ui_companion_qt_t*) calloc(1, sizeof(*handle)); MainWindow *mainwindow = NULL; QHBoxLayout *browserButtonsHBoxLayout = NULL; QVBoxLayout *layout = NULL; QVBoxLayout *launchWithWidgetLayout = NULL; QHBoxLayout *coreComboBoxLayout = NULL; QMenuBar *menu = NULL; QScreen *screen = NULL; QMenu *fileMenu = NULL; QMenu *editMenu = NULL; QMenu *viewMenu = NULL; QMenu *viewClosedDocksMenu = NULL; QMenu *helpMenu = NULL; QDockWidget *thumbnailDock = NULL; QDockWidget *thumbnail2Dock = NULL; QDockWidget *thumbnail3Dock = NULL; QDockWidget *thumbnail4Dock = NULL; QDockWidget *browserAndPlaylistTabDock = NULL; QDockWidget *coreSelectionDock = NULL; QTabWidget *browserAndPlaylistTabWidget = NULL; QStackedWidget *centralWidget = NULL; QStackedWidget *widget = NULL; QFrame *browserWidget = NULL; QFrame *playlistWidget = NULL; QWidget *coreSelectionWidget = NULL; QWidget *launchWithWidget = NULL; ThumbnailWidget *thumbnailWidget = NULL; ThumbnailWidget *thumbnail2Widget = NULL; ThumbnailWidget *thumbnail3Widget = NULL; ThumbnailWidget *thumbnail4Widget = NULL; QPushButton *browserDownloadsButton = NULL; QPushButton *browserUpButton = NULL; QPushButton *browserStartButton = NULL; ThumbnailLabel *thumbnail = NULL; ThumbnailLabel *thumbnail2 = NULL; ThumbnailLabel *thumbnail3 = NULL; ThumbnailLabel *thumbnail4 = NULL; QAction *editSearchAction = NULL; QAction *loadCoreAction = NULL; QAction *unloadCoreAction = NULL; QAction *exitAction = NULL; QComboBox *launchWithComboBox = NULL; QSettings *qsettings = NULL; QListWidget *listWidget = NULL; bool foundPlaylist = false; if (!handle) return NULL; handle->app = static_cast (ui_application_qt.initialize()); handle->window = static_cast(ui_window_qt.init()); screen = qApp->primaryScreen(); desktopRect = screen->availableGeometry(); mainwindow = handle->window->qtWindow; qsettings = mainwindow->settings(); initialPlaylist = qsettings->value("initial_playlist", mainwindow->getSpecialPlaylistPath(SPECIAL_PLAYLIST_HISTORY)).toString(); mainwindow->resize(qMin(desktopRect.width(), INITIAL_WIDTH), qMin(desktopRect.height(), INITIAL_HEIGHT)); mainwindow->setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, mainwindow->size(), desktopRect)); mainwindow->setWindowTitle("RetroArch"); mainwindow->setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks | GROUPED_DRAGGING); listWidget = mainwindow->playlistListWidget(); widget = mainwindow->playlistViews(); widget->setContextMenuPolicy(Qt::CustomContextMenu); QObject::connect(widget, SIGNAL(filesDropped(QStringList)), mainwindow, SLOT(onPlaylistFilesDropped(QStringList))); QObject::connect(widget, SIGNAL(enterPressed()), mainwindow, SLOT(onDropWidgetEnterPressed())); QObject::connect(widget, SIGNAL(deletePressed()), mainwindow, SLOT(deleteCurrentPlaylistItem())); QObject::connect(widget, SIGNAL(customContextMenuRequested(const QPoint&)), mainwindow, SLOT(onFileDropWidgetContextMenuRequested(const QPoint&))); centralWidget = mainwindow->centralWidget(); centralWidget->addWidget(mainwindow->playlistViewsAndFooter()); centralWidget->addWidget(mainwindow->fileTableView()); mainwindow->setCentralWidget(centralWidget); menu = mainwindow->menuBar(); fileMenu = menu->addMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_FILE)); loadCoreAction = fileMenu->addAction(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_FILE_LOAD_CORE), mainwindow, SLOT(onLoadCoreClicked())); loadCoreAction->setShortcut(QKeySequence("Ctrl+L")); unloadCoreAction = fileMenu->addAction(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_FILE_UNLOAD_CORE), mainwindow, SLOT(onUnloadCoreMenuAction())); unloadCoreAction->setObjectName("unloadCoreAction"); unloadCoreAction->setEnabled(false); unloadCoreAction->setShortcut(QKeySequence("Ctrl+U")); exitAction = fileMenu->addAction(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_FILE_EXIT), mainwindow, SLOT(close())); exitAction->setShortcut(QKeySequence::Quit); editMenu = menu->addMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_EDIT)); editSearchAction = editMenu->addAction(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_EDIT_SEARCH), mainwindow->searchLineEdit(), SLOT(setFocus())); editSearchAction->setShortcut(QKeySequence::Find); viewMenu = menu->addMenu(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW)); viewClosedDocksMenu = viewMenu->addMenu(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_CLOSED_DOCKS)); viewClosedDocksMenu->setObjectName("viewClosedDocksMenu"); QObject::connect(viewClosedDocksMenu, SIGNAL(aboutToShow()), mainwindow, SLOT(onViewClosedDocksAboutToShow())); viewMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CORE_OPTIONS), mainwindow, SLOT(onCoreOptionsClicked())); #if defined(HAVE_MENU) #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) viewMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SHADER_OPTIONS), mainwindow, SLOT(onShaderParamsClicked())); #endif #endif viewMenu->addSeparator(); viewMenu->addAction(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_ICONS), mainwindow, SLOT(onIconViewClicked())); viewMenu->addAction(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_LIST), mainwindow, SLOT(onListViewClicked())); viewMenu->addSeparator(); viewMenu->addAction(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS), mainwindow->viewOptionsDialog(), SLOT(showDialog())); helpMenu = menu->addMenu(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_HELP)); helpMenu->addAction(QString(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_DOCUMENTATION)), mainwindow, SLOT(showDocs())); helpMenu->addAction(QString(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_ABOUT)) + QStringLiteral("..."), mainwindow, SLOT(showAbout())); helpMenu->addAction(QStringLiteral("About Qt..."), qApp, SLOT(aboutQt())); playlistWidget = new QFrame(); playlistWidget->setLayout(new QVBoxLayout()); playlistWidget->setObjectName("playlistWidget"); playlistWidget->layout()->setContentsMargins(0, 0, 0, 0); playlistWidget->layout()->addWidget(mainwindow->playlistListWidget()); browserWidget = new QFrame(); browserWidget->setLayout(new QVBoxLayout()); browserWidget->setObjectName("browserWidget"); browserWidget->layout()->setContentsMargins(0, 0, 0, 0); browserDownloadsButton = new QPushButton(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_CORE_ASSETS_DIRECTORY)); browserUpButton = new QPushButton(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_TAB_FILE_BROWSER_UP)); browserStartButton = new QPushButton(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_FAVORITES)); QObject::connect(browserDownloadsButton, SIGNAL(clicked()), mainwindow, SLOT(onBrowserDownloadsClicked())); QObject::connect(browserUpButton, SIGNAL(clicked()), mainwindow, SLOT(onBrowserUpClicked())); QObject::connect(browserStartButton, SIGNAL(clicked()), mainwindow, SLOT(onBrowserStartClicked())); browserButtonsHBoxLayout = new QHBoxLayout(); browserButtonsHBoxLayout->addWidget(browserUpButton); browserButtonsHBoxLayout->addWidget(browserStartButton); browserButtonsHBoxLayout->addWidget(browserDownloadsButton); qobject_cast(browserWidget->layout())->addLayout(browserButtonsHBoxLayout); browserWidget->layout()->addWidget(mainwindow->dirTreeView()); browserAndPlaylistTabWidget = mainwindow->browserAndPlaylistTabWidget(); browserAndPlaylistTabWidget->setObjectName("browserAndPlaylistTabWidget"); /* Several functions depend on the same tab title strings here, * so if you change these, make sure to change those too * setCoreActions() * onTabWidgetIndexChanged() * onCurrentListItemChanged() */ browserAndPlaylistTabWidget->addTab(playlistWidget, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_TAB_PLAYLISTS)); browserAndPlaylistTabWidget->addTab(browserWidget, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_TAB_FILE_BROWSER)); browserAndPlaylistTabDock = new QDockWidget(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_DOCK_CONTENT_BROWSER), mainwindow); browserAndPlaylistTabDock->setObjectName("browserAndPlaylistTabDock"); browserAndPlaylistTabDock->setProperty("default_area", Qt::LeftDockWidgetArea); browserAndPlaylistTabDock->setProperty("menu_text", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_DOCK_CONTENT_BROWSER)); browserAndPlaylistTabDock->setWidget(browserAndPlaylistTabWidget); mainwindow->addDockWidget(static_cast( browserAndPlaylistTabDock->property("default_area").toInt()), browserAndPlaylistTabDock); browserButtonsHBoxLayout->addItem(new QSpacerItem( browserAndPlaylistTabWidget->tabBar()->width(), 20, QSizePolicy::Expanding, QSizePolicy::Minimum)); thumbnailWidget = new ThumbnailWidget(THUMBNAIL_TYPE_BOXART); thumbnailWidget->setObjectName("thumbnail"); thumbnail2Widget = new ThumbnailWidget(THUMBNAIL_TYPE_TITLE_SCREEN); thumbnail2Widget->setObjectName("thumbnail2"); thumbnail3Widget = new ThumbnailWidget(THUMBNAIL_TYPE_SCREENSHOT); thumbnail3Widget->setObjectName("thumbnail3"); thumbnail4Widget = new ThumbnailWidget(THUMBNAIL_TYPE_LOGO); thumbnail4Widget->setObjectName("thumbnail4"); QObject::connect(thumbnailWidget, SIGNAL(filesDropped(const QImage&, ThumbnailType)), mainwindow, SLOT(onThumbnailDropped(const QImage&, ThumbnailType))); QObject::connect(thumbnail2Widget, SIGNAL(filesDropped(const QImage&, ThumbnailType)), mainwindow, SLOT(onThumbnailDropped(const QImage&, ThumbnailType))); QObject::connect(thumbnail3Widget, SIGNAL(filesDropped(const QImage&, ThumbnailType)), mainwindow, SLOT(onThumbnailDropped(const QImage&, ThumbnailType))); QObject::connect(thumbnail4Widget, SIGNAL(filesDropped(const QImage&, ThumbnailType)), mainwindow, SLOT(onThumbnailDropped(const QImage&, ThumbnailType))); thumbnailDock = new QDockWidget(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_BOXART), mainwindow); thumbnailDock->setObjectName("thumbnailDock"); thumbnailDock->setProperty("default_area", Qt::RightDockWidgetArea); thumbnailDock->setProperty("menu_text", msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_BOXART)); thumbnailDock->setWidget(thumbnailWidget); mainwindow->addDockWidget(static_cast( thumbnailDock->property("default_area").toInt()), thumbnailDock); thumbnail2Dock = new QDockWidget(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_TITLE_SCREEN), mainwindow); thumbnail2Dock->setObjectName("thumbnail2Dock"); thumbnail2Dock->setProperty("default_area", Qt::RightDockWidgetArea); thumbnail2Dock->setProperty("menu_text", msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_TITLE_SCREEN)); thumbnail2Dock->setWidget(thumbnail2Widget); mainwindow->addDockWidget(static_cast( thumbnail2Dock->property("default_area").toInt()), thumbnail2Dock); thumbnail3Dock = new QDockWidget(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_SCREENSHOT), mainwindow); thumbnail3Dock->setObjectName("thumbnail3Dock"); thumbnail3Dock->setProperty("default_area", Qt::RightDockWidgetArea); thumbnail3Dock->setProperty("menu_text", msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_SCREENSHOT)); thumbnail3Dock->setWidget(thumbnail3Widget); mainwindow->addDockWidget(static_cast( thumbnail3Dock->property("default_area").toInt()), thumbnail3Dock); thumbnail4Dock = new QDockWidget(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_LOGO), mainwindow); thumbnail4Dock->setObjectName("thumbnail4Dock"); thumbnail4Dock->setProperty("default_area", Qt::RightDockWidgetArea); thumbnail4Dock->setProperty("menu_text", msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_LOGO)); thumbnail4Dock->setWidget(thumbnail4Widget); mainwindow->addDockWidget(static_cast( thumbnail4Dock->property("default_area").toInt()), thumbnail4Dock); mainwindow->tabifyDockWidget(thumbnailDock, thumbnail2Dock); mainwindow->tabifyDockWidget(thumbnailDock, thumbnail3Dock); mainwindow->tabifyDockWidget(thumbnailDock, thumbnail4Dock); /* When tabifying the dock widgets, the last tab added is selected * by default, so we need to re-select the first tab */ thumbnailDock->raise(); coreSelectionWidget = new QWidget(); coreSelectionWidget->setLayout(new QVBoxLayout()); launchWithComboBox = mainwindow->launchWithComboBox(); launchWithWidgetLayout = new QVBoxLayout(); launchWithWidget = new QWidget(); launchWithWidget->setLayout(launchWithWidgetLayout); coreComboBoxLayout = new QHBoxLayout(); mainwindow->runPushButton()->setSizePolicy( QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); mainwindow->stopPushButton()->setSizePolicy( QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); mainwindow->startCorePushButton()->setSizePolicy( QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); coreComboBoxLayout->addWidget(launchWithComboBox); coreComboBoxLayout->addWidget(mainwindow->startCorePushButton()); coreComboBoxLayout->addWidget(mainwindow->coreInfoPushButton()); coreComboBoxLayout->addWidget(mainwindow->runPushButton()); coreComboBoxLayout->addWidget(mainwindow->stopPushButton()); mainwindow->stopPushButton()->hide(); coreComboBoxLayout->setStretchFactor(launchWithComboBox, 1); launchWithWidgetLayout->addLayout(coreComboBoxLayout); coreSelectionWidget->layout()->addWidget(launchWithWidget); coreSelectionWidget->layout()->addItem(new QSpacerItem(20, browserAndPlaylistTabWidget->height(), QSizePolicy::Minimum, QSizePolicy::Expanding)); coreSelectionDock = new QDockWidget(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_CORE), mainwindow); coreSelectionDock->setObjectName("coreSelectionDock"); coreSelectionDock->setProperty("default_area", Qt::LeftDockWidgetArea); coreSelectionDock->setProperty("menu_text", msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_CORE)); coreSelectionDock->setWidget(coreSelectionWidget); coreSelectionDock->setFixedHeight(coreSelectionDock->minimumSizeHint().height()); mainwindow->addDockWidget(static_cast( coreSelectionDock->property("default_area").toInt()), coreSelectionDock); mainwindow->splitDockWidget(browserAndPlaylistTabDock, coreSelectionDock, Qt::Vertical); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) mainwindow->resizeDocks(QList() << coreSelectionDock, QList() << 1, Qt::Vertical); #endif if (qsettings->contains("all_playlists_list_max_count")) mainwindow->setAllPlaylistsListMaxCount(qsettings->value( "all_playlists_list_max_count", 0).toInt()); if (qsettings->contains("all_playlists_grid_max_count")) mainwindow->setAllPlaylistsGridMaxCount(qsettings->value( "all_playlists_grid_max_count", 5000).toInt()); if (qsettings->contains("thumbnail_cache_limit")) mainwindow->setThumbnailCacheLimit(qsettings->value( "thumbnail_cache_limit", 500).toInt()); else mainwindow->setThumbnailCacheLimit(500); if (qsettings->contains("geometry")) if (qsettings->contains("save_geometry")) mainwindow->restoreGeometry(qsettings->value( "geometry").toByteArray()); if (qsettings->contains("options_dialog_geometry")) mainwindow->viewOptionsDialog()->restoreGeometry( qsettings->value("options_dialog_geometry").toByteArray()); if (qsettings->contains("save_dock_positions")) if (qsettings->contains("dock_positions")) mainwindow->restoreState(qsettings->value( "dock_positions").toByteArray()); if (qsettings->contains("file_browser_table_headers")) mainwindow->fileTableView()->horizontalHeader()->restoreState( qsettings->value("file_browser_table_headers").toByteArray()); else mainwindow->fileTableView()->horizontalHeader()->resizeSection(0, 300); if (qsettings->contains("icon_view_zoom")) mainwindow->setIconViewZoom(qsettings->value( "icon_view_zoom", 50).toInt()); if (qsettings->contains("theme")) { QString themeStr = qsettings->value("theme").toString(); MainWindow::Theme theme = mainwindow->getThemeFromString(themeStr); if ( qsettings->contains("custom_theme") && theme == MainWindow::THEME_CUSTOM) { QString customThemeFilePath = qsettings->value("custom_theme").toString(); mainwindow->setCustomThemeFile(customThemeFilePath); } mainwindow->setTheme(theme); } else mainwindow->setTheme(); if (qsettings->contains("view_type")) { QString viewType = qsettings->value("view_type", "list").toString(); if (viewType == QLatin1String("list")) mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_LIST); else if (viewType == QLatin1String("icons")) mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_ICONS); else mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_LIST); } else mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_LIST); if (qsettings->contains("icon_view_thumbnail_type")) { QString thumbnailType = qsettings->value("icon_view_thumbnail_type", "boxart").toString(); if (thumbnailType == QLatin1String("boxart")) mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_BOXART); else if (thumbnailType == QLatin1String("screenshot")) mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_SCREENSHOT); else if (thumbnailType == QLatin1String("title")) mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_TITLE_SCREEN); else if (thumbnailType == QLatin1String("logo")) mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_LOGO); else mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_BOXART); } /* We make sure to hook up the tab widget callback only after the tabs * themselves have been added, but before changing to a specific one, * to avoid the callback firing before the view type is set. */ QObject::connect(browserAndPlaylistTabWidget, SIGNAL(currentChanged(int)), mainwindow, SLOT(onTabWidgetIndexChanged(int))); /* Setting the last tab must come after setting the view type */ if (qsettings->contains("save_last_tab")) { int lastTabIndex = qsettings->value("last_tab", 0).toInt(); if ( lastTabIndex >= 0 && browserAndPlaylistTabWidget->count() > lastTabIndex) { browserAndPlaylistTabWidget->setCurrentIndex(lastTabIndex); mainwindow->onTabWidgetIndexChanged(lastTabIndex); } } else { browserAndPlaylistTabWidget->setCurrentIndex(0); mainwindow->onTabWidgetIndexChanged(0); } /* The initial playlist that is selected is based on * the user's setting (initialPlaylist) */ for (i = 0; listWidget->count() && i < listWidget->count(); i++) { QString path; QListWidgetItem *item = listWidget->item(i); if (!item) continue; path = item->data(Qt::UserRole).toString(); if (path == initialPlaylist) { foundPlaylist = true; listWidget->setRowHidden(i, false); listWidget->setCurrentRow(i); break; } } /* couldn't find the user's initial playlist, just find anything */ if (!foundPlaylist) { for (i = 0; listWidget->count() && i < listWidget->count(); i++) { /* select the first non-hidden row */ if (!listWidget->isRowHidden(i)) { listWidget->setCurrentRow(i); break; } } } mainwindow->initContentTableWidget(); return handle; } static void ui_companion_qt_toggle(void *data, bool force) { static bool already_started = false; ui_companion_qt_t *handle = (ui_companion_qt_t*)data; ui_window_qt_t *win_handle = (ui_window_qt_t*)handle->window; settings_t *settings = config_get_ptr(); bool ui_companion_toggle = settings->bools.ui_companion_toggle; bool video_fullscreen = settings->bools.video_fullscreen; bool mouse_grabbed = (input_state_get_ptr()->flags & INP_FLAG_GRAB_MOUSE_STATE) ? true : false; if (ui_companion_toggle || force) { video_driver_state_t *video_st = video_state_get_ptr(); if (mouse_grabbed) command_event(CMD_EVENT_GRAB_MOUSE_TOGGLE, NULL); if ( video_st->poke && video_st->poke->show_mouse) video_st->poke->show_mouse(video_st->data, true); if (video_fullscreen) command_event(CMD_EVENT_FULLSCREEN_TOGGLE, NULL); win_handle->qtWindow->activateWindow(); win_handle->qtWindow->raise(); win_handle->qtWindow->show(); if ( video_st && (video_st->flags & VIDEO_FLAG_STARTED_FULLSCREEN)) win_handle->qtWindow->lower(); if (!already_started) { already_started = true; if (win_handle->qtWindow->settings()->value( "show_welcome_screen", true).toBool()) win_handle->qtWindow->showWelcomeScreen(); } } } static void ui_companion_qt_event_command(void *data, enum event_command cmd) { ui_companion_qt_t *handle = (ui_companion_qt_t*)data; ui_window_qt_t *win_handle = (ui_window_qt_t*)handle->window; if (!handle) return; switch (cmd) { case CMD_EVENT_SHADERS_APPLY_CHANGES: case CMD_EVENT_SHADER_PRESET_LOADED: #if defined(HAVE_MENU) #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) RARCH_LOG("[Qt]: Reloading shader parameters.\n"); win_handle->qtWindow->deferReloadShaderParams(); #endif #endif break; default: break; } } static void ui_companion_qt_notify_refresh(void *data) { ui_companion_qt_t *handle = (ui_companion_qt_t*)data; ui_window_qt_t *win_handle = (ui_window_qt_t*)handle->window; win_handle->qtWindow->deferReloadPlaylists(); } static void ui_companion_qt_log_msg(void *data, const char *msg) { ui_companion_qt_t *handle = (ui_companion_qt_t*)data; ui_window_qt_t *win_handle = (ui_window_qt_t*)handle->window; win_handle->qtWindow->appendLogMessage(msg); } static bool ui_companion_qt_is_active(void *data) { ui_companion_qt_t *handle = (ui_companion_qt_t*)data; ui_window_qt_t *win_handle = (ui_window_qt_t*)handle->window; return win_handle->qtWindow->isVisible(); } void ui_companion_qt_msg_queue_push(void *data, const char *msg, unsigned priority, unsigned duration, bool flush) { ui_companion_qt_t *handle = (ui_companion_qt_t*)data; ui_window_qt_t *win_handle = NULL; if (handle && (win_handle = (ui_window_qt_t*)handle->window)) win_handle->qtWindow->showStatusMessage(msg, priority, duration, flush); } ui_companion_driver_t ui_companion_qt = { ui_companion_qt_init, ui_companion_qt_deinit, ui_companion_qt_toggle, ui_companion_qt_event_command, ui_companion_qt_notify_refresh, ui_companion_qt_msg_queue_push, NULL, NULL, ui_companion_qt_log_msg, ui_companion_qt_is_active, NULL, /* get_app_icons */ NULL, /* set_app_icon */ NULL, /* get_app_icon_texture */ &ui_browser_window_qt, &ui_msg_window_qt, &ui_window_qt, &ui_application_qt, "qt", }; QStringList string_split_to_qt(QString str, char delim) { int at = 0; QStringList list = QStringList(); for (;;) { /* Find next split */ int spl = str.indexOf(delim, at); /* Store split into list of extensions */ list << str.mid(at, (spl < 0 ? -1 : spl - at)); /* No more splits */ if (spl < 0) break; at = spl + 1; } return list; } #define CORE_NAME_COLUMN 0 #define CORE_VERSION_COLUMN 1 LoadCoreTableWidget::LoadCoreTableWidget(QWidget *parent) : QTableWidget(parent) { } void LoadCoreTableWidget::keyPressEvent(QKeyEvent *event) { int key = event->key(); if ( key == Qt::Key_Return || key == Qt::Key_Enter) { event->accept(); emit enterPressed(); } else QTableWidget::keyPressEvent(event); } LoadCoreWindow::LoadCoreWindow(QWidget *parent) : QMainWindow(parent) ,m_layout() ,m_table(new LoadCoreTableWidget()) ,m_statusLabel(new QLabel()) { QHBoxLayout *hbox = new QHBoxLayout(); QPushButton *customCoreButton = new QPushButton( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOAD_CUSTOM_CORE)); connect(customCoreButton, SIGNAL(clicked()), this, SLOT(onLoadCustomCoreClicked())); connect(m_table, SIGNAL(enterPressed()), this, SLOT(onCoreEnterPressed())); connect(m_table, SIGNAL(cellDoubleClicked(int,int)), this, SLOT(onCellDoubleClicked(int,int))); setWindowTitle(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOAD_CORE)); setCentralWidget(new QWidget()); centralWidget()->setLayout(&m_layout); hbox->addWidget(customCoreButton); hbox->addItem(new QSpacerItem(width(), 20, QSizePolicy::Expanding, QSizePolicy::Minimum)); m_layout.addWidget(m_table); m_layout.addLayout(hbox); statusBar()->addPermanentWidget(m_statusLabel); } void LoadCoreWindow::closeEvent(QCloseEvent *event) { emit windowClosed(); QWidget::closeEvent(event); } void LoadCoreWindow::keyPressEvent(QKeyEvent *event) { int key = event->key(); if (key == Qt::Key_Escape) { event->accept(); close(); } else QMainWindow::keyPressEvent(event); } void LoadCoreWindow::setStatusLabel(QString label) { m_statusLabel->setText(label); } void LoadCoreWindow::onCellDoubleClicked(int, int) { onCoreEnterPressed(); } void LoadCoreWindow::loadCore(const char *path) { QProgressDialog progress(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_LOADING_CORE), QString(), 0, 0, this); progress.setWindowTitle(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_LOAD_CORE)); progress.setMinimumDuration(0); progress.setValue(progress.minimum()); progress.show(); /* 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. */ qApp->processEvents(); qApp->processEvents(); #ifdef HAVE_DYNAMIC path_set(RARCH_PATH_CORE, path); command_event(CMD_EVENT_CORE_INFO_DEINIT, NULL); command_event(CMD_EVENT_CORE_INFO_INIT, NULL); core_info_init_current_core(); if (!command_event(CMD_EVENT_LOAD_CORE, NULL)) { QMessageBox::critical(this, msg_hash_to_str(MSG_ERROR), msg_hash_to_str(MSG_FAILED_TO_OPEN_LIBRETRO_CORE)); return; } setProperty("last_launch_with_index", -1); emit coreLoaded(); #endif } void LoadCoreWindow::onCoreEnterPressed() { QByteArray pathArray; const char *pathData = NULL; QTableWidgetItem *selectedCoreItem = m_table->item(m_table->currentRow(), CORE_NAME_COLUMN); QVariantHash hash = selectedCoreItem->data( Qt::UserRole).toHash(); QString path = hash["path"].toString(); #if (QT_VERSION > QT_VERSION_CHECK(6, 0, 0)) pathArray.append(path.toStdString()); #else pathArray.append(path); #endif pathData = pathArray.constData(); loadCore(pathData); } void LoadCoreWindow::onLoadCustomCoreClicked() { QString path; QByteArray pathArray; char filters[128]; const char *pathData = NULL; settings_t *settings = config_get_ptr(); const char *path_dir_libretro = settings->paths.directory_libretro; size_t _len = strlcpy(filters, "Cores (*.", sizeof(filters)); _len += frontend_driver_get_core_extension(filters + _len, sizeof(filters) - _len); strlcpy(filters + _len, ");;All Files (*.*)", sizeof(filters) - _len); path = QFileDialog::getOpenFileName( this, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOAD_CORE), path_dir_libretro, filters, NULL); if (path.isEmpty()) return; pathArray.append(path.toUtf8()); pathData = pathArray.constData(); loadCore(pathData); } void LoadCoreWindow::initCoreList(const QStringList &extensionFilters) { int j; unsigned i; QStringList horizontal_header_labels; core_info_list_t *cores = NULL; QScreen *desktop = qApp->primaryScreen(); QRect desktopRect = desktop->availableGeometry(); horizontal_header_labels << msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NAME); horizontal_header_labels << msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CORE_VERSION); core_info_get_list(&cores); m_table->clear(); m_table->setColumnCount(0); m_table->setRowCount(0); m_table->setSelectionBehavior(QAbstractItemView::SelectRows); m_table->setSelectionMode(QAbstractItemView::SingleSelection); m_table->setSortingEnabled(false); m_table->setColumnCount(2); m_table->setHorizontalHeaderLabels(horizontal_header_labels); if (cores) { m_table->setRowCount(cores->count); for (i = 0; i < cores->count; i++) { QVariantHash hash; core_info_t *core = core_info_get(cores, i); QTableWidgetItem *name_item = NULL; QTableWidgetItem *version_item = new QTableWidgetItem(core->display_version); const char *name = core->display_name; if (string_is_empty(name)) name = path_basename(core->path); name_item = new QTableWidgetItem(name); hash["path"] = QByteArray(core->path); hash["extensions"] = string_split_to_qt( QString(core->supported_extensions), '|'); name_item->setData(Qt::UserRole, hash); name_item->setFlags(name_item->flags() & ~Qt::ItemIsEditable); version_item->setFlags(version_item->flags() & ~Qt::ItemIsEditable); m_table->setItem(i, CORE_NAME_COLUMN, name_item); m_table->setItem(i, CORE_VERSION_COLUMN, version_item); } } if (!extensionFilters.isEmpty()) { QVector rowsToHide; for (j = 0; j < m_table->rowCount(); j++) { int k; QVariantHash hash; QStringList extensions; bool found = false; QTableWidgetItem *item = m_table->item(j, CORE_NAME_COLUMN); if (!item) continue; hash = item->data(Qt::UserRole).toHash(); extensions = hash["extensions"].toStringList(); if (!extensions.isEmpty()) { for (k = 0; k < extensions.size(); k++) { QString ext = extensions.at(k).toLower(); if (extensionFilters.contains(ext, Qt::CaseInsensitive)) { found = true; break; } } if (!found) rowsToHide.append(j); } } if (rowsToHide.size() != m_table->rowCount()) { int i; for (i = 0; i < rowsToHide.count() && rowsToHide.count() > 0; i++) { const int &row = rowsToHide.at(i); m_table->setRowHidden(row, true); } } } m_table->setSortingEnabled(true); m_table->resizeColumnsToContents(); m_table->sortByColumn(0, Qt::AscendingOrder); m_table->selectRow(0); m_table->setAlternatingRowColors(true); resize(qMin(desktopRect.width(), contentsMargins().left() + m_table->horizontalHeader()->length() + contentsMargins().right()), height()); }