Merge pull request #2016 from ts-korhonen/qt

Qt port
This commit is contained in:
Miran Grča
2022-02-07 00:17:50 +01:00
committed by GitHub
149 changed files with 42162 additions and 43 deletions

View File

@@ -17,7 +17,7 @@
# WIN32 marks us as a GUI app on Windows
add_executable(86Box WIN32 MACOSX_BUNDLE 86box.c config.c log.c random.c timer.c io.c acpi.c apm.c
dma.c ddma.c nmi.c pic.c pit.c port_6x.c port_92.c ppi.c pci.c mca.c usb.c fifo8.c
dma.c ddma.c discord.c nmi.c pic.c pit.c port_6x.c port_92.c ppi.c pci.c mca.c usb.c fifo8.c
device.c nvr.c nvr_at.c nvr_ps2.c)
if(CPPTHREADS)
@@ -75,8 +75,31 @@ if(APPLE)
# Force using the newest library if it's installed by homebrew
set(CMAKE_FIND_FRAMEWORK LAST)
# setting our compilation target to macOS 10.13 High Sierra
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13")
# setting our compilation target to macOS 10.15 Catalina if targetting Qt6, macOS 10.13 High Sierra otherwise
if (USE_QT6)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15")
else()
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13")
endif()
endif()
if(QT)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if (USE_QT6)
set(QT_MAJOR 6)
else()
set(QT_MAJOR 5)
endif()
# if we want to use qt5-static to eliminate need for bundling qt dlls (windows)
#set(QT_STATIC ON)
# needed to build with qt5-static if both qt5 and qt5-static are installed
#set(CMAKE_PREFIX_PATH "path/to/qt5-static")
find_package(Qt${QT_MAJOR} COMPONENTS Core Widgets OpenGL REQUIRED)
find_package(Qt${QT_MAJOR}LinguistTools REQUIRED)
endif()
find_package(Freetype REQUIRED)
@@ -155,15 +178,85 @@ else()
install(TARGETS 86Box)
endif()
# prepare everything for Qt5 bundle creation
if (APPLE AND QT)
set(prefix "86Box.app/Contents")
set(INSTALL_RUNTIME_DIR "${prefix}/MacOS")
set(INSTALL_CMAKE_DIR "${prefix}/Resources")
endif()
# loads a macro to install Qt5 plugins on macOS
# based on https://stackoverflow.com/questions/35612687/cmake-macos-x-bundle-with-bundleutiliies-for-qt-application
macro(install_qt5_plugin _qt_plugin_name _qt_plugins_var _prefix)
get_target_property(_qt_plugin_path "${_qt_plugin_name}" LOCATION)
if(EXISTS "${_qt_plugin_path}")
get_filename_component(_qt_plugin_file "${_qt_plugin_path}" NAME)
get_filename_component(_qt_plugin_type "${_qt_plugin_path}" PATH)
get_filename_component(_qt_plugin_type "${_qt_plugin_type}" NAME)
set(_qt_plugin_dest "${_prefix}/PlugIns/${_qt_plugin_type}")
install(FILES "${_qt_plugin_path}"
DESTINATION "${_qt_plugin_dest}")
set(${_qt_plugins_var}
"${${_qt_plugins_var}};\$ENV{DEST_DIR}\${CMAKE_INSTALL_PREFIX}/${_qt_plugin_dest}/${_qt_plugin_file}")
else()
message(FATAL_ERROR "QT plugin ${_qt_plugin_name} not found")
endif()
endmacro()
# Install our dependencies to the macOS bundle
if(APPLE)
install(CODE "
include(BundleUtilities)
get_filename_component(CMAKE_INSTALL_PREFIX_ABSOLUTE \${CMAKE_INSTALL_PREFIX} ABSOLUTE)
fixup_bundle(\"\${CMAKE_INSTALL_PREFIX_ABSOLUTE}/86Box.app\" \"\" \"\")"
COMPONENT Runtime)
if (NOT QT)
install(CODE "
include(BundleUtilities)
get_filename_component(CMAKE_INSTALL_PREFIX_ABSOLUTE \${CMAKE_INSTALL_PREFIX} ABSOLUTE)
fixup_bundle(\"\${CMAKE_INSTALL_PREFIX_ABSOLUTE}/86Box.app\" \"\" \"\")"
COMPONENT Runtime)
endif()
if(QT)
# using the install_qt5_plugin to add Qt plugins into the macOS app bundle
if (USE_QT6)
install_qt5_plugin("Qt6::QCocoaIntegrationPlugin" QT_PLUGINS ${prefix})
else()
install_qt5_plugin("Qt5::QCocoaIntegrationPlugin" QT_PLUGINS ${prefix})
install_qt5_plugin("Qt5::QMacStylePlugin" QT_PLUGINS ${prefix})
install_qt5_plugin("Qt5::QICOPlugin" QT_PLUGINS ${prefix})
install_qt5_plugin("Qt5::QICNSPlugin" QT_PLUGINS ${prefix})
endif()
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/qt.conf"
"[Paths]\nPlugins = ${_qt_plugin_dir}\n")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/qt.conf"
DESTINATION "${INSTALL_CMAKE_DIR}")
# Note Mac specific extension .app
set(APPS "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/86Box.app")
# Directories to look for dependencies
set(DIRS "${CMAKE_BINARY_DIR}")
# Path used for searching by FIND_XXX(), with appropriate suffixes added
if(CMAKE_PREFIX_PATH)
foreach(dir ${CMAKE_PREFIX_PATH})
list(APPEND DIRS "${dir}/bin" "${dir}/lib")
endforeach()
endif()
# Append Qt's lib folder which is two levels above Qt5Widgets_DIR
list(APPEND DIRS "${Qt5Widgets_DIR}/../..")
include(InstallRequiredSystemLibraries)
message(STATUS "APPS: ${APPS}")
message(STATUS "QT_PLUGINS: ${QT_PLUGINS}")
message(STATUS "DIRS: ${DIRS}")
install(CODE "include(BundleUtilities)
fixup_bundle(\"${APPS}\" \"${QT_PLUGINS}\" \"${DIRS}\")")
endif()
endif()
# Install our dependencies if using vcpkg
if(VCPKG_TOOLCHAIN)
x_vcpkg_install_local_dependencies(TARGETS 86Box DESTINATION ".")
@@ -198,9 +291,12 @@ add_subdirectory(sio)
add_subdirectory(scsi)
add_subdirectory(sound)
add_subdirectory(video)
if(APPLE)
add_subdirectory(mac)
add_subdirectory(unix)
if (APPLE)
add_subdirectory(mac)
endif()
if (QT)
add_subdirectory(qt)
elseif(WIN32)
add_subdirectory(win)
else()

View File

@@ -19,18 +19,23 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <time.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include "cpu.h"
#include "cpu/cpu.h"
#include <86box/machine.h>
#include <86box/plat.h>
#include <86box/plat_dynld.h>
#include <86box/win_discord.h>
#include <86box/discord.h>
#include <discord_game_sdk.h>
#ifdef _WIN32
#define PATH_DISCORD_DLL "discord_game_sdk.dll"
#elif defined __APPLE__
#define PATH_DISCORD_DLL "discord_game_sdk.dylib"
#else
#define PATH_DISCORD_DLL "discord_game_sdk.so"
#endif
int discord_loaded = 0;
@@ -74,7 +79,7 @@ discord_update_activity(int paused)
if(discord_activities == NULL)
return;
discord_log("win_discord: discord_update_activity(paused=%d)\n", paused);
discord_log("discord: discord_update_activity(paused=%d)\n", paused);
memset(&activity, 0x00, sizeof(activity));
@@ -85,13 +90,13 @@ discord_update_activity(int paused)
if (strlen(vm_name) < 100)
{
sprintf_s(activity.details, sizeof(activity.details), "Running \"%s\"", vm_name);
sprintf_s(activity.state, sizeof(activity.state), "%s (%s/%s)", strchr(machine_getname(), ']') + 2, cpufamily, cpu_s->name);
snprintf(activity.details, sizeof(activity.details), "Running \"%s\"", vm_name);
snprintf(activity.state, sizeof(activity.state), "%s (%s/%s)", strchr(machine_getname(), ']') + 2, cpufamily, cpu_s->name);
}
else
{
strncpy(activity.details, strchr(machine_getname(), ']') + 2, sizeof(activity.details) - 1);
sprintf_s(activity.state, sizeof(activity.state), "%s/%s", cpufamily, cpu_s->name);
snprintf(activity.state, sizeof(activity.state), "%s/%s", cpufamily, cpu_s->name);
}
activity.timestamps.start = time(NULL);
@@ -139,7 +144,7 @@ discord_load()
if (discord_handle == NULL)
{
discord_log("win_discord: couldn't load " PATH_DISCORD_DLL "\n");
discord_log("discord: couldn't load " PATH_DISCORD_DLL "\n");
discord_close();
return(0);
@@ -165,7 +170,7 @@ discord_init()
result = discord_create(DISCORD_VERSION, &params, &discord_core);
if (result != DiscordResult_Ok)
{
discord_log("win_discord: DiscordCreate returned %d\n", result);
discord_log("discord: DiscordCreate returned %d\n", result);
discord_close();
return;
}

View File

@@ -17,6 +17,11 @@
#ifndef WIN_DISCORD_H
# define WIN_DISCORD_H
#ifdef __cplusplus
extern "C"
{
#endif
extern int discord_loaded;
extern int discord_load();
@@ -25,4 +30,8 @@ extern void discord_close();
extern void discord_update_activity(int paused);
extern void discord_run_callbacks();
#endif
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -20,6 +20,8 @@
#ifndef EMU_PLAT_H
# define EMU_PLAT_H
#include "86box/device.h"
#include "86box/machine.h"
#ifndef GLOBAL
# define GLOBAL extern
#endif
@@ -164,6 +166,25 @@ extern int ioctl_open(uint8_t id, char d);
extern void ioctl_reset(uint8_t id);
extern void ioctl_close(uint8_t id);
#ifdef __APPLE__
#define thread_t plat_thread_t
#define event_t plat_event_t
#define mutex_t plat_mutex_t
#define thread_create plat_thread_create
#define thread_wait plat_thread_wait
#define thread_create_event plat_thread_create_event
#define thread_set_event plat_thread_set_event
#define thread_reset_event plat_thread_reset_event
#define thread_wait_event plat_thread_wait_event
#define thread_destroy_event plat_thread_destroy_event
#define thread_create_mutex plat_thread_create_mutex
#define thread_create_mutex_with_spin_count plat_thread_create_mutex_with_spin_count
#define thread_close_mutex plat_thread_close_mutex
#define thread_wait_mutex plat_thread_wait_mutex
#define thread_release_mutex plat_thread_release_mutex
#endif
/* Thread support. */
typedef void thread_t;

View File

@@ -48,6 +48,7 @@ configure_file(Info.plist.in Info.plist @ONLY)
set_target_properties(86Box
PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist)
#set(XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES")
#set(XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-")
set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "-o linker-signed")
set(XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES")
set(XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-")
#set(XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/mac/codesign/dev/app.entitlements)

View File

@@ -30,6 +30,7 @@
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdatomic.h>
#endif
#include <minitrace/minitrace.h>
@@ -66,8 +67,8 @@ typedef struct raw_event {
static raw_event_t *event_buffer;
static raw_event_t *flush_buffer;
static volatile int event_count;
static __attribute__ ((aligned (32))) volatile long is_tracing = FALSE;
static __attribute__ ((aligned (32))) volatile long stop_flushing_requested = FALSE;
static __attribute__ ((aligned (32))) atomic_long is_tracing = FALSE;
static __attribute__ ((aligned (32))) atomic_long stop_flushing_requested = FALSE;
static int is_flushing = FALSE;
static int events_in_progress = 0;
static int64_t time_offset;
@@ -93,8 +94,6 @@ void mtr_flush_with_state(int);
// mtr_time_s()
// pthread basics
#ifdef _WIN32
#define atomic_load(a) InterlockedOr((a), 0)
#define atomic_store(a, b) InterlockedExchange((a), b)
static int get_cur_thread_id() {
return (int)GetCurrentThreadId();
@@ -162,6 +161,31 @@ static inline int get_cur_process_id() {
return (int)getpid();
}
static pthread_t thread_handle = 0;
static void* thread_flush_proc(void* param) {
while(1) {
mtr_flush_with_state(0);
if(atomic_load(&stop_flushing_requested)) {
break;
}
}
return 0;
}
static void init_flushing_thread(void) {
pthread_mutex_lock(&mutex);
is_flushing = FALSE;
pthread_mutex_unlock(&mutex);
if (pthread_create(&thread_handle, NULL, thread_flush_proc, NULL) != 0)
{
thread_handle = 0;
}
}
static void join_flushing_thread(void) {
if (thread_handle) pthread_join(thread_handle, NULL);
thread_handle = 0;
}
#if defined(BLACKBERRY)
double mtr_time_s() {
struct timespec time;
@@ -269,9 +293,9 @@ void mtr_start() {
#ifndef MTR_ENABLED
return;
#endif
pthread_cond_init(&buffer_not_full_cond);
pthread_cond_init(&buffer_full_cond);
atomic_store(&is_tracing, TRUE);
pthread_cond_init(&buffer_not_full_cond, NULL);
pthread_cond_init(&buffer_full_cond, NULL);
atomic_store(&is_tracing, TRUE);
init_flushing_thread();
}

View File

@@ -13,12 +13,27 @@
# Copyright 2020,2021 David Hrdlička.
#
add_library(slirp STATIC tinyglib.c arp_table.c bootp.c cksum.c dnssearch.c if.c ip_icmp.c
add_library(slirp STATIC arp_table.c bootp.c cksum.c dnssearch.c if.c ip_icmp.c
ip_input.c ip_output.c mbuf.c misc.c sbuf.c slirp.c socket.c tcp_input.c
tcp_output.c tcp_subr.c tcp_timer.c udp.c util.c version.c)
#target_link_libraries(slirp wsock32 iphlpapi)
#target_link_libraries(slirp)
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
target_link_libraries(slirp wsock32 iphlpapi)
target_link_libraries(slirp wsock32 iphlpapi)
endif()
if(QT)
if(UNIX AND NOT APPLE)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GLIB_PKG IMPORTED_TARGET glib-2.0)
if (GLIB_PKG_FOUND)
target_compile_definitions(slirp PRIVATE TINYGLIB_USE_GLIB)
target_link_libraries(slirp PkgConfig::GLIB_PKG)
else()
message(ERROR "GLib development headers are required when compiling with Qt on Unix")
endif()
else()
target_sources(slirp PRIVATE tinyglib.c)
endif()
else()
target_sources(slirp PRIVATE tinyglib.c)
endif()

View File

@@ -11,7 +11,7 @@
#ifdef BSD
#define g_strlcpy strlcpy
#else
extern int g_strlcpy(gchar* dest, const gchar* src, gsize dest_size);
extern gsize g_strlcpy(gchar* dest, const gchar* src, gsize dest_size);
#endif
#endif

220
src/qt/CMakeLists.txt Normal file
View File

@@ -0,0 +1,220 @@
# Find includes in corresponding build directories
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
find_package(Threads REQUIRED)
add_library(plat STATIC
qt.c
qt_main.cpp
qt_platform.cpp
)
add_library(ui STATIC
qt_ui.cpp
qt_cdrom.c
qt_mainwindow.cpp
qt_mainwindow.hpp
qt_mainwindow.ui
qt_machinestatus.cpp
qt_machinestatus.hpp
qt_mediamenu.cpp
qt_mediamenu.hpp
qt_rendererstack.cpp
qt_rendererstack.hpp
qt_rendererstack.ui
qt_renderercommon.cpp
qt_renderercommon.hpp
qt_softwarerenderer.cpp
qt_softwarerenderer.hpp
qt_hardwarerenderer.cpp
qt_hardwarerenderer.hpp
qt_settings.cpp
qt_settings.hpp
qt_settings.ui
qt_settingsmachine.cpp
qt_settingsmachine.hpp
qt_settingsmachine.ui
qt_settingsdisplay.cpp
qt_settingsdisplay.hpp
qt_settingsdisplay.ui
qt_settingsinput.cpp
qt_settingsinput.hpp
qt_settingsinput.ui
qt_settingssound.cpp
qt_settingssound.hpp
qt_settingssound.ui
qt_settingsnetwork.cpp
qt_settingsnetwork.hpp
qt_settingsnetwork.ui
qt_settingsports.cpp
qt_settingsports.hpp
qt_settingsports.ui
qt_settingsstoragecontrollers.cpp
qt_settingsstoragecontrollers.hpp
qt_settingsstoragecontrollers.ui
qt_settingsharddisks.cpp
qt_settingsharddisks.hpp
qt_settingsharddisks.ui
qt_settingsfloppycdrom.cpp
qt_settingsfloppycdrom.hpp
qt_settingsfloppycdrom.ui
qt_settingsotherremovable.cpp
qt_settingsotherremovable.hpp
qt_settingsotherremovable.ui
qt_settingsotherperipherals.cpp
qt_settingsotherperipherals.hpp
qt_settingsotherperipherals.ui
qt_settings_bus_tracking.cpp
qt_settings_bus_tracking.hpp
qt_deviceconfig.cpp
qt_deviceconfig.hpp
qt_deviceconfig.ui
qt_joystickconfiguration.cpp
qt_joystickconfiguration.hpp
qt_joystickconfiguration.ui
qt_filefield.cpp
qt_filefield.hpp
qt_filefield.ui
qt_newfloppydialog.cpp
qt_newfloppydialog.hpp
qt_newfloppydialog.ui
qt_harddiskdialog.cpp
qt_harddiskdialog.hpp
qt_harddiskdialog.ui
qt_harddrive_common.cpp
qt_harddrive_common.hpp
qt_models_common.cpp
qt_models_common.hpp
qt_specifydimensions.h
qt_specifydimensions.cpp
qt_specifydimensions.ui
qt_soundgain.hpp
qt_soundgain.cpp
qt_soundgain.ui
qt_styleoverride.cpp
qt_styleoverride.hpp
qt_progsettings.hpp
qt_progsettings.cpp
qt_progsettings.ui
qt_util.hpp
qt_util.cpp
../qt_resources.qrc
)
if(WIN32)
enable_language(RC)
target_sources(86Box PUBLIC ../win/86Box-qt.rc)
target_sources(plat PRIVATE win_joystick_rawinput.c)
target_link_libraries(86Box hid)
# CMake 3.22 messed this up for clang/clang++
# See https://gitlab.kitware.com/cmake/cmake/-/issues/22611
if(MSVC OR (NOT MINGW AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.22))
# MSVC linker adds its own manifest to the executable, which fails if
# we include ours in 86Box.rc. We therefore need to pass the manifest
# directly as as a source file, so the linker can use that instead.
set_property(SOURCE ../win/86Box-qt.rc PROPERTY COMPILE_DEFINITIONS NO_INCLUDE_MANIFEST)
target_sources(86Box PRIVATE ../win/86Box.manifest)
endif()
else()
target_sources(plat PRIVATE sdl_joystick.cpp)
endif()
if(WIN32 AND NOT MINGW)
target_sources(plat PRIVATE ../win/win_opendir.c)
endif()
if (APPLE)
target_sources(ui PRIVATE macos_event_filter.mm)
endif()
if (WIN32)
target_sources(ui PRIVATE
qt_winrawinputfilter.hpp
qt_winrawinputfilter.cpp
qt_winmanagerfilter.hpp
qt_winmanagerfilter.cpp
)
endif()
target_link_libraries(
plat
PRIVATE
Qt${QT_MAJOR}::Widgets
Qt${QT_MAJOR}::Gui
Threads::Threads
)
target_link_libraries(
ui
PRIVATE
Qt${QT_MAJOR}::Widgets
Qt${QT_MAJOR}::Gui
Qt${QT_MAJOR}::OpenGL
Threads::Threads
)
# needed for static builds
if (WIN32)
qt_import_plugins(plat INCLUDE Qt${QT_MAJOR}::QWindowsIntegrationPlugin Qt${QT_MAJOR}::QICOPlugin QWindowsVistaStylePlugin)
endif()
if (UNIX AND NOT APPLE)
find_package(X11 REQUIRED)
target_link_libraries(ui PRIVATE X11::X11)
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBEVDEV IMPORTED_TARGET libevdev)
if (LIBEVDEV_FOUND)
target_compile_definitions(ui PRIVATE EVDEV_INPUT)
target_link_libraries(ui PUBLIC PkgConfig::LIBEVDEV)
target_sources(ui PRIVATE evdev_mouse.cpp)
endif()
find_package(ECM NO_MODULE)
if (ECM_FOUND)
list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
find_package(Wayland COMPONENTS Client)
if (Wayland_FOUND)
target_link_libraries(ui PRIVATE Wayland::Client)
find_package(WaylandScanner REQUIRED)
if (WaylandScanner_FOUND)
set(WL_SOURCE_VAR)
ecm_add_wayland_client_protocol(WL_SOURCE_VAR PROTOCOL ${CMAKE_SOURCE_DIR}/wl_protocols/relative-pointer-unstable-v1.xml BASENAME relative-pointer-unstable-v1)
ecm_add_wayland_client_protocol(WL_SOURCE_VAR PROTOCOL ${CMAKE_SOURCE_DIR}/wl_protocols/pointer-constraints-unstable-v1.xml BASENAME pointer-constraints-unstable-v1)
target_include_directories(ui PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${Qt${QT_MAJOR}Gui_PRIVATE_INCLUDE_DIRS})
target_sources(ui PRIVATE ${WL_SOURCE_VAR} wl_mouse.cpp)
target_compile_definitions(ui PRIVATE WAYLAND)
endif()
endif()
endif()
endif()
set(QM_FILES)
file(GLOB po_files "${CMAKE_CURRENT_SOURCE_DIR}/languages/*.po")
foreach(po_file ${po_files})
get_target_property(LCONVERT_EXECUTABLE Qt${QT_MAJOR}::lconvert IMPORTED_LOCATION)
get_filename_component(_lconvert_bin_dir "${LCONVERT_EXECUTABLE}" DIRECTORY)
find_program(LCONVERT_EXECUTABLE lconvert HINTS "${_lconvert_bin_dir}")
get_filename_component(PO_FILE_NAME ${po_file} NAME_WE)
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/86box_${PO_FILE_NAME}.qm"
COMMAND ${LCONVERT_EXECUTABLE} -i ${po_file} -o ${CMAKE_CURRENT_BINARY_DIR}/86box_${PO_FILE_NAME}.qm
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
DEPENDS "${po_file}")
list(APPEND QM_FILES "${CMAKE_CURRENT_BINARY_DIR}/86box_${PO_FILE_NAME}.qm")
list(APPEND QM_FILES "${po_file}")
endforeach()
configure_file(qt_translations.qrc ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
target_sources(ui PRIVATE ${QM_FILES} ${CMAKE_CURRENT_BINARY_DIR}/qt_translations.qrc)

1
src/qt/TODO Normal file
View File

@@ -0,0 +1 @@
* Joystick support

16
src/qt/cocoa_mouse.hpp Normal file
View File

@@ -0,0 +1,16 @@
#include <QAbstractNativeEventFilter>
#include <QByteArray>
#if QT_VERSION_MAJOR >= 6
#define result_t qintptr
#else
#define result_t long
#endif
class CocoaEventFilter : public QAbstractNativeEventFilter
{
public:
CocoaEventFilter() {};
~CocoaEventFilter();
virtual bool nativeEventFilter(const QByteArray &eventType, void *message, result_t *result) override;
};

102
src/qt/evdev_mouse.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "evdev_mouse.hpp"
#include <libevdev/libevdev.h>
#include <unistd.h>
#include <fcntl.h>
#include <vector>
#include <atomic>
#include <string>
#include <tuple>
#include <QThread>
extern "C"
{
#include <86box/86box.h>
#include <86box/plat.h>
#include <86box/mouse.h>
}
static std::vector<std::pair<int, libevdev*>> evdev_mice;
static std::atomic<bool> stopped = false;
static QThread* evdev_thread;
static std::atomic<int> evdev_mouse_rel_x = 0, evdev_mouse_rel_y = 0;
void evdev_mouse_poll()
{
if (!evdev_mice.size() || !mouse_capture)
{
evdev_mouse_rel_x = 0;
evdev_mouse_rel_y = 0;
return;
}
mouse_x = evdev_mouse_rel_x;
mouse_y = evdev_mouse_rel_y;
evdev_mouse_rel_x = evdev_mouse_rel_y = 0;
}
void evdev_thread_func()
{
while (!stopped)
{
for (int i = 0; i < evdev_mice.size(); i++)
{
struct input_event ev;
int rc = libevdev_next_event(evdev_mice[i].second, LIBEVDEV_READ_FLAG_NORMAL, &ev);
if (rc == 0 && ev.type == EV_REL && mouse_capture)
{
if (ev.code == REL_X) evdev_mouse_rel_x += ev.value;
if (ev.code == REL_Y) evdev_mouse_rel_y += ev.value;
}
}
}
for (int i = 0; i < evdev_mice.size(); i++)
{
libevdev_free(evdev_mice[i].second);
close(evdev_mice[i].first);
}
evdev_mice.clear();
}
void evdev_stop()
{
stopped = true;
evdev_thread->wait();
}
void evdev_init()
{
for (int i = 0; i < 256; i++)
{
std::string evdev_device_path = "/dev/input/event" + std::to_string(i);
int fd = open(evdev_device_path.c_str(), O_NONBLOCK | O_RDONLY);
if (fd != -1)
{
libevdev* input_struct = nullptr;
int rc = libevdev_new_from_fd(fd, &input_struct);
if (rc <= -1)
{
close(fd);
continue;
}
else
{
if (!libevdev_has_event_type(input_struct, EV_REL) || !libevdev_has_event_code(input_struct, EV_KEY, BTN_LEFT))
{
libevdev_free(input_struct);
close(fd);
continue;
}
evdev_mice.push_back(std::make_pair(fd, input_struct));
}
}
else if (errno == ENOENT) break;
}
if (evdev_mice.size() != 0)
{
evdev_thread = QThread::create(evdev_thread_func);
evdev_thread->start();
atexit(evdev_stop);
}
}

4
src/qt/evdev_mouse.hpp Normal file
View File

@@ -0,0 +1,4 @@
#ifdef EVDEV_INPUT
void evdev_init();
void evdev_mouse_poll();
#endif

1215
src/qt/languages/cs-CZ.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/de-DE.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/en-GB.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/en-US.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/es-ES.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/fi-FI.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/fr-FR.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/hr-HR.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/hu-HU.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/it-IT.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/ja-JP.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/ko-KR.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/pl-PL.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/pt-BR.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/pt-PT.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/ru-RU.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/sl-SI.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/tr-TR.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/uk-UA.po Normal file

File diff suppressed because it is too large Load Diff

1215
src/qt/languages/zh-CN.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,103 @@
#include <SDL.h>
//#include "86box/plat.h"
#include "cocoa_mouse.hpp"
#import <AppKit/AppKit.h>
extern "C"
{
#include <86box/86box.h>
#include <86box/keyboard.h>
#include <86box/mouse.h>
#include <86box/config.h>
//#include <86box/plat.h>
#include <86box/plat_dynld.h>
#include <86box/device.h>
#include <86box/timer.h>
#include <86box/ui.h>
#include <86box/video.h>
extern int mouse_capture;
extern void plat_mouse_capture(int);
}
typedef struct mouseinputdata
{
int deltax, deltay, deltaz;
int mousebuttons;
} mouseinputdata;
static mouseinputdata mousedata;
CocoaEventFilter::~CocoaEventFilter()
{
}
bool CocoaEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, result_t *result)
{
if (mouse_capture)
{
if (eventType == "mac_generic_NSEvent")
{
NSEvent* event = (NSEvent*)message;
if ([event type] == NSEventTypeMouseMoved
|| [event type] == NSEventTypeLeftMouseDragged
|| [event type] == NSEventTypeRightMouseDragged
|| [event type] == NSEventTypeOtherMouseDragged)
{
mousedata.deltax += [event deltaX];
mousedata.deltay += [event deltaY];
return true;
}
if ([event type] == NSEventTypeScrollWheel)
{
mousedata.deltaz += [event deltaY];
return true;
}
switch ([event type])
{
default: return false;
case NSEventTypeLeftMouseDown:
{
mousedata.mousebuttons |= 1;
break;
}
case NSEventTypeLeftMouseUp:
{
mousedata.mousebuttons &= ~1;
break;
}
case NSEventTypeRightMouseDown:
{
mousedata.mousebuttons |= 2;
break;
}
case NSEventTypeRightMouseUp:
{
mousedata.mousebuttons &= ~2;
break;
}
case NSEventTypeOtherMouseDown:
{
mousedata.mousebuttons |= 4;
break;
}
case NSEventTypeOtherMouseUp:
{
if (mouse_get_buttons() < 3) { plat_mouse_capture(0); return true; }
mousedata.mousebuttons &= ~4;
break;
}
}
return true;
}
}
return false;
}
extern "C" void macos_poll_mouse()
{
mouse_x = mousedata.deltax;
mouse_y = mousedata.deltay;
mouse_z = mousedata.deltaz;
mousedata.deltax = mousedata.deltay = mousedata.deltaz = 0;
mouse_buttons = mousedata.mousebuttons;
}

63
src/qt/qt.c Normal file
View File

@@ -0,0 +1,63 @@
/*
* C functionality for Qt platform, where the C equivalent is not easily
* implemented in Qt
*/
#if !defined(_WIN32) || !defined(__clang__)
#include <strings.h>
#endif
#include <string.h>
#include <stdint.h>
#include <wchar.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/plat.h>
#include <86box/timer.h>
#include <86box/nvr.h>
int qt_nvr_save(void) {
return nvr_save();
}
char icon_set[256] = ""; /* name of the iconset to be used */
int
plat_vidapi(char* api) {
if (!strcasecmp(api, "default") || !strcasecmp(api, "system")) {
return 0;
} else if (!strcasecmp(api, "qt_software")) {
return 0;
} else if (!strcasecmp(api, "qt_opengl")) {
return 1;
} else if (!strcasecmp(api, "qt_opengles")) {
return 2;
} else if (!strcasecmp(api, "qt_opengl3")) {
return 3;
}
return 0;
}
char* plat_vidapi_name(int api) {
char* name = "default";
switch (api) {
case 0:
name = "qt_software";
break;
case 1:
name = "qt_opengl";
break;
case 2:
name = "qt_opengles";
break;
case 3:
name = "qt_opengl3";
break;
default:
fatal("Unknown renderer: %i\n", api);
break;
}
return name;
}

55
src/qt/qt_cdrom.c Normal file
View File

@@ -0,0 +1,55 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Handle the platform-side of CDROM/ZIP/MO drives.
*
*
*
* Authors: Sarah Walker, <http://pcem-emulator.co.uk/>
* Miran Grca, <mgrca8@gmail.com>
* Fred N. van Kempen, <decwiz@yahoo.com>
*
* Copyright 2016-2018 Miran Grca.
* Copyright 2017,2018 Fred N. van Kempen.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <wchar.h>
#include <86box/86box.h>
#include <86box/config.h>
#include <86box/timer.h>
#include <86box/device.h>
#include <86box/cassette.h>
#include <86box/cartridge.h>
#include <86box/fdd.h>
#include <86box/hdd.h>
#include <86box/scsi_device.h>
#include <86box/cdrom.h>
#include <86box/mo.h>
#include <86box/zip.h>
#include <86box/scsi_disk.h>
#include <86box/plat.h>
#include <86box/ui.h>
void
plat_cdrom_ui_update(uint8_t id, uint8_t reload)
{
cdrom_t *drv = &cdrom[id];
if (drv->host_drive == 0) {
ui_sb_update_icon_state(SB_CDROM|id, 1);
} else {
ui_sb_update_icon_state(SB_CDROM|id, 0);
}
//media_menu_update_cdrom(id);
ui_sb_update_tip(SB_CDROM|id);
}

213
src/qt/qt_deviceconfig.cpp Normal file
View File

@@ -0,0 +1,213 @@
#include "qt_deviceconfig.hpp"
#include "ui_qt_deviceconfig.h"
#include <QDebug>
#include <QComboBox>
#include <QFormLayout>
#include <QSpinBox>
#include <QCheckBox>
extern "C" {
#include <86box/86box.h>
#include <86box/config.h>
#include <86box/device.h>
#include <86box/midi_rtmidi.h>
}
#include "qt_filefield.hpp"
#include "qt_models_common.hpp"
DeviceConfig::DeviceConfig(QWidget *parent) :
QDialog(parent),
ui(new Ui::DeviceConfig)
{
ui->setupUi(this);
}
DeviceConfig::~DeviceConfig()
{
delete ui;
}
void DeviceConfig::ConfigureDevice(const _device_* device, int instance) {
DeviceConfig dc;
dc.setWindowTitle(QString("%1 Device Configuration").arg(device->name));
device_context_t device_context;
device_set_context(&device_context, device, instance);
const auto* config = device->config;
while (config->type != -1) {
switch (config->type) {
case CONFIG_BINARY:
{
auto value = config_get_int(device_context.name, const_cast<char*>(config->name), config->default_int);
auto* cbox = new QCheckBox();
cbox->setObjectName(config->name);
cbox->setChecked(value > 0);
dc.ui->formLayout->addRow(config->description, cbox);
break;
}
case CONFIG_MIDI:
{
auto* cbox = new QComboBox();
cbox->setObjectName(config->name);
auto* model = cbox->model();
int currentIndex = -1;
int selected = config_get_int(device_context.name, const_cast<char*>(config->name), config->default_int);
for (int i = 0; i < rtmidi_get_num_devs(); i++) {
char midiName[512] = { 0 };
rtmidi_get_dev_name(i, midiName);
Models::AddEntry(model, midiName, i);
if (selected == i) {
currentIndex = i;
}
}
dc.ui->formLayout->addRow(config->description, cbox);
cbox->setCurrentIndex(currentIndex);
break;
}
case CONFIG_MIDI_IN:
{
auto* cbox = new QComboBox();
cbox->setObjectName(config->name);
auto* model = cbox->model();
int currentIndex = -1;
int selected = config_get_int(device_context.name, const_cast<char*>(config->name), config->default_int);
for (int i = 0; i < rtmidi_in_get_num_devs(); i++) {
char midiName[512] = { 0 };
rtmidi_in_get_dev_name(i, midiName);
Models::AddEntry(model, midiName, i);
if (selected == i) {
currentIndex = i;
}
}
dc.ui->formLayout->addRow(config->description, cbox);
cbox->setCurrentIndex(currentIndex);
break;
}
case CONFIG_SELECTION:
case CONFIG_HEX16:
case CONFIG_HEX20:
{
auto* cbox = new QComboBox();
cbox->setObjectName(config->name);
auto* model = cbox->model();
int currentIndex = -1;
int selected;
switch (config->type) {
case CONFIG_SELECTION:
selected = config_get_int(device_context.name, const_cast<char*>(config->name), config->default_int);
break;
case CONFIG_HEX16:
selected = config_get_hex16(device_context.name, const_cast<char*>(config->name), config->default_int);
break;
case CONFIG_HEX20:
selected = config_get_hex20(device_context.name, const_cast<char*>(config->name), config->default_int);
break;
}
for (auto* sel = config->selection; (sel->description != nullptr) && (strlen(sel->description) > 0); ++sel) {
int row = Models::AddEntry(model, sel->description, sel->value);
if (selected == sel->value) {
currentIndex = row;
}
}
dc.ui->formLayout->addRow(config->description, cbox);
cbox->setCurrentIndex(currentIndex);
break;
}
case CONFIG_SPINNER:
{
int value = config_get_int(device_context.name, const_cast<char*>(config->name), config->default_int);
auto* spinBox = new QSpinBox();
spinBox->setObjectName(config->name);
spinBox->setMaximum(config->spinner.max);
spinBox->setMinimum(config->spinner.min);
if (config->spinner.step > 0) {
spinBox->setSingleStep(config->spinner.step);
}
spinBox->setValue(value);
dc.ui->formLayout->addRow(config->description, spinBox);
break;
}
case CONFIG_FNAME:
{
auto* fileName = config_get_string(device_context.name, const_cast<char*>(config->name), const_cast<char*>(config->default_string));
auto* fileField = new FileField();
fileField->setObjectName(config->name);
fileField->setFileName(fileName);
fileField->setFilter(QString(config->file_filter).left(strcspn(config->file_filter, "|")));
dc.ui->formLayout->addRow(config->description, fileField);
break;
}
}
++config;
}
dc.setFixedSize(dc.minimumSizeHint());
int res = dc.exec();
if (res == QDialog::Accepted) {
config = device->config;
while (config->type != -1) {
switch (config->type) {
case CONFIG_BINARY:
{
auto* cbox = dc.findChild<QCheckBox*>(config->name);
config_set_int(device_context.name, const_cast<char*>(config->name), cbox->isChecked() ? 1 : 0);
break;
}
case CONFIG_MIDI:
case CONFIG_MIDI_IN:
case CONFIG_SELECTION:
{
auto* cbox = dc.findChild<QComboBox*>(config->name);
config_set_int(device_context.name, const_cast<char*>(config->name), cbox->currentData().toInt());
break;
}
case CONFIG_HEX16:
{
auto* cbox = dc.findChild<QComboBox*>(config->name);
config_set_hex16(device_context.name, const_cast<char*>(config->name), cbox->currentData().toInt());
break;
}
case CONFIG_HEX20:
{
auto* cbox = dc.findChild<QComboBox*>(config->name);
config_set_hex20(device_context.name, const_cast<char*>(config->name), cbox->currentData().toInt());
break;
}
case CONFIG_FNAME:
{
auto* fbox = dc.findChild<FileField*>(config->name);
auto fileName = fbox->fileName().toUtf8();
config_set_string(device_context.name, const_cast<char*>(config->name), fileName.data());
break;
}
case CONFIG_SPINNER:
{
auto* spinBox = dc.findChild<QSpinBox*>(config->name);
config_set_int(device_context.name, const_cast<char*>(config->name), spinBox->value());
break;
}
}
config++;
}
}
}
QString DeviceConfig::DeviceName(const _device_* device, const char *internalName, int bus) {
if (QStringLiteral("none") == internalName) {
return tr("None");
} else if (QStringLiteral("internal") == internalName) {
return tr("Internal controller");
} else if (device == nullptr) {
return QString();
} else {
char temp[512];
device_get_name(device, bus, temp);
return tr(temp, nullptr, 512);
}
}

View File

@@ -0,0 +1,28 @@
#ifndef QT_DEVICECONFIG_HPP
#define QT_DEVICECONFIG_HPP
#include <QDialog>
extern "C" {
struct _device_;
}
namespace Ui {
class DeviceConfig;
}
class DeviceConfig : public QDialog
{
Q_OBJECT
public:
explicit DeviceConfig(QWidget *parent = nullptr);
~DeviceConfig();
static void ConfigureDevice(const _device_* device, int instance = 0);
static QString DeviceName(const _device_* device, const char* internalName, int bus);
private:
Ui::DeviceConfig *ui;
};
#endif // QT_DEVICECONFIG_HPP

74
src/qt/qt_deviceconfig.ui Normal file
View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DeviceConfig</class>
<widget class="QDialog" name="DeviceConfig">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout"/>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DeviceConfig</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DeviceConfig</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

41
src/qt/qt_filefield.cpp Normal file
View File

@@ -0,0 +1,41 @@
#include "qt_filefield.hpp"
#include "ui_qt_filefield.h"
#include <QFileDialog>
FileField::FileField(QWidget *parent) :
QWidget(parent),
ui(new Ui::FileField)
{
ui->setupUi(this);
connect(ui->label, &QLineEdit::editingFinished, this, [this] () {
fileName_ = ui->label->text();
emit fileSelected(ui->label->text());
});
}
FileField::~FileField()
{
delete ui;
}
void FileField::setFileName(const QString &fileName) {
fileName_ = fileName;
ui->label->setText(fileName);
}
void FileField::on_pushButton_clicked() {
QString fileName;
if (createFile_) {
fileName = QFileDialog::getSaveFileName(this, QString(), QString(), filter_, &selectedFilter_);
} else {
fileName = QFileDialog::getOpenFileName(this, QString(), QString(), filter_, &selectedFilter_);
}
if (!fileName.isNull()) {
fileName_ = fileName;
ui->label->setText(fileName);
emit fileSelected(fileName);
}
}

41
src/qt/qt_filefield.hpp Normal file
View File

@@ -0,0 +1,41 @@
#ifndef QT_FILEFIELD_HPP
#define QT_FILEFIELD_HPP
#include <QWidget>
namespace Ui {
class FileField;
}
class FileField : public QWidget
{
Q_OBJECT
public:
explicit FileField(QWidget *parent = nullptr);
~FileField();
QString fileName() const { return fileName_; }
void setFileName(const QString& fileName);
void setFilter(const QString& filter) { filter_ = filter; }
QString selectedFilter() const { return selectedFilter_; }
void setCreateFile(bool createFile) { createFile_ = createFile; }
bool createFile() { return createFile_; }
signals:
void fileSelected(const QString& fileName);
private slots:
void on_pushButton_clicked();
private:
Ui::FileField *ui;
QString fileName_;
QString selectedFilter_;
QString filter_;
bool createFile_ = false;
};
#endif // QT_FILEFIELD_HPP

62
src/qt/qt_filefield.ui Normal file
View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FileField</class>
<widget class="QWidget" name="FileField">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>354</width>
<height>25</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="3,1">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Specify...</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,739 @@
#include "qt_harddiskdialog.hpp"
#include "ui_qt_harddiskdialog.h"
extern "C" {
#include <86box/86box.h>
#include <86box/hdd.h>
#include "../disk/minivhd/minivhd.h"
#include "../disk/minivhd/minivhd_util.h"
}
#include <thread>
#include <QMessageBox>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QFileDialog>
#include <QProgressDialog>
#include <QPushButton>
#include <QStringBuilder>
#include "qt_harddrive_common.hpp"
#include "qt_models_common.hpp"
#include "qt_util.hpp"
HarddiskDialog::HarddiskDialog(bool existing, QWidget *parent) :
QDialog(parent),
ui(new Ui::HarddiskDialog)
{
ui->setupUi(this);
ui->fileField->setFilter(tr("Hard disk images") % util::DlgFilter({ "hd?","im?","vhd" }) % tr("All files") % util::DlgFilter({ "*" }, true));
if (existing) {
setWindowTitle(tr("Add Existing Hard Disk"));
ui->lineEditCylinders->setEnabled(false);
ui->lineEditHeads->setEnabled(false);
ui->lineEditSectors->setEnabled(false);
ui->lineEditSize->setEnabled(false);
ui->comboBoxType->setEnabled(false);
ui->comboBoxFormat->hide();
ui->labelFormat->hide();
connect(ui->fileField, &FileField::fileSelected, this, &HarddiskDialog::onExistingFileSelected);
} else {
setWindowTitle(tr("Add New Hard Disk"));
ui->fileField->setCreateFile(true);
}
auto* model = ui->comboBoxFormat->model();
model->insertRows(0, 6);
model->setData(model->index(0, 0), tr("Raw image (.img)"));
model->setData(model->index(1, 0), tr("HDI image (.hdi)"));
model->setData(model->index(2, 0), tr("HDX image (.hdx)"));
model->setData(model->index(3, 0), tr("Fixed-size VHD (.vhd)"));
model->setData(model->index(4, 0), tr("Dynamic-size VHD (.vhd)"));
model->setData(model->index(5, 0), tr("Differencing VHD (.vhd)"));
model = ui->comboBoxBlockSize->model();
model->insertRows(0, 2);
model->setData(model->index(0, 0), tr("Large blocks (2 MB)"));
model->setData(model->index(1, 0), tr("Small blocks (512 KB)"));
ui->comboBoxBlockSize->hide();
ui->labelBlockSize->hide();
Harddrives::populateBuses(ui->comboBoxBus->model());
ui->comboBoxBus->setCurrentIndex(3);
model = ui->comboBoxType->model();
for (int i = 0; i < 127; i++) {
uint64_t size = ((uint64_t) hdd_table[i][0]) * hdd_table[i][1] * hdd_table[i][2];
uint32_t size_mb = size >> 11LL;
//QString text = QString("%1 MiB (CHS: %2, %3, %4)").arg(size_mb).arg(hdd_table[i][0]).arg(hdd_table[i][1]).arg(hdd_table[i][2]);
QString text = QString::asprintf(tr("%u MB (CHS: %i, %i, %i)").toUtf8().constData(), (size_mb), (hdd_table[i][0]), (hdd_table[i][1]), (hdd_table[i][2]));
Models::AddEntry(model, text, i);
}
Models::AddEntry(model, tr("Custom..."), 127);
Models::AddEntry(model, tr("Custom (large)..."), 128);
ui->lineEditSize->setValidator(new QIntValidator());
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
if (!existing) connect(ui->fileField, &FileField::fileSelected, this, [this] {
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
});
}
HarddiskDialog::~HarddiskDialog()
{
delete ui;
}
uint8_t HarddiskDialog::bus() const {
return static_cast<uint8_t>(ui->comboBoxBus->currentData().toUInt());
}
uint8_t HarddiskDialog::channel() const {
return static_cast<uint8_t>(ui->comboBoxChannel->currentData().toUInt());
}
QString HarddiskDialog::fileName() const {
return ui->fileField->fileName();
}
void HarddiskDialog::on_comboBoxFormat_currentIndexChanged(int index) {
bool enabled;
if (index == 5) { /* They switched to a diff VHD; disable the geometry fields. */
enabled = false;
ui->lineEditCylinders->setText(tr("(N/A)"));
ui->lineEditHeads->setText(tr("(N/A)"));
ui->lineEditSectors->setText(tr("(N/A)"));
ui->lineEditSize->setText(tr("(N/A)"));
} else {
enabled = true;
ui->lineEditCylinders->setText(QString::number(cylinders_));
ui->lineEditHeads->setText(QString::number(heads_));
ui->lineEditSectors->setText(QString::number(sectors_));
recalcSize();
}
ui->lineEditCylinders->setEnabled(enabled);
ui->lineEditHeads->setEnabled(enabled);
ui->lineEditSectors->setEnabled(enabled);
ui->lineEditSize->setEnabled(enabled);
ui->comboBoxType->setEnabled(enabled);
if (index < 4) {
ui->comboBoxBlockSize->hide();
ui->labelBlockSize->hide();
} else {
ui->comboBoxBlockSize->show();
ui->labelBlockSize->show();
}
}
/* If the disk geometry requested in the 86Box GUI is not compatible with the internal VHD geometry,
* we adjust it to the next-largest size that is compatible. On average, this will be a difference
* of about 21 MB, and should only be necessary for VHDs larger than 31.5 GB, so should never be more
* than a tenth of a percent change in size.
*/
static void adjust_86box_geometry_for_vhd(MVHDGeom *_86box_geometry, MVHDGeom *vhd_geometry)
{
if (_86box_geometry->cyl <= 65535) {
vhd_geometry->cyl = _86box_geometry->cyl;
vhd_geometry->heads = _86box_geometry->heads;
vhd_geometry->spt = _86box_geometry->spt;
return;
}
int desired_sectors = _86box_geometry->cyl * _86box_geometry->heads * _86box_geometry->spt;
if (desired_sectors > 267321600)
desired_sectors = 267321600;
int remainder = desired_sectors % 85680; /* 8560 is the LCM of 1008 (63*16) and 4080 (255*16) */
if (remainder > 0)
desired_sectors += (85680 - remainder);
_86box_geometry->cyl = desired_sectors / (16 * 63);
_86box_geometry->heads = 16;
_86box_geometry->spt = 63;
vhd_geometry->cyl = desired_sectors / (16 * 255);
vhd_geometry->heads = 16;
vhd_geometry->spt = 255;
}
static HarddiskDialog* callbackPtr = nullptr;
static MVHDGeom create_drive_vhd_fixed(const QString& fileName, HarddiskDialog* p, uint16_t cyl, uint8_t heads, uint8_t spt) {
MVHDGeom _86box_geometry = { .cyl = cyl, .heads = heads, .spt = spt };
MVHDGeom vhd_geometry;
adjust_86box_geometry_for_vhd(&_86box_geometry, &vhd_geometry);
int vhd_error = 0;
QByteArray filenameBytes = fileName.toUtf8();
callbackPtr = p;
MVHDMeta *vhd = mvhd_create_fixed(filenameBytes.data(), vhd_geometry, &vhd_error, [](uint32_t current_sector, uint32_t total_sectors) {
callbackPtr->fileProgress((current_sector * 100) / total_sectors);
});
callbackPtr = nullptr;
if (vhd == NULL) {
_86box_geometry.cyl = 0;
_86box_geometry.heads = 0;
_86box_geometry.spt = 0;
} else {
mvhd_close(vhd);
}
return _86box_geometry;
}
static MVHDGeom create_drive_vhd_dynamic(const QString& fileName, uint16_t cyl, uint8_t heads, uint8_t spt, int blocksize) {
MVHDGeom _86box_geometry = { .cyl = cyl, .heads = heads, .spt = spt };
MVHDGeom vhd_geometry;
adjust_86box_geometry_for_vhd(&_86box_geometry, &vhd_geometry);
int vhd_error = 0;
QByteArray filenameBytes = fileName.toUtf8();
MVHDCreationOptions options;
options.block_size_in_sectors = blocksize;
options.path = filenameBytes.data();
options.size_in_bytes = 0;
options.geometry = vhd_geometry;
options.type = MVHD_TYPE_DYNAMIC;
MVHDMeta *vhd = mvhd_create_ex(options, &vhd_error);
if (vhd == NULL) {
_86box_geometry.cyl = 0;
_86box_geometry.heads = 0;
_86box_geometry.spt = 0;
} else {
mvhd_close(vhd);
}
return _86box_geometry;
}
static MVHDGeom create_drive_vhd_diff(const QString& fileName, const QString& parentFileName, int blocksize) {
int vhd_error = 0;
QByteArray filenameBytes = fileName.toUtf8();
QByteArray parentFilenameBytes = fileName.toUtf8();
MVHDCreationOptions options;
options.block_size_in_sectors = blocksize;
options.path = filenameBytes.data();
options.parent_path = parentFilenameBytes.data();
options.type = MVHD_TYPE_DIFF;
MVHDMeta *vhd = mvhd_create_ex(options, &vhd_error);
MVHDGeom vhd_geometry;
if (vhd == NULL) {
vhd_geometry.cyl = 0;
vhd_geometry.heads = 0;
vhd_geometry.spt = 0;
} else {
vhd_geometry = mvhd_get_geometry(vhd);
if (vhd_geometry.spt > 63) {
vhd_geometry.cyl = mvhd_calc_size_sectors(&vhd_geometry) / (16 * 63);
vhd_geometry.heads = 16;
vhd_geometry.spt = 63;
}
mvhd_close(vhd);
}
return vhd_geometry;
}
void HarddiskDialog::onCreateNewFile() {
for (auto& curObject : children())
{
if (qobject_cast<QWidget*>(curObject)) qobject_cast<QWidget*>(curObject)->setDisabled(true);
}
ui->progressBar->setEnabled(true);
setResult(QDialog::Rejected);
qint64 size = ui->lineEditSize->text().toUInt() << 20U;
if (size > 0x1FFFFFFE00ll) {
QMessageBox::critical(this, tr("Disk image too large"), tr("Disk images cannot be larger than 127 GB."));
return;
}
int img_format = ui->comboBoxFormat->currentIndex();
uint32_t zero = 0;
uint32_t base = 0x1000;
uint32_t sector_size = 512;
auto fileName = ui->fileField->fileName();
QString expectedSuffix;
switch (img_format) {
case 1:
expectedSuffix = "hdi";
break;
case 2:
expectedSuffix = "hdx";
break;
case 3:
case 4:
case 5:
expectedSuffix = "vhd";
break;
}
if (! expectedSuffix.isEmpty()) {
QFileInfo fileInfo(fileName);
if (fileInfo.suffix().compare(expectedSuffix, Qt::CaseInsensitive) != 0) {
fileName = QString("%1.%2").arg(fileName, expectedSuffix);
ui->fileField->setFileName(fileName);
}
}
QFile file(fileName);
if (! file.open(QIODevice::WriteOnly)) {
QMessageBox::critical(this, tr("Unable to write file"), tr("Make sure the file is being saved to a writable directory."));
return;
}
if (img_format == 1) { /* HDI file */
QDataStream stream(&file);
stream.setByteOrder(QDataStream::LittleEndian);
if (size >= 0x100000000ll) {
QMessageBox::critical(this, tr("Disk image too large"), tr("HDI disk images cannot be larger than 4 GB."));
return;
}
uint32_t s = static_cast<uint32_t>(size);
stream << zero; /* 00000000: Zero/unknown */
stream << zero; /* 00000004: Zero/unknown */
stream << base; /* 00000008: Offset at which data starts */
stream << s; /* 0000000C: Full size of the data (32-bit) */
stream << sector_size; /* 00000010: Sector size in bytes */
stream << sectors_; /* 00000014: Sectors per cylinder */
stream << heads_; /* 00000018: Heads per cylinder */
stream << cylinders_; /* 0000001C: Cylinders */
for (int i = 0; i < 0x3f8; i++) {
stream << zero;
}
} else if (img_format == 2) { /* HDX file */
QDataStream stream(&file);
stream.setByteOrder(QDataStream::LittleEndian);
quint64 signature = 0xD778A82044445459;
stream << signature; /* 00000000: Signature */
stream << size; /* 00000008: Full size of the data (64-bit) */
stream << sector_size; /* 00000010: Sector size in bytes */
stream << sectors_; /* 00000014: Sectors per cylinder */
stream << heads_; /* 00000018: Heads per cylinder */
stream << cylinders_; /* 0000001C: Cylinders */
stream << zero; /* 00000020: [Translation] Sectors per cylinder */
stream << zero; /* 00000004: [Translation] Heads per cylinder */
} else if (img_format >= 3) { /* VHD file */
file.close();
MVHDGeom _86box_geometry{};
int block_size = ui->comboBoxBlockSize->currentIndex() == 0 ? MVHD_BLOCK_LARGE : MVHD_BLOCK_SMALL;
switch (img_format) {
case 3:
{
connect(this, &HarddiskDialog::fileProgress, this, [this] (int value) { ui->progressBar->setValue(value); QApplication::processEvents(); } );
ui->progressBar->setVisible(true);
[&_86box_geometry, fileName, this] {
_86box_geometry = create_drive_vhd_fixed(fileName, this, cylinders_, heads_, sectors_);
}();
}
break;
case 4:
_86box_geometry = create_drive_vhd_dynamic(fileName, cylinders_, heads_, sectors_, block_size);
break;
case 5:
QString vhdParent = QFileDialog::getOpenFileName(
this,
tr("Select the parent VHD"),
QString(),
tr("VHD files") %
util::DlgFilter({ "vhd" }) %
tr("All files") %
util::DlgFilter({ "*" }, true));
if (vhdParent.isEmpty()) {
return;
}
_86box_geometry = create_drive_vhd_diff(fileName, vhdParent, block_size);
break;
}
if (_86box_geometry.cyl == 0 &&
_86box_geometry.heads == 0 &&
_86box_geometry.spt == 0)
{
QMessageBox::critical(this, tr("Unable to write file"), tr("Make sure the file is being saved to a writable directory."));
return;
}
else if (img_format != 5) {
QMessageBox::information(this, tr("Disk image created"), tr("Remember to partition and format the newly-created drive."));
}
ui->lineEditCylinders->setText(QString::number(_86box_geometry.cyl));
ui->lineEditHeads->setText(QString::number(_86box_geometry.heads));
ui->lineEditSectors->setText(QString::number(_86box_geometry.spt));
cylinders_ = _86box_geometry.cyl;
heads_ = _86box_geometry.heads;
sectors_ = _86box_geometry.spt;
setResult(QDialog::Accepted);
return;
}
// formats 0, 1 and 2
connect(this, &HarddiskDialog::fileProgress, this, [this] (int value) { ui->progressBar->setValue(value); QApplication::processEvents(); } );
ui->progressBar->setVisible(true);
[size, &file, this] {
QDataStream stream(&file);
stream.setByteOrder(QDataStream::LittleEndian);
QByteArray buf(1048576, 0);
uint64_t mibBlocks = size >> 20;
uint64_t restBlock = size & 0xfffff;
if (restBlock) {
stream.writeRawData(buf.data(), restBlock);
}
if (mibBlocks) {
for (uint64_t i = 0; i < mibBlocks; ++i) {
stream.writeRawData(buf.data(), buf.size());
emit fileProgress(static_cast<int>((i * 100) / mibBlocks));
}
}
emit fileProgress(100);
}();
QMessageBox::information(this, tr("Disk image created"), tr("Remember to partition and format the newly-created drive."));
setResult(QDialog::Accepted);
}
static void adjust_vhd_geometry_for_86box(MVHDGeom *vhd_geometry) {
if (vhd_geometry->spt <= 63)
return;
int desired_sectors = vhd_geometry->cyl * vhd_geometry->heads * vhd_geometry->spt;
if (desired_sectors > 267321600)
desired_sectors = 267321600;
int remainder = desired_sectors % 85680; /* 8560 is the LCM of 1008 (63*16) and 4080 (255*16) */
if (remainder > 0)
desired_sectors -= remainder;
vhd_geometry->cyl = desired_sectors / (16 * 63);
vhd_geometry->heads = 16;
vhd_geometry->spt = 63;
}
void HarddiskDialog::recalcSelection() {
int selection = 127;
for (int i = 0; i < 127; i++) {
if ((cylinders_ == hdd_table[i][0]) &&
(heads_ == hdd_table[i][1]) &&
(sectors_ == hdd_table[i][2]))
selection = i;
}
if ((selection == 127) && (heads_ == 16) && (sectors_ == 63)) {
selection = 128;
}
ui->comboBoxType->setCurrentIndex(selection);
}
void HarddiskDialog::onExistingFileSelected(const QString &fileName) {
// TODO : Over to non-existing file selected
/*
if (!(existing & 1)) {
f = _wfopen(wopenfilestring, L"rb");
if (f != NULL) {
fclose(f);
if (settings_msgbox_ex(MBX_QUESTION_YN, (wchar_t *) IDS_4111, (wchar_t *) IDS_4118, (wchar_t *) IDS_4120, (wchar_t *) IDS_4121, NULL) != 0) / * yes * /
return FALSE;
}
}
f = _wfopen(wopenfilestring, (existing & 1) ? L"rb" : L"wb");
if (f == NULL) {
hdd_add_file_open_error:
fclose(f);
settings_msgbox_header(MBX_ERROR, (existing & 1) ? (wchar_t *) IDS_4114 : (wchar_t *) IDS_4115, (existing & 1) ? (wchar_t *) IDS_4107 : (wchar_t *) IDS_4108);
return TRUE;
}
*/
uint64_t size = 0;
uint32_t sector_size = 0;
uint32_t sectors = 0;
uint32_t heads = 0;
uint32_t cylinders = 0;
int vhd_error = 0;
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
QFile file(fileName);
if (! file.open(QIODevice::ReadOnly)) {
QMessageBox::critical(this, tr("Unable to read file"), tr("Make sure the file exists and is readable."));
return;
}
QByteArray fileNameUtf8 = fileName.toUtf8();
QFileInfo fi(file);
if (image_is_hdi(fileNameUtf8.data()) || image_is_hdx(fileNameUtf8.data(), 1)) {
file.seek(0x10);
QDataStream stream(&file);
stream.setByteOrder(QDataStream::LittleEndian);
stream >> sector_size;
if (sector_size != 512) {
QMessageBox::critical(this, tr("Unsupported disk image"), tr("HDI or HDX images with a sector size other than 512 are not supported."));
return;
}
sectors = heads = cylinders = 0;
stream >> sectors;
stream >> heads;
stream >> cylinders;
} else if (image_is_vhd(fileNameUtf8.data(), 1)) {
MVHDMeta* vhd = mvhd_open(fileNameUtf8.data(), 0, &vhd_error);
if (vhd == nullptr) {
QMessageBox::critical(this, tr("Unable to read file"), tr("Make sure the file exists and is readable"));
return;
} else if (vhd_error == MVHD_ERR_TIMESTAMP) {
QMessageBox::StandardButton btn = QMessageBox::warning(this, tr("Parent and child disk timestamps do not match"), tr("This could mean that the parent image was modified after the differencing image was created.\n\nIt can also happen if the image files were moved or copied, or by a bug in the program that created this disk.\n\nDo you want to fix the timestamps?"), QMessageBox::Yes | QMessageBox::No);
if (btn == QMessageBox::Yes) {
int ts_res = mvhd_diff_update_par_timestamp(vhd, &vhd_error);
if (ts_res != 0) {
QMessageBox::critical(this, tr("Error"), tr("Could not fix VHD timestamp"));
mvhd_close(vhd);
return;
}
} else {
mvhd_close(vhd);
return;
}
}
MVHDGeom vhd_geom = mvhd_get_geometry(vhd);
adjust_vhd_geometry_for_86box(&vhd_geom);
cylinders = vhd_geom.cyl;
heads = vhd_geom.heads;
sectors = vhd_geom.spt;
size = static_cast<uint64_t>(cylinders * heads * sectors * 512);
mvhd_close(vhd);
} else {
size = file.size();
if (((size % 17) == 0) && (size <= 142606336)) {
sectors = 17;
if (size <= 26738688)
heads = 4;
else if (((size % 3072) == 0) && (size <= 53477376))
heads = 6;
else {
int i;
for (i = 5; i < 16; i++) {
if (((size % (i << 9)) == 0) && (size <= ((i * 17) << 19)))
break;
if (i == 5)
i++;
}
heads = i;
}
} else {
sectors = 63;
heads = 16;
}
cylinders = ((size >> 9) / heads) / sectors;
}
if ((sectors > max_sectors) || (heads > max_heads) || (cylinders > max_cylinders)) {
QMessageBox::critical(this, tr("Unable to read file"), tr("Make sure the file exists and is readable"));
return;
}
heads_ = heads;
sectors_ = sectors;
cylinders_ = cylinders;
ui->lineEditCylinders->setText(QString::number(cylinders));
ui->lineEditHeads->setText(QString::number(heads));
ui->lineEditSectors->setText(QString::number(sectors));
recalcSize();
recalcSelection();
ui->lineEditCylinders->setEnabled(true);
ui->lineEditHeads->setEnabled(true);
ui->lineEditSectors->setEnabled(true);
ui->lineEditSize->setEnabled(true);
ui->comboBoxType->setEnabled(true);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
}
void HarddiskDialog::recalcSize() {
uint64_t size = (static_cast<uint64_t>(cylinders_) * static_cast<uint64_t>(heads_) * static_cast<uint64_t>(sectors_)) << 9;
ui->lineEditSize->setText(QString::number(size >> 20));
}
bool HarddiskDialog::checkAndAdjustSectors() {
if (sectors_ > max_sectors) {
sectors_ = max_sectors;
ui->lineEditSectors->setText(QString::number(max_sectors));
recalcSize();
recalcSelection();
return false;
}
return true;
}
bool HarddiskDialog::checkAndAdjustHeads() {
if (heads_ > max_heads) {
heads_ = max_heads;
ui->lineEditHeads->setText(QString::number(max_heads));
recalcSize();
recalcSelection();
return false;
}
return true;
}
bool HarddiskDialog::checkAndAdjustCylinders() {
if (cylinders_ > max_cylinders) {
cylinders_ = max_cylinders;
ui->lineEditCylinders->setText(QString::number(max_cylinders));
recalcSize();
recalcSelection();
return false;
}
return true;
}
void HarddiskDialog::on_comboBoxBus_currentIndexChanged(int index) {
if (index < 0) {
return;
}
switch (ui->comboBoxBus->currentData().toInt()) {
case HDD_BUS_DISABLED:
default:
max_sectors = max_heads = max_cylinders = 0;
break;
case HDD_BUS_MFM:
max_sectors = 26; /* 17 for MFM, 26 for RLL. */
max_heads = 15;
max_cylinders = 2047;
break;
case HDD_BUS_XTA:
max_sectors = 63;
max_heads = 16;
max_cylinders = 1023;
break;
case HDD_BUS_ESDI:
max_sectors = 99; /* ESDI drives usually had 32 to 43 sectors per track. */
max_heads = 16;
max_cylinders = 266305;
break;
case HDD_BUS_IDE:
max_sectors = 63;
max_heads = 255;
max_cylinders = 266305;
break;
case HDD_BUS_ATAPI:
case HDD_BUS_SCSI:
max_sectors = 99;
max_heads = 255;
max_cylinders = 266305;
break;
}
checkAndAdjustCylinders();
checkAndAdjustHeads();
checkAndAdjustSectors();
if (ui->lineEditCylinders->validator() != nullptr) {
delete ui->lineEditCylinders->validator();
}
if (ui->lineEditHeads->validator() != nullptr) {
delete ui->lineEditHeads->validator();
}
if (ui->lineEditSectors->validator() != nullptr) {
delete ui->lineEditSectors->validator();
}
ui->lineEditCylinders->setValidator(new QIntValidator(1, max_cylinders, this));
ui->lineEditHeads->setValidator(new QIntValidator(1, max_heads, this));
ui->lineEditSectors->setValidator(new QIntValidator(1, max_sectors, this));
Harddrives::populateBusChannels(ui->comboBoxChannel->model(), ui->comboBoxBus->currentData().toInt());
}
void HarddiskDialog::on_lineEditSize_textEdited(const QString &text) {
uint32_t size = text.toUInt();
/* This is needed to ensure VHD standard compliance. */
hdd_image_calc_chs(&cylinders_, &heads_, &sectors_, size);
ui->lineEditCylinders->setText(QString::number(cylinders_));
ui->lineEditHeads->setText(QString::number(heads_));
ui->lineEditSectors->setText(QString::number(sectors_));
recalcSelection();
checkAndAdjustCylinders();
checkAndAdjustHeads();
checkAndAdjustSectors();
}
void HarddiskDialog::on_lineEditCylinders_textEdited(const QString &text) {
cylinders_ = text.toUInt();
if (checkAndAdjustCylinders()) {
recalcSize();
recalcSelection();
}
}
void HarddiskDialog::on_lineEditHeads_textEdited(const QString &text) {
heads_ = text.toUInt();
if (checkAndAdjustHeads()) {
recalcSize();
recalcSelection();
}
}
void HarddiskDialog::on_lineEditSectors_textEdited(const QString &text) {
sectors_ = text.toUInt();
if (checkAndAdjustSectors()) {
recalcSize();
recalcSelection();
}
}
void HarddiskDialog::on_comboBoxType_currentIndexChanged(int index) {
if (index < 0) {
return;
}
if ((index != 127) && (index != 128)) {
cylinders_ = hdd_table[index][0];
heads_ = hdd_table[index][1];
sectors_ = hdd_table[index][2];
ui->lineEditCylinders->setText(QString::number(cylinders_));
ui->lineEditHeads->setText(QString::number(heads_));
ui->lineEditSectors->setText(QString::number(sectors_));
recalcSize();
} else if (index == 128) {
heads_ = 16;
sectors_ = 63;
ui->lineEditHeads->setText(QString::number(heads_));
ui->lineEditSectors->setText(QString::number(sectors_));
recalcSize();
}
checkAndAdjustCylinders();
checkAndAdjustHeads();
checkAndAdjustSectors();
}
void HarddiskDialog::accept()
{
if (ui->fileField->createFile()) onCreateNewFile();
else setResult(QDialog::Accepted);
QDialog::done(result());
}

View File

@@ -0,0 +1,59 @@
#ifndef QT_HARDDISKDIALOG_HPP
#define QT_HARDDISKDIALOG_HPP
#include <QDialog>
namespace Ui {
class HarddiskDialog;
}
class HarddiskDialog : public QDialog
{
Q_OBJECT
public:
explicit HarddiskDialog(bool existing, QWidget *parent = nullptr);
~HarddiskDialog();
uint8_t bus() const;
uint8_t channel() const;
QString fileName() const;
uint32_t cylinders() const { return cylinders_; }
uint32_t heads() const { return heads_; }
uint32_t sectors() const { return sectors_; }
signals:
void fileProgress(int i);
public slots:
void accept() override;
private slots:
void on_comboBoxType_currentIndexChanged(int index);
void on_lineEditSectors_textEdited(const QString &arg1);
void on_lineEditHeads_textEdited(const QString &arg1);
void on_lineEditCylinders_textEdited(const QString &arg1);
void on_lineEditSize_textEdited(const QString &arg1);
void on_comboBoxBus_currentIndexChanged(int index);
void on_comboBoxFormat_currentIndexChanged(int index);
void onCreateNewFile();
void onExistingFileSelected(const QString& fileName);
private:
Ui::HarddiskDialog *ui;
uint32_t cylinders_;
uint32_t heads_;
uint32_t sectors_;
uint32_t max_sectors = 0;
uint32_t max_heads = 0;
uint32_t max_cylinders = 0;
bool checkAndAdjustCylinders();
bool checkAndAdjustHeads();
bool checkAndAdjustSectors();
void recalcSize();
void recalcSelection();
};
#endif // QT_HARDDISKDIALOG_HPP

285
src/qt/qt_harddiskdialog.ui Normal file
View File

@@ -0,0 +1,285 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>HarddiskDialog</class>
<widget class="QDialog" name="HarddiskDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>421</width>
<height>269</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>421</width>
<height>269</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>421</width>
<height>269</height>
</size>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1" colspan="5">
<widget class="FileField" name="fileField" native="true"/>
</item>
<item row="5" column="4">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Channel:</string>
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Sectors:</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="lineEditCylinders">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>64</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="lineEditSize">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>64</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Size (MB):</string>
</property>
</widget>
</item>
<item row="5" column="5">
<widget class="QComboBox" name="comboBoxChannel"/>
</item>
<item row="11" column="0" colspan="6">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="9" column="0" colspan="6">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Cylinders:</string>
</property>
</widget>
</item>
<item row="3" column="5">
<widget class="QLineEdit" name="lineEditSectors">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>64</width>
<height>16777215</height>
</size>
</property>
<property name="maxLength">
<number>32767</number>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="labelFormat">
<property name="text">
<string>Image Format:</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Heads:</string>
</property>
</widget>
</item>
<item row="4" column="3" colspan="3">
<widget class="QComboBox" name="comboBoxType"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Bus:</string>
</property>
</widget>
</item>
<item row="6" column="1" colspan="3">
<widget class="QComboBox" name="comboBoxFormat"/>
</item>
<item row="7" column="1" colspan="3">
<widget class="QComboBox" name="comboBoxBlockSize"/>
</item>
<item row="3" column="3">
<widget class="QLineEdit" name="lineEditHeads">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>64</width>
<height>16777215</height>
</size>
</property>
<property name="maxLength">
<number>32767</number>
</property>
</widget>
</item>
<item row="5" column="1" colspan="3">
<widget class="QComboBox" name="comboBoxBus"/>
</item>
<item row="7" column="0">
<widget class="QLabel" name="labelBlockSize">
<property name="text">
<string>Block Size:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>File name:</string>
</property>
</widget>
</item>
<item row="8" column="0" colspan="6">
<widget class="QProgressBar" name="progressBar">
<property name="visible">
<bool>false</bool>
</property>
<property name="value">
<number>0</number>
</property>
<property name="textVisible">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FileField</class>
<extends>QWidget</extends>
<header>qt_filefield.hpp</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>lineEditCylinders</tabstop>
<tabstop>lineEditHeads</tabstop>
<tabstop>lineEditSectors</tabstop>
<tabstop>lineEditSize</tabstop>
<tabstop>comboBoxType</tabstop>
<tabstop>comboBoxBus</tabstop>
<tabstop>comboBoxChannel</tabstop>
<tabstop>comboBoxFormat</tabstop>
<tabstop>comboBoxBlockSize</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>HarddiskDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>HarddiskDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,101 @@
#include "qt_harddrive_common.hpp"
#include <cstdint>
extern "C" {
#include <86box/hdd.h>
}
#include <QAbstractItemModel>
void Harddrives::populateBuses(QAbstractItemModel *model) {
model->removeRows(0, model->rowCount());
model->insertRows(0, 6);
model->setData(model->index(0, 0), "MFM/RLL");
model->setData(model->index(1, 0), "XT IDE");
model->setData(model->index(2, 0), "ESDI");
model->setData(model->index(3, 0), "IDE");
model->setData(model->index(4, 0), "ATAPI");
model->setData(model->index(5, 0), "SCSI");
model->setData(model->index(0, 0), HDD_BUS_MFM, Qt::UserRole);
model->setData(model->index(1, 0), HDD_BUS_XTA, Qt::UserRole);
model->setData(model->index(2, 0), HDD_BUS_ESDI, Qt::UserRole);
model->setData(model->index(3, 0), HDD_BUS_IDE, Qt::UserRole);
model->setData(model->index(4, 0), HDD_BUS_ATAPI, Qt::UserRole);
model->setData(model->index(5, 0), HDD_BUS_SCSI, Qt::UserRole);
}
void Harddrives::populateRemovableBuses(QAbstractItemModel *model) {
model->removeRows(0, model->rowCount());
model->insertRows(0, 3);
model->setData(model->index(0, 0), QObject::tr("Disabled"));
model->setData(model->index(1, 0), QObject::tr("ATAPI"));
model->setData(model->index(2, 0), QObject::tr("SCSI"));
model->setData(model->index(0, 0), HDD_BUS_DISABLED, Qt::UserRole);
model->setData(model->index(1, 0), HDD_BUS_ATAPI, Qt::UserRole);
model->setData(model->index(2, 0), HDD_BUS_SCSI, Qt::UserRole);
}
void Harddrives::populateBusChannels(QAbstractItemModel *model, int bus) {
model->removeRows(0, model->rowCount());
int busRows = 0;
int shifter = 1;
int orer = 1;
int subChannelWidth = 1;
switch (bus) {
case HDD_BUS_MFM:
case HDD_BUS_XTA:
case HDD_BUS_ESDI:
busRows = 2;
break;
case HDD_BUS_IDE:
case HDD_BUS_ATAPI:
busRows = 8;
break;
case HDD_BUS_SCSI:
shifter = 4;
orer = 15;
busRows = 64;
subChannelWidth = 2;
break;
}
model->insertRows(0, busRows);
for (int i = 0; i < busRows; ++i) {
auto idx = model->index(i, 0);
model->setData(idx, QString("%1:%2").arg(i >> shifter).arg(i & orer, subChannelWidth, 10, QChar('0')));
model->setData(idx, ((i >> shifter) << shifter) | (i & orer), Qt::UserRole);
}
}
QString Harddrives::BusChannelName(uint8_t bus, uint8_t channel) {
QString busName;
switch(bus) {
case HDD_BUS_DISABLED:
busName = QString(QObject::tr("Disabled"));
break;
case HDD_BUS_MFM:
busName = QString("MFM/RLL (%1:%2)").arg(channel >> 1).arg(channel & 1);
break;
case HDD_BUS_XTA:
busName = QString("XT IDE (%1:%2)").arg(channel >> 1).arg(channel & 1);
break;
case HDD_BUS_ESDI:
busName = QString("ESDI (%1:%2)").arg(channel >> 1).arg(channel & 1);
break;
case HDD_BUS_IDE:
busName = QString("IDE (%1:%2)").arg(channel >> 1).arg(channel & 1);
break;
case HDD_BUS_ATAPI:
busName = QString("ATAPI (%1:%2)").arg(channel >> 1).arg(channel & 1);
break;
case HDD_BUS_SCSI:
busName = QString("SCSI (%1:%2)").arg(channel >> 4).arg(channel & 15, 2, 10, QChar('0'));
break;
}
return busName;
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include <cstdint>
class QString;
class QAbstractItemModel;
class SettingsBusTracking;
namespace Harddrives {
void populateBuses(QAbstractItemModel* model);
void populateRemovableBuses(QAbstractItemModel* model);
void populateBusChannels(QAbstractItemModel* model, int bus);
QString BusChannelName(uint8_t bus, uint8_t channel);
inline SettingsBusTracking* busTrackClass = nullptr;
};

View File

@@ -0,0 +1,202 @@
#include "qt_hardwarerenderer.hpp"
#include <QApplication>
#include <QVector2D>
#include <atomic>
#include <vector>
extern "C" {
#include <86box/86box.h>
#include <86box/plat.h>
#include <86box/video.h>
}
void HardwareRenderer::resizeGL(int w, int h)
{
glViewport(0, 0, w * devicePixelRatio(), h * devicePixelRatio());
}
#define PROGRAM_VERTEX_ATTRIBUTE 0
#define PROGRAM_TEXCOORD_ATTRIBUTE 1
void HardwareRenderer::initializeGL()
{
m_context->makeCurrent(this);
initializeOpenGLFunctions();
m_texture = new QOpenGLTexture(QImage(2048,2048, QImage::Format::Format_RGB32));
m_blt = new QOpenGLTextureBlitter;
m_blt->setRedBlueSwizzle(true);
m_blt->create();
QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
const char *vsrc =
"attribute highp vec4 VertexCoord;\n"
"attribute mediump vec4 TexCoord;\n"
"varying mediump vec4 texc;\n"
"uniform mediump mat4 MVPMatrix;\n"
"void main(void)\n"
"{\n"
" gl_Position = MVPMatrix * VertexCoord;\n"
" texc = TexCoord;\n"
"}\n";
QString vsrccore =
"in highp vec4 VertexCoord;\n"
"in mediump vec4 TexCoord;\n"
"out mediump vec4 texc;\n"
"uniform mediump mat4 MVPMatrix;\n"
"void main(void)\n"
"{\n"
" gl_Position = MVPMatrix * VertexCoord;\n"
" texc = TexCoord;\n"
"}\n";
if (m_context->isOpenGLES() && m_context->format().version() >= qMakePair(3, 0))
{
vsrccore.prepend("#version 300 es\n");
vshader->compileSourceCode(vsrccore);
}
else if (m_context->format().version() >= qMakePair(3, 0) && m_context->format().profile() == QSurfaceFormat::CoreProfile)
{
vsrccore.prepend("#version 130\n");
vshader->compileSourceCode(vsrccore);
}
else vshader->compileSourceCode(vsrc);
QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
const char *fsrc =
"uniform sampler2D texture;\n"
"varying mediump vec4 texc;\n"
"void main(void)\n"
"{\n"
" gl_FragColor = texture2D(texture, texc.st).bgra;\n"
"}\n";
QString fsrccore =
"uniform sampler2D texture;\n"
"in mediump vec4 texc;\n"
"out highp vec4 FragColor;\n"
"void main(void)\n"
"{\n"
" FragColor = texture2D(texture, texc.st).bgra;\n"
"}\n";
if (m_context->isOpenGLES() && m_context->format().version() >= qMakePair(3, 0))
{
fsrccore.prepend("#version 300 es\n");
fshader->compileSourceCode(fsrccore);
}
else if (m_context->format().version() >= qMakePair(3, 0) && m_context->format().profile() == QSurfaceFormat::CoreProfile)
{
fsrccore.prepend("#version 130\n");
fshader->compileSourceCode(fsrccore);
}
else fshader->compileSourceCode(fsrc);
m_prog = new QOpenGLShaderProgram;
m_prog->addShader(vshader);
m_prog->addShader(fshader);
m_prog->bindAttributeLocation("VertexCoord", PROGRAM_VERTEX_ATTRIBUTE);
m_prog->bindAttributeLocation("TexCoord", PROGRAM_TEXCOORD_ATTRIBUTE);
m_prog->link();
m_prog->bind();
m_prog->setUniformValue("texture", 0);
if (m_context->format().version() >= qMakePair(3, 0) && m_vao.create()) {
m_vao.bind();
}
m_vbo[PROGRAM_VERTEX_ATTRIBUTE].create();
m_vbo[PROGRAM_VERTEX_ATTRIBUTE].bind();
m_vbo[PROGRAM_VERTEX_ATTRIBUTE].allocate(sizeof(QVector2D) * 4);
m_vbo[PROGRAM_TEXCOORD_ATTRIBUTE].create();
m_vbo[PROGRAM_TEXCOORD_ATTRIBUTE].bind();
m_vbo[PROGRAM_TEXCOORD_ATTRIBUTE].allocate(sizeof(QVector2D) * 4);
pclog("OpenGL vendor: %s\n", glGetString(GL_VENDOR));
pclog("OpenGL renderer: %s\n", glGetString(GL_RENDERER));
pclog("OpenGL version: %s\n", glGetString(GL_VERSION));
pclog("OpenGL shader language version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
glClearColor(0, 0, 0, 1);
}
void HardwareRenderer::paintGL() {
m_context->makeCurrent(this);
glClear(GL_COLOR_BUFFER_BIT);
QVector<QVector2D> verts, texcoords;
QMatrix4x4 mat;
mat.setToIdentity();
mat.ortho(QRect(0, 0, width(), height()));
verts.push_back(QVector2D((float)destination.x(), (float)destination.y()));
verts.push_back(QVector2D((float)destination.x(), (float)destination.y() + destination.height()));
verts.push_back(QVector2D((float)destination.x() + destination.width(), (float)destination.y() + destination.height()));
verts.push_back(QVector2D((float)destination.x() + destination.width(), (float)destination.y()));
texcoords.push_back(QVector2D((float)source.x() / 2048.f, (float)(source.y()) / 2048.f));
texcoords.push_back(QVector2D((float)source.x() / 2048.f, (float)(source.y() + source.height()) / 2048.f));
texcoords.push_back(QVector2D((float)(source.x() + source.width()) / 2048.f, (float)(source.y() + source.height()) / 2048.f));
texcoords.push_back(QVector2D((float)(source.x() + source.width()) / 2048.f, (float)(source.y()) / 2048.f));
m_vbo[PROGRAM_VERTEX_ATTRIBUTE].bind(); m_vbo[PROGRAM_VERTEX_ATTRIBUTE].write(0, verts.data(), sizeof(QVector2D) * 4); m_vbo[PROGRAM_VERTEX_ATTRIBUTE].release();
m_vbo[PROGRAM_TEXCOORD_ATTRIBUTE].bind(); m_vbo[PROGRAM_TEXCOORD_ATTRIBUTE].write(0, texcoords.data(), sizeof(QVector2D) * 4); m_vbo[PROGRAM_TEXCOORD_ATTRIBUTE].release();
m_prog->setUniformValue("MVPMatrix", mat);
m_prog->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
m_prog->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
m_vbo[PROGRAM_VERTEX_ATTRIBUTE].bind(); m_prog->setAttributeBuffer(PROGRAM_VERTEX_ATTRIBUTE, GL_FLOAT, 0, 2, 0); m_vbo[PROGRAM_VERTEX_ATTRIBUTE].release();
m_vbo[PROGRAM_TEXCOORD_ATTRIBUTE].bind(); m_prog->setAttributeBuffer(PROGRAM_TEXCOORD_ATTRIBUTE, GL_FLOAT, 0, 2, 0); m_vbo[PROGRAM_TEXCOORD_ATTRIBUTE].release();
m_texture->bind();
m_texture->setMinMagFilters(video_filter_method ? QOpenGLTexture::Linear : QOpenGLTexture::Nearest, video_filter_method ? QOpenGLTexture::Linear : QOpenGLTexture::Nearest);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
void HardwareRenderer::setRenderType(RenderType type) {
QSurfaceFormat format;
switch (type) {
case RenderType::OpenGL3:
format.setVersion(3, 0);
format.setProfile(QSurfaceFormat::CoreProfile);
case RenderType::OpenGL:
format.setRenderableType(QSurfaceFormat::OpenGL);
break;
case RenderType::OpenGLES:
format.setRenderableType(QSurfaceFormat::OpenGLES);
break;
}
format.setSwapInterval(0);
setFormat(format);
}
void HardwareRenderer::onBlit(int buf_idx, int x, int y, int w, int h) {
auto tval = this;
void* nuldata = 0;
if (memcmp(&tval, &nuldata, sizeof(void*)) == 0) return;
if (!m_texture || !m_texture->isCreated())
{
buf_usage[buf_idx].clear();
source.setRect(x, y, w, h);
return;
}
m_context->makeCurrent(this);
m_texture->setData(QOpenGLTexture::PixelFormat::RGBA, QOpenGLTexture::PixelType::UInt8, (const void*)imagebufs[buf_idx].get());
buf_usage[buf_idx].clear();
source.setRect(x, y, w, h);
update();
}
void HardwareRenderer::resizeEvent(QResizeEvent *event) {
onResize(width(), height());
QOpenGLWindow::resizeEvent(event);
}
bool HardwareRenderer::event(QEvent *event)
{
bool res = false;
if (!eventDelegate(event, res)) return QOpenGLWindow::event(event);
return res;
}
std::vector<std::tuple<uint8_t*, std::atomic_flag*>> HardwareRenderer::getBuffers()
{
std::vector<std::tuple<uint8_t*, std::atomic_flag*>> buffers;
buffers.push_back(std::make_tuple(imagebufs[0].get(), &buf_usage[0]));
buffers.push_back(std::make_tuple(imagebufs[1].get(), &buf_usage[1]));
return buffers;
}

View File

@@ -0,0 +1,92 @@
#pragma once
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLWindow>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLTexture>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QOpenGLTextureBlitter>
#include <QPainter>
#include <QEvent>
#include <QKeyEvent>
#include <QWidget>
#include <atomic>
#include <mutex>
#include <array>
#include <vector>
#include <memory>
#include <QApplication>
#include "qt_renderercommon.hpp"
#ifdef WAYLAND
#include "wl_mouse.hpp"
#endif
class HardwareRenderer : public QOpenGLWindow, protected QOpenGLFunctions, public RendererCommon
{
Q_OBJECT
private:
bool wayland = false;
QOpenGLContext* m_context;
QOpenGLTexture* m_texture{nullptr};
QOpenGLShaderProgram* m_prog{nullptr};
QOpenGLTextureBlitter* m_blt{nullptr};
QOpenGLBuffer m_vbo[2];
QOpenGLVertexArrayObject m_vao;
public:
enum class RenderType {
OpenGL,
OpenGLES,
OpenGL3,
};
void resizeGL(int w, int h) override;
void initializeGL() override;
void paintGL() override;
std::vector<std::tuple<uint8_t*, std::atomic_flag*>> getBuffers() override;
HardwareRenderer(QWidget* parent = nullptr, RenderType rtype = RenderType::OpenGL)
: QOpenGLWindow(QOpenGLWindow::NoPartialUpdate, parent->windowHandle()), QOpenGLFunctions()
{
imagebufs[0] = std::unique_ptr<uint8_t>(new uint8_t[2048 * 2048 * 4]);
imagebufs[1] = std::unique_ptr<uint8_t>(new uint8_t[2048 * 2048 * 4]);
buf_usage = std::vector<std::atomic_flag>(2);
buf_usage[0].clear();
buf_usage[1].clear();
setMinimumSize(QSize(16, 16));
setFlags(Qt::FramelessWindowHint);
parentWidget = parent;
setRenderType(rtype);
m_context = new QOpenGLContext();
m_context->setFormat(format());
m_context->create();
}
~HardwareRenderer()
{
m_context->makeCurrent(this);
if (m_blt) m_blt->destroy();
m_prog->release();
delete m_prog;
m_prog = nullptr;
m_context->doneCurrent();
delete m_context;
}
void setRenderType(RenderType type);
public slots:
void onBlit(int buf_idx, int x, int y, int w, int h);
protected:
std::array<std::unique_ptr<uint8_t>, 2> imagebufs;
void resizeEvent(QResizeEvent *event) override;
bool event(QEvent* event) override;
};

View File

@@ -0,0 +1,186 @@
#include "qt_joystickconfiguration.hpp"
#include "ui_qt_joystickconfiguration.h"
extern "C" {
#include <86box/device.h>
#include <86box/gameport.h>
}
#include <QLabel>
#include <QComboBox>
#include <QDialogButtonBox>
#include "qt_models_common.hpp"
JoystickConfiguration::JoystickConfiguration(int type, int joystick_nr, QWidget *parent) :
QDialog(parent),
ui(new Ui::JoystickConfiguration),
type(type),
joystick_nr(joystick_nr)
{
ui->setupUi(this);
auto model = ui->comboBoxDevice->model();
Models::AddEntry(model, "None", 0);
for (int c = 0; c < joysticks_present; c++) {
Models::AddEntry(model, plat_joystick_state[c].name, c+1);
}
ui->comboBoxDevice->setCurrentIndex(joystick_state[joystick_nr].plat_joystick_nr);
setFixedSize(minimumSizeHint());
}
JoystickConfiguration::~JoystickConfiguration()
{
delete ui;
}
int JoystickConfiguration::selectedDevice() {
return ui->comboBoxDevice->currentIndex();
}
int JoystickConfiguration::selectedAxis(int axis) {
auto* cbox = findChild<QComboBox*>(QString("cboxAxis%1").arg(QString::number(axis)));
if (cbox == nullptr) {
return 0;
}
return cbox->currentIndex();
}
int JoystickConfiguration::selectedButton(int button) {
auto* cbox = findChild<QComboBox*>(QString("cboxButton%1").arg(QString::number(button)));
if (cbox == nullptr) {
return 0;
}
return cbox->currentIndex();
}
int JoystickConfiguration::selectedPov(int pov) {
auto* cbox = findChild<QComboBox*>(QString("cboxPov%1").arg(QString::number(pov)));
if (cbox == nullptr) {
return 0;
}
return cbox->currentIndex();
}
void JoystickConfiguration::on_comboBoxDevice_currentIndexChanged(int index) {
for (auto w : widgets) {
ui->ct->removeWidget(w);
}
if (index == 0) {
return;
}
int joystick = index - 1;
int row = 0;
for (int c = 0; c < joystick_get_axis_count(type); c++) {
/*Combo box*/
auto label = new QLabel(joystick_get_axis_name(type, c), this);
auto cbox = new QComboBox(this);
cbox->setObjectName(QString("cboxAxis%1").arg(QString::number(c)));
auto model = cbox->model();
for (int d = 0; d < plat_joystick_state[joystick].nr_axes; d++) {
Models::AddEntry(model, plat_joystick_state[joystick].axis[d].name, 0);
}
for (int d = 0; d < plat_joystick_state[joystick].nr_povs; d++) {
Models::AddEntry(model, QString("%1 (X axis)").arg(plat_joystick_state[joystick].pov[d].name), 0);
Models::AddEntry(model, QString("%1 (Y axis)").arg(plat_joystick_state[joystick].pov[d].name), 0);
}
for (int d = 0; d < plat_joystick_state[joystick].nr_sliders; d++) {
Models::AddEntry(model, plat_joystick_state[joystick].slider[d].name, 0);
}
int nr_axes = plat_joystick_state[joystick].nr_axes;
int nr_povs = plat_joystick_state[joystick].nr_povs;
int mapping = joystick_state[joystick_nr].axis_mapping[c];
if (mapping & POV_X)
cbox->setCurrentIndex(nr_axes + (mapping & 3) * 2);
else if (mapping & POV_Y)
cbox->setCurrentIndex(nr_axes + (mapping & 3) * 2 + 1);
else if (mapping & SLIDER)
cbox->setCurrentIndex(nr_axes + nr_povs * 2 + (mapping & 3));
else
cbox->setCurrentIndex(mapping);
ui->ct->addWidget(label, row, 0);
ui->ct->addWidget(cbox, row, 1);
widgets.append(label);
widgets.append(cbox);
++row;
}
for (int c = 0; c < joystick_get_button_count(type); c++) {
auto label = new QLabel(joystick_get_button_name(type, c), this);
auto cbox = new QComboBox(this);
cbox->setObjectName(QString("cboxButton%1").arg(QString::number(c)));
auto model = cbox->model();
for (int d = 0; d < plat_joystick_state[joystick].nr_buttons; d++) {
Models::AddEntry(model, plat_joystick_state[joystick].button[d].name, 0);
}
cbox->setCurrentIndex(joystick_state[joystick_nr].button_mapping[c]);
ui->ct->addWidget(label, row, 0);
ui->ct->addWidget(cbox, row, 1);
widgets.append(label);
widgets.append(cbox);
++row;
}
for (int c = 0; c < joystick_get_pov_count(type) * 2; c++) {
QLabel* label;
if (c & 1) {
label = new QLabel(QString("%1 (Y axis)").arg(joystick_get_pov_name(type, c/2)), this);
} else {
label = new QLabel(QString("%1 (X axis)").arg(joystick_get_pov_name(type, c/2)), this);
}
auto cbox = new QComboBox(this);
cbox->setObjectName(QString("cboxPov%1").arg(QString::number(c)));
auto model = cbox->model();
for (int d = 0; d < plat_joystick_state[joystick].nr_povs; d++) {
Models::AddEntry(model, QString("%1 (X axis)").arg(plat_joystick_state[joystick].pov[d].name), 0);
Models::AddEntry(model, QString("%1 (Y axis)").arg(plat_joystick_state[joystick].pov[d].name), 0);
}
for (int d = 0; d < plat_joystick_state[joystick].nr_axes; d++) {
Models::AddEntry(model, plat_joystick_state[joystick].axis[d].name, 0);
}
int mapping = joystick_state[joystick_nr].pov_mapping[c][0];
int nr_povs = plat_joystick_state[joystick].nr_povs;
if (mapping & POV_X)
cbox->setCurrentIndex((mapping & 3) * 2);
else if (mapping & POV_Y)
cbox->setCurrentIndex((mapping & 3)*2 + 1);
else
cbox->setCurrentIndex(mapping + nr_povs * 2);
mapping = joystick_state[joystick_nr].pov_mapping[c][1];
if (mapping & POV_X)
cbox->setCurrentIndex((mapping & 3)*2);
else if (mapping & POV_Y)
cbox->setCurrentIndex((mapping & 3)*2 + 1);
else
cbox->setCurrentIndex(mapping + nr_povs*2);
ui->ct->addWidget(label, row, 0);
ui->ct->addWidget(cbox, row, 1);
widgets.append(label);
widgets.append(cbox);
++row;
}
setFixedSize(minimumSizeHint());
}

View File

@@ -0,0 +1,32 @@
#ifndef QT_JOYSTICKCONFIGURATION_HPP
#define QT_JOYSTICKCONFIGURATION_HPP
#include <QDialog>
namespace Ui {
class JoystickConfiguration;
}
class JoystickConfiguration : public QDialog
{
Q_OBJECT
public:
explicit JoystickConfiguration(int type, int joystick_nr, QWidget *parent = nullptr);
~JoystickConfiguration();
int selectedDevice();
int selectedAxis(int axis);
int selectedButton(int button);
int selectedPov(int pov);
private slots:
void on_comboBoxDevice_currentIndexChanged(int index);
private:
Ui::JoystickConfiguration *ui;
QList<QWidget*> widgets;
int type;
int joystick_nr;
};
#endif // QT_JOYSTICKCONFIGURATION_HPP

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>JoystickConfiguration</class>
<widget class="QDialog" name="JoystickConfiguration">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="2">
<layout class="QGridLayout" name="ct"/>
</item>
<item row="3" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboBoxDevice"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Device</string>
</property>
</widget>
</item>
<item row="2" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>JoystickConfiguration</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>223</x>
<y>278</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>JoystickConfiguration</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>223</x>
<y>278</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
</connections>
</ui>

571
src/qt/qt_machinestatus.cpp Normal file
View File

@@ -0,0 +1,571 @@
#include "qt_machinestatus.hpp"
extern "C" {
#define EMU_CPU_H // superhack - don't want timer.h to include cpu.h here, and some combo is preventing a compile
extern uint64_t tsc;
#include <86box/hdd.h>
#include <86box/timer.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/cartridge.h>
#include <86box/cassette.h>
#include <86box/cdrom.h>
#include <86box/fdd.h>
#include <86box/hdc.h>
#include <86box/scsi.h>
#include <86box/scsi_device.h>
#include <86box/zip.h>
#include <86box/mo.h>
#include <86box/plat.h>
#include <86box/machine.h>
#include <86box/network.h>
#include <86box/ui.h>
};
#include <QIcon>
#include <QPicture>
#include <QLabel>
#include <QTimer>
#include <QStatusBar>
#include <QMenu>
#include "qt_mediamenu.hpp"
#include "qt_mainwindow.hpp"
#include "qt_soundgain.hpp"
#include "qt_progsettings.hpp"
#include <array>
extern MainWindow* main_window;
namespace {
struct PixmapSetActive {
QPixmap normal;
QPixmap active;
void load(const QString& basePath);
};
struct PixmapSetEmpty {
QPixmap normal;
QPixmap empty;
void load(const QString& basePath);
};
struct PixmapSetEmptyActive {
QPixmap normal;
QPixmap active;
QPixmap empty;
QPixmap empty_active;
void load(QString basePath);
};
struct Pixmaps {
PixmapSetEmpty cartridge;
PixmapSetEmptyActive cassette;
PixmapSetEmptyActive floppy_disabled;
PixmapSetEmptyActive floppy_525;
PixmapSetEmptyActive floppy_35;
PixmapSetEmptyActive cdrom;
PixmapSetEmptyActive zip;
PixmapSetEmptyActive mo;
PixmapSetActive hd;
PixmapSetActive net;
QPixmap sound;
};
struct StateActive {
std::unique_ptr<QLabel> label;
QTimer timer;
PixmapSetActive* pixmaps = nullptr;
bool active = false;
void setActive(bool b) {
active = b;
if (! label) {
return;
}
label->setPixmap(active ? pixmaps->active : pixmaps->normal);
timer.start(75);
}
};
struct StateEmpty {
std::unique_ptr<QLabel> label;
PixmapSetEmpty* pixmaps = nullptr;
bool empty = false;
void setEmpty(bool e) {
empty = e;
if (! label) {
return;
}
label->setPixmap(empty ? pixmaps->empty : pixmaps->normal);
}
};
struct StateEmptyActive {
std::unique_ptr<QLabel> label;
QTimer timer;
PixmapSetEmptyActive* pixmaps = nullptr;
bool empty = false;
bool active = false;
void setActive(bool b) {
active = b;
refresh();
timer.start(75);
}
void setEmpty(bool b) {
empty = b;
refresh();
}
void refresh() {
if (! label) {
return;
}
if (empty) {
label->setPixmap(active ? pixmaps->empty_active : pixmaps->empty);
} else {
label->setPixmap(active ? pixmaps->active : pixmaps->normal);
}
}
};
static const QSize pixmap_size(16, 16);
static const QString pixmap_empty = QStringLiteral("_empty");
static const QString pixmap_active = QStringLiteral("_active");
static const QString pixmap_empty_active = QStringLiteral("_empty_active");
void PixmapSetEmpty::load(const QString &basePath) {
normal = ProgSettings::loadIcon(basePath.arg(QStringLiteral(""))).pixmap(pixmap_size);
empty = ProgSettings::loadIcon(basePath.arg(pixmap_empty)).pixmap(pixmap_size);
}
void PixmapSetActive::load(const QString &basePath) {
normal = ProgSettings::loadIcon(basePath.arg(QStringLiteral(""))).pixmap(pixmap_size);
active = ProgSettings::loadIcon(basePath.arg(pixmap_active)).pixmap(pixmap_size);
}
void PixmapSetEmptyActive::load(QString basePath) {
normal = ProgSettings::loadIcon(basePath.arg(QStringLiteral(""))).pixmap(pixmap_size);
active = ProgSettings::loadIcon(basePath.arg(pixmap_active)).pixmap(pixmap_size);
empty = ProgSettings::loadIcon(basePath.arg(pixmap_empty)).pixmap(pixmap_size);
empty_active = ProgSettings::loadIcon(basePath.arg(pixmap_empty_active)).pixmap(pixmap_size);
}
}
struct MachineStatus::States {
Pixmaps pixmaps;
States(QObject* parent) {
pixmaps.cartridge.load("/cartridge%1.ico");
pixmaps.cassette.load("/cassette%1.ico");
pixmaps.floppy_disabled.normal = ProgSettings::loadIcon(QStringLiteral("/floppy_disabled.ico")).pixmap(pixmap_size);
pixmaps.floppy_disabled.active = pixmaps.floppy_disabled.normal;
pixmaps.floppy_disabled.empty = pixmaps.floppy_disabled.normal;
pixmaps.floppy_disabled.empty_active = pixmaps.floppy_disabled.normal;
pixmaps.floppy_525.load("/floppy_525%1.ico");
pixmaps.floppy_35.load("/floppy_35%1.ico");
pixmaps.cdrom.load("/cdrom%1.ico");
pixmaps.zip.load("/zip%1.ico");
pixmaps.mo.load("/mo%1.ico");
pixmaps.hd.load("/hard_disk%1.ico");
pixmaps.net.load("/network%1.ico");
pixmaps.sound = ProgSettings::loadIcon("/sound.ico").pixmap(pixmap_size);
cartridge[0].pixmaps = &pixmaps.cartridge;
cartridge[1].pixmaps = &pixmaps.cartridge;
cassette.pixmaps = &pixmaps.cassette;
QObject::connect(&cassette.timer, &QTimer::timeout, parent, [&]{ cassette.setActive(false); });
for (auto& f : fdd) {
f.pixmaps = &pixmaps.floppy_disabled;
QObject::connect(&f.timer, &QTimer::timeout, parent, [&]{ f.setActive(false); });
}
for (auto& c : cdrom) {
c.pixmaps = &pixmaps.cdrom;
QObject::connect(&c.timer, &QTimer::timeout, parent, [&]{ c.setActive(false); });
}
for (auto& z : zip) {
z.pixmaps = &pixmaps.zip;
QObject::connect(&z.timer, &QTimer::timeout, parent, [&]{ z.setActive(false); });
}
for (auto& m : mo) {
m.pixmaps = &pixmaps.mo;
QObject::connect(&m.timer, &QTimer::timeout, parent, [&]{ m.setActive(false); });
}
for (auto& h : hdds) {
h.pixmaps = &pixmaps.hd;
QObject::connect(&h.timer, &QTimer::timeout, parent, [&]{ h.setActive(false); });
}
net.pixmaps = &pixmaps.net;
}
std::array<StateEmpty, 2> cartridge;
StateEmptyActive cassette;
std::array<StateEmptyActive, FDD_NUM> fdd;
std::array<StateEmptyActive, CDROM_NUM> cdrom;
std::array<StateEmptyActive, ZIP_NUM> zip;
std::array<StateEmptyActive, MO_NUM> mo;
std::array<StateActive, HDD_BUS_USB> hdds;
StateActive net;
std::unique_ptr<ClickableLabel> sound;
std::unique_ptr<QLabel> text;
};
MachineStatus::MachineStatus(QObject *parent) :
QObject(parent)
{
d = std::make_unique<MachineStatus::States>(this);
}
MachineStatus::~MachineStatus() = default;
bool MachineStatus::hasCassette() {
return cassette_enable > 0 ? true : false;
}
bool MachineStatus::hasIDE() {
return machine_has_flags(machine, MACHINE_IDE_QUAD) > 0;
}
bool MachineStatus::hasSCSI() {
return machine_has_flags(machine, MACHINE_SCSI_DUAL) > 0;
}
void MachineStatus::iterateFDD(const std::function<void (int)> &cb) {
for (int i = 0; i < FDD_NUM; ++i) {
if (fdd_get_type(i) != 0) {
cb(i);
}
}
}
void MachineStatus::iterateCDROM(const std::function<void (int)> &cb) {
auto hdc_name = QString(hdc_get_internal_name(hdc_current));
for (size_t i = 0; i < CDROM_NUM; i++) {
/* Could be Internal or External IDE.. */
if ((cdrom[i].bus_type == CDROM_BUS_ATAPI) &&
!hasIDE() && hdc_name != QStringLiteral("ide") &&
hdc_name != QStringLiteral("xtide"))
continue;
if ((cdrom[i].bus_type == CDROM_BUS_SCSI) && !hasSCSI() &&
(scsi_card_current[0] == 0) && (scsi_card_current[1] == 0) &&
(scsi_card_current[2] == 0) && (scsi_card_current[3] == 0))
continue;
if (cdrom[i].bus_type != 0) {
cb(i);
}
}
}
void MachineStatus::iterateZIP(const std::function<void (int)> &cb) {
auto hdc_name = QString(hdc_get_internal_name(hdc_current));
for (size_t i = 0; i < ZIP_NUM; i++) {
/* Could be Internal or External IDE.. */
if ((zip_drives[i].bus_type == ZIP_BUS_ATAPI) &&
!hasIDE() && hdc_name != QStringLiteral("ide") &&
hdc_name != QStringLiteral("xtide"))
continue;
if ((zip_drives[i].bus_type == ZIP_BUS_SCSI) && !hasSCSI() &&
(scsi_card_current[0] == 0) && (scsi_card_current[1] == 0) &&
(scsi_card_current[2] == 0) && (scsi_card_current[3] == 0))
continue;
if (zip_drives[i].bus_type != 0) {
cb(i);
}
}
}
void MachineStatus::iterateMO(const std::function<void (int)> &cb) {
auto hdc_name = QString(hdc_get_internal_name(hdc_current));
for (size_t i = 0; i < MO_NUM; i++) {
/* Could be Internal or External IDE.. */
if ((mo_drives[i].bus_type == MO_BUS_ATAPI) &&
!hasIDE() && hdc_name != QStringLiteral("ide") &&
hdc_name != QStringLiteral("xtide"))
continue;
if ((mo_drives[i].bus_type == MO_BUS_SCSI) && !hasSCSI() &&
(scsi_card_current[0] == 0) && (scsi_card_current[1] == 0) &&
(scsi_card_current[2] == 0) && (scsi_card_current[3] == 0))
continue;
if (mo_drives[i].bus_type != 0) {
cb(i);
}
}
}
static int hdd_count(int bus) {
int c = 0;
int i;
for (i = 0; i < HDD_NUM; i++) {
if (hdd[i].bus == bus) {
c++;
}
}
return(c);
}
void MachineStatus::refresh(QStatusBar* sbar) {
bool has_mfm = machine_has_flags(machine, MACHINE_MFM) > 0;
bool has_xta = machine_has_flags(machine, MACHINE_XTA) > 0;
bool has_esdi = machine_has_flags(machine, MACHINE_ESDI) > 0;
int c_mfm = hdd_count(HDD_BUS_MFM);
int c_esdi = hdd_count(HDD_BUS_ESDI);
int c_xta = hdd_count(HDD_BUS_XTA);
int c_ide = hdd_count(HDD_BUS_IDE);
int c_scsi = hdd_count(HDD_BUS_SCSI);
int do_net = (network_type == NET_TYPE_NONE) || (network_card == 0);
sbar->removeWidget(d->cassette.label.get());
for (int i = 0; i < 2; ++i) {
sbar->removeWidget(d->cartridge[i].label.get());
}
for (size_t i = 0; i < FDD_NUM; ++i) {
sbar->removeWidget(d->fdd[i].label.get());
}
for (size_t i = 0; i < CDROM_NUM; i++) {
sbar->removeWidget(d->cdrom[i].label.get());
}
for (size_t i = 0; i < ZIP_NUM; i++) {
sbar->removeWidget(d->zip[i].label.get());
}
for (size_t i = 0; i < MO_NUM; i++) {
sbar->removeWidget(d->mo[i].label.get());
}
for (size_t i = 0; i < HDD_BUS_USB; i++) {
sbar->removeWidget(d->hdds[i].label.get());
}
sbar->removeWidget(d->net.label.get());
sbar->removeWidget(d->sound.get());
if (cassette_enable) {
d->cassette.label = std::make_unique<ClickableLabel>();
d->cassette.setEmpty(QString(cassette_fname).isEmpty());
connect((ClickableLabel*)d->cassette.label.get(), &ClickableLabel::clicked, [this](QPoint pos) {
MediaMenu::ptr->cassetteMenu->popup(pos - QPoint(0, MediaMenu::ptr->cassetteMenu->sizeHint().height()));
});
d->cassette.label->setToolTip(MediaMenu::ptr->cassetteMenu->title());
sbar->addWidget(d->cassette.label.get());
}
if (machine_has_cartridge(machine)) {
for (int i = 0; i < 2; ++i) {
d->cartridge[i].label = std::make_unique<ClickableLabel>();
d->cartridge[i].setEmpty(QString(cart_fns[i]).isEmpty());
connect((ClickableLabel*)d->cartridge[i].label.get(), &ClickableLabel::clicked, [this, i](QPoint pos) {
MediaMenu::ptr->cartridgeMenus[i]->popup(pos - QPoint(0, MediaMenu::ptr->cartridgeMenus[i]->sizeHint().height()));
});
d->cartridge[i].label->setToolTip(MediaMenu::ptr->cartridgeMenus[i]->title());
sbar->addWidget(d->cartridge[i].label.get());
}
}
iterateFDD([this, sbar](int i) {
int t = fdd_get_type(i);
if (t == 0) {
d->fdd[i].pixmaps = &d->pixmaps.floppy_disabled;
} else if (t >= 1 && t <= 6) {
d->fdd[i].pixmaps = &d->pixmaps.floppy_525;
} else {
d->fdd[i].pixmaps = &d->pixmaps.floppy_35;
}
d->fdd[i].label = std::make_unique<ClickableLabel>();
d->fdd[i].setEmpty(QString(floppyfns[i]).isEmpty());
d->fdd[i].setActive(false);
connect((ClickableLabel*)d->fdd[i].label.get(), &ClickableLabel::clicked, [this, i](QPoint pos) {
MediaMenu::ptr->floppyMenus[i]->popup(pos - QPoint(0, MediaMenu::ptr->floppyMenus[i]->sizeHint().height()));
});
d->fdd[i].label->setToolTip(MediaMenu::ptr->floppyMenus[i]->title());
sbar->addWidget(d->fdd[i].label.get());
});
iterateCDROM([this, sbar](int i) {
d->cdrom[i].label = std::make_unique<ClickableLabel>();
d->cdrom[i].setEmpty(cdrom[i].host_drive != 200 || QString(cdrom[i].image_path).isEmpty());
d->cdrom[i].setActive(false);
connect((ClickableLabel*)d->cdrom[i].label.get(), &ClickableLabel::clicked, [this, i](QPoint pos) {
MediaMenu::ptr->cdromMenus[i]->popup(pos - QPoint(0, MediaMenu::ptr->cdromMenus[i]->sizeHint().height()));
});
d->cdrom[i].label->setToolTip(MediaMenu::ptr->cdromMenus[i]->title());
sbar->addWidget(d->cdrom[i].label.get());
});
iterateZIP([this, sbar](int i) {
d->zip[i].label = std::make_unique<ClickableLabel>();
d->zip[i].setEmpty(QString(zip_drives[i].image_path).isEmpty());
d->zip[i].setActive(false);
connect((ClickableLabel*)d->zip[i].label.get(), &ClickableLabel::clicked, [this, i](QPoint pos) {
MediaMenu::ptr->zipMenus[i]->popup(pos - QPoint(0, MediaMenu::ptr->zipMenus[i]->sizeHint().height()));
});
d->zip[i].label->setToolTip(MediaMenu::ptr->zipMenus[i]->title());
sbar->addWidget(d->zip[i].label.get());
});
iterateMO([this, sbar](int i) {
d->mo[i].label = std::make_unique<ClickableLabel>();
d->mo[i].setEmpty(QString(mo_drives[i].image_path).isEmpty());
d->mo[i].setActive(false);
connect((ClickableLabel*)d->mo[i].label.get(), &ClickableLabel::clicked, [this, i](QPoint pos) {
MediaMenu::ptr->moMenus[i]->popup(pos - QPoint(0, MediaMenu::ptr->moMenus[i]->sizeHint().height()));
});
d->mo[i].label->setToolTip(MediaMenu::ptr->moMenus[i]->title());
sbar->addWidget(d->mo[i].label.get());
});
auto hdc_name = QString(hdc_get_internal_name(hdc_current));
if ((has_mfm || hdc_name.left(5) == QStringLiteral("st506")) && c_mfm > 0) {
d->hdds[HDD_BUS_MFM].label = std::make_unique<QLabel>();
d->hdds[HDD_BUS_MFM].setActive(false);
d->hdds[HDD_BUS_MFM].label->setToolTip(tr("Hard disk (%s)").replace("%s", "MFM/RLL"));
sbar->addWidget(d->hdds[HDD_BUS_MFM].label.get());
}
if ((has_esdi || hdc_name.left(4) == QStringLiteral("esdi")) && c_esdi > 0) {
d->hdds[HDD_BUS_ESDI].label = std::make_unique<QLabel>();
d->hdds[HDD_BUS_ESDI].setActive(false);
d->hdds[HDD_BUS_ESDI].label->setToolTip(tr("Hard disk (%s)").replace("%s", "ESDI"));
sbar->addWidget(d->hdds[HDD_BUS_ESDI].label.get());
}
if ((has_xta || hdc_name.left(3) == QStringLiteral("xta")) && c_xta > 0) {
d->hdds[HDD_BUS_XTA].label = std::make_unique<QLabel>();
d->hdds[HDD_BUS_XTA].setActive(false);
d->hdds[HDD_BUS_XTA].label->setToolTip(tr("Hard disk (%s)").replace("%s", "XTA"));
sbar->addWidget(d->hdds[HDD_BUS_XTA].label.get());
}
if ((hasIDE() || hdc_name.left(5) == QStringLiteral("xtide") || hdc_name.left(3) == QStringLiteral("ide")) && c_ide > 0) {
d->hdds[HDD_BUS_IDE].label = std::make_unique<QLabel>();
d->hdds[HDD_BUS_IDE].setActive(false);
d->hdds[HDD_BUS_IDE].label->setToolTip(tr("Hard disk (%s)").replace("%s", "IDE"));
sbar->addWidget(d->hdds[HDD_BUS_IDE].label.get());
}
if ((hasSCSI() || (scsi_card_current[0] != 0) || (scsi_card_current[1] != 0) ||
(scsi_card_current[2] != 0) || (scsi_card_current[3] != 0)) && c_scsi > 0) {
d->hdds[HDD_BUS_SCSI].label = std::make_unique<QLabel>();
d->hdds[HDD_BUS_SCSI].setActive(false);
d->hdds[HDD_BUS_SCSI].label->setToolTip(tr("Hard disk (%s)").replace("%s", "SCSI"));
sbar->addWidget(d->hdds[HDD_BUS_SCSI].label.get());
}
if (do_net) {
d->net.label = std::make_unique<QLabel>();
d->net.setActive(false);
d->net.label->setToolTip(tr("Network"));
sbar->addWidget(d->net.label.get());
}
d->sound = std::make_unique<ClickableLabel>();
d->sound->setPixmap(d->pixmaps.sound);
connect(d->sound.get(), &ClickableLabel::doubleClicked, d->sound.get(), [this](QPoint pos) {
SoundGain gain(main_window);
gain.exec();
});
d->sound->setToolTip(tr("Sound"));
sbar->addWidget(d->sound.get());
d->text = std::make_unique<QLabel>();
sbar->addWidget(d->text.get());
}
void MachineStatus::setActivity(int tag, bool active) {
int category = tag & 0xfffffff0;
int item = tag & 0xf;
switch (category) {
case SB_CASSETTE:
break;
case SB_CARTRIDGE:
break;
case SB_FLOPPY:
d->fdd[item].setActive(active);
break;
case SB_CDROM:
d->cdrom[item].setActive(active);
break;
case SB_ZIP:
d->zip[item].setActive(active);
break;
case SB_MO:
d->mo[item].setActive(active);
break;
case SB_HDD:
d->hdds[item].setActive(active);
break;
case SB_NETWORK:
d->net.setActive(active);
break;
case SB_SOUND:
break;
case SB_TEXT:
break;
}
}
void MachineStatus::setEmpty(int tag, bool empty) {
int category = tag & 0xfffffff0;
int item = tag & 0xf;
switch (category) {
case SB_CASSETTE:
d->cassette.setEmpty(empty);
break;
case SB_CARTRIDGE:
d->cartridge[item].setEmpty(empty);
break;
case SB_FLOPPY:
d->fdd[item].setEmpty(empty);
break;
case SB_CDROM:
d->cdrom[item].setEmpty(empty);
break;
case SB_ZIP:
d->zip[item].setEmpty(empty);
break;
case SB_MO:
d->mo[item].setEmpty(empty);
break;
case SB_HDD:
break;
case SB_NETWORK:
break;
case SB_SOUND:
break;
case SB_TEXT:
break;
}
}
void MachineStatus::message(const QString &msg) {
d->text->setText(msg);
}
QString MachineStatus::getMessage() {
return d->text->text();
}
void MachineStatus::updateTip(int tag)
{
int category = tag & 0xfffffff0;
int item = tag & 0xf;
switch (category) {
case SB_CASSETTE:
d->cassette.label->setToolTip(MediaMenu::ptr->cassetteMenu->title());
break;
case SB_CARTRIDGE:
d->cartridge[item].label->setToolTip(MediaMenu::ptr->cartridgeMenus[item]->title());
break;
case SB_FLOPPY:
d->fdd[item].label->setToolTip(MediaMenu::ptr->floppyMenus[item]->title());
break;
case SB_CDROM:
d->cdrom[item].label->setToolTip(MediaMenu::ptr->cdromMenus[item]->title());
break;
case SB_ZIP:
d->zip[item].label->setToolTip(MediaMenu::ptr->zipMenus[item]->title());
break;
case SB_MO:
d->mo[item].label->setToolTip(MediaMenu::ptr->moMenus[item]->title());
break;
case SB_HDD:
break;
case SB_NETWORK:
break;
case SB_SOUND:
break;
case SB_TEXT:
break;
}
}

View File

@@ -0,0 +1,57 @@
#ifndef QT_MACHINESTATUS_HPP
#define QT_MACHINESTATUS_HPP
#include <QWidget>
#include <QLabel>
#include <QMouseEvent>
#include <memory>
class QStatusBar;
class ClickableLabel : public QLabel {
Q_OBJECT;
public:
explicit ClickableLabel(QWidget* parent = nullptr)
: QLabel(parent) {}
~ClickableLabel() {};
signals:
void clicked(QPoint);
void doubleClicked(QPoint);
protected:
void mousePressEvent(QMouseEvent* event) override { emit clicked(event->globalPos()); }
void mouseDoubleClickEvent(QMouseEvent* event) override { emit doubleClicked(event->globalPos()); }
};
class MachineStatus : public QObject
{
Q_OBJECT
public:
explicit MachineStatus(QObject *parent = nullptr);
~MachineStatus();
static bool hasCassette();
static bool hasIDE();
static bool hasSCSI();
static void iterateFDD(const std::function<void(int i)>& cb);
static void iterateCDROM(const std::function<void(int i)>& cb);
static void iterateZIP(const std::function<void(int i)>& cb);
static void iterateMO(const std::function<void(int i)>& cb);
QString getMessage();
public slots:
void refresh(QStatusBar* sbar);
void setActivity(int tag, bool active);
void setEmpty(int tag, bool active);
void message(const QString& msg);
void updateTip(int tag);
private:
struct States;
std::unique_ptr<States> d;
};
#endif // QT_MACHINESTATUS_HPP

233
src/qt/qt_main.cpp Normal file
View File

@@ -0,0 +1,233 @@
#include <QApplication>
#include <QSurfaceFormat>
#include <QDebug>
#include <QElapsedTimer>
#include <QThread>
#include <QTimer>
#include <QTranslator>
#include <QDirIterator>
#include <QLibraryInfo>
#ifdef QT_STATIC
/* Static builds need plugin imports */
#include <QtPlugin>
Q_IMPORT_PLUGIN(QICOPlugin)
#ifdef Q_OS_WINDOWS
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
Q_IMPORT_PLUGIN(QWindowsVistaStylePlugin)
#endif
#endif
#ifdef Q_OS_WINDOWS
#include "qt_winrawinputfilter.hpp"
#include "qt_winmanagerfilter.hpp"
#include <86box/win.h>
#endif
extern "C"
{
#include <86box/86box.h>
#include <86box/config.h>
#include <86box/plat.h>
#include <86box/ui.h>
#include <86box/video.h>
#include <86box/discord.h>
}
#include <thread>
#include <iostream>
#include <memory>
#include "qt_mainwindow.hpp"
#include "qt_progsettings.hpp"
#include "qt_settings.hpp"
#include "cocoa_mouse.hpp"
#include "qt_styleoverride.hpp"
// Void Cast
#define VC(x) const_cast<wchar_t*>(x)
extern QElapsedTimer elapsed_timer;
extern MainWindow* main_window;
extern "C" {
#include <86box/timer.h>
#include <86box/nvr.h>
extern int qt_nvr_save(void);
}
void qt_set_sequence_auto_mnemonic(bool b);
void
main_thread_fn()
{
uint64_t old_time, new_time;
int drawits, frames;
QThread::currentThread()->setPriority(QThread::HighestPriority);
framecountx = 0;
//title_update = 1;
old_time = elapsed_timer.elapsed();
drawits = frames = 0;
while (!is_quit && cpu_thread_run) {
/* See if it is time to run a frame of code. */
new_time = elapsed_timer.elapsed();
drawits += (new_time - old_time);
old_time = new_time;
if (drawits > 0 && !dopause) {
/* Yes, so do one frame now. */
drawits -= 10;
if (drawits > 50)
drawits = 0;
/* Run a block of code. */
pc_run();
/* Every 200 frames we save the machine status. */
if (++frames >= 200 && nvr_dosave) {
qt_nvr_save();
nvr_dosave = 0;
frames = 0;
}
} else {
/* Just so we dont overload the host OS. */
if (drawits < -1)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
else
std::this_thread::yield();
}
/* If needed, handle a screen resize. */
if (!atomic_flag_test_and_set(&doresize) && !video_fullscreen && !is_quit) {
if (vid_resize & 2)
plat_resize(fixed_size_x, fixed_size_y);
else
plat_resize(scrnsz_x, scrnsz_y);
}
}
is_quit = 1;
}
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
qt_set_sequence_auto_mnemonic(false);
Q_INIT_RESOURCE(qt_resources);
Q_INIT_RESOURCE(qt_translations);
QSurfaceFormat fmt = QSurfaceFormat::defaultFormat();
fmt.setSwapInterval(0);
QSurfaceFormat::setDefaultFormat(fmt);
app.setStyle(new StyleOverride());
#ifdef __APPLE__
CocoaEventFilter cocoafilter;
app.installNativeEventFilter(&cocoafilter);
#endif
elapsed_timer.start();
if (!pc_init(argc, argv))
{
return 0;
}
ProgSettings::loadTranslators(&app);
if (! pc_init_modules()) {
ui_msgbox_header(MBX_FATAL, (void*)IDS_2120, (void*)IDS_2056);
return 6;
}
if (settings_only)
{
Settings settings;
if (settings.exec() == QDialog::Accepted)
{
settings.save();
config_save();
}
return 0;
}
discord_load();
main_window = new MainWindow();
main_window->show();
app.installEventFilter(main_window);
#ifdef Q_OS_WINDOWS
/* Setup VM-manager messages */
std::unique_ptr<WindowsManagerFilter> wmfilter;
if (source_hwnd)
{
HWND main_hwnd = (HWND)main_window->winId();
wmfilter.reset(new WindowsManagerFilter());
QObject::connect(wmfilter.get(), &WindowsManagerFilter::showsettings, main_window, &MainWindow::showSettings);
QObject::connect(wmfilter.get(), &WindowsManagerFilter::pause, main_window, &MainWindow::togglePause);
QObject::connect(wmfilter.get(), &WindowsManagerFilter::reset, main_window, &MainWindow::hardReset);
QObject::connect(wmfilter.get(), &WindowsManagerFilter::shutdown, [](){ plat_power_off(); });
QObject::connect(wmfilter.get(), &WindowsManagerFilter::ctrlaltdel, [](){ pc_send_cad(); });
QObject::connect(wmfilter.get(), &WindowsManagerFilter::dialogstatus, [main_hwnd](bool open){
PostMessage((HWND)(uintptr_t)source_hwnd, WM_SENDDLGSTATUS, (WPARAM)(open ? 1 : 0), (LPARAM)main_hwnd);
});
/* Native filter to catch VM-managers commands */
app.installNativeEventFilter(wmfilter.get());
/* Filter to catch main window being blocked (by modal dialog) */
main_window->installEventFilter(wmfilter.get());
/* Send main window HWND to manager */
PostMessage((HWND)(uintptr_t)source_hwnd, WM_SENDHWND, (WPARAM)unique_id, (LPARAM)main_hwnd);
/* Send shutdown message to manager */
QObject::connect(&app, &QApplication::destroyed, [main_hwnd](QObject*) {
PostMessage((HWND)(uintptr_t)source_hwnd, WM_HAS_SHUTDOWN, (WPARAM)0, (LPARAM)main_hwnd);
});
}
/* Setup raw input */
auto rawInputFilter = WindowsRawInputFilter::Register(main_window);
if (rawInputFilter)
{
app.installNativeEventFilter(rawInputFilter.get());
QObject::disconnect(main_window, &MainWindow::pollMouse, 0, 0);
QObject::connect(main_window, &MainWindow::pollMouse, (WindowsRawInputFilter*)rawInputFilter.get(), &WindowsRawInputFilter::mousePoll, Qt::DirectConnection);
main_window->setSendKeyboardInput(false);
}
#endif
pc_reset_hard_init();
/* Set the PAUSE mode depending on the renderer. */
// plat_pause(0);
QTimer onesec;
QTimer discordupdate;
QObject::connect(&onesec, &QTimer::timeout, &app, [] {
pc_onesec();
});
onesec.setTimerType(Qt::PreciseTimer);
onesec.start(1000);
if (discord_loaded) {
QTimer::singleShot(1000, &app, [] {
if (enable_discord) {
discord_init();
discord_update_activity(dopause);
} else
discord_close();
});
QObject::connect(&discordupdate, &QTimer::timeout, &app, [] {
discord_run_callbacks();
});
discordupdate.start(0);
}
/* Initialize the rendering window, or fullscreen. */
auto main_thread = std::thread([] {
main_thread_fn();
});
auto ret = app.exec();
cpu_thread_run = 0;
main_thread.join();
return ret;
}

1577
src/qt/qt_mainwindow.cpp Normal file

File diff suppressed because it is too large Load Diff

136
src/qt/qt_mainwindow.hpp Normal file
View File

@@ -0,0 +1,136 @@
#ifndef QT_MAINWINDOW_HPP
#define QT_MAINWINDOW_HPP
#include <QMainWindow>
#include <QLabel>
#include <QEvent>
#include <QFocusEvent>
#include <memory>
class MediaMenu;
namespace Ui {
class MainWindow;
}
class MachineStatus;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void showMessage(const QString& header, const QString& message);
void getTitle(wchar_t* title);
void blitToWidget(int x, int y, int w, int h);
QSize getRenderWidgetSize();
void setSendKeyboardInput(bool enabled);
signals:
void paint(const QImage& image);
void resizeContents(int w, int h);
void pollMouse();
void statusBarMessage(const QString& msg);
void updateStatusBarPanes();
void updateStatusBarActivity(int tag, bool active);
void updateStatusBarEmpty(int tag, bool empty);
void updateStatusBarTip(int tag);
void updateMenuResizeOptions();
void updateWindowRememberOption();
void setTitle(const QString& title);
void setFullscreen(bool state);
void setMouseCapture(bool state);
void showMessageForNonQtThread(const QString& header, const QString& message);
void getTitleForNonQtThread(wchar_t* title);
public slots:
void showSettings();
void hardReset();
void togglePause();
private slots:
void on_actionFullscreen_triggered();
void on_actionSettings_triggered();
void on_actionExit_triggered();
void on_actionPause_triggered();
void on_actionCtrl_Alt_Del_triggered();
void on_actionCtrl_Alt_Esc_triggered();
void on_actionHard_Reset_triggered();
void on_actionRight_CTRL_is_left_ALT_triggered();
void on_actionKeyboard_requires_capture_triggered();
void on_actionHardware_Renderer_OpenGL_ES_triggered();
void on_actionHardware_Renderer_OpenGL_triggered();
void on_actionSoftware_Renderer_triggered();
void on_actionResizable_window_triggered(bool checked);
void on_actionInverted_VGA_monitor_triggered();
void on_action0_5x_triggered();
void on_action1x_triggered();
void on_action1_5x_triggered();
void on_action2x_triggered();
void on_actionLinear_triggered();
void on_actionNearest_triggered();
void on_actionFullScreen_int_triggered();
void on_actionFullScreen_keepRatio_triggered();
void on_actionFullScreen_43_triggered();
void on_actionFullScreen_stretch_triggered();
void on_actionWhite_monitor_triggered();
void on_actionGreen_monitor_triggered();
void on_actionAmber_monitor_triggered();
void on_actionRGB_Grayscale_triggered();
void on_actionRGB_Color_triggered();
void on_actionAverage_triggered();
void on_actionBT709_HDTV_triggered();
void on_actionBT601_NTSC_PAL_triggered();
void on_actionDocumentation_triggered();
void on_actionAbout_86Box_triggered();
void on_actionAbout_Qt_triggered();
void on_actionForce_4_3_display_ratio_triggered();
void on_actionChange_contrast_for_monochrome_display_triggered();
void on_actionCGA_PCjr_Tandy_EGA_S_VGA_overscan_triggered();
void on_actionRemember_size_and_position_triggered();
void on_actionSpecify_dimensions_triggered();
void on_actionHiDPI_scaling_triggered();
void on_actionHide_status_bar_triggered();
void on_actionHide_tool_bar_triggered();
void on_actionUpdate_status_bar_icons_triggered();
void refreshMediaMenu();
void showMessage_(const QString& header, const QString& message);
void getTitle_(wchar_t* title);
void on_actionTake_screenshot_triggered();
void on_actionSound_gain_triggered();
void on_actionOpenGL_3_0_Core_triggered();
void on_actionPreferences_triggered();
void on_actionEnable_Discord_integration_triggered(bool checked);
protected:
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
void focusInEvent(QFocusEvent* event) override;
void focusOutEvent(QFocusEvent* event) override;
bool eventFilter(QObject* receiver, QEvent* event) override;
void showEvent(QShowEvent* event) override;
void closeEvent(QCloseEvent* event) override;
void changeEvent(QEvent* event) override;
private:
Ui::MainWindow *ui;
std::unique_ptr<MachineStatus> status;
std::shared_ptr<MediaMenu> mm;
/* If main window should send keyboard input */
bool send_keyboard_input = true;
bool shownonce = false;
friend class SpecifyDimensions;
friend class ProgSettings;
};
#endif // QT_MAINWINDOW_HPP

713
src/qt/qt_mainwindow.ui Normal file
View File

@@ -0,0 +1,713 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>724</width>
<height>427</height>
</rect>
</property>
<property name="windowTitle">
<string>86Box</string>
</property>
<widget class="QWidget" name="centralwidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="RendererStack" name="stackedWidget"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>724</width>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuAction">
<property name="title">
<string>&amp;Action</string>
</property>
<addaction name="actionKeyboard_requires_capture"/>
<addaction name="actionRight_CTRL_is_left_ALT"/>
<addaction name="separator"/>
<addaction name="actionHard_Reset"/>
<addaction name="actionCtrl_Alt_Del"/>
<addaction name="separator"/>
<addaction name="actionCtrl_Alt_Esc"/>
<addaction name="separator"/>
<addaction name="actionPause"/>
<addaction name="separator"/>
<addaction name="actionExit"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
<string>&amp;Tools</string>
</property>
<addaction name="actionSettings"/>
<addaction name="actionUpdate_status_bar_icons"/>
<addaction name="separator"/>
<addaction name="actionEnable_Discord_integration"/>
<addaction name="separator"/>
<addaction name="actionTake_screenshot"/>
<addaction name="actionSound_gain"/>
<addaction name="separator"/>
<addaction name="actionPreferences"/>
<addaction name="separator"/>
<addaction name="actionBegin_trace"/>
<addaction name="actionEnd_trace"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>&amp;View</string>
</property>
<widget class="QMenu" name="menuRenderer">
<property name="title">
<string>Re&amp;nderer</string>
</property>
<addaction name="actionSoftware_Renderer"/>
<addaction name="actionHardware_Renderer_OpenGL"/>
<addaction name="actionHardware_Renderer_OpenGL_ES"/>
<addaction name="actionOpenGL_3_0_Core"/>
</widget>
<widget class="QMenu" name="menuWindow_scale_factor">
<property name="title">
<string>&amp;Window scale factor</string>
</property>
<addaction name="action0_5x"/>
<addaction name="action1x"/>
<addaction name="action1_5x"/>
<addaction name="action2x"/>
</widget>
<widget class="QMenu" name="menuFilter_method">
<property name="title">
<string>Filter method</string>
</property>
<addaction name="actionNearest"/>
<addaction name="actionLinear"/>
</widget>
<widget class="QMenu" name="menuFullscreen_stretch_mode">
<property name="title">
<string>Fullscreen &amp;stretch mode</string>
</property>
<addaction name="actionFullScreen_stretch"/>
<addaction name="actionFullScreen_43"/>
<addaction name="actionFullScreen_keepRatio"/>
<addaction name="actionFullScreen_int"/>
</widget>
<widget class="QMenu" name="menuEGA_S_VGA_settings">
<property name="title">
<string>E&amp;GA/(S)VGA settings</string>
</property>
<widget class="QMenu" name="menuVGA_screen_type">
<property name="title">
<string>VGA screen &amp;type</string>
</property>
<addaction name="actionRGB_Color"/>
<addaction name="actionRGB_Grayscale"/>
<addaction name="actionAmber_monitor"/>
<addaction name="actionGreen_monitor"/>
<addaction name="actionWhite_monitor"/>
</widget>
<widget class="QMenu" name="menuGrayscale_conversion_type">
<property name="title">
<string>Grayscale &amp;conversion type</string>
</property>
<addaction name="actionBT601_NTSC_PAL"/>
<addaction name="actionBT709_HDTV"/>
<addaction name="actionAverage"/>
</widget>
<addaction name="actionInverted_VGA_monitor"/>
<addaction name="menuVGA_screen_type"/>
<addaction name="menuGrayscale_conversion_type"/>
</widget>
<addaction name="actionHide_tool_bar"/>
<addaction name="actionHide_status_bar"/>
<addaction name="separator"/>
<addaction name="actionResizable_window"/>
<addaction name="actionRemember_size_and_position"/>
<addaction name="separator"/>
<addaction name="menuRenderer"/>
<addaction name="actionSpecify_dimensions"/>
<addaction name="actionForce_4_3_display_ratio"/>
<addaction name="menuWindow_scale_factor"/>
<addaction name="menuFilter_method"/>
<addaction name="actionHiDPI_scaling"/>
<addaction name="separator"/>
<addaction name="actionFullscreen"/>
<addaction name="menuFullscreen_stretch_mode"/>
<addaction name="menuEGA_S_VGA_settings"/>
<addaction name="separator"/>
<addaction name="actionCGA_PCjr_Tandy_EGA_S_VGA_overscan"/>
<addaction name="actionChange_contrast_for_monochrome_display"/>
</widget>
<widget class="QMenu" name="menuMedia">
<property name="title">
<string>&amp;Media</string>
</property>
</widget>
<widget class="QMenu" name="menuAbout">
<property name="title">
<string>&amp;Help</string>
</property>
<addaction name="actionDocumentation"/>
<addaction name="actionAbout_86Box"/>
<addaction name="actionAbout_Qt"/>
</widget>
<addaction name="menuAction"/>
<addaction name="menuView"/>
<addaction name="menuMedia"/>
<addaction name="menuTools"/>
<addaction name="menuAbout"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
<property name="contextMenuPolicy">
<enum>Qt::PreventContextMenu</enum>
</property>
<property name="windowTitle">
<string notr="true">toolBar</string>
</property>
<property name="movable">
<bool>false</bool>
</property>
<property name="allowedAreas">
<set>Qt::TopToolBarArea</set>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonIconOnly</enum>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionPause"/>
<addaction name="actionHard_Reset"/>
<addaction name="actionACPI_Shutdown"/>
<addaction name="separator"/>
<addaction name="actionCtrl_Alt_Del"/>
<addaction name="actionCtrl_Alt_Esc"/>
<addaction name="separator"/>
<addaction name="actionSettings"/>
</widget>
<action name="actionKeyboard_requires_capture">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Keyboard requires capture</string>
</property>
</action>
<action name="actionRight_CTRL_is_left_ALT">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Right CTRL is left ALT</string>
</property>
</action>
<action name="actionHard_Reset">
<property name="icon">
<iconset resource="../qt_resources.qrc">
<normaloff>:/menuicons/win/icons/hard_reset.ico</normaloff>:/menuicons/win/icons/hard_reset.ico</iconset>
</property>
<property name="text">
<string>&amp;Hard Reset...</string>
</property>
<property name="iconVisibleInMenu">
<bool>false</bool>
</property>
</action>
<action name="actionCtrl_Alt_Del">
<property name="icon">
<iconset resource="../qt_resources.qrc">
<normaloff>:/menuicons/win/icons/send_cad.ico</normaloff>:/menuicons/win/icons/send_cad.ico</iconset>
</property>
<property name="text">
<string>&amp;Ctrl+Alt+Del</string>
</property>
<property name="toolTip">
<string>Ctrl+Alt+Del</string>
</property>
<property name="shortcut">
<string>Ctrl+F12</string>
</property>
<property name="iconVisibleInMenu">
<bool>false</bool>
</property>
<property name="shortcutVisibleInContextMenu">
<bool>false</bool>
</property>
</action>
<action name="actionCtrl_Alt_Esc">
<property name="icon">
<iconset resource="../qt_resources.qrc">
<normaloff>:/menuicons/win/icons/send_cae.ico</normaloff>:/menuicons/win/icons/send_cae.ico</iconset>
</property>
<property name="text">
<string>Ctrl+Alt+&amp;Esc</string>
</property>
<property name="iconVisibleInMenu">
<bool>false</bool>
</property>
</action>
<action name="actionPause">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../qt_resources.qrc">
<normaloff>:/menuicons/win/icons/pause.ico</normaloff>:/menuicons/win/icons/pause.ico</iconset>
</property>
<property name="text">
<string>&amp;Pause</string>
</property>
<property name="iconVisibleInMenu">
<bool>false</bool>
</property>
</action>
<action name="actionExit">
<property name="text">
<string>Exit</string>
</property>
</action>
<action name="actionSettings">
<property name="icon">
<iconset resource="../qt_resources.qrc">
<normaloff>:/menuicons/win/icons/settings.ico</normaloff>:/menuicons/win/icons/settings.ico</iconset>
</property>
<property name="text">
<string>&amp;Settings...</string>
</property>
<property name="menuRole">
<enum>QAction::PreferencesRole</enum>
</property>
<property name="iconVisibleInMenu">
<bool>false</bool>
</property>
</action>
<action name="actionFullscreen">
<property name="text">
<string>&amp;Fullscreen</string>
</property>
<property name="shortcut">
<string>Ctrl+Alt+PgUp</string>
</property>
<property name="shortcutVisibleInContextMenu">
<bool>false</bool>
</property>
</action>
<action name="actionSoftware_Renderer">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Qt (Software)</string>
</property>
</action>
<action name="actionHardware_Renderer_OpenGL">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Qt (&amp;OpenGL)</string>
</property>
</action>
<action name="actionHardware_Renderer_OpenGL_ES">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Qt (OpenGL &amp;ES)</string>
</property>
</action>
<action name="actionHide_status_bar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Hide status bar</string>
</property>
</action>
<action name="actionResizable_window">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Resizeable window</string>
</property>
</action>
<action name="actionRemember_size_and_position">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>R&amp;emember size &amp;&amp; position</string>
</property>
</action>
<action name="actionSpecify_dimensions">
<property name="text">
<string>Specify dimensions...</string>
</property>
</action>
<action name="actionForce_4_3_display_ratio">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>F&amp;orce 4:3 display ratio</string>
</property>
</action>
<action name="actionHiDPI_scaling">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Hi&amp;DPI scaling</string>
</property>
</action>
<action name="actionCGA_PCjr_Tandy_EGA_S_VGA_overscan">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>CGA/PCjr/Tandy/E&amp;GA/(S)VGA overscan</string>
</property>
</action>
<action name="actionChange_contrast_for_monochrome_display">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Change contrast for &amp;monochrome display</string>
</property>
</action>
<action name="action0_5x">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;0.5x</string>
</property>
</action>
<action name="action1x">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;1x</string>
</property>
</action>
<action name="action1_5x">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>1.&amp;5x</string>
</property>
</action>
<action name="action2x">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;2x</string>
</property>
</action>
<action name="actionNearest">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Nearest</string>
</property>
</action>
<action name="actionLinear">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Linear</string>
</property>
</action>
<action name="actionFullScreen_stretch">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Full screen stretch</string>
</property>
</action>
<action name="actionFullScreen_43">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;4:3</string>
</property>
</action>
<action name="actionFullScreen_keepRatio">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Square pixels (Keep ratio)</string>
</property>
</action>
<action name="actionFullScreen_int">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Integer scale</string>
</property>
</action>
<action name="actionInverted_VGA_monitor">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Inverted VGA monitor</string>
</property>
</action>
<action name="actionRGB_Color">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>RGB &amp;Color</string>
</property>
</action>
<action name="actionRGB_Grayscale">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;RGB Grayscale</string>
</property>
</action>
<action name="actionAmber_monitor">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Amber monitor</string>
</property>
</action>
<action name="actionGreen_monitor">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Green monitor</string>
</property>
</action>
<action name="actionWhite_monitor">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;White monitor</string>
</property>
</action>
<action name="actionBT601_NTSC_PAL">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>BT&amp;601 (NTSC/PAL)</string>
</property>
</action>
<action name="actionBT709_HDTV">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>BT&amp;709 (HDTV)</string>
</property>
</action>
<action name="actionAverage">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Average</string>
</property>
</action>
<action name="actionAbout_Qt">
<property name="text">
<string>About Qt</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
<property name="menuRole">
<enum>QAction::AboutQtRole</enum>
</property>
</action>
<action name="actionAbout_86Box">
<property name="text">
<string>&amp;About 86Box...</string>
</property>
<property name="menuRole">
<enum>QAction::AboutRole</enum>
</property>
</action>
<action name="actionDocumentation">
<property name="text">
<string>&amp;Documentation...</string>
</property>
</action>
<action name="actionUpdate_status_bar_icons">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Update status bar icons</string>
</property>
</action>
<action name="actionTake_screenshot">
<property name="text">
<string>Take s&amp;creenshot</string>
</property>
<property name="shortcut">
<string>Ctrl+F11</string>
</property>
<property name="shortcutVisibleInContextMenu">
<bool>false</bool>
</property>
</action>
<action name="actionSound_gain">
<property name="text">
<string>Sound &amp;gain...</string>
</property>
</action>
<action name="actionOpenGL_3_0_Core">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Open&amp;GL (3.0 Core)</string>
</property>
</action>
<action name="actionPreferences">
<property name="text">
<string>&amp;Preferences...</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionEnable_Discord_integration">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Enable &amp;Discord integration</string>
</property>
</action>
<action name="actionHide_tool_bar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Hide tool bar</string>
</property>
<property name="toolTip">
<string>Hide tool bar</string>
</property>
</action>
<action name="actionACPI_Shutdown">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../qt_resources.qrc">
<normaloff>:/menuicons/win/icons/acpi_shutdown.ico</normaloff>:/menuicons/win/icons/acpi_shutdown.ico</iconset>
</property>
<property name="text">
<string>ACPI Shutdown</string>
</property>
<property name="toolTip">
<string>ACPI Shutdown</string>
</property>
</action>
<action name="actionBegin_trace">
<property name="text">
<string>Begin trace</string>
</property>
<property name="shortcut">
<string>Ctrl+T</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
<property name="shortcutVisibleInContextMenu">
<bool>false</bool>
</property>
</action>
<action name="actionEnd_trace">
<property name="text">
<string>End trace</string>
</property>
<property name="shortcut">
<string>Ctrl+T</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
<property name="shortcutVisibleInContextMenu">
<bool>false</bool>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>RendererStack</class>
<extends>QStackedWidget</extends>
<header>qt_rendererstack.hpp</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../qt_resources.qrc"/>
</resources>
<connections/>
</ui>

636
src/qt/qt_mediamenu.cpp Normal file
View File

@@ -0,0 +1,636 @@
#include "qt_mediamenu.hpp"
#include "qt_machinestatus.hpp"
#include <QMenu>
#include <QFileDialog>
#include <QMessageBox>
#include <QStringBuilder>
extern "C" {
#include <86box/config.h>
#include <86box/device.h>
#include <86box/timer.h>
#include <86box/plat.h>
#include <86box/cassette.h>
#include <86box/cartridge.h>
#include <86box/fdd.h>
#include <86box/fdd_86f.h>
#include <86box/cdrom.h>
#include <86box/scsi_device.h>
#include <86box/zip.h>
#include <86box/mo.h>
#include <86box/sound.h>
#include <86box/ui.h>
};
#include "qt_newfloppydialog.hpp"
#include "qt_util.hpp"
std::shared_ptr<MediaMenu> MediaMenu::ptr;
MediaMenu::MediaMenu(QWidget* parent) : QObject(parent) {
parentWidget = parent;
}
void MediaMenu::refresh(QMenu *parentMenu) {
parentMenu->clear();
if(MachineStatus::hasCassette()) {
cassetteMenu = parentMenu->addMenu("");
cassetteMenu->addAction(tr("&New image..."), [this]() { cassetteNewImage(); });
cassetteMenu->addSeparator();
cassetteMenu->addAction(tr("&Existing image..."), [this]() { cassetteSelectImage(false); });
cassetteMenu->addAction(tr("Existing image (&Write-protected)..."), [this]() { cassetteSelectImage(true); });
cassetteMenu->addSeparator();
cassetteRecordPos = cassetteMenu->children().count();
cassetteMenu->addAction(tr("&Record"), [this] { pc_cas_set_mode(cassette, 1); cassetteUpdateMenu(); })->setCheckable(true);
cassettePlayPos = cassetteMenu->children().count();
cassetteMenu->addAction(tr("&Play"), [this] { pc_cas_set_mode(cassette, 0); cassetteUpdateMenu(); })->setCheckable(true);
cassetteRewindPos = cassetteMenu->children().count();
cassetteMenu->addAction(tr("&Rewind to the beginning"), [] { pc_cas_rewind(cassette); });
cassetteFastFwdPos = cassetteMenu->children().count();
cassetteMenu->addAction(tr("&Fast forward to the end"), [] { pc_cas_append(cassette); });
cassetteMenu->addSeparator();
cassetteEjectPos = cassetteMenu->children().count();
cassetteMenu->addAction(tr("E&ject"), [this]() { cassetteEject(); });
cassetteUpdateMenu();
}
cartridgeMenus.clear();
if (machine_has_cartridge(machine)) {
for(int i = 0; i < 2; i++) {
auto* menu = parentMenu->addMenu("");
menu->addAction(tr("&Image..."), [this, i]() { cartridgeSelectImage(i); });
menu->addSeparator();
cartridgeEjectPos = menu->children().count();
menu->addAction(tr("E&ject"), [this, i]() { cartridgeEject(i); });
cartridgeMenus[i] = menu;
cartridgeUpdateMenu(i);
}
}
floppyMenus.clear();
MachineStatus::iterateFDD([this, parentMenu](int i) {
auto* menu = parentMenu->addMenu("");
menu->addAction(tr("&New image..."), [this, i]() { floppyNewImage(i); });
menu->addSeparator();
menu->addAction(tr("&Existing image..."), [this, i]() { floppySelectImage(i, false); });
menu->addAction(tr("Existing image (&Write-protected)..."), [this, i]() { floppySelectImage(i, true); });
menu->addSeparator();
floppyExportPos = menu->children().count();
menu->addAction(tr("E&xport to 86F..."), [this, i]() { floppyExportTo86f(i); });
menu->addSeparator();
floppyEjectPos = menu->children().count();
menu->addAction(tr("E&ject"), [this, i]() { floppyEject(i); });
floppyMenus[i] = menu;
floppyUpdateMenu(i);
});
cdromMenus.clear();
MachineStatus::iterateCDROM([this, parentMenu](int i) {
auto* menu = parentMenu->addMenu("");
cdromMutePos = menu->children().count();
menu->addAction(tr("&Mute"), [this, i]() { cdromMute(i); })->setCheckable(true);
menu->addSeparator();
cdromEmptyPos = menu->children().count();
menu->addAction(tr("E&mpty"), [this, i]() { cdromEject(i); })->setCheckable(true);
cdromReloadPos = menu->children().count();
menu->addAction(tr("&Reload previous image"), [this, i]() { cdromReload(i); });
menu->addSeparator();
cdromImagePos = menu->children().count();
menu->addAction(tr("&Image"), [this, i]() { cdromMount(i); })->setCheckable(true);
cdromMenus[i] = menu;
cdromUpdateMenu(i);
});
zipMenus.clear();
MachineStatus::iterateZIP([this, parentMenu](int i) {
auto* menu = parentMenu->addMenu("");
menu->addAction(tr("&New image..."), [this, i]() { zipNewImage(i); });
menu->addSeparator();
menu->addAction(tr("&Existing image..."), [this, i]() { zipSelectImage(i, false); });
menu->addAction(tr("Existing image (&Write-protected)..."), [this, i]() { zipSelectImage(i, true); });
menu->addSeparator();
zipEjectPos = menu->children().count();
menu->addAction(tr("E&ject"), [this, i]() { zipEject(i); });
zipReloadPos = menu->children().count();
menu->addAction(tr("&Reload previous image"), [this, i]() { zipReload(i); });
zipMenus[i] = menu;
zipUpdateMenu(i);
});
moMenus.clear();
MachineStatus::iterateMO([this, parentMenu](int i) {
auto* menu = parentMenu->addMenu("");
menu->addAction(tr("&New image..."), [this, i]() { moNewImage(i); });
menu->addSeparator();
menu->addAction(tr("&Existing image..."), [this, i]() { moSelectImage(i, false); });
menu->addAction(tr("Existing image (&Write-protected)..."), [this, i]() { moSelectImage(i, true); });
menu->addSeparator();
moEjectPos = menu->children().count();
menu->addAction(tr("E&ject"), [this, i]() { moEject(i); });
moReloadPos = menu->children().count();
menu->addAction(tr("&Reload previous image"), [this, i]() { moReload(i); });
moMenus[i] = menu;
moUpdateMenu(i);
});
}
void MediaMenu::cassetteNewImage() {
auto filename = QFileDialog::getSaveFileName(parentWidget, tr("Create..."));
QFileInfo fileinfo(filename);
if (fileinfo.suffix().isEmpty()) {
filename.append(".cas");
}
if (!filename.isNull()) {
if (filename.isEmpty()) cassetteEject();
else cassetteMount(filename, false);
}
}
void MediaMenu::cassetteSelectImage(bool wp) {
auto filename = QFileDialog::getOpenFileName(parentWidget,
QString(),
QString(),
tr("Cassette images") %
util::DlgFilter({ "pcm","raw","wav","cas" }) %
tr("All files") %
util::DlgFilter({ "*" }, true));
if (!filename.isEmpty()) cassetteMount(filename, wp);
}
void MediaMenu::cassetteMount(const QString& filename, bool wp) {
pc_cas_set_fname(cassette, nullptr);
memset(cassette_fname, 0, sizeof(cassette_fname));
cassette_ui_writeprot = wp ? 1 : 0;
if (! filename.isEmpty()) {
QByteArray filenameBytes = filename.toUtf8();
strncpy(cassette_fname, filenameBytes.data(), sizeof(cassette_fname));
pc_cas_set_fname(cassette, cassette_fname);
}
ui_sb_update_icon_state(SB_CASSETTE, filename.isEmpty() ? 1 : 0);
cassetteUpdateMenu();
ui_sb_update_tip(SB_CASSETTE);
config_save();
}
void MediaMenu::cassetteEject() {
pc_cas_set_fname(cassette, nullptr);
memset(cassette_fname, 0, sizeof(cassette_fname));
ui_sb_update_icon_state(SB_CASSETTE, 1);
cassetteUpdateMenu();
ui_sb_update_tip(SB_CASSETTE);
config_save();
}
void MediaMenu::cassetteUpdateMenu() {
QString name = cassette_fname;
QString mode = cassette_mode;
auto childs = cassetteMenu->children();
auto* recordMenu = dynamic_cast<QAction*>(childs[cassetteRecordPos]);
auto* playMenu = dynamic_cast<QAction*>(childs[cassettePlayPos]);
auto* rewindMenu = dynamic_cast<QAction*>(childs[cassetteRewindPos]);
auto* fastFwdMenu = dynamic_cast<QAction*>(childs[cassetteFastFwdPos]);
auto* ejectMenu = dynamic_cast<QAction*>(childs[cassetteEjectPos]);
recordMenu->setEnabled(!name.isEmpty());
playMenu->setEnabled(!name.isEmpty());
rewindMenu->setEnabled(!name.isEmpty());
fastFwdMenu->setEnabled(!name.isEmpty());
ejectMenu->setEnabled(!name.isEmpty());
bool isSaving = mode == QStringLiteral("save");
recordMenu->setChecked(isSaving);
playMenu->setChecked(! isSaving);
cassetteMenu->setTitle(QString::asprintf(tr("Cassette: %s").toUtf8().constData(), (name.isEmpty() ? tr("(empty)") : name).toUtf8().constData()));
}
void MediaMenu::cartridgeSelectImage(int i) {
auto filename = QFileDialog::getOpenFileName(
parentWidget,
QString(),
QString(),
tr("Cartridge images") %
util::DlgFilter({ "a","b","jrc" }) %
tr("All files") %
util::DlgFilter({ "*" }, true));
if (filename.isEmpty()) {
return;
}
cart_close(i);
QByteArray filenameBytes = filename.toUtf8();
cart_load(i, filenameBytes.data());
ui_sb_update_icon_state(SB_CARTRIDGE | i, filename.isEmpty() ? 1 : 0);
cartridgeUpdateMenu(i);
ui_sb_update_tip(SB_CARTRIDGE | i);
config_save();
}
void MediaMenu::cartridgeEject(int i) {
cart_close(i);
ui_sb_update_icon_state(SB_CARTRIDGE | i, 1);
cartridgeUpdateMenu(i);
ui_sb_update_tip(SB_CARTRIDGE | i);
config_save();
}
void MediaMenu::cartridgeUpdateMenu(int i) {
QString name = cart_fns[i];
auto* menu = cartridgeMenus[i];
auto childs = menu->children();
auto* ejectMenu = dynamic_cast<QAction*>(childs[cartridgeEjectPos]);
ejectMenu->setEnabled(!name.isEmpty());
//menu->setTitle(tr("Cartridge %1: %2").arg(QString::number(i+1), name.isEmpty() ? tr("(empty)") : name));
menu->setTitle(QString::asprintf(tr("Cartridge %i: %ls").toUtf8().constData(), i + 1, name.isEmpty() ? tr("(empty)").toStdU16String().data() : name.toStdU16String().data()));
}
void MediaMenu::floppyNewImage(int i) {
NewFloppyDialog dialog(NewFloppyDialog::MediaType::Floppy, parentWidget);
switch (dialog.exec()) {
case QDialog::Accepted:
QByteArray filename = dialog.fileName().toUtf8();
floppyMount(i, filename, false);
break;
}
}
void MediaMenu::floppySelectImage(int i, bool wp) {
auto filename = QFileDialog::getOpenFileName(
parentWidget,
QString(),
QString(),
tr("All images") %
util::DlgFilter({ "0??","1??","??0","86f","bin","cq?","d??","flp","hdm","im?","json","td0","*fd?","mfm","xdf" }) %
tr("Advanced sector images") %
util::DlgFilter({ "imd","json","td0" }) %
tr("Basic sector images") %
util::DlgFilter({ "0??","1??","??0","bin","cq?","d??","flp","hdm","im?","xdf","*fd?" }) %
tr("Flux images") %
util::DlgFilter({ "fdi" }) %
tr("Surface images") %
util::DlgFilter({ "86f","mfm" }) %
tr("All files") %
util::DlgFilter({ "*" }, true));
if (!filename.isEmpty()) floppyMount(i, filename, wp);
}
void MediaMenu::floppyMount(int i, const QString &filename, bool wp) {
fdd_close(i);
ui_writeprot[i] = wp ? 1 : 0;
if (! filename.isEmpty()) {
QByteArray filenameBytes = filename.toUtf8();
fdd_load(i, filenameBytes.data());
}
ui_sb_update_icon_state(SB_FLOPPY | i, filename.isEmpty() ? 1 : 0);
floppyUpdateMenu(i);
ui_sb_update_tip(SB_FLOPPY | i);
config_save();
}
void MediaMenu::floppyEject(int i) {
fdd_close(i);
ui_sb_update_icon_state(SB_FLOPPY | i, 1);
floppyUpdateMenu(i);
ui_sb_update_tip(SB_FLOPPY | i);
config_save();
}
void MediaMenu::floppyExportTo86f(int i) {
auto filename = QFileDialog::getSaveFileName(parentWidget, QString(), QString(), tr("Surface images") % util::DlgFilter({ "86f" }, true));
if (! filename.isEmpty()) {
QByteArray filenameBytes = filename.toUtf8();
plat_pause(1);
if (d86f_export(i, filenameBytes.data()) == 0) {
QMessageBox::critical(parentWidget, tr("Unable to write file"), tr("Make sure the file is being saved to a writable directory"));
}
plat_pause(0);
}
}
void MediaMenu::floppyUpdateMenu(int i) {
QString name = floppyfns[i];
auto* menu = floppyMenus[i];
auto childs = menu->children();
auto* ejectMenu = dynamic_cast<QAction*>(childs[floppyEjectPos]);
auto* exportMenu = dynamic_cast<QAction*>(childs[floppyExportPos]);
ejectMenu->setEnabled(!name.isEmpty());
exportMenu->setEnabled(!name.isEmpty());
int type = fdd_get_type(i);
//floppyMenus[i]->setTitle(tr("Floppy %1 (%2): %3").arg(QString::number(i+1), fdd_getname(type), name.isEmpty() ? tr("(empty)") : name));
floppyMenus[i]->setTitle(QString::asprintf(tr("Floppy %i (%s): %ls").toUtf8().constData(), i + 1, fdd_getname(type), name.isEmpty() ? tr("(empty)").toStdU16String().data() : name.toStdU16String().data()));
}
void MediaMenu::cdromMute(int i) {
cdrom[i].sound_on ^= 1;
config_save();
cdromUpdateMenu(i);
sound_cd_thread_reset();
}
void MediaMenu::cdromMount(int i) {
QString dir;
QFileInfo fi(cdrom[i].image_path);
auto filename = QFileDialog::getOpenFileName(
parentWidget,
QString(),
QString(),
tr("CD-ROM images") %
util::DlgFilter({ "iso","cue" }) %
tr("All files") %
util::DlgFilter({ "*" }, true));
if (filename.isEmpty()) {
return;
}
QByteArray fn = filename.toUtf8().data();
cdrom[i].prev_host_drive = cdrom[i].host_drive;
strcpy(cdrom[i].prev_image_path, cdrom[i].image_path);
if (cdrom[i].ops && cdrom[i].ops->exit)
cdrom[i].ops->exit(&(cdrom[i]));
cdrom[i].ops = nullptr;
memset(cdrom[i].image_path, 0, sizeof(cdrom[i].image_path));
cdrom_image_open(&(cdrom[i]), fn.data());
/* Signal media change to the emulated machine. */
if (cdrom[i].insert)
cdrom[i].insert(cdrom[i].priv);
cdrom[i].host_drive = (strlen(cdrom[i].image_path) == 0) ? 0 : 200;
if (cdrom[i].host_drive == 200) {
ui_sb_update_icon_state(SB_CDROM | i, 0);
} else {
ui_sb_update_icon_state(SB_CDROM | i, 1);
}
cdromUpdateMenu(i);
ui_sb_update_tip(SB_CDROM | i);
config_save();
}
void MediaMenu::cdromEject(int i) {
cdrom_eject(i);
cdromUpdateMenu(i);
ui_sb_update_tip(SB_CDROM | i);
}
void MediaMenu::cdromReload(int i) {
cdrom_reload(i);
cdromUpdateMenu(i);
ui_sb_update_tip(SB_CDROM | i);
}
void MediaMenu::cdromUpdateMenu(int i) {
QString name = cdrom[i].image_path;
auto* menu = cdromMenus[i];
auto childs = menu->children();
auto* muteMenu = dynamic_cast<QAction*>(childs[cdromMutePos]);
muteMenu->setChecked(cdrom[i].sound_on == 0);
auto* imageMenu = dynamic_cast<QAction*>(childs[cdromImagePos]);
auto* emptyMenu = dynamic_cast<QAction*>(childs[cdromEmptyPos]);
imageMenu->setChecked(cdrom[i].host_drive == 200);
emptyMenu->setChecked(cdrom[i].host_drive != 200);
auto* prevMenu = dynamic_cast<QAction*>(childs[cdromReloadPos]);
prevMenu->setEnabled(cdrom[i].prev_host_drive != 0);
QString busName = tr("Unknown Bus");
switch (cdrom[i].bus_type) {
case CDROM_BUS_ATAPI:
busName = "ATAPI";
break;
case CDROM_BUS_SCSI:
busName = "SCSI";
break;
}
//menu->setTitle(tr("CD-ROM %1 (%2): %3").arg(QString::number(i+1), busName, name.isEmpty() ? tr("(empty)") : name));
menu->setTitle(QString::asprintf(tr("CD-ROM %i (%s): %s").toUtf8().constData(), i + 1, busName.toUtf8().data(), name.isEmpty() ? tr("(empty)").toUtf8().data() : name.toUtf8().data()));
}
void MediaMenu::zipNewImage(int i) {
NewFloppyDialog dialog(NewFloppyDialog::MediaType::Zip, parentWidget);
switch (dialog.exec()) {
case QDialog::Accepted:
QByteArray filename = dialog.fileName().toUtf8();
zipMount(i, filename, false);
break;
}
}
void MediaMenu::zipSelectImage(int i, bool wp) {
auto filename = QFileDialog::getOpenFileName(
parentWidget,
QString(),
QString(),
tr("ZIP images") %
util::DlgFilter({ "im?","zdi" }) %
tr("All files") %
util::DlgFilter({ "*" }, true));
if (!filename.isEmpty()) zipMount(i, filename, wp);
}
void MediaMenu::zipMount(int i, const QString &filename, bool wp) {
zip_t *dev = (zip_t *) zip_drives[i].priv;
zip_disk_close(dev);
zip_drives[i].read_only = wp;
if (! filename.isEmpty()) {
QByteArray filenameBytes = filename.toUtf8();
zip_load(dev, filenameBytes.data());
zip_insert(dev);
}
ui_sb_update_icon_state(SB_ZIP | i, filename.isEmpty() ? 1 : 0);
zipUpdateMenu(i);
ui_sb_update_tip(SB_ZIP | i);
config_save();
}
void MediaMenu::zipEject(int i) {
zip_t *dev = (zip_t *) zip_drives[i].priv;
zip_disk_close(dev);
if (zip_drives[i].bus_type) {
/* Signal disk change to the emulated machine. */
zip_insert(dev);
}
ui_sb_update_icon_state(SB_ZIP | i, 1);
zipUpdateMenu(i);
ui_sb_update_tip(SB_ZIP | i);
config_save();
}
void MediaMenu::zipReload(int i) {
zip_t *dev = (zip_t *) zip_drives[i].priv;
zip_disk_reload(dev);
if (strlen(zip_drives[i].image_path) == 0) {
ui_sb_update_icon_state(SB_ZIP|i, 1);
} else {
ui_sb_update_icon_state(SB_ZIP|i, 0);
}
zipUpdateMenu(i);
ui_sb_update_tip(SB_ZIP|i);
config_save();
}
void MediaMenu::zipUpdateMenu(int i) {
QString name = zip_drives[i].image_path;
QString prev_name = zip_drives[i].prev_image_path;
auto* menu = zipMenus[i];
auto childs = menu->children();
auto* ejectMenu = dynamic_cast<QAction*>(childs[zipEjectPos]);
auto* reloadMenu = dynamic_cast<QAction*>(childs[zipReloadPos]);
ejectMenu->setEnabled(!name.isEmpty());
reloadMenu->setEnabled(!prev_name.isEmpty());
QString busName = tr("Unknown Bus");
switch (zip_drives[i].bus_type) {
case ZIP_BUS_ATAPI:
busName = "ATAPI";
break;
case ZIP_BUS_SCSI:
busName = "SCSI";
break;
}
//menu->setTitle(tr("ZIP %1 %2 (%3): %4").arg((zip_drives[i].is_250 > 0) ? "250" : "100", QString::number(i+1), busName, name.isEmpty() ? tr("(empty)") : name));
menu->setTitle(QString::asprintf(tr("ZIP %03i %i (%s): %ls").toUtf8().constData(), (zip_drives[i].is_250 > 0) ? 250 : 100, i + 1, busName.toUtf8().data(), name.isEmpty() ? tr("(empty)").toStdU16String().data() : name.toStdU16String().data()));
}
void MediaMenu::moNewImage(int i) {
NewFloppyDialog dialog(NewFloppyDialog::MediaType::Mo, parentWidget);
switch (dialog.exec()) {
case QDialog::Accepted:
QByteArray filename = dialog.fileName().toUtf8();
moMount(i, filename, false);
break;
}
}
void MediaMenu::moSelectImage(int i, bool wp) {
auto filename = QFileDialog::getOpenFileName(
parentWidget,
QString(),
QString(),
tr("MO images") %
util::DlgFilter({ "im?", "mdi" }) %
tr("All files") %
util::DlgFilter({ "*", }, true));
if (!filename.isEmpty()) moMount(i, filename, wp);
}
void MediaMenu::moMount(int i, const QString &filename, bool wp) {
mo_t *dev = (mo_t *) mo_drives[i].priv;
mo_disk_close(dev);
mo_drives[i].read_only = wp;
if (! filename.isEmpty()) {
QByteArray filenameBytes = filename.toUtf8();
mo_load(dev, filenameBytes.data());
mo_insert(dev);
}
ui_sb_update_icon_state(SB_MO | i, filename.isEmpty() ? 1 : 0);
moUpdateMenu(i);
ui_sb_update_tip(SB_MO | i);
config_save();
}
void MediaMenu::moEject(int i) {
mo_t *dev = (mo_t *) mo_drives[i].priv;
mo_disk_close(dev);
if (mo_drives[i].bus_type) {
/* Signal disk change to the emulated machine. */
mo_insert(dev);
}
ui_sb_update_icon_state(SB_MO | i, 1);
moUpdateMenu(i);
ui_sb_update_tip(SB_MO | i);
config_save();
}
void MediaMenu::moReload(int i) {
mo_t *dev = (mo_t *) mo_drives[i].priv;
mo_disk_reload(dev);
if (strlen(mo_drives[i].image_path) == 0) {
ui_sb_update_icon_state(SB_MO|i, 1);
} else {
ui_sb_update_icon_state(SB_MO|i, 0);
}
moUpdateMenu(i);
ui_sb_update_tip(SB_MO|i);
config_save();
}
void MediaMenu::moUpdateMenu(int i) {
QString name = mo_drives[i].image_path;
QString prev_name = mo_drives[i].prev_image_path;
auto* menu = moMenus[i];
auto childs = menu->children();
auto* ejectMenu = dynamic_cast<QAction*>(childs[moEjectPos]);
auto* reloadMenu = dynamic_cast<QAction*>(childs[moReloadPos]);
ejectMenu->setEnabled(!name.isEmpty());
reloadMenu->setEnabled(!prev_name.isEmpty());
QString busName = tr("Unknown Bus");
switch (mo_drives[i].bus_type) {
case MO_BUS_ATAPI:
busName = "ATAPI";
break;
case MO_BUS_SCSI:
busName = "SCSI";
break;
}
menu->setTitle(QString::asprintf(tr("MO %i (%ls): %ls").toUtf8().constData(), i + 1, busName.toStdU16String().data(), name.isEmpty() ? tr("(empty)").toStdU16String().data() : name.toStdU16String().data()));
}
// callbacks from 86box C code
extern "C" {
void zip_eject(uint8_t id) {
MediaMenu::ptr->zipEject(id);
}
void zip_reload(uint8_t id) {
MediaMenu::ptr->zipReload(id);
}
void mo_eject(uint8_t id) {
MediaMenu::ptr->moEject(id);
}
void mo_reload(uint8_t id) {
MediaMenu::ptr->moReload(id);
}
}

90
src/qt/qt_mediamenu.hpp Normal file
View File

@@ -0,0 +1,90 @@
#pragma once
#include <memory>
#include <QObject>
#include <QMap>
class QMenu;
class MediaMenu : QObject
{
Q_OBJECT
public:
MediaMenu(QWidget* parent);
void refresh(QMenu* parentMenu);
// because some 86box C-only code needs to call zip and
// mo eject directly
static std::shared_ptr<MediaMenu> ptr;
void cassetteNewImage();
void cassetteSelectImage(bool wp);
void cassetteMount(const QString& filename, bool wp);
void cassetteEject();
void cassetteUpdateMenu();
void cartridgeSelectImage(int i);
void cartridgeEject(int i);
void cartridgeUpdateMenu(int i);
void floppyNewImage(int i);
void floppySelectImage(int i, bool wp);
void floppyMount(int i, const QString& filename, bool wp);
void floppyEject(int i);
void floppyExportTo86f(int i);
void floppyUpdateMenu(int i);
void cdromMute(int i);
void cdromMount(int i);
void cdromEject(int i);
void cdromReload(int i);
void cdromUpdateMenu(int i);
void zipNewImage(int i);
void zipSelectImage(int i, bool wp);
void zipMount(int i, const QString& filename, bool wp);
void zipEject(int i);
void zipReload(int i);
void zipUpdateMenu(int i);
void moNewImage(int i);
void moSelectImage(int i, bool wp);
void moMount(int i, const QString& filename, bool wp);
void moEject(int i);
void moReload(int i);
void moUpdateMenu(int i);
private:
QWidget* parentWidget = nullptr;
QMenu* cassetteMenu = nullptr;
QMap<int, QMenu*> cartridgeMenus;
QMap<int, QMenu*> floppyMenus;
QMap<int, QMenu*> cdromMenus;
QMap<int, QMenu*> zipMenus;
QMap<int, QMenu*> moMenus;
int cassetteRecordPos;
int cassettePlayPos;
int cassetteRewindPos;
int cassetteFastFwdPos;
int cassetteEjectPos;
int cartridgeEjectPos;
int floppyExportPos;
int floppyEjectPos;
int cdromMutePos;
int cdromEmptyPos;
int cdromReloadPos;
int cdromImagePos;
int zipEjectPos;
int zipReloadPos;
int moEjectPos;
int moReloadPos;
friend class MachineStatus;
};

44
src/qt/qt_midi.cpp Normal file
View File

@@ -0,0 +1,44 @@
#include <cstdint>
extern "C" {
void plat_midi_play_msg(uint8_t *msg)
{}
void plat_midi_play_sysex(uint8_t *sysex, unsigned int len)
{}
void plat_midi_input_init(void)
{}
void plat_midi_input_close(void)
{}
int plat_midi_write(uint8_t val)
{ return 0; }
void plat_midi_init()
{}
void plat_midi_close()
{}
int plat_midi_get_num_devs()
{ return 0; }
int plat_midi_in_get_num_devs(void)
{ return 0; }
void plat_midi_get_dev_name(int num, char *s)
{
s[0] = ' ';
s[1] = 0;
}
void plat_midi_in_get_dev_name(int num, char *s)
{
s[0] = ' ';
s[1] = 0;
}
}

View File

@@ -0,0 +1,15 @@
#include "qt_models_common.hpp"
#include <QAbstractItemModel>
int Models::AddEntry(QAbstractItemModel *model, const QString& displayRole, int userRole)
{
int row = model->rowCount();
model->insertRow(row);
auto idx = model->index(row, 0);
model->setData(idx, displayRole, Qt::DisplayRole);
model->setData(idx, userRole, Qt::UserRole);
return row;
}

View File

@@ -0,0 +1,8 @@
#pragma once
class QString;
class QAbstractItemModel;
namespace Models
{
int AddEntry(QAbstractItemModel* model, const QString& displayRole, int userRole);
};

View File

@@ -0,0 +1,664 @@
#include "qt_newfloppydialog.hpp"
#include "ui_qt_newfloppydialog.h"
#include "qt_models_common.hpp"
#include "qt_util.hpp"
extern "C" {
#include <86box/random.h>
#include <86box/scsi_device.h>
#include <86box/zip.h>
#include <86box/mo.h>
}
#include <QFile>
#include <QFileInfo>
#include <QMessageBox>
#include <QProgressDialog>
#include <thread>
#include <QStringBuilder>
struct disk_size_t {
int hole;
int sides;
int data_rate;
int encoding;
int rpm;
int tracks;
int sectors; /* For IMG and Japanese FDI only. */
int sector_len; /* For IMG and Japanese FDI only. */
int media_desc;
int spc;
int num_fats;
int spfat;
int root_dir_entries;
};
static const disk_size_t disk_sizes[14] = { { 0, 1, 2, 1, 0, 40, 8, 2, 0xfe, 2, 2, 1, 64 }, /* 160k */
{ 0, 1, 2, 1, 0, 40, 9, 2, 0xfc, 2, 2, 1, 64 }, /* 180k */
{ 0, 2, 2, 1, 0, 40, 8, 2, 0xff, 2, 2, 1, 112 }, /* 320k */
{ 0, 2, 2, 1, 0, 40, 9, 2, 0xfd, 2, 2, 2, 112 }, /* 360k */
{ 0, 2, 2, 1, 0, 80, 8, 2, 0xfb, 2, 2, 2, 112 }, /* 640k */
{ 0, 2, 2, 1, 0, 80, 9, 2, 0xf9, 2, 2, 3, 112 }, /* 720k */
{ 1, 2, 0, 1, 1, 80, 15, 2, 0xf9, 1, 2, 7, 224 }, /* 1.2M */
{ 1, 2, 0, 1, 1, 77, 8, 3, 0xfe, 1, 2, 2, 192 }, /* 1.25M */
{ 1, 2, 0, 1, 0, 80, 18, 2, 0xf0, 1, 2, 9, 224 }, /* 1.44M */
{ 1, 2, 0, 1, 0, 80, 21, 2, 0xf0, 2, 2, 5, 16 }, /* DMF cluster 1024 */
{ 1, 2, 0, 1, 0, 80, 21, 2, 0xf0, 4, 2, 3, 16 }, /* DMF cluster 2048 */
{ 2, 2, 3, 1, 0, 80, 36, 2, 0xf0, 2, 2, 9, 240 }, /* 2.88M */
{ 0, 64, 0, 0, 0, 96, 32, 2, 0, 0, 0, 0, 0 }, /* ZIP 100 */
{ 0, 64, 0, 0, 0, 239, 32, 2, 0, 0, 0, 0, 0 } }; /* ZIP 250 */
static const QStringList rpmModes = {
"Perfect RPM",
"1%% below perfect RPM",
"1.5%% below perfect RPM",
"2%% below perfect RPM",
};
static const QStringList floppyTypes = {
"160 kB",
"180 kB",
"320 kB",
"360 kB",
"640 kB",
"720 kB",
"1.2 MB",
"1.25 MB",
"1.44 MB",
"DMF (cluster 1024)",
"DMF (cluster 2048)",
"2.88 MB",
};
static const QStringList zipTypes = {
"ZIP 100",
"ZIP 250",
};
static const QStringList moTypes = {
"3.5\" 128Mb M.O. (ISO 10090)",
"3.5\" 230Mb M.O. (ISO 13963)",
"3.5\" 540Mb M.O. (ISO 15498)",
"3.5\" 640Mb M.O. (ISO 15498)",
"3.5\" 1.3Gb M.O. (GigaMO)",
"3.5\" 2.3Gb M.O. (GigaMO 2)",
"5.25\" 600Mb M.O.",
"5.25\" 650Mb M.O.",
"5.25\" 1Gb M.O.",
"5.25\" 1.3Gb M.O.",
};
NewFloppyDialog::NewFloppyDialog(MediaType type, QWidget *parent) :
QDialog(parent),
ui(new Ui::NewFloppyDialog),
mediaType_(type)
{
ui->setupUi(this);
ui->fileField->setCreateFile(true);
auto* model = ui->comboBoxSize->model();
switch (type) {
case MediaType::Floppy:
for (int i = 0; i < floppyTypes.size(); ++i) {
Models::AddEntry(model, tr(floppyTypes[i].toUtf8().data()), i);
}
ui->fileField->setFilter(
tr("All images") %
util::DlgFilter({ "86f","dsk","flp","im?","*fd?" }) %
tr("Basic sector images") %
util::DlgFilter({ "dsk","flp","im?","img","*fd?" }) %
tr("Surface images") %
util::DlgFilter({ "86f" }, true));
break;
case MediaType::Zip:
for (int i = 0; i < zipTypes.size(); ++i) {
Models::AddEntry(model, tr(zipTypes[i].toUtf8().data()), i);
}
ui->fileField->setFilter(tr("ZIP images") % util::DlgFilter({ "im?","zdi" }, true));
break;
case MediaType::Mo:
for (int i = 0; i < moTypes.size(); ++i) {
Models::AddEntry(model, tr(moTypes[i].toUtf8().data()), i);
}
ui->fileField->setFilter(tr("MO images") % util::DlgFilter({ "im?","mdi" }) % tr("All files") % util::DlgFilter({ "*" }, true));
break;
}
model = ui->comboBoxRpm->model();
for (int i = 0; i < rpmModes.size(); ++i) {
Models::AddEntry(model, tr(rpmModes[i].toUtf8().data()).replace("%%", "%"), i);
}
connect(ui->fileField, &FileField::fileSelected, this, [this](const QString& filename) {
bool hide = true;
if (mediaType_ == MediaType::Floppy) {
if (QFileInfo(filename).suffix().toLower() == QStringLiteral("86f")) {
hide = false;
}
}
ui->labelRpm->setHidden(hide);
ui->comboBoxRpm->setHidden(hide);
});
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &NewFloppyDialog::onCreate);
ui->labelRpm->setHidden(true);
ui->comboBoxRpm->setHidden(true);
}
NewFloppyDialog::~NewFloppyDialog() {
delete ui;
}
QString NewFloppyDialog::fileName() const{
return ui->fileField->fileName();
}
void NewFloppyDialog::onCreate() {
auto filename = ui->fileField->fileName();
QFileInfo fi(filename);
FileType fileType;
QProgressDialog progress("Creating floppy image", QString(), 0, 100, this);
connect(this, &NewFloppyDialog::fileProgress, &progress, &QProgressDialog::setValue);
switch (mediaType_) {
case MediaType::Floppy:
if (fi.suffix().toLower() == QStringLiteral("86f")) {
if (create86f(filename, disk_sizes[ui->comboBoxSize->currentIndex()], ui->comboBoxRpm->currentIndex())) {
return;
}
} else {
fileType = fi.suffix().toLower() == QStringLiteral("zdi") ? FileType::Fdi : FileType::Img;
if (createSectorImage(filename, disk_sizes[ui->comboBoxSize->currentIndex()], fileType)) {
return;
}
}
break;
case MediaType::Zip:
{
fileType = fi.suffix().toLower() == QStringLiteral("zdi") ? FileType::Zdi: FileType::Img;
std::atomic_bool res;
std::thread t([this, &res, filename, fileType, &progress] {
res = createZipSectorImage(filename, disk_sizes[ui->comboBoxSize->currentIndex() + 12], fileType, progress);
});
progress.exec();
t.join();
if (res) {
return;
}
}
break;
case MediaType::Mo:
{
fileType = fi.suffix().toLower() == QStringLiteral("mdi") ? FileType::Mdi: FileType::Img;
std::atomic_bool res;
std::thread t([this, &res, filename, fileType, &progress] {
res = createMoSectorImage(filename, ui->comboBoxSize->currentIndex(), fileType, progress);
});
progress.exec();
t.join();
if (res) {
return;
}
}
break;
}
QMessageBox::critical(this, tr("Unable to write file"), tr("Make sure the file is being saved to a writable directory"));
reject();
}
bool NewFloppyDialog::create86f(const QString& filename, const disk_size_t& disk_size, uint8_t rpm_mode)
{
uint32_t magic = 0x46423638;
uint16_t version = 0x020C;
uint16_t dflags = 0;
uint16_t tflags = 0;
uint32_t index_hole_pos = 0;
uint32_t tarray[512];
uint32_t array_size;
uint32_t track_base, track_size;
int i;
uint32_t shift = 0;
dflags = 0; /* Has surface data? - Assume no for now. */
dflags |= (disk_size.hole << 1); /* Hole */
dflags |= ((disk_size.sides - 1) << 3); /* Sides. */
dflags |= (0 << 4); /* Write protect? - Assume no for now. */
dflags |= (rpm_mode << 5); /* RPM mode. */
dflags |= (0 << 7); /* Has extra bit cells? - Assume no for now. */
tflags = disk_size.data_rate; /* Data rate. */
tflags |= (disk_size.encoding << 3); /* Encoding. */
tflags |= (disk_size.rpm << 5); /* RPM. */
switch (disk_size.hole) {
case 0:
case 1:
default:
switch(rpm_mode) {
case 1:
array_size = 25250;
break;
case 2:
array_size = 25374;
break;
case 3:
array_size = 25750;
break;
default:
array_size = 25000;
break;
}
break;
case 2:
switch(rpm_mode) {
case 1:
array_size = 50500;
break;
case 2:
array_size = 50750;
break;
case 3:
array_size = 51000;
break;
default:
array_size = 50000;
break;
}
break;
}
QByteArray bytes(array_size, 0);
memset(tarray, 0, 2048);
QFile file(filename);
if (! file.open(QIODevice::WriteOnly)) {
return false;
}
QDataStream stream(&file);
stream.setByteOrder(QDataStream::LittleEndian);
stream << magic;
stream << version;
stream << dflags;
track_size = array_size + 6;
track_base = 8 + ((disk_size.sides == 2) ? 2048 : 1024);
if (disk_size.tracks <= 43)
shift = 1;
for (i = 0; i < (disk_size.tracks * disk_size.sides) << shift; i++)
tarray[i] = track_base + (i * track_size);
stream.writeRawData(reinterpret_cast<const char *>(tarray), (disk_size.sides == 2) ? 2048 : 1024);
int max = i < (disk_size.tracks * disk_size.sides) << shift;
for (i = 0; i < max; i++) {
stream << tflags;
stream << index_hole_pos;
stream.writeRawData(bytes, bytes.size());
}
return true;
}
bool NewFloppyDialog::createSectorImage(const QString &filename, const disk_size_t& disk_size, FileType type)
{
uint32_t total_size = 0;
uint32_t total_sectors = 0;
uint32_t sector_bytes = 0;
uint32_t root_dir_bytes = 0;
uint32_t fat_size = 0;
uint32_t fat1_offs = 0;
uint32_t fat2_offs = 0;
uint32_t zero_bytes = 0;
uint16_t base = 0x1000;
QFile file(filename);
if (! file.open(QIODevice::WriteOnly)) {
return false;
}
QDataStream stream(&file);
stream.setByteOrder(QDataStream::LittleEndian);
sector_bytes = (128 << disk_size.sector_len);
total_sectors = disk_size.sides * disk_size.tracks * disk_size.sectors;
if (total_sectors > ZIP_SECTORS)
total_sectors = ZIP_250_SECTORS;
total_size = total_sectors * sector_bytes;
root_dir_bytes = (disk_size.root_dir_entries << 5);
fat_size = (disk_size.spfat * sector_bytes);
fat1_offs = sector_bytes;
fat2_offs = fat1_offs + fat_size;
zero_bytes = fat2_offs + fat_size + root_dir_bytes;
if (type == FileType::Fdi) {
QByteArray bytes(base, 0);
auto empty = bytes.data();
*(uint32_t *) &(empty[0x08]) = (uint32_t) base;
*(uint32_t *) &(empty[0x0C]) = total_size;
*(uint16_t *) &(empty[0x10]) = (uint16_t) sector_bytes;
*(uint8_t *) &(empty[0x14]) = (uint8_t) disk_size.sectors;
*(uint8_t *) &(empty[0x18]) = (uint8_t) disk_size.sides;
*(uint8_t *) &(empty[0x1C]) = (uint8_t) disk_size.tracks;
stream.writeRawData(empty, base);
}
QByteArray bytes(total_size, 0);
auto empty = bytes.data();
memset(empty + zero_bytes, 0xF6, total_size - zero_bytes);
empty[0x00] = 0xEB; /* Jump to make MS-DOS happy. */
empty[0x01] = 0x58;
empty[0x02] = 0x90;
empty[0x03] = 0x38; /* '86BOX5.0' OEM ID. */
empty[0x04] = 0x36;
empty[0x05] = 0x42;
empty[0x06] = 0x4F;
empty[0x07] = 0x58;
empty[0x08] = 0x35;
empty[0x09] = 0x2E;
empty[0x0A] = 0x30;
*(uint16_t *) &(empty[0x0B]) = (uint16_t) sector_bytes;
*(uint8_t *) &(empty[0x0D]) = (uint8_t) disk_size.spc;
*(uint16_t *) &(empty[0x0E]) = (uint16_t) 1;
*(uint8_t *) &(empty[0x10]) = (uint8_t) disk_size.num_fats;
*(uint16_t *) &(empty[0x11]) = (uint16_t) disk_size.root_dir_entries;
*(uint16_t *) &(empty[0x13]) = (uint16_t) total_sectors;
*(uint8_t *) &(empty[0x15]) = (uint8_t) disk_size.media_desc;
*(uint16_t *) &(empty[0x16]) = (uint16_t) disk_size.spfat;
*(uint8_t *) &(empty[0x18]) = (uint8_t) disk_size.sectors;
*(uint8_t *) &(empty[0x1A]) = (uint8_t) disk_size.sides;
empty[0x26] = 0x29; /* ')' followed by randomly-generated volume serial number. */
empty[0x27] = random_generate();
empty[0x28] = random_generate();
empty[0x29] = random_generate();
empty[0x2A] = random_generate();
memset(&(empty[0x2B]), 0x20, 11);
empty[0x36] = 'F';
empty[0x37] = 'A';
empty[0x38] = 'T';
empty[0x39] = '1';
empty[0x3A] = '2';
memset(&(empty[0x3B]), 0x20, 0x0003);
empty[0x1FE] = 0x55;
empty[0x1FF] = 0xAA;
empty[fat1_offs + 0x00] = empty[fat2_offs + 0x00] = empty[0x15];
empty[fat1_offs + 0x01] = empty[fat2_offs + 0x01] = 0xFF;
empty[fat1_offs + 0x02] = empty[fat2_offs + 0x02] = 0xFF;
stream.writeRawData(empty, total_size);
return true;
}
bool NewFloppyDialog::createZipSectorImage(const QString &filename, const disk_size_t& disk_size, FileType type, QProgressDialog& pbar)
{
uint32_t total_size = 0;
uint32_t total_sectors = 0;
uint32_t sector_bytes = 0;
uint32_t root_dir_bytes = 0;
uint32_t fat_size = 0;
uint32_t fat1_offs = 0;
uint32_t fat2_offs = 0;
uint32_t zero_bytes = 0;
uint16_t base = 0x1000;
uint32_t pbar_max = 0;
QFile file(filename);
if (! file.open(QIODevice::WriteOnly)) {
return false;
}
QDataStream stream(&file);
stream.setByteOrder(QDataStream::LittleEndian);
sector_bytes = (128 << disk_size.sector_len);
total_sectors = disk_size.sides * disk_size.tracks * disk_size.sectors;
if (total_sectors > ZIP_SECTORS)
total_sectors = ZIP_250_SECTORS;
total_size = total_sectors * sector_bytes;
root_dir_bytes = (disk_size.root_dir_entries << 5);
fat_size = (disk_size.spfat * sector_bytes);
fat1_offs = sector_bytes;
fat2_offs = fat1_offs + fat_size;
zero_bytes = fat2_offs + fat_size + root_dir_bytes;
pbar_max = total_size;
if (type == FileType::Zdi) {
pbar_max += base;
}
pbar_max >>= 11;
if (type == FileType::Zdi) {
QByteArray data(base, 0);
auto empty = data.data();
*(uint32_t *) &(empty[0x08]) = (uint32_t) base;
*(uint32_t *) &(empty[0x0C]) = total_size;
*(uint16_t *) &(empty[0x10]) = (uint16_t) sector_bytes;
*(uint8_t *) &(empty[0x14]) = (uint8_t) disk_size.sectors;
*(uint8_t *) &(empty[0x18]) = (uint8_t) disk_size.sides;
*(uint8_t *) &(empty[0x1C]) = (uint8_t) disk_size.tracks;
stream.writeRawData(empty, base);
pbar_max -= 2;
}
QByteArray bytes(total_size, 0);
auto empty = bytes.data();
if (total_sectors == ZIP_SECTORS) {
/* ZIP 100 */
/* MBR */
*(uint64_t *) &(empty[0x0000]) = 0x2054524150492EEBLL;
*(uint64_t *) &(empty[0x0008]) = 0x3930302065646F63LL;
*(uint64_t *) &(empty[0x0010]) = 0x67656D6F49202D20LL;
*(uint64_t *) &(empty[0x0018]) = 0x726F70726F432061LL;
*(uint64_t *) &(empty[0x0020]) = 0x202D206E6F697461LL;
*(uint64_t *) &(empty[0x0028]) = 0x30392F33322F3131LL;
*(uint64_t *) &(empty[0x01AE]) = 0x0116010100E90644LL;
*(uint64_t *) &(empty[0x01B6]) = 0xED08BBE5014E0135LL;
*(uint64_t *) &(empty[0x01BE]) = 0xFFFFFE06FFFFFE80LL;
*(uint64_t *) &(empty[0x01C6]) = 0x0002FFE000000020LL;
*(uint16_t *) &(empty[0x01FE]) = 0xAA55;
/* 31 sectors filled with 0x48 */
memset(&(empty[0x0200]), 0x48, 0x3E00);
/* Boot sector */
*(uint64_t *) &(empty[0x4000]) = 0x584F4236389058EBLL;
*(uint64_t *) &(empty[0x4008]) = 0x0008040200302E35LL;
*(uint64_t *) &(empty[0x4010]) = 0x00C0F80000020002LL;
*(uint64_t *) &(empty[0x4018]) = 0x0000002000FF003FLL;
*(uint32_t *) &(empty[0x4020]) = 0x0002FFE0;
*(uint16_t *) &(empty[0x4024]) = 0x0080;
empty[0x4026] = 0x29; /* ')' followed by randomly-generated volume serial number. */
empty[0x4027] = random_generate();
empty[0x4028] = random_generate();
empty[0x4029] = random_generate();
empty[0x402A] = random_generate();
memset(&(empty[0x402B]), 0x00, 0x000B);
memset(&(empty[0x4036]), 0x20, 0x0008);
empty[0x4036] = 'F';
empty[0x4037] = 'A';
empty[0x4038] = 'T';
empty[0x4039] = '1';
empty[0x403A] = '6';
memset(&(empty[0x403B]), 0x20, 0x0003);
empty[0x41FE] = 0x55;
empty[0x41FF] = 0xAA;
empty[0x5000] = empty[0x1D000] = empty[0x4015];
empty[0x5001] = empty[0x1D001] = 0xFF;
empty[0x5002] = empty[0x1D002] = 0xFF;
empty[0x5003] = empty[0x1D003] = 0xFF;
/* Root directory = 0x35000
Data = 0x39000 */
} else {
/* ZIP 250 */
/* MBR */
*(uint64_t *) &(empty[0x0000]) = 0x2054524150492EEBLL;
*(uint64_t *) &(empty[0x0008]) = 0x3930302065646F63LL;
*(uint64_t *) &(empty[0x0010]) = 0x67656D6F49202D20LL;
*(uint64_t *) &(empty[0x0018]) = 0x726F70726F432061LL;
*(uint64_t *) &(empty[0x0020]) = 0x202D206E6F697461LL;
*(uint64_t *) &(empty[0x0028]) = 0x30392F33322F3131LL;
*(uint64_t *) &(empty[0x01AE]) = 0x0116010100E900E9LL;
*(uint64_t *) &(empty[0x01B6]) = 0x2E32A7AC014E0135LL;
*(uint64_t *) &(empty[0x01EE]) = 0xEE203F0600010180LL;
*(uint64_t *) &(empty[0x01F6]) = 0x000777E000000020LL;
*(uint16_t *) &(empty[0x01FE]) = 0xAA55;
/* 31 sectors filled with 0x48 */
memset(&(empty[0x0200]), 0x48, 0x3E00);
/* The second sector begins with some strange data
in my reference image. */
*(uint64_t *) &(empty[0x0200]) = 0x3831393230334409LL;
*(uint64_t *) &(empty[0x0208]) = 0x6A57766964483130LL;
*(uint64_t *) &(empty[0x0210]) = 0x3C3A34676063653FLL;
*(uint64_t *) &(empty[0x0218]) = 0x586A56A8502C4161LL;
*(uint64_t *) &(empty[0x0220]) = 0x6F2D702535673D6CLL;
*(uint64_t *) &(empty[0x0228]) = 0x255421B8602D3456LL;
*(uint64_t *) &(empty[0x0230]) = 0x577B22447B52603ELL;
*(uint64_t *) &(empty[0x0238]) = 0x46412CC871396170LL;
*(uint64_t *) &(empty[0x0240]) = 0x704F55237C5E2626LL;
*(uint64_t *) &(empty[0x0248]) = 0x6C7932C87D5C3C20LL;
*(uint64_t *) &(empty[0x0250]) = 0x2C50503E47543D6ELL;
*(uint64_t *) &(empty[0x0258]) = 0x46394E807721536ALL;
*(uint64_t *) &(empty[0x0260]) = 0x505823223F245325LL;
*(uint64_t *) &(empty[0x0268]) = 0x365C79B0393B5B6ELL;
/* Boot sector */
*(uint64_t *) &(empty[0x4000]) = 0x584F4236389058EBLL;
*(uint64_t *) &(empty[0x4008]) = 0x0001080200302E35LL;
*(uint64_t *) &(empty[0x4010]) = 0x00EFF80000020002LL;
*(uint64_t *) &(empty[0x4018]) = 0x0000002000400020LL;
*(uint32_t *) &(empty[0x4020]) = 0x000777E0;
*(uint16_t *) &(empty[0x4024]) = 0x0080;
empty[0x4026] = 0x29; /* ')' followed by randomly-generated volume serial number. */
empty[0x4027] = random_generate();
empty[0x4028] = random_generate();
empty[0x4029] = random_generate();
empty[0x402A] = random_generate();
memset(&(empty[0x402B]), 0x00, 0x000B);
memset(&(empty[0x4036]), 0x20, 0x0008);
empty[0x4036] = 'F';
empty[0x4037] = 'A';
empty[0x4038] = 'T';
empty[0x4039] = '1';
empty[0x403A] = '6';
memset(&(empty[0x403B]), 0x20, 0x0003);
empty[0x41FE] = 0x55;
empty[0x41FF] = 0xAA;
empty[0x4200] = empty[0x22000] = empty[0x4015];
empty[0x4201] = empty[0x22001] = 0xFF;
empty[0x4202] = empty[0x22002] = 0xFF;
empty[0x4203] = empty[0x22003] = 0xFF;
/* Root directory = 0x3FE00
Data = 0x38200 */
}
pbar.setMaximum(pbar_max);
for (uint32_t i = 0; i < pbar_max; i++) {
stream.writeRawData(&empty[i << 11], 2048);
fileProgress(i);
}
fileProgress(pbar_max);
return true;
}
bool NewFloppyDialog::createMoSectorImage(const QString& filename, int8_t disk_size, FileType type, QProgressDialog& pbar)
{
const mo_type_t *dp = &mo_types[disk_size];
uint32_t total_size = 0, total_size2;
uint32_t total_sectors = 0;
uint32_t sector_bytes = 0;
uint16_t base = 0x1000;
uint32_t pbar_max = 0, blocks_num;
QFile file(filename);
if (! file.open(QIODevice::WriteOnly)) {
return false;
}
QDataStream stream(&file);
stream.setByteOrder(QDataStream::LittleEndian);
sector_bytes = dp->bytes_per_sector;
total_sectors = dp->sectors;
total_size = total_sectors * sector_bytes;
total_size2 = (total_size >> 20) << 20;
total_size2 = total_size - total_size2;
pbar_max = total_size;
pbar_max >>= 20;
blocks_num = pbar_max;
if (type == FileType::Mdi)
pbar_max++;
if (total_size2 == 0)
pbar_max++;
if (type == FileType::Mdi) {
QByteArray bytes(base, 0);
auto empty = bytes.data();
*(uint32_t *) &(empty[0x08]) = (uint32_t) base;
*(uint32_t *) &(empty[0x0C]) = total_size;
*(uint16_t *) &(empty[0x10]) = (uint16_t) sector_bytes;
*(uint8_t *) &(empty[0x14]) = (uint8_t) 25;
*(uint8_t *) &(empty[0x18]) = (uint8_t) 64;
*(uint8_t *) &(empty[0x1C]) = (uint8_t) (dp->sectors / 64) / 25;
stream.writeRawData(empty, base);
}
QByteArray bytes(1048576, 0);
auto empty = bytes.data();
pbar.setMaximum(blocks_num);
for (uint32_t i = 0; i < blocks_num; i++) {
stream.writeRawData(empty, bytes.size());
fileProgress(i);
}
if (total_size2 > 0) {
QByteArray extra_bytes(total_size2, 0);
stream.writeRawData(extra_bytes.data(), total_size2);
}
fileProgress(blocks_num);
return true;
}

View File

@@ -0,0 +1,50 @@
#ifndef QT_NEWFLOPPYDIALOG_HPP
#define QT_NEWFLOPPYDIALOG_HPP
#include <QDialog>
namespace Ui {
class NewFloppyDialog;
}
struct disk_size_t;
class QProgressDialog;
class NewFloppyDialog : public QDialog
{
Q_OBJECT
public:
enum class MediaType {
Floppy,
Zip,
Mo,
};
enum class FileType {
Img,
Fdi,
Zdi,
Mdi,
};
explicit NewFloppyDialog(MediaType type, QWidget *parent = nullptr);
~NewFloppyDialog();
QString fileName() const;
signals:
void fileProgress(int i);
private slots:
void onCreate();
private:
Ui::NewFloppyDialog *ui;
MediaType mediaType_;
bool create86f(const QString& filename, const disk_size_t& disk_size, uint8_t rpm_mode);
bool createSectorImage(const QString& filename, const disk_size_t& disk_size, FileType type);
bool createZipSectorImage(const QString& filename, const disk_size_t& disk_size, FileType type, QProgressDialog& pbar);
bool createMoSectorImage(const QString& filename, int8_t disk_size, FileType type, QProgressDialog& pbar);
};
#endif // QT_NEWFLOPPYDIALOG_HPP

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewFloppyDialog</class>
<widget class="QDialog" name="NewFloppyDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>327</width>
<height>200</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>327</width>
<height>200</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>327</width>
<height>200</height>
</size>
</property>
<property name="windowTitle">
<string>New Image</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>File name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="FileField" name="fileField" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Disk size:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBoxSize">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelRpm">
<property name="text">
<string>RPM mode:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="comboBoxRpm">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="3" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FileField</class>
<extends>QWidget</extends>
<header>qt_filefield.hpp</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>NewFloppyDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>NewFloppyDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

533
src/qt/qt_platform.cpp Normal file
View File

@@ -0,0 +1,533 @@
#include <cstdio>
#include <mutex>
#include <thread>
#include <memory>
#include <algorithm>
#include <map>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QTemporaryFile>
#include <QCoreApplication>
#include <QDateTime>
#include <QLibrary>
#include <QElapsedTimer>
#include "qt_mainwindow.hpp"
#include "qt_progsettings.hpp"
#ifdef Q_OS_UNIX
#include <sys/mman.h>
#endif
// static QByteArray buf;
extern QElapsedTimer elapsed_timer;
extern MainWindow* main_window;
QElapsedTimer elapsed_timer;
static std::atomic_int blitmx_contention = 0;
static std::mutex blitmx;
class CharPointer {
public:
CharPointer(char* buf, int size) : b(buf), s(size) {}
CharPointer& operator=(const QByteArray &ba) {
if (s > 0) {
strncpy(b, ba.data(), s-1);
b[s] = 0;
} else {
// if we haven't been told the length of b, just assume enough
// because we didn't get it from emulator code
strcpy(b, ba.data());
b[ba.size()] = 0;
}
return *this;
}
private:
char* b;
int s;
};
extern "C" {
#ifdef Q_OS_WINDOWS
#define NOMINMAX
#include <windows.h>
#include <86box/win.h>
#else
#include <strings.h>
#endif
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/gameport.h>
#include <86box/timer.h>
#include <86box/nvr.h>
#include <86box/plat_dynld.h>
#include <86box/config.h>
#include <86box/ui.h>
#include <86box/discord.h>
#include "../cpu/cpu.h"
#include <86box/plat.h>
volatile int cpu_thread_run = 1;
int mouse_capture = 0;
int fixed_size_x = 640;
int fixed_size_y = 480;
int rctrl_is_lalt = 0;
int update_icons = 0;
int kbd_req_capture = 0;
int hide_status_bar = 0;
int hide_tool_bar = 0;
uint32_t lang_id = 0x0409, lang_sys = 0x0409; // Multilangual UI variables, for now all set to LCID of en-US
int stricmp(const char* s1, const char* s2)
{
#ifdef Q_OS_WINDOWS
return _stricmp(s1, s2);
#else
return strcasecmp(s1, s2);
#endif
}
int strnicmp(const char *s1, const char *s2, size_t n)
{
#ifdef Q_OS_WINDOWS
return _strnicmp(s1, s2, n);
#else
return strncasecmp(s1, s2, n);
#endif
}
void
do_stop(void)
{
cpu_thread_run = 0;
//main_window->close();
}
void plat_get_exe_name(char *s, int size)
{
QByteArray exepath_temp = QCoreApplication::applicationDirPath().toLocal8Bit();
memcpy(s, exepath_temp.data(), std::min((qsizetype)exepath_temp.size(),(qsizetype)size));
plat_path_slash(s);
}
uint32_t
plat_get_ticks(void)
{
return elapsed_timer.elapsed();
}
uint64_t
plat_timer_read(void)
{
return elapsed_timer.elapsed();
}
FILE *
plat_fopen(const char *path, const char *mode)
{
return fopen(QString::fromUtf8(path).toLocal8Bit(), mode);
}
FILE *
plat_fopen64(const char *path, const char *mode)
{
return fopen(path, mode);
}
int
plat_dir_create(char *path)
{
return QDir().mkdir(path) ? 0 : -1;
}
int
plat_dir_check(char *path)
{
QFileInfo fi(path);
return fi.isDir() ? 1 : 0;
}
int
plat_getcwd(char *bufp, int max)
{
CharPointer(bufp, max) = QDir::currentPath().toUtf8();
return 0;
}
void
plat_get_dirname(char *dest, const char *path)
{
QFileInfo fi(path);
CharPointer(dest, -1) = fi.dir().path().toUtf8();
}
char *
plat_get_extension(char *s)
{
auto len = strlen(s);
auto idx = QByteArray::fromRawData(s, len).lastIndexOf('.');
if (idx >= 0) {
return s+idx+1;
}
return s+len;
}
char *
plat_get_filename(char *s)
{
#ifdef Q_OS_WINDOWS
int c = strlen(s) - 1;
while (c > 0) {
if (s[c] == '/' || s[c] == '\\')
return(&s[c+1]);
c--;
}
return(s);
#else
auto idx = QByteArray::fromRawData(s, strlen(s)).lastIndexOf(QDir::separator().toLatin1());
if (idx >= 0) {
return s+idx+1;
}
return s;
#endif
}
int
plat_path_abs(char *path)
{
QFileInfo fi(path);
return fi.isAbsolute() ? 1 : 0;
}
void
plat_path_slash(char *path)
{
auto len = strlen(path);
auto separator = QDir::separator().toLatin1();
if (path[len-1] != separator) {
path[len] = separator;
path[len+1] = 0;
}
}
void
plat_append_filename(char *dest, const char *s1, const char *s2)
{
strcpy(dest, s1);
plat_path_slash(dest);
strcat(dest, s2);
}
void
plat_tempfile(char *bufp, char *prefix, char *suffix)
{
QString name;
if (prefix != nullptr) {
name.append(QString("%1-").arg(prefix));
}
name.append(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss-zzzz"));
if (suffix) name.append(suffix);
sprintf(&bufp[strlen(bufp)], "%s", name.toUtf8().data());
}
void plat_remove(char* path)
{
QFile(path).remove();
}
void *
plat_mmap(size_t size, uint8_t executable)
{
#if defined Q_OS_WINDOWS
return VirtualAlloc(NULL, size, MEM_COMMIT, executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE);
#elif defined Q_OS_UNIX
#if defined Q_OS_DARWIN && defined MAP_JIT
void *ret = mmap(0, size, PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0), MAP_ANON | MAP_PRIVATE | (executable ? MAP_JIT : 0), -1, 0);
#else
void *ret = mmap(0, size, PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0), MAP_ANON | MAP_PRIVATE, -1, 0);
#endif
return (ret == MAP_FAILED) ? nullptr : ret;
#endif
}
void
plat_munmap(void *ptr, size_t size)
{
#if defined Q_OS_WINDOWS
VirtualFree(ptr, 0, MEM_RELEASE);
#else
munmap(ptr, size);
#endif
}
void
plat_pause(int p)
{
static wchar_t oldtitle[512];
wchar_t title[512];
if ((p == 0) && (time_sync & TIME_SYNC_ENABLED))
nvr_time_sync();
dopause = p;
if (p) {
wcsncpy(oldtitle, ui_window_title(NULL), sizeof_w(oldtitle) - 1);
wcscpy(title, oldtitle);
wcscat(title, L" - PAUSED -");
ui_window_title(title);
} else {
ui_window_title(oldtitle);
}
discord_update_activity(dopause);
#ifdef Q_OS_WINDOWS
if (source_hwnd)
PostMessage((HWND)(uintptr_t)source_hwnd, WM_SENDSTATUS, (WPARAM)!!p, (LPARAM)(HWND)main_window->winId());
#endif
}
// because we can't include nvr.h because it's got fields named new
extern int nvr_save(void);
void
plat_power_off(void)
{
confirm_exit = 0;
nvr_save();
config_save();
/* Deduct a sufficiently large number of cycles that no instructions will
run before the main thread is terminated */
cycles -= 99999999;
cpu_thread_run = 0;
main_window->close();
}
void set_language(uint32_t id) {
lang_id = id;
}
extern "C++"
{
QMap<uint32_t, QPair<QString, QString>> ProgSettings::lcid_langcode =
{
{0x0405, {"cs-CZ", "Czech (Czech Republic)"} },
{0x0407, {"de-DE", "German (Germany)"} },
{0x0408, {"en-US", "English (United States)"} },
{0x0809, {"en-GB", "English (United Kingdom)"} },
{0x0C0A, {"es-ES", "Spanish (Spain)"} },
{0x040B, {"fi-FI", "Finnish (Finland)"} },
{0x040C, {"fr-FR", "French (France)"} },
{0x041A, {"hr-HR", "Croatian (Croatia)"} },
{0x040E, {"hu-HU", "Hungarian (Hungary)"} },
{0x0410, {"it-IT", "Italian (Italy)"} },
{0x0411, {"ja-JP", "Japanese (Japan)"} },
{0x0412, {"ko-KR", "Korean (Korea)"} },
{0x0415, {"pl-PL", "Polish (Poland)"} },
{0x0416, {"pt-BR", "Portuguese (Brazil)"} },
{0x0816, {"pt-PT", "Portuguese (Portugal)"} },
{0x0419, {"ru-RU", "Russian (Russia)"} },
{0x0424, {"sl-SI", "Slovenian (Slovenia)"} },
{0x041F, {"tr-TR", "Turkish (Turkey)"} },
{0x0422, {"uk-UA", "Ukrainian (Ukraine)"} },
{0x0804, {"zh-CN", "Chinese (China)"} },
{0xFFFF, {"system", "(System Default)"} },
};
}
/* Sets up the program language before initialization. */
uint32_t plat_language_code(char* langcode) {
for (auto& curKey : ProgSettings::lcid_langcode.keys())
{
if (ProgSettings::lcid_langcode[curKey].first == langcode)
{
return curKey;
}
}
return 0xFFFF;
}
/* Converts back the language code to LCID */
void plat_language_code_r(uint32_t lcid, char* outbuf, int len) {
if (!ProgSettings::lcid_langcode.contains(lcid))
{
qstrncpy(outbuf, "system", len);
return;
}
qstrncpy(outbuf, ProgSettings::lcid_langcode[lcid].first.toUtf8().constData(), len);
return;
}
void* dynld_module(const char *name, dllimp_t *table)
{
QString libraryName = name;
QFileInfo fi(libraryName);
QStringList removeSuffixes = {"dll", "dylib", "so"};
if (removeSuffixes.contains(fi.suffix())) {
libraryName = fi.completeBaseName();
}
auto lib = std::unique_ptr<QLibrary>(new QLibrary(libraryName));
if (lib->load()) {
for (auto imp = table; imp->name != nullptr; imp++)
{
auto ptr = lib->resolve(imp->name);
if (ptr == nullptr) {
return nullptr;
}
auto imp_ptr = reinterpret_cast<void**>(imp->func);
*imp_ptr = reinterpret_cast<void*>(ptr);
}
} else {
return nullptr;
}
return lib.release();
}
void dynld_close(void *handle)
{
delete reinterpret_cast<QLibrary*>(handle);
}
void startblit()
{
blitmx_contention++;
if (blitmx.try_lock()) {
return;
}
blitmx.lock();
}
void endblit()
{
blitmx_contention--;
blitmx.unlock();
if (blitmx_contention > 0) {
// a deadlock has been observed on linux when toggling via video_toggle_option
// because the mutex is typically unfair on linux
// => sleep if there's contention
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
}
#ifdef Q_OS_WINDOWS
size_t mbstoc16s(uint16_t dst[], const char src[], int len)
{
if (src == NULL) return 0;
if (len < 0) return 0;
size_t ret = MultiByteToWideChar(CP_UTF8, 0, src, -1, reinterpret_cast<LPWSTR>(dst), dst == NULL ? 0 : len);
if (!ret) {
return -1;
}
return ret;
}
size_t c16stombs(char dst[], const uint16_t src[], int len)
{
if (src == NULL) return 0;
if (len < 0) return 0;
size_t ret = WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast<LPCWCH>(src), -1, dst, dst == NULL ? 0 : len, NULL, NULL);
if (!ret) {
return -1;
}
return ret;
}
#endif
#ifdef _WIN32
#define LIB_NAME_FLUIDSYNTH "libfluidsynth.dll"
#define LIB_NAME_GS "gsdll32.dll"
#define LIB_NAME_FREETYPE "freetype.dll"
#define MOUSE_CAPTURE_KEYSEQ "F8+F12"
#else
#define LIB_NAME_FLUIDSYNTH "libfluidsynth"
#define LIB_NAME_GS "libgs"
#define LIB_NAME_FREETYPE "libfreetype"
#define MOUSE_CAPTURE_KEYSEQ "CTRL-END"
#endif
#ifdef Q_OS_MACOS
#define ROMDIR "~/Library/Application Support/net.86box.86box/roms"
#else
#define ROMDIR "roms"
#endif
QMap<int, std::wstring> ProgSettings::translatedstrings;
void ProgSettings::reloadStrings()
{
translatedstrings.clear();
translatedstrings[IDS_2077] = QCoreApplication::translate("", "Click to capture mouse").toStdWString();
translatedstrings[IDS_2078] = QCoreApplication::translate("", "Press F8+F12 to release mouse").replace("F8+F12", MOUSE_CAPTURE_KEYSEQ).replace("CTRL-END", QLocale::system().name() == "de_DE" ? "Strg+Ende" : "CTRL-END").toStdWString();
translatedstrings[IDS_2079] = QCoreApplication::translate("", "Press F8+F12 or middle button to release mouse").replace("F8+F12", MOUSE_CAPTURE_KEYSEQ).replace("CTRL-END", QLocale::system().name() == "de_DE" ? "Strg+Ende" : "CTRL-END").toStdWString();
translatedstrings[IDS_2080] = QCoreApplication::translate("", "Failed to initialize FluidSynth").toStdWString();
translatedstrings[IDS_4099] = QCoreApplication::translate("", "MFM/RLL or ESDI CD-ROM drives never existed").toStdWString();
translatedstrings[IDS_2093] = QCoreApplication::translate("", "Failed to set up PCap").toStdWString();
translatedstrings[IDS_2094] = QCoreApplication::translate("", "No PCap devices found").toStdWString();
translatedstrings[IDS_2110] = QCoreApplication::translate("", "Unable to initialize FreeType").toStdWString();
translatedstrings[IDS_2111] = QCoreApplication::translate("", "Unable to initialize SDL, libsdl2 is required").toStdWString();
translatedstrings[IDS_2129] = QCoreApplication::translate("", "Make sure libpcap is installed and that you are on a libpcap-compatible network connection.").toStdWString();
translatedstrings[IDS_2114] = QCoreApplication::translate("", "Unable to initialize Ghostscript").toStdWString();
translatedstrings[IDS_2063] = QCoreApplication::translate("", "Machine \"%hs\" is not available due to missing ROMs in the roms/machines directory. Switching to an available machine.").toStdWString();
translatedstrings[IDS_2064] = QCoreApplication::translate("", "Video card \"%hs\" is not available due to missing ROMs in the roms/video directory. Switching to an available video card.").toStdWString();
translatedstrings[IDS_2128] = QCoreApplication::translate("", "Hardware not available").toStdWString();
translatedstrings[IDS_2120] = QCoreApplication::translate("", "No ROMs found").toStdWString();
translatedstrings[IDS_2056] = QCoreApplication::translate("", "86Box could not find any usable ROM images.\n\nPlease <a href=\"https://github.com/86Box/roms/releases/latest\">download</a> a ROM set and extract it into the \"roms\" directory.").replace("roms", ROMDIR).toStdWString();
auto flsynthstr = QCoreApplication::translate("", " is required for FluidSynth MIDI output.");
if (flsynthstr.contains("libfluidsynth"))
{
flsynthstr.replace("libfluidsynth", LIB_NAME_FLUIDSYNTH);
}
else flsynthstr.prepend(LIB_NAME_FLUIDSYNTH);
translatedstrings[IDS_2133] = flsynthstr.toStdWString();
auto gssynthstr = QCoreApplication::translate("", " is required for automatic conversion of PostScript files to PDF.\n\nAny documents sent to the generic PostScript printer will be saved as PostScript (.ps) files.");
if (gssynthstr.contains("libgs"))
{
gssynthstr.replace("libgs", LIB_NAME_GS);
}
else gssynthstr.prepend(LIB_NAME_GS);
translatedstrings[IDS_2132] = flsynthstr.toStdWString();
auto ftsynthstr = QCoreApplication::translate("", " is required for ESC/P printer emulation.");
if (ftsynthstr.contains("libfreetype"))
{
ftsynthstr.replace("libfreetype", LIB_NAME_FREETYPE);
}
else ftsynthstr.prepend(LIB_NAME_FREETYPE);
translatedstrings[IDS_2131] = ftsynthstr.toStdWString();
}
wchar_t* plat_get_string(int i)
{
if (ProgSettings::translatedstrings.empty()) ProgSettings::reloadStrings();
return ProgSettings::translatedstrings[i].data();
}
int
plat_chdir(char *path)
{
return QDir::setCurrent(QString(path)) ? 0 : -1;
}

176
src/qt/qt_progsettings.cpp Normal file
View File

@@ -0,0 +1,176 @@
#include <QDebug>
#include "qt_progsettings.hpp"
#include "ui_qt_progsettings.h"
#include "qt_mainwindow.hpp"
#include "ui_qt_mainwindow.h"
#include "qt_machinestatus.hpp"
#include <QMap>
#include <QDir>
#include <QFile>
#include <QLibraryInfo>
extern "C"
{
#include <86box/86box.h>
#include <86box/config.h>
#include <86box/plat.h>
}
static QMap<QString, QString> iconset_to_qt;
extern MainWindow* main_window;
ProgSettings::CustomTranslator* ProgSettings::translator = nullptr;
QTranslator* ProgSettings::qtTranslator = nullptr;
QString ProgSettings::getIconSetPath()
{
QString roms_root;
if (rom_path[0])
roms_root = rom_path;
else {
roms_root = QString("%1/roms").arg(exe_path);
}
if (iconset_to_qt.isEmpty())
{
iconset_to_qt.insert("", ":/settings/win/icons");
QDir dir(roms_root + "/icons/");
if (dir.isReadable())
{
auto dirList = dir.entryList(QDir::AllDirs | QDir::Executable | QDir::Readable);
for (auto &curIconSet : dirList)
{
if (curIconSet == "." || curIconSet == "..") continue;
iconset_to_qt.insert(curIconSet, (dir.canonicalPath() + '/') + curIconSet);
}
}
}
return iconset_to_qt[icon_set];
}
QIcon ProgSettings::loadIcon(QString file)
{
(void)getIconSetPath();
if (!QFile::exists(iconset_to_qt[icon_set] + file)) return QIcon(iconset_to_qt[""] + file);
return QIcon(iconset_to_qt[icon_set] + file);
}
ProgSettings::ProgSettings(QWidget *parent) :
QDialog(parent),
ui(new Ui::ProgSettings)
{
ui->setupUi(this);
(void)getIconSetPath();
ui->comboBox->setItemData(0, "");
ui->comboBox->setCurrentIndex(0);
for (auto i = iconset_to_qt.begin(); i != iconset_to_qt.end(); i++)
{
if (i.key() == "") continue;
QFile iconfile(i.value() + "/iconinfo.txt");
iconfile.open(QFile::ReadOnly);
QString friendlyName;
QString iconsetinfo(iconfile.readAll());
iconfile.close();
if (iconsetinfo.isEmpty()) friendlyName = i.key();
else friendlyName = iconsetinfo.split('\n')[0];
ui->comboBox->addItem(friendlyName, i.key());
if (strcmp(icon_set, i.key().toUtf8().data()) == 0)
{
ui->comboBox->setCurrentIndex(ui->comboBox->findData(i.key()));
}
}
ui->comboBox->setItemData(0, '(' + tr("Default") + ')', Qt::DisplayRole);
ui->comboBoxLanguage->setItemData(0, 0xFFFF);
for (auto i = lcid_langcode.begin(); i != lcid_langcode.end(); i++)
{
if (i.key() == 0xFFFF) continue;
ui->comboBoxLanguage->addItem(lcid_langcode[i.key()].second, i.key());
if (i.key() == lang_id)
{
ui->comboBoxLanguage->setCurrentIndex(ui->comboBoxLanguage->findData(i.key()));
}
}
}
void ProgSettings::accept()
{
strcpy(icon_set, ui->comboBox->currentData().toString().toUtf8().data());
lang_id = ui->comboBoxLanguage->currentData().toUInt();
loadTranslators(QCoreApplication::instance());
reloadStrings();
update_mouse_msg();
main_window->ui->retranslateUi(main_window);
QString msg = main_window->status->getMessage();
main_window->status.reset(new MachineStatus(main_window));
main_window->refreshMediaMenu();
main_window->status->message(msg);
connect(main_window, &MainWindow::updateStatusBarTip, main_window->status.get(), &MachineStatus::updateTip);
connect(main_window, &MainWindow::updateStatusBarActivity, main_window->status.get(), &MachineStatus::setActivity);
connect(main_window, &MainWindow::updateStatusBarEmpty, main_window->status.get(), &MachineStatus::setEmpty);
connect(main_window, &MainWindow::statusBarMessage, main_window->status.get(), &MachineStatus::message);
QDialog::accept();
}
ProgSettings::~ProgSettings()
{
delete ui;
}
void ProgSettings::on_pushButton_released()
{
ui->comboBox->setCurrentIndex(0);
}
void ProgSettings::loadTranslators(QObject *parent)
{
if (qtTranslator)
{
QApplication::removeTranslator(qtTranslator);
qtTranslator = nullptr;
}
if (translator)
{
QApplication::removeTranslator(translator);
translator = nullptr;
}
qtTranslator = new QTranslator(parent);
translator = new CustomTranslator(parent);
QString localetofilename = "";
if (lang_id == 0xFFFF || lcid_langcode.contains(lang_id) == false)
{
for (int i = 0; i < QLocale::system().uiLanguages().size(); i++)
{
localetofilename = QLocale::system().uiLanguages()[i];
if (translator->load(QLatin1String("86box_") + localetofilename, QLatin1String(":/")))
{
qDebug() << "Translations loaded.\n";
QCoreApplication::installTranslator(translator);
if (!qtTranslator->load(QLatin1String("qtbase_") + localetofilename.replace('-', '_'), QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
qtTranslator->load(QLatin1String("qt_") + localetofilename.replace('-', '_'), QApplication::applicationDirPath() + "/./translations/");
if (QApplication::installTranslator(qtTranslator))
{
qDebug() << "Qt translations loaded." << "\n";
}
break;
}
}
}
else
{
translator->load(QLatin1String("86box_") + lcid_langcode[lang_id].first, QLatin1String(":/"));
QCoreApplication::installTranslator(translator);
if (!qtTranslator->load(QLatin1String("qtbase_") + QString(lcid_langcode[lang_id].first).replace('-', '_'), QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
qtTranslator->load(QLatin1String("qt_") + QString(lcid_langcode[lang_id].first).replace('-', '_'), QApplication::applicationDirPath() + "/./translations/");
QCoreApplication::installTranslator(qtTranslator);
}
}
void ProgSettings::on_pushButtonLanguage_released()
{
ui->comboBoxLanguage->setCurrentIndex(0);
}

View File

@@ -0,0 +1,67 @@
#ifndef QT_PROGSETTINGS_HPP
#define QT_PROGSETTINGS_HPP
#include <QDialog>
#include <QTranslator>
namespace Ui {
class ProgSettings;
}
class ProgSettings : public QDialog
{
Q_OBJECT
public:
explicit ProgSettings(QWidget *parent = nullptr);
~ProgSettings();
static QString getIconSetPath();
static QIcon loadIcon(QString file);
static void loadTranslators(QObject* parent = nullptr);
static void reloadStrings();
class CustomTranslator : public QTranslator
{
public:
CustomTranslator(QObject* parent = nullptr) : QTranslator(parent) {};
protected:
QString translate(const char *context, const char *sourceText,
const char *disambiguation = nullptr, int n = -1) const override
{
if (strcmp(sourceText, "&Fullscreen") == 0) sourceText = "&Fullscreen\tCtrl+Alt+PageUP";
if (strcmp(sourceText, "&Ctrl+Alt+Del") == 0) sourceText = "&Ctrl+Alt+Del\tCtrl+F12";
if (strcmp(sourceText, "Take s&creenshot") == 0) sourceText = "Take s&creenshot\tCtrl+F11";
if (strcmp(sourceText, "Begin trace") == 0) sourceText = "Begin trace\tCtrl+T";
if (strcmp(sourceText, "End trace") == 0) sourceText = "End trace\tCtrl+T";
if (strcmp(sourceText, "Dump &video RAM") == 0) sourceText = "Dump &video RAM\tCtrl+F1";
if (strcmp(sourceText, "&Qt (Software)") == 0)
{
QString finalstr = QTranslator::translate("", "&SDL (Software)", disambiguation, n);
finalstr.replace("SDL", "Qt");
finalstr.replace("(&S)", "(&Q)");
return finalstr;
}
QString finalstr = QTranslator::translate("", sourceText, disambiguation, n);
#ifdef Q_OS_MACOS
if (finalstr.contains('\t')) finalstr.truncate(finalstr.indexOf('\t'));
#endif
return finalstr;
}
};
static CustomTranslator* translator;
static QTranslator* qtTranslator;
static QMap<uint32_t, QPair<QString, QString>> lcid_langcode;
static QMap<int, std::wstring> translatedstrings;
protected slots:
void accept() override;
private slots:
void on_pushButton_released();
void on_pushButtonLanguage_released();
private:
Ui::ProgSettings *ui;
friend class MainWindow;
};
#endif // QT_PROGSETTINGS_HPP

164
src/qt/qt_progsettings.ui Normal file
View File

@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProgSettings</class>
<widget class="QDialog" name="ProgSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>370</width>
<height>228</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>370</width>
<height>228</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>370</width>
<height>228</height>
</size>
</property>
<property name="windowTitle">
<string>Preferences</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="6" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0" colspan="2">
<widget class="QComboBox" name="comboBoxLanguage">
<item>
<property name="text">
<string>(System Default)</string>
</property>
</item>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Default</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Language:</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QComboBox" name="comboBox">
<property name="editable">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>(Default)</string>
</property>
</item>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="5" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Icon set:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QPushButton" name="pushButtonLanguage">
<property name="text">
<string>Default</string>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ProgSettings</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ProgSettings</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,107 @@
#include "qt_renderercommon.hpp"
#include "qt_mainwindow.hpp"
#include <QPainter>
#include <QWidget>
#include <QEvent>
#include <QApplication>
#include <cmath>
extern "C" {
#include <86box/86box.h>
#include <86box/video.h>
}
RendererCommon::RendererCommon() = default;
extern MainWindow* main_window;
static void integer_scale(double *d, double *g) {
double ratio;
if (*d > *g) {
ratio = std::floor(*d / *g);
*d = *g * ratio;
} else {
ratio = std::ceil(*d / *g);
*d = *g / ratio;
}
}
void RendererCommon::onResize(int width, int height) {
if (video_fullscreen == 0) {
destination.setRect(0, 0, width, height);
return;
}
double dx, dy, dw, dh, gsr;
double hw = width;
double hh = height;
double gw = source.width();
double gh = source.height();
double hsr = hw / hh;
switch (video_fullscreen_scale) {
case FULLSCR_SCALE_INT:
gsr = gw / gh;
if (gsr <= hsr) {
dw = hh * gsr;
dh = hh;
} else {
dw = hw;
dh = hw / gsr;
}
integer_scale(&dw, &gw);
integer_scale(&dh, &gh);
dx = (hw - dw) / 2.0;
dy = (hh - dh) / 2.0;
destination.setRect(dx, dy, dw, dh);
break;
case FULLSCR_SCALE_43:
case FULLSCR_SCALE_KEEPRATIO:
if (video_fullscreen_scale == FULLSCR_SCALE_43) {
gsr = 4.0 / 3.0;
} else {
gsr = gw / gh;
}
if (gsr <= hsr) {
dw = hh * gsr;
dh = hh;
} else {
dw = hw;
dh = hw / gsr;
}
dx = (hw - dw) / 2.0;
dy = (hh - dh) / 2.0;
destination.setRect(dx, dy, dw, dh);
break;
case FULLSCR_SCALE_FULL:
default:
destination.setRect(0, 0, hw, hh);
break;
}
}
bool RendererCommon::eventDelegate(QEvent *event, bool& result)
{
switch (event->type())
{
default:
return false;
case QEvent::KeyPress:
case QEvent::KeyRelease:
result = QApplication::sendEvent(main_window, event);
return true;
case QEvent::MouseButtonPress:
case QEvent::MouseMove:
case QEvent::MouseButtonRelease:
case QEvent::Wheel:
case QEvent::Enter:
case QEvent::Leave:
result = QApplication::sendEvent(parentWidget, event);
return true;
}
return false;
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <QRect>
#include <QImage>
#include <QEvent>
#include <vector>
#include <tuple>
#include <atomic>
class QWidget;
class RendererCommon
{
public:
RendererCommon();
void onResize(int width, int height);
virtual std::vector<std::tuple<uint8_t*, std::atomic_flag*>> getBuffers() = 0;
protected:
bool eventDelegate(QEvent* event, bool& result);
QRect source, destination;
QWidget* parentWidget{nullptr};
std::vector<std::atomic_flag> buf_usage;
};

255
src/qt/qt_rendererstack.cpp Normal file
View File

@@ -0,0 +1,255 @@
#include "qt_rendererstack.hpp"
#include "ui_qt_rendererstack.h"
#include "qt_softwarerenderer.hpp"
#include "qt_hardwarerenderer.hpp"
#include "qt_mainwindow.hpp"
#include "evdev_mouse.hpp"
#include <QScreen>
#ifdef __APPLE__
#include <CoreGraphics/CoreGraphics.h>
#endif
extern "C"
{
#include <86box/mouse.h>
#include <86box/plat.h>
#include <86box/video.h>
}
extern MainWindow* main_window;
RendererStack::RendererStack(QWidget *parent) :
QStackedWidget(parent),
ui(new Ui::RendererStack)
{
ui->setupUi(this);
#ifdef WAYLAND
if (QApplication::platformName().contains("wayland")) {
wl_init();
}
#endif
#ifdef EVDEV_INPUT
if (QApplication::platformName() == "xcb" || QApplication::platformName() == "eglfs") {
evdev_init();
}
#endif
}
RendererStack::~RendererStack()
{
delete ui;
}
extern "C" void macos_poll_mouse();
void
qt_mouse_capture(int on)
{
if (!on)
{
mouse_capture = 0;
QApplication::setOverrideCursor(Qt::ArrowCursor);
#ifdef __APPLE__
CGAssociateMouseAndMouseCursorPosition(true);
#endif
return;
}
mouse_capture = 1;
QApplication::setOverrideCursor(Qt::BlankCursor);
#ifdef __APPLE__
CGAssociateMouseAndMouseCursorPosition(false);
#endif
return;
}
void RendererStack::mousePoll()
{
#ifdef __APPLE__
return macos_poll_mouse();
#else
mouse_x = mousedata.deltax;
mouse_y = mousedata.deltay;
mouse_z = mousedata.deltaz;
mousedata.deltax = mousedata.deltay = mousedata.deltaz = 0;
mouse_buttons = mousedata.mousebuttons;
#ifdef WAYLAND
if (QApplication::platformName().contains("wayland"))
wl_mouse_poll();
#endif
#ifdef EVDEV_INPUT
evdev_mouse_poll();
#endif
#endif
}
int ignoreNextMouseEvent = 1;
void RendererStack::mouseReleaseEvent(QMouseEvent *event)
{
if (this->geometry().contains(event->pos()) && event->button() == Qt::LeftButton && !mouse_capture && (isMouseDown & 1))
{
plat_mouse_capture(1);
this->setCursor(Qt::BlankCursor);
if (!ignoreNextMouseEvent) ignoreNextMouseEvent++; // Avoid jumping cursor when moved.
isMouseDown &= ~1;
return;
}
if (mouse_capture && event->button() == Qt::MiddleButton && mouse_get_buttons() < 3)
{
plat_mouse_capture(0);
this->setCursor(Qt::ArrowCursor);
isMouseDown &= ~1;
return;
}
if (mouse_capture)
{
mousedata.mousebuttons &= ~event->button();
}
isMouseDown &= ~1;
}
void RendererStack::mousePressEvent(QMouseEvent *event)
{
isMouseDown |= 1;
if (mouse_capture)
{
mousedata.mousebuttons |= event->button();
}
if (main_window->frameGeometry().contains(event->pos()) && !geometry().contains(event->pos()))
{
main_window->windowHandle()->startSystemMove();
}
event->accept();
}
void RendererStack::wheelEvent(QWheelEvent *event)
{
if (mouse_capture)
{
mousedata.deltaz += event->pixelDelta().y();
}
}
void RendererStack::mouseMoveEvent(QMouseEvent *event)
{
if (QApplication::platformName().contains("wayland"))
{
event->accept();
return;
}
if (!mouse_capture) { event->ignore(); return; }
#ifdef __APPLE__
event->accept();
return;
#else
static QPoint oldPos = QCursor::pos();
if (ignoreNextMouseEvent) { oldPos = event->pos(); ignoreNextMouseEvent--; event->accept(); return; }
mousedata.deltax += event->pos().x() - oldPos.x();
mousedata.deltay += event->pos().y() - oldPos.y();
if (QApplication::platformName() == "eglfs")
{
leaveEvent((QEvent*)event);
ignoreNextMouseEvent--;
}
else if (event->globalPos().x() == 0 || event->globalPos().y() == 0) leaveEvent((QEvent*)event);
else if (event->globalPos().x() == (screen()->geometry().width() - 1) || event->globalPos().y() == (screen()->geometry().height() - 1)) leaveEvent((QEvent*)event);
oldPos = event->pos();
#endif
}
void RendererStack::leaveEvent(QEvent* event)
{
if (QApplication::platformName().contains("wayland"))
{
event->accept();
return;
}
if (!mouse_capture) return;
QCursor::setPos(mapToGlobal(QPoint(width() / 2, height() / 2)));
ignoreNextMouseEvent = 2;
event->accept();
}
void RendererStack::switchRenderer(Renderer renderer) {
startblit();
if (current) {
removeWidget(current.get());
}
switch (renderer) {
case Renderer::Software:
{
auto sw = new SoftwareRenderer(this);
rendererWindow = sw;
connect(this, &RendererStack::blitToRenderer, sw, &SoftwareRenderer::onBlit, Qt::QueuedConnection);
current.reset(this->createWindowContainer(sw, this));
}
break;
case Renderer::OpenGL:
{
this->createWinId();
auto hw = new HardwareRenderer(this);
rendererWindow = hw;
connect(this, &RendererStack::blitToRenderer, hw, &HardwareRenderer::onBlit, Qt::QueuedConnection);
current.reset(this->createWindowContainer(hw, this));
break;
}
case Renderer::OpenGLES:
{
this->createWinId();
auto hw = new HardwareRenderer(this, HardwareRenderer::RenderType::OpenGLES);
rendererWindow = hw;
connect(this, &RendererStack::blitToRenderer, hw, &HardwareRenderer::onBlit, Qt::QueuedConnection);
current.reset(this->createWindowContainer(hw, this));
break;
}
case Renderer::OpenGL3:
{
this->createWinId();
auto hw = new HardwareRenderer(this, HardwareRenderer::RenderType::OpenGL3);
rendererWindow = hw;
connect(this, &RendererStack::blitToRenderer, hw, &HardwareRenderer::onBlit, Qt::QueuedConnection);
current.reset(this->createWindowContainer(hw, this));
break;
}
}
imagebufs = std::move(rendererWindow->getBuffers());
current->setFocusPolicy(Qt::NoFocus);
current->setFocusProxy(this);
addWidget(current.get());
this->setStyleSheet("background-color: black");
endblit();
}
// called from blitter thread
void RendererStack::blit(int x, int y, int w, int h)
{
if ((w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (buffer32 == NULL) || std::get<std::atomic_flag*>(imagebufs[currentBuf])->test_and_set())
{
video_blit_complete();
return;
}
sx = x;
sy = y;
sw = this->w = w;
sh = this->h = h;
uint8_t* imagebits = std::get<uint8_t*>(imagebufs[currentBuf]);
for (int y1 = y; y1 < (y + h); y1++)
{
auto scanline = imagebits + (y1 * (2048) * 4) + (x * 4);
video_copy(scanline, &(buffer32->line[y1][x]), w * 4);
}
if (screenshots)
{
video_screenshot((uint32_t *)imagebits, x, y, 2048);
}
video_blit_complete();
emit blitToRenderer(currentBuf, sx, sy, sw, sh);
currentBuf = (currentBuf + 1) % imagebufs.size();
}

View File

@@ -0,0 +1,75 @@
#ifndef QT_RENDERERCONTAINER_HPP
#define QT_RENDERERCONTAINER_HPP
#include <QStackedWidget>
#include <QKeyEvent>
#include <QEvent>
#include <memory>
#include <vector>
#include <atomic>
#include <tuple>
namespace Ui {
class RendererStack;
}
class RendererCommon;
class RendererStack : public QStackedWidget
{
Q_OBJECT
public:
explicit RendererStack(QWidget *parent = nullptr);
~RendererStack();
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void wheelEvent(QWheelEvent *event) override;
void leaveEvent(QEvent *event) override;
void keyPressEvent(QKeyEvent* event) override
{
event->ignore();
}
void keyReleaseEvent(QKeyEvent* event) override
{
event->ignore();
}
enum class Renderer {
Software,
OpenGL,
OpenGLES,
OpenGL3
};
void switchRenderer(Renderer renderer);
RendererCommon* rendererWindow{nullptr};
signals:
void blitToRenderer(int buf_idx, int x, int y, int w, int h);
public slots:
void blit(int x, int y, int w, int h);
void mousePoll();
private:
Ui::RendererStack *ui;
struct mouseinputdata {
int deltax, deltay, deltaz;
int mousebuttons;
};
mouseinputdata mousedata;
int x, y, w, h, sx, sy, sw, sh;
int currentBuf = 0;
int isMouseDown = 0;
std::vector<std::tuple<uint8_t*, std::atomic_flag*>> imagebufs;
std::unique_ptr<QWidget> current;
friend class MainWindow;
};
#endif // QT_RENDERERCONTAINER_HPP

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RendererStack</class>
<widget class="QStackedWidget" name="RendererStack">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>StackedWidget</string>
</property>
</widget>
<resources/>
<connections/>
</ui>

732
src/qt/qt_sdl.c Normal file
View File

@@ -0,0 +1,732 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Rendering module for libSDL2
*
* NOTE: Given all the problems reported with FULLSCREEN use of SDL,
* we will not use that, but, instead, use a new window which
* coverrs the entire desktop.
*
*
*
* Authors: Fred N. van Kempen, <decwiz@yahoo.com>
* Michael Dr<44>ing, <michael@drueing.de>
*
* Copyright 2018-2020 Fred N. van Kempen.
* Copyright 2018-2020 Michael Dr<44>ing.
*
* Redistribution and use in source and binary forms, with
* or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the entire
* above notice, this list of conditions and the following
* disclaimer.
*
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names
* of its contributors may be used to endorse or promote
* products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <SDL2/SDL.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
/* This #undef is needed because a SDL include header redefines HAVE_STDARG_H. */
#undef HAVE_STDARG_H
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/mouse.h>
#include <86box/keyboard.h>
#include <86box/device.h>
#include <86box/plat.h>
#include <86box/plat_dynld.h>
#include <86box/video.h>
#include <86box/ui.h>
#include <86box/version.h>
#include "qt_sdl.h"
#define RENDERER_FULL_SCREEN 1
#define RENDERER_HARDWARE 2
#define RENDERER_OPENGL 4
static SDL_Window *sdl_win = NULL;
static SDL_Renderer *sdl_render = NULL;
static SDL_Texture *sdl_tex = NULL;
static int sdl_w, sdl_h;
static int sdl_fs, sdl_flags = -1;
static int cur_w, cur_h;
static int cur_ww = 0, cur_wh = 0;
static volatile int sdl_enabled = 0;
static SDL_mutex* sdl_mutex = NULL;
static const uint16_t sdl_to_xt[0x200] =
{
[SDL_SCANCODE_ESCAPE] = 0x01,
[SDL_SCANCODE_1] = 0x02,
[SDL_SCANCODE_2] = 0x03,
[SDL_SCANCODE_3] = 0x04,
[SDL_SCANCODE_4] = 0x05,
[SDL_SCANCODE_5] = 0x06,
[SDL_SCANCODE_6] = 0x07,
[SDL_SCANCODE_7] = 0x08,
[SDL_SCANCODE_8] = 0x09,
[SDL_SCANCODE_9] = 0x0A,
[SDL_SCANCODE_0] = 0x0B,
[SDL_SCANCODE_MINUS] = 0x0C,
[SDL_SCANCODE_EQUALS] = 0x0D,
[SDL_SCANCODE_BACKSPACE] = 0x0E,
[SDL_SCANCODE_TAB] = 0x0F,
[SDL_SCANCODE_Q] = 0x10,
[SDL_SCANCODE_W] = 0x11,
[SDL_SCANCODE_E] = 0x12,
[SDL_SCANCODE_R] = 0x13,
[SDL_SCANCODE_T] = 0x14,
[SDL_SCANCODE_Y] = 0x15,
[SDL_SCANCODE_U] = 0x16,
[SDL_SCANCODE_I] = 0x17,
[SDL_SCANCODE_O] = 0x18,
[SDL_SCANCODE_P] = 0x19,
[SDL_SCANCODE_LEFTBRACKET] = 0x1A,
[SDL_SCANCODE_RIGHTBRACKET] = 0x1B,
[SDL_SCANCODE_RETURN] = 0x1C,
[SDL_SCANCODE_LCTRL] = 0x1D,
[SDL_SCANCODE_A] = 0x1E,
[SDL_SCANCODE_S] = 0x1F,
[SDL_SCANCODE_D] = 0x20,
[SDL_SCANCODE_F] = 0x21,
[SDL_SCANCODE_G] = 0x22,
[SDL_SCANCODE_H] = 0x23,
[SDL_SCANCODE_J] = 0x24,
[SDL_SCANCODE_K] = 0x25,
[SDL_SCANCODE_L] = 0x26,
[SDL_SCANCODE_SEMICOLON] = 0x27,
[SDL_SCANCODE_APOSTROPHE] = 0x28,
[SDL_SCANCODE_GRAVE] = 0x29,
[SDL_SCANCODE_LSHIFT] = 0x2A,
[SDL_SCANCODE_BACKSLASH] = 0x2B,
[SDL_SCANCODE_Z] = 0x2C,
[SDL_SCANCODE_X] = 0x2D,
[SDL_SCANCODE_C] = 0x2E,
[SDL_SCANCODE_V] = 0x2F,
[SDL_SCANCODE_B] = 0x30,
[SDL_SCANCODE_N] = 0x31,
[SDL_SCANCODE_M] = 0x32,
[SDL_SCANCODE_COMMA] = 0x33,
[SDL_SCANCODE_PERIOD] = 0x34,
[SDL_SCANCODE_SLASH] = 0x35,
[SDL_SCANCODE_RSHIFT] = 0x36,
[SDL_SCANCODE_KP_MULTIPLY] = 0x37,
[SDL_SCANCODE_LALT] = 0x38,
[SDL_SCANCODE_SPACE] = 0x39,
[SDL_SCANCODE_CAPSLOCK] = 0x3A,
[SDL_SCANCODE_F1] = 0x3B,
[SDL_SCANCODE_F2] = 0x3C,
[SDL_SCANCODE_F3] = 0x3D,
[SDL_SCANCODE_F4] = 0x3E,
[SDL_SCANCODE_F5] = 0x3F,
[SDL_SCANCODE_F6] = 0x40,
[SDL_SCANCODE_F7] = 0x41,
[SDL_SCANCODE_F8] = 0x42,
[SDL_SCANCODE_F9] = 0x43,
[SDL_SCANCODE_F10] = 0x44,
[SDL_SCANCODE_NUMLOCKCLEAR] = 0x45,
[SDL_SCANCODE_SCROLLLOCK] = 0x46,
[SDL_SCANCODE_HOME] = 0x147,
[SDL_SCANCODE_UP] = 0x148,
[SDL_SCANCODE_PAGEUP] = 0x149,
[SDL_SCANCODE_KP_MINUS] = 0x4A,
[SDL_SCANCODE_LEFT] = 0x14B,
[SDL_SCANCODE_KP_5] = 0x4C,
[SDL_SCANCODE_RIGHT] = 0x14D,
[SDL_SCANCODE_KP_PLUS] = 0x4E,
[SDL_SCANCODE_END] = 0x14F,
[SDL_SCANCODE_DOWN] = 0x150,
[SDL_SCANCODE_PAGEDOWN] = 0x151,
[SDL_SCANCODE_INSERT] = 0x152,
[SDL_SCANCODE_DELETE] = 0x153,
[SDL_SCANCODE_F11] = 0x57,
[SDL_SCANCODE_F12] = 0x58,
[SDL_SCANCODE_KP_ENTER] = 0x11c,
[SDL_SCANCODE_RCTRL] = 0x11d,
[SDL_SCANCODE_KP_DIVIDE] = 0x135,
[SDL_SCANCODE_RALT] = 0x138,
[SDL_SCANCODE_KP_9] = 0x49,
[SDL_SCANCODE_KP_8] = 0x48,
[SDL_SCANCODE_KP_7] = 0x47,
[SDL_SCANCODE_KP_6] = 0x4D,
[SDL_SCANCODE_KP_4] = 0x4B,
[SDL_SCANCODE_KP_3] = 0x51,
[SDL_SCANCODE_KP_2] = 0x50,
[SDL_SCANCODE_KP_1] = 0x4F,
[SDL_SCANCODE_KP_0] = 0x52,
[SDL_SCANCODE_KP_PERIOD] = 0x53,
[SDL_SCANCODE_LGUI] = 0x15B,
[SDL_SCANCODE_RGUI] = 0x15C,
[SDL_SCANCODE_APPLICATION] = 0x15D,
[SDL_SCANCODE_PRINTSCREEN] = 0x137,
[SDL_SCANCODE_NONUSBACKSLASH] = 0x56,
};
typedef struct mouseinputdata
{
int deltax, deltay, deltaz;
int mousebuttons;
} mouseinputdata;
static mouseinputdata mousedata;
// #define ENABLE_SDL_LOG 3
#ifdef ENABLE_SDL_LOG
int sdl_do_log = ENABLE_SDL_LOG;
static void
sdl_log(const char *fmt, ...)
{
va_list ap;
if (sdl_do_log) {
va_start(ap, fmt);
pclog_ex(fmt, ap);
va_end(ap);
}
}
#else
#define sdl_log(fmt, ...)
#endif
static void
sdl_integer_scale(double *d, double *g)
{
double ratio;
if (*d > *g) {
ratio = floor(*d / *g);
*d = *g * ratio;
} else {
ratio = ceil(*d / *g);
*d = *g / ratio;
}
}
static void
sdl_stretch(int *w, int *h, int *x, int *y)
{
double hw, gw, hh, gh, dx, dy, dw, dh, gsr, hsr;
hw = (double) sdl_w;
hh = (double) sdl_h;
gw = (double) *w;
gh = (double) *h;
hsr = hw / hh;
switch (video_fullscreen_scale) {
case FULLSCR_SCALE_FULL:
default:
*w = sdl_w;
*h = sdl_h;
*x = 0;
*y = 0;
break;
case FULLSCR_SCALE_43:
case FULLSCR_SCALE_KEEPRATIO:
if (video_fullscreen_scale == FULLSCR_SCALE_43)
gsr = 4.0 / 3.0;
else
gsr = gw / gh;
if (gsr <= hsr) {
dw = hh * gsr;
dh = hh;
} else {
dw = hw;
dh = hw / gsr;
}
dx = (hw - dw) / 2.0;
dy = (hh - dh) / 2.0;
*w = (int) dw;
*h = (int) dh;
*x = (int) dx;
*y = (int) dy;
break;
case FULLSCR_SCALE_INT:
gsr = gw / gh;
if (gsr <= hsr) {
dw = hh * gsr;
dh = hh;
} else {
dw = hw;
dh = hw / gsr;
}
sdl_integer_scale(&dw, &gw);
sdl_integer_scale(&dh, &gh);
dx = (hw - dw) / 2.0;
dy = (hh - dh) / 2.0;
*w = (int) dw;
*h = (int) dh;
*x = (int) dx;
*y = (int) dy;
break;
}
}
static void
sdl_blit(int x, int y, int w, int h)
{
SDL_Rect r_src;
void *pixeldata;
int ret, pitch;
if (!sdl_enabled || (x < 0) || (y < 0) || (w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (buffer32 == NULL) || (sdl_render == NULL) || (sdl_tex == NULL)) {
video_blit_complete();
return;
}
SDL_LockMutex(sdl_mutex);
SDL_LockTexture(sdl_tex, 0, &pixeldata, &pitch);
video_copy(pixeldata, &(buffer32->line[y][x]), h * (2048) * sizeof(uint32_t));
if (screenshots)
video_screenshot((uint32_t *) pixeldata, 0, 0, (2048));
SDL_UnlockTexture(sdl_tex);
video_blit_complete();
SDL_RenderClear(sdl_render);
r_src.x = 0;
r_src.y = 0;
r_src.w = w;
r_src.h = h;
ret = SDL_RenderCopy(sdl_render, sdl_tex, &r_src, 0);
if (ret)
sdl_log("SDL: unable to copy texture to renderer (%s)\n", sdl_GetError());
SDL_RenderPresent(sdl_render);
SDL_UnlockMutex(sdl_mutex);
}
static void
sdl_destroy_window(void)
{
if (sdl_win != NULL) {
SDL_DestroyWindow(sdl_win);
sdl_win = NULL;
}
}
static void
sdl_destroy_texture(void)
{
if (sdl_tex != NULL) {
SDL_DestroyTexture(sdl_tex);
sdl_tex = NULL;
}
/* SDL_DestroyRenderer also automatically destroys all associated textures. */
if (sdl_render != NULL) {
SDL_DestroyRenderer(sdl_render);
sdl_render = NULL;
}
}
void
sdl_close(void)
{
if (sdl_mutex != NULL)
SDL_LockMutex(sdl_mutex);
/* Unregister our renderer! */
video_setblit(NULL);
if (sdl_enabled)
sdl_enabled = 0;
if (sdl_mutex != NULL) {
SDL_DestroyMutex(sdl_mutex);
sdl_mutex = NULL;
}
sdl_destroy_texture();
sdl_destroy_window();
/* Quit. */
SDL_Quit();
sdl_flags = -1;
}
static void sdl_select_best_hw_driver(void) {
int i;
SDL_RendererInfo renderInfo;
for (i = 0; i < SDL_GetNumRenderDrivers(); ++i) {
SDL_GetRenderDriverInfo(i, &renderInfo);
if (renderInfo.flags & SDL_RENDERER_ACCELERATED) {
SDL_SetHint(SDL_HINT_RENDER_DRIVER, renderInfo.name);
return;
}
}
}
static void
sdl_init_texture(void)
{
if (sdl_flags & RENDERER_HARDWARE) {
sdl_render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_ACCELERATED);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, video_filter_method ? "1" : "0");
} else {
sdl_render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_SOFTWARE);
}
sdl_tex = SDL_CreateTexture(sdl_render, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, (2048), (2048));
if (sdl_render == NULL) {
sdl_log("SDL: unable to SDL_CreateRenderer (%s)\n", SDL_GetError());
}
if (sdl_tex == NULL) {
sdl_log("SDL: unable to SDL_CreateTexture (%s)\n", SDL_GetError());
}
}
static void
sdl_reinit_texture(void)
{
if (sdl_flags == -1)
return;
sdl_destroy_texture();
sdl_init_texture();
}
void sdl_set_fs(int fs) {
SDL_SetWindowFullscreen(sdl_win, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
SDL_SetRelativeMouseMode((SDL_bool)mouse_capture);
sdl_fs = fs;
if (fs) {
sdl_flags |= RENDERER_FULL_SCREEN;
} else {
sdl_flags &= ~RENDERER_FULL_SCREEN;
}
sdl_reinit_texture();
}
static int
sdl_init_common(void* win, int flags)
{
wchar_t temp[128];
SDL_version ver;
sdl_log("SDL: init (fs=%d)\n", 0);
/* Get and log the version of the DLL we are using. */
SDL_GetVersion(&ver);
sdl_log("SDL: version %d.%d.%d\n", ver.major, ver.minor, ver.patch);
/* Initialize the SDL system. */
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
sdl_log("SDL: initialization failed (%s)\n", SDL_GetError());
return(0);
}
if (flags & RENDERER_HARDWARE) {
if (flags & RENDERER_OPENGL)
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "OpenGL");
else
sdl_select_best_hw_driver();
}
/* Get the size of the (current) desktop. */
SDL_DisplayMode dm;
if (SDL_GetDesktopDisplayMode(0, &dm) != 0) {
sdl_log("SDL: SDL_GetDesktopDisplayMode failed (%s)\n", SDL_GetError());
return(0);
}
sdl_w = dm.w;
sdl_h = dm.h;
sdl_flags = flags;
sdl_win = SDL_CreateWindow("86Box renderer", 640, 480, 100, 100, sdl_flags);
if (sdl_win == NULL) {
sdl_log("SDL: unable to CreateWindowFrom (%s)\n", SDL_GetError());
}
sdl_init_texture();
sdl_set_fs(video_fullscreen & 1);
/* Make sure we get a clean exit. */
atexit(sdl_close);
/* Register our renderer! */
video_setblit(sdl_blit);
sdl_enabled = 1;
sdl_mutex = SDL_CreateMutex();
return(1);
}
int
sdl_inits(void* win)
{
return sdl_init_common(win, 0);
}
int
sdl_inith(void* win)
{
return sdl_init_common(win, RENDERER_HARDWARE);
}
int
sdl_initho(void* win)
{
return sdl_init_common(win, RENDERER_HARDWARE | RENDERER_OPENGL);
}
int
sdl_pause(void)
{
return(0);
}
void
sdl_resize(int w, int h)
{
int ww = 0, wh = 0;
if (video_fullscreen & 2)
return;
if ((w == cur_w) && (h == cur_h))
return;
SDL_LockMutex(sdl_mutex);
ww = w;
wh = h;
if (sdl_fs) {
// sdl_stretch(&ww, &wh, &wx, &wy);
// MoveWindow(hwndRender, wx, wy, ww, wh, TRUE);
}
cur_w = w;
cur_h = h;
cur_ww = ww;
cur_wh = wh;
SDL_SetWindowSize(sdl_win, cur_ww, cur_wh);
sdl_reinit_texture();
SDL_UnlockMutex(sdl_mutex);
}
void
sdl_enable(int enable)
{
if (sdl_flags == -1)
return;
SDL_LockMutex(sdl_mutex);
sdl_enabled = !!enable;
if (enable == 1) {
SDL_SetWindowSize(sdl_win, cur_ww, cur_wh);
sdl_reinit_texture();
}
SDL_UnlockMutex(sdl_mutex);
}
void
sdl_reload(void)
{
if (sdl_flags & RENDERER_HARDWARE) {
SDL_LockMutex(sdl_mutex);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, video_filter_method ? "1" : "0");
sdl_reinit_texture();
SDL_UnlockMutex(sdl_mutex);
}
}
static int mouse_inside = 0;
enum sdl_main_status sdl_main() {
int ret = SdlMainOk;
SDL_Rect r_src;
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch(event.type)
{
case SDL_QUIT:
ret = SdlMainQuit;
break;
case SDL_MOUSEWHEEL:
{
if (mouse_capture || video_fullscreen)
{
if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED)
{
event.wheel.x *= -1;
event.wheel.y *= -1;
}
mousedata.deltaz = event.wheel.y;
}
break;
}
case SDL_MOUSEMOTION:
{
if (mouse_capture || video_fullscreen)
{
mousedata.deltax += event.motion.xrel;
mousedata.deltay += event.motion.yrel;
}
break;
}
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
{
if ((event.button.button == SDL_BUTTON_LEFT)
&& !(mouse_capture || video_fullscreen)
&& event.button.state == SDL_RELEASED
&& mouse_inside)
{
plat_mouse_capture(1);
break;
}
if (mouse_get_buttons() < 3 && event.button.button == SDL_BUTTON_MIDDLE && !video_fullscreen)
{
plat_mouse_capture(0);
break;
}
if (mouse_capture || video_fullscreen)
{
int buttonmask = 0;
switch(event.button.button)
{
case SDL_BUTTON_LEFT:
buttonmask = 1;
break;
case SDL_BUTTON_RIGHT:
buttonmask = 2;
break;
case SDL_BUTTON_MIDDLE:
buttonmask = 4;
break;
}
if (event.button.state == SDL_PRESSED)
{
mousedata.mousebuttons |= buttonmask;
}
else mousedata.mousebuttons &= ~buttonmask;
}
break;
}
case SDL_RENDER_DEVICE_RESET:
case SDL_RENDER_TARGETS_RESET:
{
sdl_reinit_texture();
break;
}
case SDL_KEYDOWN:
case SDL_KEYUP:
{
uint16_t xtkey = 0;
switch(event.key.keysym.scancode)
{
default:
xtkey = sdl_to_xt[event.key.keysym.scancode];
break;
}
keyboard_input(event.key.state == SDL_PRESSED, xtkey);
}
break;
case SDL_WINDOWEVENT:
{
switch (event.window.event)
{
case SDL_WINDOWEVENT_ENTER:
mouse_inside = 1;
break;
case SDL_WINDOWEVENT_LEAVE:
mouse_inside = 0;
break;
}
}
}
}
if (mouse_capture && keyboard_ismsexit()) {
plat_mouse_capture(0);
}
if (video_fullscreen && keyboard_isfsexit()) {
plat_setfullscreen(0);
}
return ret;
}
void sdl_mouse_capture(int on) {
SDL_SetRelativeMouseMode((SDL_bool)on);
}
void sdl_mouse_poll() {
mouse_x = mousedata.deltax;
mouse_y = mousedata.deltay;
mouse_z = mousedata.deltaz;
mousedata.deltax = mousedata.deltay = mousedata.deltaz = 0;
mouse_buttons = mousedata.mousebuttons;
}

73
src/qt/qt_sdl.h Normal file
View File

@@ -0,0 +1,73 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Definitions for the libSDL2 rendering module.
*
*
*
* Authors: Fred N. van Kempen, <decwiz@yahoo.com>
* Michael Drüing, <michael@drueing.de>
*
* Copyright 2018,2019 Fred N. van Kempen.
* Copyright 2018,2019 Michael Drüing.
*
* Redistribution and use in source and binary forms, with
* or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the entire
* above notice, this list of conditions and the following
* disclaimer.
*
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names
* of its contributors may be used to endorse or promote
* products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef WIN_SDL_H
# define WIN_SDL_H
extern void* sdl_win_handle;
extern void sdl_close(void);
extern int sdl_inits();
extern int sdl_inith();
extern int sdl_initho();
extern int sdl_pause(void);
extern void sdl_resize(int w, int h);
extern void sdl_enable(int enable);
extern void sdl_set_fs(int fs);
extern void sdl_reload(void);
enum sdl_main_status {
SdlMainOk,
SdlMainQuit,
};
extern enum sdl_main_status sdl_main();
extern void sdl_mouse_capture(int on);
extern void sdl_mouse_poll();
#endif /*WIN_SDL_H*/

168
src/qt/qt_settings.cpp Normal file
View File

@@ -0,0 +1,168 @@
#include "qt_settings.hpp"
#include "ui_qt_settings.h"
#include "qt_settingsmachine.hpp"
#include "qt_settingsdisplay.hpp"
#include "qt_settingsinput.hpp"
#include "qt_settingssound.hpp"
#include "qt_settingsnetwork.hpp"
#include "qt_settingsports.hpp"
#include "qt_settingsstoragecontrollers.hpp"
#include "qt_settingsharddisks.hpp"
#include "qt_settingsfloppycdrom.hpp"
#include "qt_settingsotherremovable.hpp"
#include "qt_settingsotherperipherals.hpp"
#include "qt_progsettings.hpp"
#include "qt_harddrive_common.hpp"
#include "qt_settings_bus_tracking.hpp"
extern "C"
{
#include <86box/86box.h>
}
#include <QDebug>
#include <QMessageBox>
#include <QCheckBox>
class SettingsModel : public QAbstractListModel {
public:
SettingsModel(QObject* parent) : QAbstractListModel(parent) {}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
private:
QStringList pages = {
"Machine",
"Display",
"Input devices",
"Sound",
"Network",
"Ports (COM & LPT)",
"Storage controllers",
"Hard disks",
"Floppy & CD-ROM drives",
"Other removable devices",
"Other peripherals",
};
QStringList page_icons = {
"machine",
"display",
"input_devices",
"sound",
"network",
"ports",
"storage_controllers",
"hard_disk",
"floppy_and_cdrom_drives",
"other_removable_devices",
"other_peripherals",
};
};
QVariant SettingsModel::data(const QModelIndex &index, int role) const {
Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::ParentIsInvalid));
switch (role) {
case Qt::DisplayRole:
return tr(pages.at(index.row()).toUtf8().data());
case Qt::DecorationRole:
return QIcon(QString("%1/%2.ico").arg(ProgSettings::getIconSetPath(), page_icons[index.row()]));
default:
return {};
}
}
int SettingsModel::rowCount(const QModelIndex &parent) const {
(void) parent;
return pages.size();
}
Settings::Settings(QWidget *parent) :
QDialog(parent),
ui(new Ui::Settings)
{
ui->setupUi(this);
ui->listView->setModel(new SettingsModel(this));
Harddrives::busTrackClass = new SettingsBusTracking;
machine = new SettingsMachine(this);
display = new SettingsDisplay(this);
input = new SettingsInput(this);
sound = new SettingsSound(this);
network = new SettingsNetwork(this);
ports = new SettingsPorts(this);
storageControllers = new SettingsStorageControllers(this);
harddisks = new SettingsHarddisks(this);
floppyCdrom = new SettingsFloppyCDROM(this);
otherRemovable = new SettingsOtherRemovable(this);
otherPeripherals = new SettingsOtherPeripherals(this);
ui->stackedWidget->addWidget(machine);
ui->stackedWidget->addWidget(display);
ui->stackedWidget->addWidget(input);
ui->stackedWidget->addWidget(sound);
ui->stackedWidget->addWidget(network);
ui->stackedWidget->addWidget(ports);
ui->stackedWidget->addWidget(storageControllers);
ui->stackedWidget->addWidget(harddisks);
ui->stackedWidget->addWidget(floppyCdrom);
ui->stackedWidget->addWidget(otherRemovable);
ui->stackedWidget->addWidget(otherPeripherals);
ui->listView->setFixedWidth(ui->listView->sizeHintForColumn(0) + 5);
connect(machine, &SettingsMachine::currentMachineChanged, display, &SettingsDisplay::onCurrentMachineChanged);
connect(machine, &SettingsMachine::currentMachineChanged, input, &SettingsInput::onCurrentMachineChanged);
connect(machine, &SettingsMachine::currentMachineChanged, sound, &SettingsSound::onCurrentMachineChanged);
connect(machine, &SettingsMachine::currentMachineChanged, network, &SettingsNetwork::onCurrentMachineChanged);
connect(machine, &SettingsMachine::currentMachineChanged, storageControllers, &SettingsStorageControllers::onCurrentMachineChanged);
connect(machine, &SettingsMachine::currentMachineChanged, otherPeripherals, &SettingsOtherPeripherals::onCurrentMachineChanged);
connect(ui->listView->selectionModel(), &QItemSelectionModel::currentChanged, this, [this](const QModelIndex &current, const QModelIndex &previous) {
ui->stackedWidget->setCurrentIndex(current.row());
});
}
Settings::~Settings()
{
delete ui;
delete Harddrives::busTrackClass;
Harddrives::busTrackClass = nullptr;
}
void Settings::save() {
machine->save();
display->save();
input->save();
sound->save();
network->save();
ports->save();
storageControllers->save();
harddisks->save();
floppyCdrom->save();
otherRemovable->save();
otherPeripherals->save();
}
void Settings::accept()
{
if (confirm_save && !settings_only)
{
QMessageBox questionbox(QMessageBox::Icon::Question, "86Box", QStringLiteral("%1\n\n%2").arg(tr("Do you want to save the settings?"), tr("This will hard reset the emulated machine.")), QMessageBox::Save | QMessageBox::Cancel, this);
QCheckBox *chkbox = new QCheckBox(tr("Don't show this message again"));
questionbox.setCheckBox(chkbox);
chkbox->setChecked(!confirm_save);
QObject::connect(chkbox, &QCheckBox::stateChanged, [](int state) {
confirm_save = (state == Qt::CheckState::Unchecked);
});
questionbox.exec();
if (questionbox.result() == QMessageBox::Cancel) {
confirm_save = true;
return;
}
}
QDialog::accept();
}

49
src/qt/qt_settings.hpp Normal file
View File

@@ -0,0 +1,49 @@
#ifndef QT_SETTINGS_HPP
#define QT_SETTINGS_HPP
#include <QDialog>
namespace Ui {
class Settings;
}
class SettingsMachine;
class SettingsDisplay;
class SettingsInput;
class SettingsSound;
class SettingsNetwork;
class SettingsPorts;
class SettingsStorageControllers;
class SettingsHarddisks;
class SettingsFloppyCDROM;
class SettingsOtherRemovable;
class SettingsOtherPeripherals;
class Settings : public QDialog
{
Q_OBJECT
public:
explicit Settings(QWidget *parent = nullptr);
~Settings();
void save();
protected slots:
void accept() override;
private:
Ui::Settings *ui;
SettingsMachine* machine;
SettingsDisplay* display;
SettingsInput* input;
SettingsSound* sound;
SettingsNetwork* network;
SettingsPorts* ports;
SettingsStorageControllers* storageControllers;
SettingsHarddisks* harddisks;
SettingsFloppyCDROM* floppyCdrom;
SettingsOtherRemovable* otherRemovable;
SettingsOtherPeripherals* otherPeripherals;
};
#endif // QT_SETTINGS_HPP

99
src/qt/qt_settings.ui Normal file
View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Settings</class>
<widget class="QDialog" name="Settings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>831</width>
<height>595</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>831</width>
<height>595</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>831</width>
<height>595</height>
</size>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,3">
<item>
<widget class="QListView" name="listView"/>
</item>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Settings</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Settings</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,256 @@
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "86box/hdd.h"
#include "qt_settings_bus_tracking.hpp"
SettingsBusTracking::SettingsBusTracking()
{
int i;
mfm_tracking = 0x0000000000000000ULL;
esdi_tracking = 0x0000000000000000ULL;
xta_tracking = 0x0000000000000000ULL;
for (i = 0; i < 8; i++) {
if (i < 4)
ide_tracking[i] = 0x0000000000000000ULL;
scsi_tracking[i] = 0x0000000000000000ULL;
}
}
uint8_t
SettingsBusTracking::next_free_mfm_channel()
{
if ((mfm_tracking & 0xff00ULL) && !(mfm_tracking & 0x00ffULL))
return 1;
if (!(mfm_tracking & 0xff00ULL) && (mfm_tracking & 0x00ffULL))
return 0;
return CHANNEL_NONE;
}
uint8_t
SettingsBusTracking::next_free_esdi_channel()
{
if ((esdi_tracking & 0xff00ULL) && !(esdi_tracking & 0x00ffULL))
return 1;
if (!(esdi_tracking & 0xff00ULL) && (esdi_tracking & 0x00ffULL))
return 0;
return CHANNEL_NONE;
}
uint8_t
SettingsBusTracking::next_free_xta_channel()
{
if ((xta_tracking & 0xff00ULL) && !(xta_tracking & 0x00ffULL))
return 1;
if (!(xta_tracking & 0xff00ULL) && (xta_tracking & 0x00ffULL))
return 0;
return CHANNEL_NONE;
}
uint8_t
SettingsBusTracking::next_free_ide_channel()
{
int i, element;
uint64_t mask;
uint8_t ret = CHANNEL_NONE;
for (i = 0; i < 32; i++) {
element = ((i << 3) >> 6);
mask = 0xffULL << ((uint64_t) ((i << 3) & 0x3f));
if (!(ide_tracking[element] & mask)) {
ret = (uint8_t) i;
break;
}
}
return ret;
}
uint8_t
SettingsBusTracking::next_free_scsi_id()
{
int i, element;
uint64_t mask;
uint8_t ret = CHANNEL_NONE;
for (i = 0; i < 64; i++) {
element = ((i << 3) >> 6);
mask = 0xffULL << ((uint64_t) ((i << 3) & 0x3f));
if (!(scsi_tracking[element] & mask)) {
ret = (uint8_t) i;
break;
}
}
return ret;
}
int
SettingsBusTracking::mfm_bus_full()
{
int i;
uint64_t mask;
uint8_t count = 0;
for (i = 0; i < 2; i++) {
mask = 0xffULL << ((uint64_t) ((i << 3) & 0x3f));
if (mfm_tracking & mask)
count++;
}
return (count == 2);
}
int
SettingsBusTracking::esdi_bus_full()
{
int i;
uint64_t mask;
uint8_t count = 0;
for (i = 0; i < 2; i++) {
mask = 0xffULL << ((uint64_t) ((i << 3) & 0x3f));
if (esdi_tracking & mask)
count++;
}
return (count == 2);
}
int
SettingsBusTracking::xta_bus_full()
{
int i;
uint64_t mask;
uint8_t count = 0;
for (i = 0; i < 2; i++) {
mask = 0xffULL << ((uint64_t) ((i << 3) & 0x3f));
if (xta_tracking & mask)
count++;
}
return (count == 2);
}
int
SettingsBusTracking::ide_bus_full()
{
int i, element;
uint64_t mask;
uint8_t count = 0;
for (i = 0; i < 32; i++) {
element = ((i << 3) >> 6);
mask = 0xffULL << ((uint64_t) ((i << 3) & 0x3f));
if (ide_tracking[element] & mask)
count++;
}
return (count == 32);
}
int
SettingsBusTracking::scsi_bus_full()
{
int i, element;
uint64_t mask;
uint8_t count = 0;
for (i = 0; i < 64; i++) {
element = ((i << 3) >> 6);
mask = 0xffULL << ((uint64_t) ((i << 3) & 0x3f));
if (scsi_tracking[element] & mask)
count++;
}
return (count == 64);
}
void
SettingsBusTracking::device_track(int set, uint8_t dev_type, int bus, int channel)
{
int i, element;
uint64_t mask;
uint8_t count = 0;
switch (bus) {
case HDD_BUS_MFM:
mask = ((uint64_t) dev_type) << ((uint64_t) ((channel << 3) & 0x3f));
if (set)
mfm_tracking |= mask;
else
mfm_tracking &= ~mask;
break;
case HDD_BUS_ESDI:
mask = ((uint64_t) dev_type) << ((uint64_t) ((channel << 3) & 0x3f));
if (set)
esdi_tracking |= mask;
else
esdi_tracking &= ~mask;
break;
case HDD_BUS_XTA:
mask = ((uint64_t) dev_type) << ((uint64_t) ((channel << 3) & 0x3f));
if (set)
xta_tracking |= mask;
else
xta_tracking &= ~mask;
break;
case HDD_BUS_IDE:
case HDD_BUS_ATAPI:
element = ((channel << 3) >> 6);
mask = ((uint64_t) dev_type) << ((uint64_t) ((channel << 3) & 0x3f));
if (set)
ide_tracking[element] |= mask;
else
ide_tracking[element] &= ~mask;
break;
case HDD_BUS_SCSI:
element = ((channel << 3) >> 6);
mask = ((uint64_t) dev_type) << ((uint64_t) ((channel << 3) & 0x3f));
if (set)
scsi_tracking[element] |= mask;
else
scsi_tracking[element] &= ~mask;
break;
}
}

View File

@@ -0,0 +1,63 @@
#ifndef QT_SETTINGS_BUS_TRACKING_HPP
#define QT_SETTINGS_BUS_TRACKING_HPP
#include <QWidget>
#define TRACK_CLEAR 0
#define TRACK_SET 1
#define DEV_HDD 0x01
#define DEV_CDROM 0x02
#define DEV_ZIP 0x04
#define DEV_MO 0x08
#define BUS_MFM 0
#define BUS_ESDI 1
#define BUS_XTA 2
#define BUS_IDE 3
#define BUS_SCSI 4
#define CHANNEL_NONE 0xff
namespace Ui {
class SettingsBusTracking;
}
class SettingsBusTracking
{
public:
explicit SettingsBusTracking();
~SettingsBusTracking() = default;
/* These return 0xff is none is free. */
uint8_t next_free_mfm_channel();
uint8_t next_free_esdi_channel();
uint8_t next_free_xta_channel();
uint8_t next_free_ide_channel();
uint8_t next_free_scsi_id();
int mfm_bus_full();
int esdi_bus_full();
int xta_bus_full();
int ide_bus_full();
int scsi_bus_full();
/* Set: 0 = Clear the device from the tracking, 1 = Set the device on the tracking.
Device type: 1 = Hard Disk, 2 = CD-ROM, 4 = ZIP, 8 = Magneto-Optical.
Bus: 0 = MFM, 1 = ESDI, 2 = XTA, 3 = IDE, 4 = SCSI. */
void device_track(int set, uint8_t dev_type, int bus, int channel);
private:
/* 1 channel, 2 devices per channel, 8 bits per device = 16 bits. */
uint64_t mfm_tracking{0};
/* 1 channel, 2 devices per channel, 8 bits per device = 16 bits. */
uint64_t esdi_tracking{0};
/* 1 channel, 2 devices per channel, 8 bits per device = 16 bits. */
uint64_t xta_tracking{0};
/* 16 channels (prepatation for that weird IDE card), 2 devices per channel, 8 bits per device = 256 bits. */
uint64_t ide_tracking[4]{0, 0, 0, 0};
/* 4 buses, 16 devices per bus, 8 bits per device (future-proofing) = 512 bits. */
uint64_t scsi_tracking[8]{0, 0, 0, 0, 0, 0, 0, 0};
};
#endif // QT_SETTINGS_BUS_TRACKING_HPP

View File

@@ -0,0 +1,104 @@
#include "qt_settingsdisplay.hpp"
#include "ui_qt_settingsdisplay.h"
#include <QDebug>
extern "C" {
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/machine.h>
#include <86box/video.h>
}
#include "qt_deviceconfig.hpp"
#include "qt_models_common.hpp"
SettingsDisplay::SettingsDisplay(QWidget *parent) :
QWidget(parent),
ui(new Ui::SettingsDisplay)
{
ui->setupUi(this);
onCurrentMachineChanged(machine);
}
SettingsDisplay::~SettingsDisplay()
{
delete ui;
}
void SettingsDisplay::save() {
gfxcard = ui->comboBoxVideo->currentData().toInt();
voodoo_enabled = ui->checkBoxVoodoo->isChecked() ? 1 : 0;
}
void SettingsDisplay::onCurrentMachineChanged(int machineId) {
// win_settings_video_proc, WM_INITDIALOG
this->machineId = machineId;
auto* model = ui->comboBoxVideo->model();
auto removeRows = model->rowCount();
int c = 0;
int selectedRow = 0;
while (true) {
/* Skip "internal" if machine doesn't have it. */
if ((c == 1) && (machine_has_flags(machineId, MACHINE_VIDEO) == 0)) {
c++;
continue;
}
const device_t* video_dev = video_card_getdevice(c);
QString name = DeviceConfig::DeviceName(video_dev, video_get_internal_name(c), 1);
if (name.isEmpty()) {
break;
}
if (video_card_available(c) &&
device_is_valid(video_dev, machineId)) {
int row = Models::AddEntry(model, name, c);
if (c == gfxcard) {
selectedRow = row - removeRows;
}
}
c++;
}
model->removeRows(0, removeRows);
if (machine_has_flags(machineId, MACHINE_VIDEO_ONLY) > 0) {
ui->comboBoxVideo->setEnabled(false);
selectedRow = 1;
} else {
ui->comboBoxVideo->setEnabled(true);
}
ui->comboBoxVideo->setCurrentIndex(selectedRow);
}
void SettingsDisplay::on_pushButtonConfigure_clicked() {
auto* device = video_card_getdevice(ui->comboBoxVideo->currentData().toInt());
DeviceConfig::ConfigureDevice(device);
}
void SettingsDisplay::on_pushButtonConfigureVoodoo_clicked() {
DeviceConfig::ConfigureDevice(&voodoo_device);
}
void SettingsDisplay::on_comboBoxVideo_currentIndexChanged(int index) {
if (index < 0) {
return;
}
int videoCard = ui->comboBoxVideo->currentData().toInt();
ui->pushButtonConfigure->setEnabled(video_card_has_config(videoCard) > 0);
bool machineHasPci = machine_has_bus(machineId, MACHINE_BUS_PCI) > 0;
ui->checkBoxVoodoo->setEnabled(machineHasPci);
if (machineHasPci) {
ui->checkBoxVoodoo->setChecked(voodoo_enabled);
}
ui->pushButtonConfigureVoodoo->setEnabled(machineHasPci && ui->checkBoxVoodoo->isChecked());
}
void SettingsDisplay::on_checkBoxVoodoo_stateChanged(int state) {
ui->pushButtonConfigureVoodoo->setEnabled(state == Qt::Checked);
}

View File

@@ -0,0 +1,34 @@
#ifndef QT_SETTINGSDISPLAY_HPP
#define QT_SETTINGSDISPLAY_HPP
#include <QWidget>
namespace Ui {
class SettingsDisplay;
}
class SettingsDisplay : public QWidget
{
Q_OBJECT
public:
explicit SettingsDisplay(QWidget *parent = nullptr);
~SettingsDisplay();
void save();
public slots:
void onCurrentMachineChanged(int machineId);
private slots:
void on_checkBoxVoodoo_stateChanged(int state);
void on_comboBoxVideo_currentIndexChanged(int index);
void on_pushButtonConfigureVoodoo_clicked();
void on_pushButtonConfigure_clicked();
private:
Ui::SettingsDisplay *ui;
int machineId = 0;
};
#endif // QT_SETTINGSDISPLAY_HPP

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsDisplay</class>
<widget class="QWidget" name="SettingsDisplay">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="1">
<widget class="QComboBox" name="comboBoxVideo"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Video:</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="pushButtonConfigure">
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="pushButtonConfigureVoodoo">
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="checkBoxVoodoo">
<property name="text">
<string>Voodoo Graphics</string>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,235 @@
#include "qt_settingsfloppycdrom.hpp"
#include "ui_qt_settingsfloppycdrom.h"
extern "C" {
#include <86box/timer.h>
#include <86box/fdd.h>
#include <86box/cdrom.h>
}
#include <QStandardItemModel>
#include "qt_models_common.hpp"
#include "qt_harddrive_common.hpp"
#include "qt_settings_bus_tracking.hpp"
#include "qt_progsettings.hpp"
static void setFloppyType(QAbstractItemModel* model, const QModelIndex& idx, int type) {
QIcon icon;
if (type == 0) {
icon = ProgSettings::loadIcon("/floppy_disabled.ico");
} else if (type >= 1 && type <= 6) {
icon = ProgSettings::loadIcon("/floppy_525.ico");
} else {
icon = ProgSettings::loadIcon("/floppy_35.ico");
}
model->setData(idx, QObject::tr(fdd_getname(type)));
model->setData(idx, type, Qt::UserRole);
model->setData(idx, icon, Qt::DecorationRole);
}
static void setCDROMBus(QAbstractItemModel* model, const QModelIndex& idx, uint8_t bus, uint8_t channel) {
QIcon icon;
switch (bus) {
case CDROM_BUS_DISABLED:
icon = ProgSettings::loadIcon("/cdrom_disabled.ico");
break;
case CDROM_BUS_ATAPI:
case CDROM_BUS_SCSI:
icon = ProgSettings::loadIcon("/cdrom.ico");
break;
}
auto i = idx.siblingAtColumn(0);
model->setData(i, Harddrives::BusChannelName(bus, channel));
model->setData(i, bus, Qt::UserRole);
model->setData(i, channel, Qt::UserRole + 1);
model->setData(i, icon, Qt::DecorationRole);
}
static void setCDROMSpeed(QAbstractItemModel* model, const QModelIndex& idx, uint8_t speed) {
auto i = idx.siblingAtColumn(1);
model->setData(i, QString("%1x").arg(speed));
model->setData(i, speed, Qt::UserRole);
}
SettingsFloppyCDROM::SettingsFloppyCDROM(QWidget *parent) :
QWidget(parent),
ui(new Ui::SettingsFloppyCDROM)
{
ui->setupUi(this);
auto* model = ui->comboBoxFloppyType->model();
int i = 0;
while (true) {
QString name = tr(fdd_getname(i));
if (name.isEmpty()) {
break;
}
Models::AddEntry(model, name, i);
++i;
}
model = new QStandardItemModel(0, 3, this);
ui->tableViewFloppy->setModel(model);
model->setHeaderData(0, Qt::Horizontal, tr("Type"));
model->setHeaderData(1, Qt::Horizontal, tr("Turbo"));
model->setHeaderData(2, Qt::Horizontal, tr("Check BPB"));
model->insertRows(0, FDD_NUM);
/* Floppy drives category */
for (int i = 0; i < FDD_NUM; i++) {
auto idx = model->index(i, 0);
int type = fdd_get_type(i);
setFloppyType(model, idx, type);
model->setData(idx.siblingAtColumn(1), fdd_get_turbo(i) > 0 ? tr("On") : tr("Off"));
model->setData(idx.siblingAtColumn(2), fdd_get_check_bpb(i) > 0 ? tr("On") : tr("Off"));
}
ui->tableViewFloppy->resizeColumnsToContents();
ui->tableViewFloppy->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
connect(ui->tableViewFloppy->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &SettingsFloppyCDROM::onFloppyRowChanged);
ui->tableViewFloppy->setCurrentIndex(model->index(0, 0));
Harddrives::populateRemovableBuses(ui->comboBoxBus->model());
model = ui->comboBoxSpeed->model();
for (int i = 0; i < 72; i++) {
Models::AddEntry(model, QString("%1x").arg(i + 1), i + 1);
}
model = new QStandardItemModel(0, 2, this);
ui->tableViewCDROM->setModel(model);
model->setHeaderData(0, Qt::Horizontal, tr("Bus"));
model->setHeaderData(1, Qt::Horizontal, tr("Speed"));
model->insertRows(0, CDROM_NUM);
for (int i = 0; i < CDROM_NUM; i++) {
auto idx = model->index(i, 0);
setCDROMBus(model, idx, cdrom[i].bus_type, cdrom[i].res);
setCDROMSpeed(model, idx.siblingAtColumn(1), cdrom[i].speed);
Harddrives::busTrackClass->device_track(1, DEV_CDROM, cdrom[i].bus_type, cdrom[i].bus_type == CDROM_BUS_ATAPI ? cdrom[i].ide_channel : cdrom[i].scsi_device_id);
}
ui->tableViewCDROM->resizeColumnsToContents();
ui->tableViewCDROM->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
connect(ui->tableViewCDROM->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &SettingsFloppyCDROM::onCDROMRowChanged);
ui->tableViewCDROM->setCurrentIndex(model->index(0, 0));
}
SettingsFloppyCDROM::~SettingsFloppyCDROM()
{
delete ui;
}
void SettingsFloppyCDROM::save() {
auto* model = ui->tableViewFloppy->model();
for (int i = 0; i < FDD_NUM; i++) {
fdd_set_type(i, model->index(i, 0).data(Qt::UserRole).toInt());
fdd_set_turbo(i, model->index(i, 1).data() == tr("On") ? 1 : 0);
fdd_set_check_bpb(i, model->index(i, 2).data() == tr("On") ? 1 : 0);
}
/* Removable devices category */
model = ui->tableViewCDROM->model();
for (int i = 0; i < CDROM_NUM; i++) {
cdrom[i].img_fp = NULL;
cdrom[i].priv = NULL;
cdrom[i].ops = NULL;
cdrom[i].image = NULL;
cdrom[i].insert = NULL;
cdrom[i].close = NULL;
cdrom[i].get_volume = NULL;
cdrom[i].get_channel = NULL;
cdrom[i].bus_type = model->index(i, 0).data(Qt::UserRole).toUInt();
cdrom[i].res = model->index(i, 0).data(Qt::UserRole + 1).toUInt();
cdrom[i].speed = model->index(i, 1).data(Qt::UserRole).toUInt();
}
}
void SettingsFloppyCDROM::onFloppyRowChanged(const QModelIndex &current) {
int type = current.siblingAtColumn(0).data(Qt::UserRole).toInt();
ui->comboBoxFloppyType->setCurrentIndex(type);
ui->checkBoxTurboTimings->setChecked(current.siblingAtColumn(1).data() == tr("On"));
ui->checkBoxCheckBPB->setChecked(current.siblingAtColumn(2).data() == tr("On"));
}
void SettingsFloppyCDROM::onCDROMRowChanged(const QModelIndex &current) {
uint8_t bus = current.siblingAtColumn(0).data(Qt::UserRole).toUInt();
uint8_t channel = current.siblingAtColumn(0).data(Qt::UserRole + 1).toUInt();
uint8_t speed = current.siblingAtColumn(1).data(Qt::UserRole).toUInt();
ui->comboBoxBus->setCurrentIndex(-1);
auto* model = ui->comboBoxBus->model();
auto match = model->match(model->index(0, 0), Qt::UserRole, bus);
if (! match.isEmpty()) {
ui->comboBoxBus->setCurrentIndex(match.first().row());
}
model = ui->comboBoxChannel->model();
match = model->match(model->index(0, 0), Qt::UserRole, channel);
if (! match.isEmpty()) {
ui->comboBoxChannel->setCurrentIndex(match.first().row());
}
else ui->comboBoxChannel->setCurrentIndex(8);
ui->comboBoxSpeed->setCurrentIndex(speed - 1);
}
void SettingsFloppyCDROM::on_checkBoxTurboTimings_stateChanged(int arg1) {
auto idx = ui->tableViewFloppy->selectionModel()->currentIndex();
ui->tableViewFloppy->model()->setData(idx.siblingAtColumn(1), arg1 == Qt::Checked ? tr("On") : tr("Off"));
}
void SettingsFloppyCDROM::on_checkBoxCheckBPB_stateChanged(int arg1) {
auto idx = ui->tableViewFloppy->selectionModel()->currentIndex();
ui->tableViewFloppy->model()->setData(idx.siblingAtColumn(2), arg1 == Qt::Checked ? tr("On") : tr("Off"));
}
void SettingsFloppyCDROM::on_comboBoxFloppyType_activated(int index) {
setFloppyType(ui->tableViewFloppy->model(), ui->tableViewFloppy->selectionModel()->currentIndex(), index);
}
void SettingsFloppyCDROM::on_comboBoxBus_currentIndexChanged(int index) {
if (index < 0) {
return;
}
int bus = ui->comboBoxBus->currentData().toInt();
bool enabled = (bus != CDROM_BUS_DISABLED);
ui->comboBoxChannel->setEnabled(enabled);
ui->comboBoxSpeed->setEnabled(enabled);
Harddrives::populateBusChannels(ui->comboBoxChannel->model(), bus);
}
void SettingsFloppyCDROM::on_comboBoxSpeed_activated(int index) {
auto idx = ui->tableViewCDROM->selectionModel()->currentIndex();
setCDROMSpeed(ui->tableViewCDROM->model(), idx.siblingAtColumn(1), index + 1);
}
void SettingsFloppyCDROM::on_comboBoxBus_activated(int) {
auto i = ui->tableViewCDROM->selectionModel()->currentIndex().siblingAtColumn(0);
Harddrives::busTrackClass->device_track(0, DEV_CDROM, ui->tableViewCDROM->model()->data(i, Qt::UserRole).toInt(), ui->tableViewCDROM->model()->data(i, Qt::UserRole + 1).toInt());
ui->comboBoxChannel->setCurrentIndex(ui->comboBoxBus->currentData().toUInt() == CDROM_BUS_ATAPI ? Harddrives::busTrackClass->next_free_ide_channel() : Harddrives::busTrackClass->next_free_scsi_id());
setCDROMBus(
ui->tableViewCDROM->model(),
ui->tableViewCDROM->selectionModel()->currentIndex(),
ui->comboBoxBus->currentData().toUInt(),
ui->comboBoxChannel->currentData().toUInt());
Harddrives::busTrackClass->device_track(1, DEV_CDROM, ui->tableViewCDROM->model()->data(i, Qt::UserRole).toInt(), ui->tableViewCDROM->model()->data(i, Qt::UserRole + 1).toInt());
}
void SettingsFloppyCDROM::on_comboBoxChannel_activated(int) {
auto i = ui->tableViewCDROM->selectionModel()->currentIndex().siblingAtColumn(0);
Harddrives::busTrackClass->device_track(0, DEV_CDROM, ui->tableViewCDROM->model()->data(i, Qt::UserRole).toInt(), ui->tableViewCDROM->model()->data(i, Qt::UserRole + 1).toInt());
setCDROMBus(
ui->tableViewCDROM->model(),
ui->tableViewCDROM->selectionModel()->currentIndex(),
ui->comboBoxBus->currentData().toUInt(),
ui->comboBoxChannel->currentData().toUInt());
Harddrives::busTrackClass->device_track(1, DEV_CDROM, ui->tableViewCDROM->model()->data(i, Qt::UserRole).toInt(), ui->tableViewCDROM->model()->data(i, Qt::UserRole + 1).toInt());
}

View File

@@ -0,0 +1,35 @@
#ifndef QT_SETTINGSFLOPPYCDROM_HPP
#define QT_SETTINGSFLOPPYCDROM_HPP
#include <QWidget>
namespace Ui {
class SettingsFloppyCDROM;
}
class SettingsFloppyCDROM : public QWidget
{
Q_OBJECT
public:
explicit SettingsFloppyCDROM(QWidget *parent = nullptr);
~SettingsFloppyCDROM();
void save();
private slots:
void on_comboBoxChannel_activated(int index);
void on_comboBoxBus_activated(int index);
void on_comboBoxSpeed_activated(int index);
void on_comboBoxBus_currentIndexChanged(int index);
void on_comboBoxFloppyType_activated(int index);
void on_checkBoxCheckBPB_stateChanged(int arg1);
void on_checkBoxTurboTimings_stateChanged(int arg1);
void onFloppyRowChanged(const QModelIndex &current);
void onCDROMRowChanged(const QModelIndex &current);
private:
Ui::SettingsFloppyCDROM *ui;
};
#endif // QT_SETTINGSFLOPPYCDROM_HPP

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsFloppyCDROM</class>
<widget class="QWidget" name="SettingsFloppyCDROM">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>544</width>
<height>617</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Floppy drives:</string>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="tableViewFloppy">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBoxFloppyType"/>
</item>
<item>
<widget class="QCheckBox" name="checkBoxTurboTimings">
<property name="text">
<string>Turbo timings</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBoxCheckBPB">
<property name="text">
<string>Check BPB</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>CD-ROM drives:</string>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="tableViewCDROM">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Channel:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboBoxBus"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Speed:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Bus:</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QComboBox" name="comboBoxChannel"/>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBoxSpeed"/>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,279 @@
#include "qt_settingsharddisks.hpp"
#include "ui_qt_settingsharddisks.h"
extern "C" {
#include <86box/86box.h>
#include <86box/hdd.h>
}
#include <QStandardItemModel>
#include "qt_harddiskdialog.hpp"
#include "qt_harddrive_common.hpp"
#include "qt_settings_bus_tracking.hpp"
#include "qt_progsettings.hpp"
const int ColumnBus = 0;
const int ColumnFilename = 1;
const int ColumnCylinders = 2;
const int ColumnHeads = 3;
const int ColumnSectors = 4;
const int ColumnSize = 5;
const int DataBus = Qt::UserRole;
const int DataBusChannel = Qt::UserRole + 1;
const int DataBusPrevious = Qt::UserRole + 2;
const int DataBusChannelPrevious = Qt::UserRole + 3;
/*
static void
normalize_hd_list()
{
hard_disk_t ihdd[HDD_NUM];
int i, j;
j = 0;
memset(ihdd, 0x00, HDD_NUM * sizeof(hard_disk_t));
for (i = 0; i < HDD_NUM; i++) {
if (temp_hdd[i].bus != HDD_BUS_DISABLED) {
memcpy(&(ihdd[j]), &(temp_hdd[i]), sizeof(hard_disk_t));
j++;
}
}
memcpy(temp_hdd, ihdd, HDD_NUM * sizeof(hard_disk_t));
}
*/
static QString busChannelName(const QModelIndex& idx) {
return Harddrives::BusChannelName(idx.data(DataBus).toUInt(), idx.data(DataBusChannel).toUInt());
}
static void addRow(QAbstractItemModel* model, hard_disk_t* hd) {
const QString userPath = usr_path;
int row = model->rowCount();
model->insertRow(row);
QString busName = Harddrives::BusChannelName(hd->bus, hd->channel);
model->setData(model->index(row, ColumnBus), busName);
model->setData(model->index(row, ColumnBus), ProgSettings::loadIcon( "/hard_disk.ico"), Qt::DecorationRole);
model->setData(model->index(row, ColumnBus), hd->bus, DataBus);
model->setData(model->index(row, ColumnBus), hd->bus, DataBusPrevious);
model->setData(model->index(row, ColumnBus), hd->channel, DataBusChannel);
model->setData(model->index(row, ColumnBus), hd->channel, DataBusChannelPrevious);
Harddrives::busTrackClass->device_track(1, DEV_HDD, hd->bus, hd->channel);
QString fileName = hd->fn;
if (fileName.startsWith(userPath, Qt::CaseInsensitive)) {
model->setData(model->index(row, ColumnFilename), fileName.mid(userPath.size()));
} else {
model->setData(model->index(row, ColumnFilename), fileName);
}
model->setData(model->index(row, ColumnFilename), fileName, Qt::UserRole);
model->setData(model->index(row, ColumnCylinders), hd->tracks);
model->setData(model->index(row, ColumnHeads), hd->hpc);
model->setData(model->index(row, ColumnSectors), hd->spt);
model->setData(model->index(row, ColumnSize), (hd->tracks * hd->hpc * hd->spt) >> 11);
}
SettingsHarddisks::SettingsHarddisks(QWidget *parent) :
QWidget(parent),
ui(new Ui::SettingsHarddisks)
{
ui->setupUi(this);
QAbstractItemModel* model = new QStandardItemModel(0, 6, this);
model->setHeaderData(ColumnBus, Qt::Horizontal, tr("Bus"));
model->setHeaderData(ColumnFilename, Qt::Horizontal, tr("File"));
model->setHeaderData(ColumnCylinders, Qt::Horizontal, tr("C"));
model->setHeaderData(ColumnHeads, Qt::Horizontal, tr("H"));
model->setHeaderData(ColumnSectors, Qt::Horizontal, tr("S"));
model->setHeaderData(ColumnSize, Qt::Horizontal, tr("MiB"));
ui->tableView->setModel(model);
for (int i = 0; i < HDD_NUM; i++) {
if (hdd[i].bus > 0) {
addRow(model, &hdd[i]);
}
}
if (model->rowCount() == HDD_NUM)
{
ui->pushButtonNew->setEnabled(false);
ui->pushButtonExisting->setEnabled(false);
}
ui->tableView->resizeColumnsToContents();
ui->tableView->horizontalHeader()->setSectionResizeMode(ColumnFilename, QHeaderView::Stretch);
auto* tableSelectionModel = ui->tableView->selectionModel();
connect(tableSelectionModel, &QItemSelectionModel::currentRowChanged, this, &SettingsHarddisks::onTableRowChanged);
onTableRowChanged(QModelIndex());
Harddrives::populateBuses(ui->comboBoxBus->model());
on_comboBoxBus_currentIndexChanged(0);
}
SettingsHarddisks::~SettingsHarddisks()
{
delete ui;
}
void SettingsHarddisks::save() {
memset(hdd, 0, sizeof(hdd));
auto* model = ui->tableView->model();
int rows = model->rowCount();
for (int i = 0; i < rows; ++i) {
auto idx = model->index(i, ColumnBus);
hdd[i].bus = idx.data(DataBus).toUInt();
hdd[i].channel = idx.data(DataBusChannel).toUInt();
hdd[i].tracks = idx.siblingAtColumn(ColumnCylinders).data().toUInt();
hdd[i].hpc = idx.siblingAtColumn(ColumnHeads).data().toUInt();
hdd[i].spt = idx.siblingAtColumn(ColumnSectors).data().toUInt();
QByteArray fileName = idx.siblingAtColumn(ColumnFilename).data(Qt::UserRole).toString().toUtf8();
strncpy(hdd[i].fn, fileName.data(), sizeof(hdd[i].fn));
hdd[i].priv = nullptr;
}
}
void SettingsHarddisks::on_comboBoxBus_currentIndexChanged(int index) {
if (index < 0) {
return;
}
buschangeinprogress = true;
auto idx = ui->tableView->selectionModel()->currentIndex();
if (idx.isValid()) {
auto* model = ui->tableView->model();
auto col = idx.siblingAtColumn(ColumnBus);
model->setData(col, ui->comboBoxBus->currentData(Qt::UserRole), DataBus);
model->setData(col, busChannelName(col), Qt::DisplayRole);
Harddrives::busTrackClass->device_track(0, DEV_HDD, model->data(col, DataBusPrevious).toInt(), model->data(col, DataBusChannelPrevious).toInt());
model->setData(col, ui->comboBoxBus->currentData(Qt::UserRole), DataBusPrevious);
}
Harddrives::populateBusChannels(ui->comboBoxChannel->model(), ui->comboBoxBus->currentData().toInt());
int chanIdx = 0;
switch (ui->comboBoxBus->currentData().toInt())
{
case HDD_BUS_MFM:
chanIdx = (Harddrives::busTrackClass->next_free_mfm_channel());
break;
case HDD_BUS_XTA:
chanIdx = (Harddrives::busTrackClass->next_free_xta_channel());
break;
case HDD_BUS_ESDI:
chanIdx = (Harddrives::busTrackClass->next_free_esdi_channel());
break;
case HDD_BUS_ATAPI:
case HDD_BUS_IDE:
chanIdx = (Harddrives::busTrackClass->next_free_ide_channel());
break;
case HDD_BUS_SCSI:
chanIdx = (Harddrives::busTrackClass->next_free_scsi_id());
break;
}
if (idx.isValid()) {
auto* model = ui->tableView->model();
auto col = idx.siblingAtColumn(ColumnBus);
model->setData(col, chanIdx, DataBusChannelPrevious);
}
ui->comboBoxChannel->setCurrentIndex(chanIdx);
buschangeinprogress = false;
}
void SettingsHarddisks::on_comboBoxChannel_currentIndexChanged(int index) {
if (index < 0) {
return;
}
auto idx = ui->tableView->selectionModel()->currentIndex();
if (idx.isValid()) {
auto* model = ui->tableView->model();
auto col = idx.siblingAtColumn(ColumnBus);
model->setData(col, ui->comboBoxChannel->currentData(Qt::UserRole), DataBusChannel);
model->setData(col, busChannelName(col), Qt::DisplayRole);
if (!buschangeinprogress) Harddrives::busTrackClass->device_track(0, DEV_HDD, model->data(col, DataBus).toInt(), model->data(col, DataBusChannelPrevious).toUInt());
Harddrives::busTrackClass->device_track(1, DEV_HDD, model->data(col, DataBus).toInt(), model->data(col, DataBusChannel).toUInt());
model->setData(col, ui->comboBoxChannel->currentData(Qt::UserRole), DataBusChannelPrevious);
}
}
void SettingsHarddisks::onTableRowChanged(const QModelIndex &current) {
bool hidden = !current.isValid();
ui->labelBus->setHidden(hidden);
ui->labelChannel->setHidden(hidden);
ui->comboBoxBus->setHidden(hidden);
ui->comboBoxChannel->setHidden(hidden);
uint32_t bus = current.siblingAtColumn(ColumnBus).data(DataBus).toUInt();
uint32_t busChannel = current.siblingAtColumn(ColumnBus).data(DataBusChannel).toUInt();
auto* model = ui->comboBoxBus->model();
auto match = model->match(model->index(0, 0), Qt::UserRole, bus);
if (! match.isEmpty()) {
ui->comboBoxBus->setCurrentIndex(match.first().row());
}
model = ui->comboBoxChannel->model();
match = model->match(model->index(0, 0), Qt::UserRole, busChannel);
if (! match.isEmpty()) {
ui->comboBoxChannel->setCurrentIndex(match.first().row());
}
}
static void addDriveFromDialog(Ui::SettingsHarddisks* ui, const HarddiskDialog& dlg) {
QByteArray fn = dlg.fileName().toUtf8();
hard_disk_t hd;
hd.bus = dlg.bus();
hd.channel = dlg.channel();
hd.tracks = dlg.cylinders();
hd.hpc = dlg.heads();
hd.spt = dlg.sectors();
strncpy(hd.fn, fn.data(), sizeof(hd.fn));
addRow(ui->tableView->model(), &hd);
ui->tableView->resizeColumnsToContents();
ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
if (ui->tableView->model()->rowCount() == HDD_NUM)
{
ui->pushButtonNew->setEnabled(false);
ui->pushButtonExisting->setEnabled(false);
}
}
void SettingsHarddisks::on_pushButtonNew_clicked() {
HarddiskDialog dialog(false, this);
switch (dialog.exec()) {
case QDialog::Accepted:
addDriveFromDialog(ui, dialog);
break;
}
}
void SettingsHarddisks::on_pushButtonExisting_clicked() {
HarddiskDialog dialog(true, this);
switch (dialog.exec()) {
case QDialog::Accepted:
addDriveFromDialog(ui, dialog);
break;
}
}
void SettingsHarddisks::on_pushButtonRemove_clicked() {
auto idx = ui->tableView->selectionModel()->currentIndex();
if (! idx.isValid()) {
return;
}
auto* model = ui->tableView->model();
model->removeRow(idx.row());
ui->pushButtonNew->setEnabled(true);
ui->pushButtonExisting->setEnabled(true);
}

View File

@@ -0,0 +1,36 @@
#ifndef QT_SETTINGSHARDDISKS_HPP
#define QT_SETTINGSHARDDISKS_HPP
#include <QWidget>
namespace Ui {
class SettingsHarddisks;
}
class SettingsHarddisks : public QWidget
{
Q_OBJECT
public:
explicit SettingsHarddisks(QWidget *parent = nullptr);
~SettingsHarddisks();
void save();
private slots:
void on_comboBoxChannel_currentIndexChanged(int index);
private slots:
void on_pushButtonRemove_clicked();
void on_pushButtonExisting_clicked();
void on_pushButtonNew_clicked();
void on_comboBoxBus_currentIndexChanged(int index);
void onTableRowChanged(const QModelIndex& current);
private:
Ui::SettingsHarddisks *ui;
bool buschangeinprogress = false;
};
#endif // QT_SETTINGSHARDDISKS_HPP

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsHarddisks</class>
<widget class="QWidget" name="SettingsHarddisks">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="tableView">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="labelBus">
<property name="text">
<string>Bus:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBoxBus"/>
</item>
<item>
<widget class="QLabel" name="labelChannel">
<property name="text">
<string>ID:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBoxChannel"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="pushButtonNew">
<property name="text">
<string>&amp;New...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonExisting">
<property name="text">
<string>&amp;Existing...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonRemove">
<property name="text">
<string>&amp;Remove</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

187
src/qt/qt_settingsinput.cpp Normal file
View File

@@ -0,0 +1,187 @@
#include "qt_settingsinput.hpp"
#include "ui_qt_settingsinput.h"
#include <QDebug>
extern "C" {
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/machine.h>
#include <86box/mouse.h>
#include <86box/gameport.h>
}
#include "qt_models_common.hpp"
#include "qt_deviceconfig.hpp"
#include "qt_joystickconfiguration.hpp"
SettingsInput::SettingsInput(QWidget *parent) :
QWidget(parent),
ui(new Ui::SettingsInput)
{
ui->setupUi(this);
onCurrentMachineChanged(machine);
}
SettingsInput::~SettingsInput()
{
delete ui;
}
void SettingsInput::save() {
mouse_type = ui->comboBoxMouse->currentData().toInt();
joystick_type = ui->comboBoxJoystick->currentData().toInt();
}
void SettingsInput::onCurrentMachineChanged(int machineId) {
// win_settings_video_proc, WM_INITDIALOG
this->machineId = machineId;
const auto* machine = &machines[machineId];
auto* mouseModel = ui->comboBoxMouse->model();
auto removeRows = mouseModel->rowCount();
int selectedRow = 0;
for (int i = 0; i < mouse_get_ndev(); ++i) {
const auto* dev = mouse_get_device(i);
if ((i == MOUSE_TYPE_INTERNAL) && (machine_has_flags(machineId, MACHINE_MOUSE) == 0)) {
continue;
}
if (device_is_valid(dev, machineId) == 0) {
continue;
}
QString name = DeviceConfig::DeviceName(dev, mouse_get_internal_name(i), 0);
int row = mouseModel->rowCount();
mouseModel->insertRow(row);
auto idx = mouseModel->index(row, 0);
mouseModel->setData(idx, name, Qt::DisplayRole);
mouseModel->setData(idx, i, Qt::UserRole);
if (i == mouse_type) {
selectedRow = row - removeRows;
}
}
mouseModel->removeRows(0, removeRows);
ui->comboBoxMouse->setCurrentIndex(selectedRow);
int i = 0;
char* joyName = joystick_get_name(i);
auto* joystickModel = ui->comboBoxJoystick->model();
removeRows = joystickModel->rowCount();
selectedRow = 0;
while (joyName) {
int row = Models::AddEntry(joystickModel, tr(joyName).toUtf8().data(), i);
if (i == joystick_type) {
selectedRow = row - removeRows;
}
++i;
joyName = joystick_get_name(i);
}
joystickModel->removeRows(0, removeRows);
ui->comboBoxJoystick->setCurrentIndex(selectedRow);
}
void SettingsInput::on_comboBoxMouse_currentIndexChanged(int index) {
int mouseId = ui->comboBoxMouse->currentData().toInt();
ui->pushButtonConfigureMouse->setEnabled(mouse_has_config(mouseId) > 0);
}
void SettingsInput::on_comboBoxJoystick_currentIndexChanged(int index) {
int joystickId = ui->comboBoxJoystick->currentData().toInt();
for (int i = 0; i < 4; ++i) {
auto* btn = findChild<QPushButton*>(QString("pushButtonJoystick%1").arg(i+1));
if (btn == nullptr) {
continue;
}
btn->setEnabled(joystick_get_max_joysticks(joystickId) > i);
}
}
void SettingsInput::on_pushButtonConfigureMouse_clicked() {
int mouseId = ui->comboBoxMouse->currentData().toInt();
DeviceConfig::ConfigureDevice(mouse_get_device(mouseId));
}
static int get_axis(JoystickConfiguration& jc, int axis, int joystick_nr) {
int axis_sel = jc.selectedAxis(axis);
int nr_axes = plat_joystick_state[joystick_state[joystick_nr].plat_joystick_nr - 1].nr_axes;
int nr_povs = plat_joystick_state[joystick_state[joystick_nr].plat_joystick_nr - 1].nr_povs;
if (axis_sel < nr_axes) {
return axis_sel;
}
axis_sel -= nr_axes;
if (axis_sel < nr_povs * 2) {
if (axis_sel & 1)
return POV_Y | (axis_sel >> 1);
else
return POV_X | (axis_sel >> 1);
}
axis_sel -= nr_povs;
return SLIDER | (axis_sel >> 1);
}
static int get_pov(JoystickConfiguration& jc, int pov, int joystick_nr) {
int pov_sel = jc.selectedPov(pov);
int nr_povs = plat_joystick_state[joystick_state[joystick_nr].plat_joystick_nr-1].nr_povs*2;
if (pov_sel < nr_povs)
{
if (pov_sel & 1)
return POV_Y | (pov_sel >> 1);
else
return POV_X | (pov_sel >> 1);
}
return pov_sel - nr_povs;
}
static void updateJoystickConfig(int type, int joystick_nr, QWidget* parent) {
JoystickConfiguration jc(type, joystick_nr, parent);
switch (jc.exec()) {
case QDialog::Rejected:
return;
case QDialog::Accepted:
break;
}
joystick_state[joystick_nr].plat_joystick_nr = jc.selectedDevice();
if (joystick_state[joystick_nr].plat_joystick_nr) {
for (int c = 0; c < joystick_get_axis_count(type); c++) {
joystick_state[joystick_nr].axis_mapping[c] = get_axis(jc, c, joystick_nr);
}
for (int c = 0; c < joystick_get_button_count(type); c++) {
joystick_state[joystick_nr].button_mapping[c] = jc.selectedButton(c);
}
for (int c = 0; c < joystick_get_button_count(type); c++) {
joystick_state[joystick_nr].pov_mapping[c][0] = get_pov(jc, c, joystick_nr);
joystick_state[joystick_nr].pov_mapping[c][1] = get_pov(jc, c, joystick_nr);
}
}
}
void SettingsInput::on_pushButtonJoystick1_clicked() {
updateJoystickConfig(ui->comboBoxJoystick->currentData().toInt(), 0, this);
}
void SettingsInput::on_pushButtonJoystick2_clicked() {
updateJoystickConfig(ui->comboBoxJoystick->currentData().toInt(), 1, this);
}
void SettingsInput::on_pushButtonJoystick3_clicked() {
updateJoystickConfig(ui->comboBoxJoystick->currentData().toInt(), 2, this);
}
void SettingsInput::on_pushButtonJoystick4_clicked() {
updateJoystickConfig(ui->comboBoxJoystick->currentData().toInt(), 3, this);
}

View File

@@ -0,0 +1,37 @@
#ifndef QT_SETTINGSINPUT_HPP
#define QT_SETTINGSINPUT_HPP
#include <QWidget>
namespace Ui {
class SettingsInput;
}
class SettingsInput : public QWidget
{
Q_OBJECT
public:
explicit SettingsInput(QWidget *parent = nullptr);
~SettingsInput();
void save();
public slots:
void onCurrentMachineChanged(int machineId);
private slots:
void on_pushButtonConfigureMouse_clicked();
void on_comboBoxJoystick_currentIndexChanged(int index);
void on_comboBoxMouse_currentIndexChanged(int index);
void on_pushButtonJoystick1_clicked();
void on_pushButtonJoystick2_clicked();
void on_pushButtonJoystick3_clicked();
void on_pushButtonJoystick4_clicked();
private:
Ui::SettingsInput *ui;
int machineId = 0;
};
#endif // QT_SETTINGSINPUT_HPP

134
src/qt/qt_settingsinput.ui Normal file
View File

@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsInput</class>
<widget class="QWidget" name="SettingsInput">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Mouse:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboBoxMouse"/>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="pushButtonConfigureMouse">
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Joystick:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBoxJoystick"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="pushButtonJoystick1">
<property name="text">
<string>Joystick 1...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonJoystick2">
<property name="text">
<string>Joystick 2...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonJoystick3">
<property name="text">
<string>Joystick 3...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonJoystick4">
<property name="text">
<string>Joystick 4...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,270 @@
#include "qt_settingsmachine.hpp"
#include "ui_qt_settingsmachine.h"
#include <QDebug>
#include <QDialog>
#include <QFrame>
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <algorithm>
extern "C" {
#include "../cpu/cpu.h"
#include <86box/86box.h>
#include <86box/config.h>
#include <86box/device.h>
#include <86box/machine.h>
}
// from nvr.h, which we can't import into CPP code
#define TIME_SYNC_DISABLED 0
#define TIME_SYNC_ENABLED 1
#define TIME_SYNC_UTC 2
#include "qt_deviceconfig.hpp"
#include "qt_models_common.hpp"
SettingsMachine::SettingsMachine(QWidget *parent) :
QWidget(parent),
ui(new Ui::SettingsMachine)
{
ui->setupUi(this);
switch (time_sync) {
case TIME_SYNC_ENABLED:
ui->radioButtonLocalTime->setChecked(true);
break;
case TIME_SYNC_ENABLED | TIME_SYNC_UTC:
ui->radioButtonUTC->setChecked(true);
break;
case TIME_SYNC_DISABLED:
default:
ui->radioButtonDisabled->setChecked(true);
break;
}
auto* waitStatesModel = ui->comboBoxWaitStates->model();
waitStatesModel->insertRows(0, 9);
auto idx = waitStatesModel->index(0, 0);
waitStatesModel->setData(idx, tr("Default"), Qt::DisplayRole);
waitStatesModel->setData(idx, 0, Qt::UserRole);
for (int i = 0; i < 8; ++i) {
idx = waitStatesModel->index(i+1, 0);
waitStatesModel->setData(idx, QString::asprintf(tr("%i Wait state(s)").toUtf8().constData(), i), Qt::DisplayRole);
waitStatesModel->setData(idx, i+1, Qt::UserRole);
}
int selectedMachineType = 0;
auto* machineTypesModel = ui->comboBoxMachineType->model();
for (int i = 0; i < MACHINE_TYPE_MAX; ++i) {
Models::AddEntry(machineTypesModel, machine_types[i].name, machine_types[i].id);
if (machine_types[i].id == machine_get_type(machine)) {
selectedMachineType = i;
}
}
ui->comboBoxMachineType->setCurrentIndex(selectedMachineType);
}
SettingsMachine::~SettingsMachine() {
delete ui;
}
void SettingsMachine::save() {
machine = ui->comboBoxMachine->currentData().toInt();
cpu_f = const_cast<cpu_family_t*>(&cpu_families[ui->comboBoxCPU->currentData().toInt()]);
cpu = ui->comboBoxSpeed->currentData().toInt();
fpu_type = ui->comboBoxFPU->currentData().toInt();
cpu_use_dynarec = ui->checkBoxDynamicRecompiler->isChecked() ? 1 : 0;
int64_t temp_mem_size;
if (machine_get_ram_granularity(machine) < 1024) {
temp_mem_size = ui->spinBoxRAM->value();
} else {
temp_mem_size = ui->spinBoxRAM->value() * 1024;
}
temp_mem_size &= ~(machine_get_ram_granularity(machine) - 1);
if (temp_mem_size < machine_get_min_ram(machine)) {
temp_mem_size = machine_get_min_ram(machine);
} else if (temp_mem_size > machine_get_max_ram(machine)) {
temp_mem_size = machine_get_max_ram(machine);
}
mem_size = static_cast<uint32_t>(temp_mem_size);
if (ui->comboBoxWaitStates->isEnabled()) {
cpu_waitstates = ui->comboBoxWaitStates->currentData().toInt();
} else {
cpu_waitstates = 0;
}
time_sync = 0;
if (ui->radioButtonLocalTime->isChecked()) {
time_sync = TIME_SYNC_ENABLED;
}
if (ui->radioButtonUTC->isChecked()) {
time_sync = TIME_SYNC_ENABLED | TIME_SYNC_UTC;
}
}
void SettingsMachine::on_comboBoxMachineType_currentIndexChanged(int index) {
auto* model = ui->comboBoxMachine->model();
int removeRows = model->rowCount();
int selectedMachineRow = 0;
for (int i = 0; i < machine_count(); ++i) {
if ((machine_get_type(i) == index) && machine_available(i)) {
int row = Models::AddEntry(model, machines[i].name, i);
if (i == machine) {
selectedMachineRow = row - removeRows;
}
}
}
model->removeRows(0, removeRows);
ui->comboBoxMachine->setCurrentIndex(-1);
ui->comboBoxMachine->setCurrentIndex(selectedMachineRow);
}
void SettingsMachine::on_comboBoxMachine_currentIndexChanged(int index) {
// win_settings_machine_recalc_machine
if (index < 0) {
return;
}
int machineId = ui->comboBoxMachine->currentData().toInt();
const auto* device = machine_getdevice(machineId);
ui->pushButtonConfigure->setEnabled((device != nullptr) && (device->config != nullptr));
auto* modelCpu = ui->comboBoxCPU->model();
int removeRows = modelCpu->rowCount();
int i = 0;
int eligibleRows = 0;
int selectedCpuFamilyRow = 0;
while (cpu_families[i].package != 0) {
if (cpu_family_is_eligible(&cpu_families[i], machineId)) {
Models::AddEntry(modelCpu, QString("%1 %2").arg(cpu_families[i].manufacturer, cpu_families[i].name), i);
if (&cpu_families[i] == cpu_f) {
selectedCpuFamilyRow = eligibleRows;
}
++eligibleRows;
}
++i;
}
modelCpu->removeRows(0, removeRows);
ui->comboBoxCPU->setEnabled(eligibleRows > 1);
ui->comboBoxCPU->setCurrentIndex(-1);
ui->comboBoxCPU->setCurrentIndex(selectedCpuFamilyRow);
int divisor;
if ((machine_get_ram_granularity(machineId) < 1024)) {
divisor = 1;
ui->spinBoxRAM->setSuffix(QCoreApplication::translate("", "KB").prepend(' '));
} else {
divisor = 1024;
ui->spinBoxRAM->setSuffix(QCoreApplication::translate("", "MB").prepend(' '));
}
ui->spinBoxRAM->setMinimum(machine_get_min_ram(machineId) / divisor);
ui->spinBoxRAM->setMaximum(machine_get_max_ram(machineId) / divisor);
ui->spinBoxRAM->setSingleStep(machine_get_ram_granularity(machineId) / divisor);
ui->spinBoxRAM->setValue(mem_size / divisor);
ui->spinBoxRAM->setEnabled(machine_get_min_ram(machineId) != machine_get_max_ram(machineId));
emit currentMachineChanged(machineId);
}
void SettingsMachine::on_comboBoxCPU_currentIndexChanged(int index) {
if (index < 0) {
return;
}
int machineId = ui->comboBoxMachine->currentData().toInt();
int cpuFamilyId = ui->comboBoxCPU->currentData().toInt();
const auto* cpuFamily = &cpu_families[cpuFamilyId];
auto* modelSpeed = ui->comboBoxSpeed->model();
int removeRows = modelSpeed->rowCount();
// win_settings_machine_recalc_cpu_m
int i = 0;
int eligibleRows = 0;
int selectedSpeedRow = 0;
while (cpuFamily->cpus[i].cpu_type != 0) {
if (cpu_is_eligible(cpuFamily, i, machineId)) {
Models::AddEntry(modelSpeed, QString("%1").arg(cpuFamily->cpus[i].name), i);
if (cpu == i) {
selectedSpeedRow = eligibleRows;
}
++eligibleRows;
}
++i;
}
modelSpeed->removeRows(0, removeRows);
ui->comboBoxSpeed->setEnabled(eligibleRows > 1);
ui->comboBoxSpeed->setCurrentIndex(-1);
ui->comboBoxSpeed->setCurrentIndex(selectedSpeedRow);
}
void SettingsMachine::on_comboBoxSpeed_currentIndexChanged(int index) {
if (index < 0) {
return;
}
// win_settings_machine_recalc_cpu
int cpuFamilyId = ui->comboBoxCPU->currentData().toInt();
const auto* cpuFamily = &cpu_families[cpuFamilyId];
int cpuId = ui->comboBoxSpeed->currentData().toInt();
uint cpuType = cpuFamily->cpus[cpuId].cpu_type;
if ((cpuType >= CPU_286) && (cpuType <= CPU_386DX)) {
ui->comboBoxWaitStates->setEnabled(true);
ui->comboBoxWaitStates->setCurrentIndex(cpu_waitstates);
} else {
ui->comboBoxWaitStates->setCurrentIndex(0);
ui->comboBoxWaitStates->setEnabled(false);
}
#ifdef USE_DYNAREC
uint8_t flags = cpuFamily->cpus[cpuId].cpu_flags;
if (! (flags & CPU_SUPPORTS_DYNAREC)) {
ui->checkBoxDynamicRecompiler->setChecked(false);
ui->checkBoxDynamicRecompiler->setEnabled(false);
} else if (flags & CPU_REQUIRES_DYNAREC) {
ui->checkBoxDynamicRecompiler->setChecked(true);
ui->checkBoxDynamicRecompiler->setEnabled(false);
} else {
ui->checkBoxDynamicRecompiler->setChecked(cpu_use_dynarec);
ui->checkBoxDynamicRecompiler->setEnabled(true);
}
#endif
// win_settings_machine_recalc_fpu
auto* modelFpu = ui->comboBoxFPU->model();
int removeRows = modelFpu->rowCount();
int i = 0;
int selectedFpuRow = 0;
for (const char* fpuName = fpu_get_name_from_index(cpuFamily, cpuId, i); fpuName != nullptr; fpuName = fpu_get_name_from_index(cpuFamily, cpuId, ++i)) {
auto fpuType = fpu_get_type_from_index(cpuFamily, cpuId, i);
Models::AddEntry(modelFpu, QString("%1").arg(fpuName), fpuType);
if (fpu_type == fpuType) {
selectedFpuRow = i;
}
}
modelFpu->removeRows(0, removeRows);
ui->comboBoxFPU->setEnabled(modelFpu->rowCount() > 1);
ui->comboBoxFPU->setCurrentIndex(-1);
ui->comboBoxFPU->setCurrentIndex(selectedFpuRow);
}
void SettingsMachine::on_pushButtonConfigure_clicked() {
// deviceconfig_inst_open
int machineId = ui->comboBoxMachine->currentData().toInt();
const auto* device = machine_getdevice(machineId);
DeviceConfig::ConfigureDevice(device);
}

View File

@@ -0,0 +1,41 @@
#ifndef QT_SETTINGSMACHINE_HPP
#define QT_SETTINGSMACHINE_HPP
#include <QWidget>
namespace Ui {
class SettingsMachine;
}
class SettingsMachine : public QWidget
{
Q_OBJECT
public:
explicit SettingsMachine(QWidget *parent = nullptr);
~SettingsMachine();
void save();
signals:
void currentMachineChanged(int machineId);
private slots:
void on_pushButtonConfigure_clicked();
private slots:
void on_comboBoxSpeed_currentIndexChanged(int index);
private slots:
void on_comboBoxCPU_currentIndexChanged(int index);
private slots:
void on_comboBoxMachine_currentIndexChanged(int index);
private slots:
void on_comboBoxMachineType_currentIndexChanged(int index);
private:
Ui::SettingsMachine *ui;
};
#endif // QT_SETTINGSMACHINE_HPP

View File

@@ -0,0 +1,229 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsMachine</class>
<widget class="QWidget" name="SettingsMachine">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>458</width>
<height>390</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QFormLayout" name="formLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Machine type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboBoxMachineType"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Machine:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>CPU type:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>FPU:</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Wait states:</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Memory:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="comboBoxFPU"/>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="comboBoxWaitStates"/>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="spinBoxRAM"/>
</item>
<item row="3" column="1">
<widget class="QWidget" name="widget_2" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="comboBoxCPU"/>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>Speed:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBoxSpeed"/>
</item>
</layout>
</widget>
</item>
<item row="2" column="1">
<widget class="QWidget" name="widget_3" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="comboBoxMachine"/>
</item>
<item>
<widget class="QPushButton" name="pushButtonConfigure">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBoxDynamicRecompiler">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>2</horstretch>
<verstretch>2</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Dynamic Recompiler</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Time synchronization</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="radioButtonDisabled">
<property name="text">
<string>Disabled</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButtonLocalTime">
<property name="text">
<string>Enabled (local time)</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButtonUTC">
<property name="text">
<string>Enabled (UTC)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,113 @@
#include "qt_settingsnetwork.hpp"
#include "ui_qt_settingsnetwork.h"
extern "C" {
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/machine.h>
#include <86box/network.h>
}
#include "qt_models_common.hpp"
#include "qt_deviceconfig.hpp"
static void enableElements(Ui::SettingsNetwork *ui) {
int netType = ui->comboBoxNetwork->currentData().toInt();
ui->comboBoxPcap->setEnabled(netType == NET_TYPE_PCAP);
bool adaptersEnabled = netType == NET_TYPE_SLIRP ||
(netType == NET_TYPE_PCAP && ui->comboBoxPcap->currentData().toInt() > 0);
ui->comboBoxAdapter->setEnabled(adaptersEnabled);
ui->pushButtonConfigure->setEnabled(adaptersEnabled && ui->comboBoxAdapter->currentIndex() > 0 && network_card_has_config(ui->comboBoxAdapter->currentData().toInt()));
}
SettingsNetwork::SettingsNetwork(QWidget *parent) :
QWidget(parent),
ui(new Ui::SettingsNetwork)
{
ui->setupUi(this);
auto* model = ui->comboBoxNetwork->model();
Models::AddEntry(model, tr("None"), NET_TYPE_NONE);
Models::AddEntry(model, "PCap", NET_TYPE_PCAP);
Models::AddEntry(model, "SLiRP", NET_TYPE_SLIRP);
ui->comboBoxNetwork->setCurrentIndex(network_type);
int selectedRow = 0;
model = ui->comboBoxPcap->model();
QString currentPcapDevice = network_host;
for (int c = 0; c < network_ndev; c++) {
Models::AddEntry(model, tr(network_devs[c].description), c);
if (QString(network_devs[c].device) == currentPcapDevice) {
selectedRow = c;
}
}
ui->comboBoxPcap->setCurrentIndex(-1);
ui->comboBoxPcap->setCurrentIndex(selectedRow);
onCurrentMachineChanged(machine);
enableElements(ui);
}
SettingsNetwork::~SettingsNetwork()
{
delete ui;
}
void SettingsNetwork::save() {
network_type = ui->comboBoxNetwork->currentData().toInt();
memset(network_host, '\0', sizeof(network_host));
strcpy(network_host, network_devs[ui->comboBoxPcap->currentData().toInt()].device);
network_card = ui->comboBoxAdapter->currentData().toInt();
}
void SettingsNetwork::onCurrentMachineChanged(int machineId) {
this->machineId = machineId;
auto* machine = &machines[machineId];
auto* model = ui->comboBoxAdapter->model();
auto removeRows = model->rowCount();
int c = 0;
int selectedRow = 0;
while (true) {
auto name = DeviceConfig::DeviceName(network_card_getdevice(c), network_card_get_internal_name(c), 1);
if (name.isEmpty()) {
break;
}
if (network_card_available(c) && device_is_valid(network_card_getdevice(c), machineId)) {
int row = Models::AddEntry(model, name, c);
if (c == network_card) {
selectedRow = row - removeRows;
}
}
c++;
}
model->removeRows(0, removeRows);
ui->comboBoxAdapter->setEnabled(model->rowCount() > 0);
ui->comboBoxAdapter->setCurrentIndex(-1);
ui->comboBoxAdapter->setCurrentIndex(selectedRow);
}
void SettingsNetwork::on_comboBoxNetwork_currentIndexChanged(int index) {
if (index < 0) {
return;
}
enableElements(ui);
}
void SettingsNetwork::on_comboBoxAdapter_currentIndexChanged(int index) {
if (index < 0) {
return;
}
enableElements(ui);
}
void SettingsNetwork::on_pushButtonConfigure_clicked() {
DeviceConfig::ConfigureDevice(network_card_getdevice(ui->comboBoxAdapter->currentData().toInt()));
}

View File

@@ -0,0 +1,33 @@
#ifndef QT_SETTINGSNETWORK_HPP
#define QT_SETTINGSNETWORK_HPP
#include <QWidget>
namespace Ui {
class SettingsNetwork;
}
class SettingsNetwork : public QWidget
{
Q_OBJECT
public:
explicit SettingsNetwork(QWidget *parent = nullptr);
~SettingsNetwork();
void save();
public slots:
void onCurrentMachineChanged(int machineId);
private slots:
void on_pushButtonConfigure_clicked();
void on_comboBoxAdapter_currentIndexChanged(int index);
void on_comboBoxNetwork_currentIndexChanged(int index);
private:
Ui::SettingsNetwork *ui;
int machineId = 0;
};
#endif // QT_SETTINGSNETWORK_HPP

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsNetwork</class>
<widget class="QWidget" name="SettingsNetwork">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>PCap device:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Network adapter:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBoxPcap"/>
</item>
<item row="3" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboBoxNetwork"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Network type:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="comboBoxAdapter"/>
</item>
<item>
<widget class="QPushButton" name="pushButtonConfigure">
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

Some files were not shown because too many files have changed in this diff Show More