/* 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 <http://www.gnu.org/licenses/>.
 */

#include <QApplication>
#include <QAbstractEventDispatcher>
#include <QtWidgets>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include <QtCore/QString>
#include <QtGlobal>
#include <QCloseEvent>
#include <QResizeEvent>
#include <QStyle>
#include <QString>
#include <QTimer>
#include <QLabel>
#include <QFileDialog>
#include <QFileSystemModel>
#include <QListWidgetItem>
#include <QTableWidgetItem>
#include <QHash>
#include <QPushButton>
#include <QToolButton>
#include <QMenu>
#include <QDockWidget>
#include <QList>
#include <QInputDialog>
#include <QMimeData>
#include <QProgressDialog>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QtConcurrentRun>

#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 <file/file_path.h>
#include <file/archive_file.h>
#include <retro_timers.h>
#include <string/stdstring.h>
#include <retro_miscellaneous.h>

#ifdef Q_OS_UNIX
#include <locale.h>
#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<QMainWindow::DockOption>(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<QFileSystemModel*>(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<QPointer<ThumbnailWidget> >("ThumbnailWidget");
   qRegisterMetaType<retro_task_callback_t>("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<QVBoxLayout*>(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<QByteArray>::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<QDir::Filter>(0)));

   m_fileModel->setFilter(  QDir::NoDot
                          | QDir::AllEntries
                          | (m_settings->value("show_hidden_files", true).toBool()
                           ? (QDir::Hidden | QDir::System)
                           : static_cast<QDir::Filter>(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<Qt::DockWidgetArea>(
            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<Qt::DockWidgetArea>(
            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<Qt::DockWidgetArea>(
            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<int>&)), this,
         SLOT(onCurrentTableItemDataChanged(const QModelIndex&,
               const QModelIndex&, const QVector<int>&)));

   /* 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<QDockWidget*>() << m_searchDock,
         QList<int>() << 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<QModelIndex> 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<QPair<QString, QString> > MainWindow::getPlaylists()
{
   size_t i;
   QVector<QPair<QString, QString> > playlists;
   size_t _len  = m_listWidget->count();

   for (i = 0; i < _len; i++)
   {
      QString label, path;
      QPair<QString, QString> 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!<br>\n"
      "<br>\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.<br>\n"
      "<br>\n"
      "Some useful hotkeys for interacting with the Big Picture menu include:\n"
      "<ul>\n"
      "<li>F1  - Bring up the Big Picture menu</li>\n"
      "<li>F5  - Bring the Desktop Menu back if closed</li>\n"
      "<li>F   - Switch between fullscreen and windowed modes</li>\n"
      "<li>Esc - Exit RetroArch</li>\n"
      "</ul>\n"
      "\n"
      "For more hotkeys and their assignments, see:<br>\n"
      "Settings -> Input -> Hotkeys<br>\n"
      "<br>\n"
      "Documentation for RetroArch, libretro and cores:<br>\n"
      "<a href=\"https://docs.libretro.com/\">https://docs.libretro.com/</a>");

   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<QMessageBox> 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<QAction> action;
   QList<QAction*> actions;
   QScopedPointer<QAction> 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<QHash<QString, QString> >
      infoList                  = getCoreInfo();
   QVariantMap          coreMap = m_launchWithComboBox->currentData(
         Qt::UserRole).value<QVariantMap>();
   core_selection coreSelection = static_cast<core_selection>(
         coreMap.value("core_selection").toInt());

   if (infoList.count() == 0)
      return;

   for (i = 0; i < infoList.count(); i++)
   {
      const QHash<QString, QString> &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("<br>\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<QString, QString> 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<QHash<QString, QString> > MainWindow::getCoreInfo()
{
   size_t i;
   QVector<QHash<QString, QString> > infoList;
   runloop_state_t *runloop_st         = runloop_state_get_ptr();
   QHash<QString, QString> 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<QString, QString> 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<QString, QString> 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<QString, QString> 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<QString, QString> 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<QString, QString> 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<QString, QString> 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<QString, QString> 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<QString, QString> 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<QString, QString> 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<QString, QString> 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<QString, QString> 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<QString, QString> 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"]    = "<b><font color=\"#ff0000\">" + hash["key"]   + QStringLiteral("</font></b>");
                  hash["html_value"]  = "<b><font color=\"#ff0000\">" + hash["value"] + QStringLiteral("</font></b>");
               }
               else
               {
                  QString style       = QStringLiteral("font-weight: bold; color: rgb(0, 175, 0)");
                  hash["label_style"] = style;
                  hash["value_style"] = style;
                  hash["html_key"]    = "<b><font color=\"#00af00\">" + hash["key"]   + QStringLiteral("</font></b>");
                  hash["html_value"]  = "<b><font color=\"#00af00\">" + hash["value"] + QStringLiteral("</font></b>");
               }

               infoList.append(hash);
            }
         }
      }
   }

   if (core_info->notes)
   {
      for (i = 0; i < core_info->note_list->size; i++)
      {
         QHash<QString, QString> 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<QString, QString> MainWindow::getCurrentContentHash()
{
   return getCurrentContentIndex().data(PlaylistModel::HASH).value<QHash<QString, QString> >();
}

QHash<QString, QString> MainWindow::getFileContentHash(const QModelIndex &index)
{
   QHash<QString, QString> 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<QString, QString> MainWindow::getSelectedCore()
{
   QHash<QString, QString> coreHash;
   QHash<QString, QString> contentHash;
   QVariantMap coreMap          = m_launchWithComboBox->currentData(
         Qt::UserRole).value<QVariantMap>();
   core_selection coreSelection = static_cast<core_selection>(
         coreMap.value("core_selection").toInt());
   ViewType viewType            = getCurrentViewType();

   if (viewType == VIEW_TYPE_LIST)
      contentHash = m_tableView->currentIndex().data(
            PlaylistModel::HASH).value<QHash<QString, QString> >();
   else if (viewType == VIEW_TYPE_ICONS)
      contentHash = m_gridView->currentIndex().data(
            PlaylistModel::HASH).value<QHash<QString, QString> >();
   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<QString, QString> &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<QVariantMap>();
   core_selection coreSelection = static_cast<core_selection>(
         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<QString, QString> 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<QString, QString>         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("<n/a>");
         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<char32_t> textHiraToKata;
   QVector<char32_t> textKataToHira;
   QVector<unsigned> 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<QDockWidget*> dockWidgets;
   QMenu         *menu = qobject_cast<QMenu*>(sender());
   bool found          = false;

   if (!menu)
      return;

   dockWidgets         = findChildren<QDockWidget*>();

   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<QAction*>(sender());

   if (!action)
      return;

   if (!(dock = findChild<QDockWidget*>(action->property(
                  "dock_name").toString())))
      return;

   if (!dock->isVisible())
   {
      addDockWidget(static_cast<Qt::DockWidgetArea>(
               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<int> &roles)
{
   QHash<QString, QString> hash;

   if (!roles.contains(Qt::EditRole))
      return;
   if (topLeft != bottomRight)
      return;

   hash = topLeft.data(PlaylistModel::HASH).value<QHash<QString, QString>>();

   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<QHash<QString, QString>>());
}

void MainWindow::onCurrentFileChanged(const QModelIndex &index)
{
   onCurrentItemChanged(getFileContentHash(
            m_proxyFileModel->mapToSource(index)));
}

void MainWindow::onCurrentItemChanged(const QHash<QString, QString> &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<ThumbnailWidget*>(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<QAction*>("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<QAction*>("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<QAction*>(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<QDialog> 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("<pre>") + retroarch_contributors_list + QStringLiteral("</pre>"));

   dialog->resize(480, 640);
   dialog->exec();
}

void MainWindow::showAbout()
{
   QScopedPointer<QDialog> dialog(new QDialog());
   QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
   QString text = QStringLiteral("RetroArch ")
      + QStringLiteral(PACKAGE_VERSION)
      + QStringLiteral("<br><br>")
      + "<a href=\"https://www.libretro.com/\">www.libretro.com</a>"
         "<br><br>"
      + "<a href=\"https://www.retroarch.com/\">www.retroarch.com</a>"
#ifdef HAVE_GIT_VERSION
         "<br><br>"
      + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_GIT_VERSION)
      + QStringLiteral(": ")
      + retroarch_git_version
#endif
      + QStringLiteral("<br>")
      + 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<QMessageBox::StandardButton>
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<QMessageBox::StandardButton> 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<QMessageBox::StandardButton> 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<QMessageBox::StandardButton> 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<QMessageBox::StandardButton> 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<float>(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_t*>
      (ui_application_qt.initialize());
   handle->window  = static_cast<ui_window_qt_t*>(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<QVBoxLayout*>(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<Qt::DockWidgetArea>(
            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<Qt::DockWidgetArea>(
            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<Qt::DockWidgetArea>(
            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<Qt::DockWidgetArea>(
            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<Qt::DockWidgetArea>(
            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<Qt::DockWidgetArea>(
            coreSelectionDock->property("default_area").toInt()), coreSelectionDock);

   mainwindow->splitDockWidget(browserAndPlaylistTabDock, coreSelectionDock, Qt::Vertical);

#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
   mainwindow->resizeDocks(QList<QDockWidget*>() << coreSelectionDock,
         QList<int>() << 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<int> 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());
}
