diff options
author | Thomas Groman <tgroman@nuegia.net> | 2019-09-19 00:41:48 -0700 |
---|---|---|
committer | Thomas Groman <tgroman@nuegia.net> | 2019-09-19 00:41:48 -0700 |
commit | 32b3ed0a1362a4b0798ad71fac3450fb77cb7e41 (patch) | |
tree | 7be7a2f602e6a5af7bc2db86bef9cf2a659c3d3d /application | |
parent | 5fb2c6334e7d5237db11695b4c0ec0f2d1e47c88 (diff) | |
download | MultiMC-32b3ed0a1362a4b0798ad71fac3450fb77cb7e41.tar MultiMC-32b3ed0a1362a4b0798ad71fac3450fb77cb7e41.tar.gz MultiMC-32b3ed0a1362a4b0798ad71fac3450fb77cb7e41.tar.lz MultiMC-32b3ed0a1362a4b0798ad71fac3450fb77cb7e41.tar.xz MultiMC-32b3ed0a1362a4b0798ad71fac3450fb77cb7e41.zip |
merged from 0.6.7 codebase
Diffstat (limited to 'application')
240 files changed, 20053 insertions, 15526 deletions
diff --git a/application/BuildConfig.cpp.in b/application/BuildConfig.cpp.in index 0a28f074..4253afd0 100644 --- a/application/BuildConfig.cpp.in +++ b/application/BuildConfig.cpp.in @@ -5,50 +5,49 @@ Config BuildConfig; Config::Config() { - // Version information - VERSION_MAJOR = @MultiMC_VERSION_MAJOR@; - VERSION_MINOR = @MultiMC_VERSION_MINOR@; - VERSION_HOTFIX = @MultiMC_VERSION_HOTFIX@; - VERSION_BUILD = @MultiMC_VERSION_BUILD@; - - BUILD_PLATFORM = "@MultiMC_BUILD_PLATFORM@"; - CHANLIST_URL = "@MultiMC_CHANLIST_URL@"; -/* ANALYTICS_ID = "@MultiMC_ANALYTICS_ID@"; */ - NOTIFICATION_URL = "@MultiMC_NOTIFICATION_URL@"; - FULL_VERSION_STR = "@MultiMC_VERSION_MAJOR@.@MultiMC_VERSION_MINOR@.@MultiMC_VERSION_BUILD@"; - - GIT_COMMIT = "@MultiMC_GIT_COMMIT@"; - GIT_REFSPEC = "@MultiMC_GIT_REFSPEC@"; - if(GIT_REFSPEC.startsWith("refs/heads/") && !CHANLIST_URL.isEmpty() && VERSION_BUILD >= 0) - { - VERSION_CHANNEL = GIT_REFSPEC; - VERSION_CHANNEL.remove("refs/heads/"); - UPDATER_ENABLED = true; - } - else - { - VERSION_CHANNEL = QObject::tr("custom"); - } - - VERSION_STR = "@MultiMC_VERSION_STRING@"; - NEWS_RSS_URL = "@MultiMC_NEWS_RSS_URL@"; - PASTE_EE_KEY = "@MultiMC_PASTE_EE_API_KEY@"; + // Version information + VERSION_MAJOR = @MultiMC_VERSION_MAJOR@; + VERSION_MINOR = @MultiMC_VERSION_MINOR@; + VERSION_HOTFIX = @MultiMC_VERSION_HOTFIX@; + VERSION_BUILD = @MultiMC_VERSION_BUILD@; + + BUILD_PLATFORM = "@MultiMC_BUILD_PLATFORM@"; + CHANLIST_URL = "@MultiMC_CHANLIST_URL@"; + NOTIFICATION_URL = "@MultiMC_NOTIFICATION_URL@"; + FULL_VERSION_STR = "@MultiMC_VERSION_MAJOR@.@MultiMC_VERSION_MINOR@.@MultiMC_VERSION_BUILD@"; + + GIT_COMMIT = "@MultiMC_GIT_COMMIT@"; + GIT_REFSPEC = "@MultiMC_GIT_REFSPEC@"; + if(GIT_REFSPEC.startsWith("refs/heads/") && !CHANLIST_URL.isEmpty() && VERSION_BUILD >= 0) + { + VERSION_CHANNEL = GIT_REFSPEC; + VERSION_CHANNEL.remove("refs/heads/"); + UPDATER_ENABLED = true; + } + else + { + VERSION_CHANNEL = QObject::tr("custom"); + } + + VERSION_STR = "@MultiMC_VERSION_STRING@"; + NEWS_RSS_URL = "@MultiMC_NEWS_RSS_URL@"; + PASTE_EE_KEY = "@MultiMC_PASTE_EE_API_KEY@"; } QString Config::printableVersionString() const { - QString vstr = QString("%1.%2.%3").arg(QString::number(VERSION_MAJOR), QString::number(VERSION_MINOR), QString::number(VERSION_HOTFIX)); - - // If the build is not a main release, append the channel - if(VERSION_CHANNEL != "stable") - { - vstr += "-" + VERSION_CHANNEL; - } - - // if a build number is set, also add it to the end - if(VERSION_BUILD >= 0) - { - vstr += "-" + QString::number(VERSION_BUILD); - } - return vstr; + QString vstr = QString("%1.%2.%3").arg(QString::number(VERSION_MAJOR), QString::number(VERSION_MINOR), QString::number(VERSION_HOTFIX)); + + // If the build is not a main release, append the channel + if(VERSION_CHANNEL != "stable") + { + vstr += "-" + VERSION_CHANNEL; + } + + // if a build number is set, also add it to the end + if(VERSION_BUILD >= 0) + { + vstr += "-" + QString::number(VERSION_BUILD); + } + return vstr; } diff --git a/application/BuildConfig.h b/application/BuildConfig.h index e561cb52..b7965e10 100644 --- a/application/BuildConfig.h +++ b/application/BuildConfig.h @@ -7,64 +7,61 @@ class Config { public: - Config(); - /// The major version number. - int VERSION_MAJOR; - /// The minor version number. - int VERSION_MINOR; - /// The hotfix number. - int VERSION_HOTFIX; - /// The build number. - int VERSION_BUILD; + Config(); + /// The major version number. + int VERSION_MAJOR; + /// The minor version number. + int VERSION_MINOR; + /// The hotfix number. + int VERSION_HOTFIX; + /// The build number. + int VERSION_BUILD; - /** - * The version channel - * This is used by the updater to determine what channel the current version came from. - */ - QString VERSION_CHANNEL; + /** + * The version channel + * This is used by the updater to determine what channel the current version came from. + */ + QString VERSION_CHANNEL; - bool UPDATER_ENABLED = false; + bool UPDATER_ENABLED = false; - /// A short string identifying this build's platform. For example, "lin64" or "win32". - QString BUILD_PLATFORM; + /// A short string identifying this build's platform. For example, "lin64" or "win32". + QString BUILD_PLATFORM; - /// URL for the updater's channel - QString CHANLIST_URL; + /// URL for the updater's channel + QString CHANLIST_URL; -/* /// Google analytics ID - QString ANALYTICS_ID; -*/ - /// URL for notifications - QString NOTIFICATION_URL; + /// URL for notifications + QString NOTIFICATION_URL; - /// Used for matching notifications - QString FULL_VERSION_STR; + /// Used for matching notifications + QString FULL_VERSION_STR; - /// The git commit hash of this build - QString GIT_COMMIT; + /// The git commit hash of this build + QString GIT_COMMIT; - /// The git refspec of this build - QString GIT_REFSPEC; + /// The git refspec of this build + QString GIT_REFSPEC; - /// This is printed on start to standard output - QString VERSION_STR; + /// This is printed on start to standard output + QString VERSION_STR; - /** - * This is used to fetch the news RSS feed. - * It defaults in CMakeLists.txt to "http://multimc.org/rss.xml" - */ - QString NEWS_RSS_URL; + /** + * This is used to fetch the news RSS feed. + * It defaults in CMakeLists.txt to "https://multimc.org/rss.xml" + */ + QString NEWS_RSS_URL; - /** - * API key you can get from paste.ee when you register an account - */ - QString PASTE_EE_KEY; + /** + * API key you can get from paste.ee when you register an account + */ + QString PASTE_EE_KEY; - /** - * \brief Converts the Version to a string. - * \return The version number in string format (major.minor.revision.build). - */ - QString printableVersionString() const; + /** + * \brief Converts the Version to a string. + * \return The version number in string format (major.minor.revision.build). + */ + QString printableVersionString() const; }; extern Config BuildConfig; diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt index a63167dc..8e73f2d0 100644 --- a/application/CMakeLists.txt +++ b/application/CMakeLists.txt @@ -7,286 +7,296 @@ configure_file("${PROJECT_SOURCE_DIR}/BuildConfig.cpp.in" "${PROJECT_BINARY_DIR} ######## Sources and headers ######## SET(MULTIMC_SOURCES - # Application base - main.cpp - MultiMC.h - MultiMC.cpp - BuildConfig.h - ${PROJECT_BINARY_DIR}/BuildConfig.cpp - UpdateController.cpp - UpdateController.h + # Application base + main.cpp + MultiMC.h + MultiMC.cpp + BuildConfig.h + ${PROJECT_BINARY_DIR}/BuildConfig.cpp + UpdateController.cpp + UpdateController.h - # GUI - general utilities - GuiUtil.h - GuiUtil.cpp - ColumnResizer.h - ColumnResizer.cpp - InstanceProxyModel.h - InstanceProxyModel.cpp - VersionProxyModel.h - VersionProxyModel.cpp - ColorCache.h - ColorCache.cpp - HoeDown.h + # GUI - general utilities + GuiUtil.h + GuiUtil.cpp + ColumnResizer.h + ColumnResizer.cpp + InstanceProxyModel.h + InstanceProxyModel.cpp + VersionProxyModel.h + VersionProxyModel.cpp + ColorCache.h + ColorCache.cpp + HoeDown.h - # Super secret! - KonamiCode.h - KonamiCode.cpp + # Super secret! + KonamiCode.h + KonamiCode.cpp - # GUI - windows - MainWindow.h - MainWindow.cpp - InstanceWindow.h - InstanceWindow.cpp + # GUI - windows + MainWindow.h + MainWindow.cpp + InstanceWindow.h + InstanceWindow.cpp - # GUI - setup wizard - setupwizard/SetupWizard.h - setupwizard/SetupWizard.cpp - setupwizard/AnalyticsWizardPage.cpp - setupwizard/AnalyticsWizardPage.h - setupwizard/BaseWizardPage.h - setupwizard/JavaWizardPage.cpp - setupwizard/JavaWizardPage.h - setupwizard/LanguageWizardPage.cpp - setupwizard/LanguageWizardPage.h + # GUI - setup wizard + setupwizard/SetupWizard.h + setupwizard/SetupWizard.cpp + setupwizard/AnalyticsWizardPage.cpp + setupwizard/AnalyticsWizardPage.h + setupwizard/BaseWizardPage.h + setupwizard/JavaWizardPage.cpp + setupwizard/JavaWizardPage.h + setupwizard/LanguageWizardPage.cpp + setupwizard/LanguageWizardPage.h - # GUI - themes - themes/FusionTheme.cpp - themes/FusionTheme.h - themes/BrightTheme.cpp - themes/BrightTheme.h - themes/CustomTheme.cpp - themes/CustomTheme.h - themes/DarkTheme.cpp - themes/DarkTheme.h - themes/ITheme.cpp - themes/ITheme.h - themes/SystemTheme.cpp - themes/SystemTheme.h + # GUI - themes + themes/FusionTheme.cpp + themes/FusionTheme.h + themes/BrightTheme.cpp + themes/BrightTheme.h + themes/CustomTheme.cpp + themes/CustomTheme.h + themes/DarkTheme.cpp + themes/DarkTheme.h + themes/ITheme.cpp + themes/ITheme.h + themes/SystemTheme.cpp + themes/SystemTheme.h - # GUI - settings-specific wrappers for paged dialog - SettingsUI.h + # Processes + LaunchController.h + LaunchController.cpp - # Processes - LaunchController.h - LaunchController.cpp + # page provider for instances + InstancePageProvider.h - # page provider for instances - InstancePageProvider.h + # Common java checking UI + JavaCommon.h + JavaCommon.cpp - # Common java checking UI - JavaCommon.h - JavaCommon.cpp + # GUI - paged dialog base + pages/BasePage.h + pages/BasePageContainer.h + pages/BasePageProvider.h - # GUI - paged dialog base - pages/BasePage.h - pages/BasePageContainer.h - pages/BasePageProvider.h + # GUI - instance pages + pages/instance/GameOptionsPage.cpp + pages/instance/GameOptionsPage.h + pages/instance/VersionPage.cpp + pages/instance/VersionPage.h + pages/instance/TexturePackPage.h + pages/instance/ResourcePackPage.h + pages/instance/ModFolderPage.cpp + pages/instance/ModFolderPage.h + pages/instance/NotesPage.cpp + pages/instance/NotesPage.h + pages/instance/LogPage.cpp + pages/instance/LogPage.h + pages/instance/InstanceSettingsPage.cpp + pages/instance/InstanceSettingsPage.h + pages/instance/ScreenshotsPage.cpp + pages/instance/ScreenshotsPage.h + pages/instance/OtherLogsPage.cpp + pages/instance/OtherLogsPage.h + pages/instance/ServersPage.cpp + pages/instance/ServersPage.h + pages/instance/LegacyUpgradePage.cpp + pages/instance/LegacyUpgradePage.h + pages/instance/WorldListPage.cpp + pages/instance/WorldListPage.h - # GUI - instance pages - pages/instance/VersionPage.cpp - pages/instance/VersionPage.h - pages/instance/TexturePackPage.h - pages/instance/ResourcePackPage.h - pages/instance/ModFolderPage.cpp - pages/instance/ModFolderPage.h - pages/instance/NotesPage.cpp - pages/instance/NotesPage.h - pages/instance/LogPage.cpp - pages/instance/LogPage.h - pages/instance/InstanceSettingsPage.cpp - pages/instance/InstanceSettingsPage.h - pages/instance/ScreenshotsPage.cpp - pages/instance/ScreenshotsPage.h - pages/instance/OtherLogsPage.cpp - pages/instance/OtherLogsPage.h - pages/instance/LegacyUpgradePage.cpp - pages/instance/LegacyUpgradePage.h - pages/instance/WorldListPage.cpp - pages/instance/WorldListPage.h + # GUI - global settings pages + pages/global/AccountListPage.cpp + pages/global/AccountListPage.h + pages/global/CustomCommandsPage.cpp + pages/global/CustomCommandsPage.h + pages/global/ExternalToolsPage.cpp + pages/global/ExternalToolsPage.h + pages/global/JavaPage.cpp + pages/global/JavaPage.h + pages/global/LanguagePage.cpp + pages/global/LanguagePage.h + pages/global/MinecraftPage.cpp + pages/global/MinecraftPage.h + pages/global/MultiMCPage.cpp + pages/global/MultiMCPage.h + pages/global/ProxyPage.cpp + pages/global/ProxyPage.h + pages/global/PasteEEPage.cpp + pages/global/PasteEEPage.h + pages/global/PackagesPage.cpp + pages/global/PackagesPage.h - # GUI - global settings pages - pages/global/AccountListPage.cpp - pages/global/AccountListPage.h - pages/global/CustomCommandsPage.cpp - pages/global/CustomCommandsPage.h - pages/global/ExternalToolsPage.cpp - pages/global/ExternalToolsPage.h - pages/global/JavaPage.cpp - pages/global/JavaPage.h - pages/global/MinecraftPage.cpp - pages/global/MinecraftPage.h - pages/global/MultiMCPage.cpp - pages/global/MultiMCPage.h - pages/global/ProxyPage.cpp - pages/global/ProxyPage.h - pages/global/PasteEEPage.cpp - pages/global/PasteEEPage.h - pages/global/PackagesPage.cpp - pages/global/PackagesPage.h + # GUI - platform pages + pages/modplatform/VanillaPage.cpp + pages/modplatform/VanillaPage.h + pages/modplatform/FTBPage.cpp + pages/modplatform/FTBPage.h + pages/modplatform/FtbListModel.h + pages/modplatform/FtbListModel.cpp + pages/modplatform/TwitchPage.cpp + pages/modplatform/TwitchPage.h + pages/modplatform/TechnicPage.cpp + pages/modplatform/TechnicPage.h + pages/modplatform/ImportPage.cpp + pages/modplatform/ImportPage.h - # GUI - platform pages - pages/modplatform/VanillaPage.cpp - pages/modplatform/VanillaPage.h - pages/modplatform/FTBPage.cpp - pages/modplatform/FTBPage.h - pages/modplatform/FtbListModel.h - pages/modplatform/FtbListModel.cpp - pages/modplatform/TwitchPage.cpp - pages/modplatform/TwitchPage.h - pages/modplatform/TechnicPage.cpp - pages/modplatform/TechnicPage.h - pages/modplatform/ImportPage.cpp - pages/modplatform/ImportPage.h + # GUI - dialogs + dialogs/AboutDialog.cpp + dialogs/AboutDialog.h + dialogs/ProfileSelectDialog.cpp + dialogs/ProfileSelectDialog.h + dialogs/CopyInstanceDialog.cpp + dialogs/CopyInstanceDialog.h + dialogs/CustomMessageBox.cpp + dialogs/CustomMessageBox.h + dialogs/EditAccountDialog.cpp + dialogs/EditAccountDialog.h + dialogs/ExportInstanceDialog.cpp + dialogs/ExportInstanceDialog.h + dialogs/IconPickerDialog.cpp + dialogs/IconPickerDialog.h + dialogs/LoginDialog.cpp + dialogs/LoginDialog.h + dialogs/NewComponentDialog.cpp + dialogs/NewComponentDialog.h + dialogs/NewInstanceDialog.cpp + dialogs/NewInstanceDialog.h + dialogs/NotificationDialog.cpp + dialogs/NotificationDialog.h + pagedialog/PageDialog.cpp + pagedialog/PageDialog.h + dialogs/ProgressDialog.cpp + dialogs/ProgressDialog.h + dialogs/UpdateDialog.cpp + dialogs/UpdateDialog.h + dialogs/VersionSelectDialog.cpp + dialogs/VersionSelectDialog.h + dialogs/SkinUploadDialog.cpp + dialogs/SkinUploadDialog.h - # GUI - dialogs - dialogs/AboutDialog.cpp - dialogs/AboutDialog.h - dialogs/ProfileSelectDialog.cpp - dialogs/ProfileSelectDialog.h - dialogs/CopyInstanceDialog.cpp - dialogs/CopyInstanceDialog.h - dialogs/CustomMessageBox.cpp - dialogs/CustomMessageBox.h - dialogs/EditAccountDialog.cpp - dialogs/EditAccountDialog.h - dialogs/ExportInstanceDialog.cpp - dialogs/ExportInstanceDialog.h - dialogs/IconPickerDialog.cpp - dialogs/IconPickerDialog.h - dialogs/LoginDialog.cpp - dialogs/LoginDialog.h - dialogs/ModEditDialogCommon.cpp - dialogs/ModEditDialogCommon.h - dialogs/NewComponentDialog.cpp - dialogs/NewComponentDialog.h - dialogs/NewInstanceDialog.cpp - dialogs/NewInstanceDialog.h - dialogs/NotificationDialog.cpp - dialogs/NotificationDialog.h - pagedialog/PageDialog.cpp - pagedialog/PageDialog.h - dialogs/ProgressDialog.cpp - dialogs/ProgressDialog.h - dialogs/UpdateDialog.cpp - dialogs/UpdateDialog.h - dialogs/VersionSelectDialog.cpp - dialogs/VersionSelectDialog.h - dialogs/SkinUploadDialog.cpp - dialogs/SkinUploadDialog.h + # GUI - widgets + widgets/Common.cpp + widgets/Common.h + widgets/CustomCommands.cpp + widgets/CustomCommands.h + widgets/FocusLineEdit.cpp + widgets/FocusLineEdit.h + widgets/IconLabel.cpp + widgets/IconLabel.h + widgets/JavaSettingsWidget.cpp + widgets/JavaSettingsWidget.h + widgets/LabeledToolButton.cpp + widgets/LabeledToolButton.h + widgets/LanguageSelectionWidget.cpp + widgets/LanguageSelectionWidget.h + widgets/LineSeparator.cpp + widgets/LineSeparator.h + widgets/LogView.cpp + widgets/LogView.h + widgets/MCModInfoFrame.cpp + widgets/MCModInfoFrame.h + widgets/ModListView.cpp + widgets/ModListView.h + widgets/PageContainer.cpp + widgets/PageContainer.h + widgets/PageContainer_p.h + widgets/ServerStatus.cpp + widgets/ServerStatus.h + widgets/VersionListView.cpp + widgets/VersionListView.h + widgets/VersionSelectWidget.cpp + widgets/VersionSelectWidget.h + widgets/ProgressWidget.h + widgets/ProgressWidget.cpp + widgets/WideBar.h + widgets/WideBar.cpp - # GUI - widgets - widgets/Common.cpp - widgets/Common.h - widgets/CustomCommands.cpp - widgets/CustomCommands.h - widgets/FocusLineEdit.cpp - widgets/FocusLineEdit.h - widgets/IconLabel.cpp - widgets/IconLabel.h - widgets/JavaSettingsWidget.cpp - widgets/JavaSettingsWidget.h - widgets/LabeledToolButton.cpp - widgets/LabeledToolButton.h - widgets/LineSeparator.cpp - widgets/LineSeparator.h - widgets/LogView.cpp - widgets/LogView.h - widgets/MCModInfoFrame.cpp - widgets/MCModInfoFrame.h - widgets/ModListView.cpp - widgets/ModListView.h - widgets/PageContainer.cpp - widgets/PageContainer.h - widgets/PageContainer_p.h - widgets/ServerStatus.cpp - widgets/ServerStatus.h - widgets/VersionListView.cpp - widgets/VersionListView.h - widgets/VersionSelectWidget.cpp - widgets/VersionSelectWidget.h - widgets/ProgressWidget.h - widgets/ProgressWidget.cpp - - # GUI - instance group view - groupview/GroupedProxyModel.cpp - groupview/GroupedProxyModel.h - groupview/GroupView.cpp - groupview/GroupView.h - groupview/InstanceDelegate.cpp - groupview/InstanceDelegate.h - groupview/VisualGroup.cpp - groupview/VisualGroup.h - ) + # GUI - instance group view + groupview/GroupedProxyModel.cpp + groupview/GroupedProxyModel.h + groupview/AccessibleGroupView.cpp + groupview/AccessibleGroupView.h + groupview/AccessibleGroupView_p.h + groupview/GroupView.cpp + groupview/GroupView.h + groupview/InstanceDelegate.cpp + groupview/InstanceDelegate.h + groupview/VisualGroup.cpp + groupview/VisualGroup.h + ) ######## UIs ######## SET(MULTIMC_UIS - # Instance pages - pages/instance/VersionPage.ui - pages/instance/ModFolderPage.ui - pages/instance/LogPage.ui - pages/instance/InstanceSettingsPage.ui - pages/instance/NotesPage.ui - pages/instance/ScreenshotsPage.ui - pages/instance/OtherLogsPage.ui - pages/instance/LegacyUpgradePage.ui - pages/instance/WorldListPage.ui + # Instance pages + pages/instance/GameOptionsPage.ui + pages/instance/VersionPage.ui + pages/instance/ModFolderPage.ui + pages/instance/LogPage.ui + pages/instance/InstanceSettingsPage.ui + pages/instance/NotesPage.ui + pages/instance/ScreenshotsPage.ui + pages/instance/OtherLogsPage.ui + pages/instance/LegacyUpgradePage.ui + pages/instance/ServersPage.ui + pages/instance/WorldListPage.ui - # Global settings pages - pages/global/AccountListPage.ui - pages/global/ExternalToolsPage.ui - pages/global/JavaPage.ui - pages/global/MinecraftPage.ui - pages/global/MultiMCPage.ui - pages/global/ProxyPage.ui - pages/global/PasteEEPage.ui - pages/global/PackagesPage.ui + # Global settings pages + pages/global/AccountListPage.ui + pages/global/ExternalToolsPage.ui + pages/global/JavaPage.ui + pages/global/MinecraftPage.ui + pages/global/MultiMCPage.ui + pages/global/ProxyPage.ui + pages/global/PasteEEPage.ui + pages/global/PackagesPage.ui - # Platform pages - pages/modplatform/VanillaPage.ui - pages/modplatform/FTBPage.ui - pages/modplatform/TwitchPage.ui - pages/modplatform/TechnicPage.ui - pages/modplatform/ImportPage.ui + # Platform pages + pages/modplatform/VanillaPage.ui + pages/modplatform/FTBPage.ui + pages/modplatform/TwitchPage.ui + pages/modplatform/TechnicPage.ui + pages/modplatform/ImportPage.ui - # Dialogs - dialogs/CopyInstanceDialog.ui - dialogs/NewComponentDialog.ui - dialogs/NewInstanceDialog.ui - dialogs/AboutDialog.ui - dialogs/ProgressDialog.ui - dialogs/IconPickerDialog.ui - dialogs/ProfileSelectDialog.ui - dialogs/EditAccountDialog.ui - dialogs/ExportInstanceDialog.ui - dialogs/LoginDialog.ui - dialogs/UpdateDialog.ui - dialogs/NotificationDialog.ui - dialogs/SkinUploadDialog.ui + # Dialogs + dialogs/CopyInstanceDialog.ui + dialogs/NewComponentDialog.ui + dialogs/NewInstanceDialog.ui + dialogs/AboutDialog.ui + dialogs/ProgressDialog.ui + dialogs/IconPickerDialog.ui + dialogs/ProfileSelectDialog.ui + dialogs/EditAccountDialog.ui + dialogs/ExportInstanceDialog.ui + dialogs/LoginDialog.ui + dialogs/UpdateDialog.ui + dialogs/NotificationDialog.ui + dialogs/SkinUploadDialog.ui - # Widgets/other - widgets/CustomCommands.ui - widgets/MCModInfoFrame.ui + # Widgets/other + widgets/CustomCommands.ui + widgets/MCModInfoFrame.ui ) set(MULTIMC_QRCS - resources/assets/assets.qrc - resources/backgrounds/backgrounds.qrc - resources/multimc/multimc.qrc - resources/pe_dark/pe_dark.qrc - resources/pe_light/pe_light.qrc - resources/pe_colored/pe_colored.qrc - resources/pe_blue/pe_blue.qrc - resources/OSX/OSX.qrc - resources/iOS/iOS.qrc - resources/flat/flat.qrc - resources/documents/documents.qrc + resources/assets/assets.qrc + resources/backgrounds/backgrounds.qrc + resources/multimc/multimc.qrc + resources/pe_dark/pe_dark.qrc + resources/pe_light/pe_light.qrc + resources/pe_colored/pe_colored.qrc + resources/pe_blue/pe_blue.qrc + resources/OSX/OSX.qrc + resources/iOS/iOS.qrc + resources/flat/flat.qrc + resources/documents/documents.qrc ) ######## Windows resource files ######## if(WIN32) - set(MULTIMC_RCS resources/multimc.rc) + set(MULTIMC_RCS resources/multimc.rc) endif() # Qt 5 stuff @@ -298,89 +308,89 @@ add_executable(MultiMC MACOSX_BUNDLE WIN32 ${MULTIMC_SOURCES} ${MULTIMC_UI} ${MU #target_link_libraries(MultiMC MultiMC_gui ${QUAZIP_LIBRARIES} hoedown MultiMC_rainbow LocalPeer ganalytics) target_link_libraries(MultiMC MultiMC_gui ${QUAZIP_LIBRARIES} hoedown MultiMC_rainbow LocalPeer) if(DEFINED MultiMC_APP_BINARY_NAME) - set_target_properties(MultiMC PROPERTIES OUTPUT_NAME "${MultiMC_APP_BINARY_NAME}") + set_target_properties(MultiMC PROPERTIES OUTPUT_NAME "${MultiMC_APP_BINARY_NAME}") endif() if(DEFINED MultiMC_BINARY_RPATH) - SET_TARGET_PROPERTIES(MultiMC PROPERTIES INSTALL_RPATH "${MultiMC_BINARY_RPATH}") + SET_TARGET_PROPERTIES(MultiMC PROPERTIES INSTALL_RPATH "${MultiMC_BINARY_RPATH}") endif() if(DEFINED MultiMC_APP_BINARY_DEFS) - target_compile_definitions(MultiMC PRIVATE ${MultiMC_APP_BINARY_DEFS}) + target_compile_definitions(MultiMC PRIVATE ${MultiMC_APP_BINARY_DEFS}) endif() install(TARGETS MultiMC - BUNDLE DESTINATION ${BUNDLE_DEST_DIR} COMPONENT Runtime - LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime - RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime + BUNDLE DESTINATION ${BUNDLE_DEST_DIR} COMPONENT Runtime + LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime + RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime ) #### The MultiMC bundle mess! #### # Bundle utilities are used to complete the portable packages - they add all the libraries that would otherwise be missing on the target system. # NOTE: it seems that this absolutely has to be here, and nowhere else. if(INSTALL_BUNDLE STREQUAL "full") - # Add qt.conf - this makes Qt stop looking for things outside the bundle - install( - CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")" - COMPONENT Runtime - ) - # Bundle plugins - if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") - # Image formats - install( - DIRECTORY "${QT_PLUGINS_DIR}/imageformats" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "tga|tiff|mng|webp" EXCLUDE - ) - # Icon engines - install( - DIRECTORY "${QT_PLUGINS_DIR}/iconengines" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "fontawesome" EXCLUDE - ) - # Platform plugins - install( - DIRECTORY "${QT_PLUGINS_DIR}/platforms" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "minimal|linuxfb|offscreen" EXCLUDE - ) - else() - # Image formats - install( - DIRECTORY "${QT_PLUGINS_DIR}/imageformats" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "tga|tiff|mng|webp" EXCLUDE - REGEX "d\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - # Icon engines - install( - DIRECTORY "${QT_PLUGINS_DIR}/iconengines" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "fontawesome" EXCLUDE - REGEX "d\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - # Platform plugins - install( - DIRECTORY "${QT_PLUGINS_DIR}/platforms" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "minimal|linuxfb|offscreen" EXCLUDE - REGEX "d\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - endif() - configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" - @ONLY - ) - install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" COMPONENT Runtime) + # Add qt.conf - this makes Qt stop looking for things outside the bundle + install( + CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")" + COMPONENT Runtime + ) + # Bundle plugins + if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + # Image formats + install( + DIRECTORY "${QT_PLUGINS_DIR}/imageformats" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "tga|tiff|mng|webp" EXCLUDE + ) + # Icon engines + install( + DIRECTORY "${QT_PLUGINS_DIR}/iconengines" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "fontawesome" EXCLUDE + ) + # Platform plugins + install( + DIRECTORY "${QT_PLUGINS_DIR}/platforms" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "minimal|linuxfb|offscreen" EXCLUDE + ) + else() + # Image formats + install( + DIRECTORY "${QT_PLUGINS_DIR}/imageformats" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "tga|tiff|mng|webp" EXCLUDE + REGEX "d\\." EXCLUDE + REGEX "_debug\\." EXCLUDE + REGEX "\\.dSYM" EXCLUDE + ) + # Icon engines + install( + DIRECTORY "${QT_PLUGINS_DIR}/iconengines" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "fontawesome" EXCLUDE + REGEX "d\\." EXCLUDE + REGEX "_debug\\." EXCLUDE + REGEX "\\.dSYM" EXCLUDE + ) + # Platform plugins + install( + DIRECTORY "${QT_PLUGINS_DIR}/platforms" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "minimal|linuxfb|offscreen" EXCLUDE + REGEX "d\\." EXCLUDE + REGEX "_debug\\." EXCLUDE + REGEX "\\.dSYM" EXCLUDE + ) + endif() + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" + @ONLY + ) + install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" COMPONENT Runtime) endif() diff --git a/application/ColorCache.cpp b/application/ColorCache.cpp index e216b597..ef268dd2 100644 --- a/application/ColorCache.cpp +++ b/application/ColorCache.cpp @@ -6,13 +6,13 @@ */ QColor ColorCache::blend(QColor color) { - if (Rainbow::luma(m_front) > Rainbow::luma(m_back)) - { - // for dark color schemes, produce a fitting color first - color = Rainbow::tint(m_front, color, 0.5); - } - // adapt contrast - return Rainbow::mix(m_front, color, m_bias); + if (Rainbow::luma(m_front) > Rainbow::luma(m_back)) + { + // for dark color schemes, produce a fitting color first + color = Rainbow::tint(m_front, color, 0.5); + } + // adapt contrast + return Rainbow::mix(m_front, color, m_bias); } /** @@ -20,16 +20,16 @@ QColor ColorCache::blend(QColor color) */ QColor ColorCache::blendBackground(QColor color) { - // adapt contrast - return Rainbow::mix(m_back, color, m_bias); + // adapt contrast + return Rainbow::mix(m_back, color, m_bias); } void ColorCache::recolorAll() { - auto iter = m_colors.begin(); - while(iter != m_colors.end()) - { - iter->front = blend(iter->original); - iter->back = blendBackground(iter->original); - } + auto iter = m_colors.begin(); + while(iter != m_colors.end()) + { + iter->front = blend(iter->original); + iter->back = blendBackground(iter->original); + } } diff --git a/application/ColorCache.h b/application/ColorCache.h index 1ce1c211..6ae633b9 100644 --- a/application/ColorCache.h +++ b/application/ColorCache.h @@ -7,113 +7,113 @@ class ColorCache { public: - ColorCache(QColor front, QColor back, qreal bias) - { - m_front = front; - m_back = back; - m_bias = bias; - }; + ColorCache(QColor front, QColor back, qreal bias) + { + m_front = front; + m_back = back; + m_bias = bias; + }; - void addColor(int key, QColor color) - { - m_colors[key] = {color, blend(color), blendBackground(color)}; - } + void addColor(int key, QColor color) + { + m_colors[key] = {color, blend(color), blendBackground(color)}; + } - void setForeground(QColor front) - { - if(m_front != front) - { - m_front = front; - recolorAll(); - } - } + void setForeground(QColor front) + { + if(m_front != front) + { + m_front = front; + recolorAll(); + } + } - void setBackground(QColor back) - { - if(m_back != back) - { - m_back = back; - recolorAll(); - } - } + void setBackground(QColor back) + { + if(m_back != back) + { + m_back = back; + recolorAll(); + } + } - QColor getFront(int key) - { - auto iter = m_colors.find(key); - if(iter == m_colors.end()) - { - return QColor(); - } - return (*iter).front; - } + QColor getFront(int key) + { + auto iter = m_colors.find(key); + if(iter == m_colors.end()) + { + return QColor(); + } + return (*iter).front; + } - QColor getBack(int key) - { - auto iter = m_colors.find(key); - if(iter == m_colors.end()) - { - return QColor(); - } - return (*iter).back; - } + QColor getBack(int key) + { + auto iter = m_colors.find(key); + if(iter == m_colors.end()) + { + return QColor(); + } + return (*iter).back; + } - /** - * Blend the color with the front color, adapting to the back color - */ - QColor blend(QColor color); + /** + * Blend the color with the front color, adapting to the back color + */ + QColor blend(QColor color); - /** - * Blend the color with the back color - */ - QColor blendBackground(QColor color); + /** + * Blend the color with the back color + */ + QColor blendBackground(QColor color); protected: - void recolorAll(); + void recolorAll(); protected: - struct ColorEntry - { - QColor original; - QColor front; - QColor back; - }; + struct ColorEntry + { + QColor original; + QColor front; + QColor back; + }; protected: - qreal m_bias; - QColor m_front; - QColor m_back; - QMap<int, ColorEntry> m_colors; + qreal m_bias; + QColor m_front; + QColor m_back; + QMap<int, ColorEntry> m_colors; }; class LogColorCache : public ColorCache { public: - LogColorCache(QColor front, QColor back) - : ColorCache(front, back, 1.0) - { - addColor((int)MessageLevel::MultiMC, QColor("purple")); - addColor((int)MessageLevel::Debug, QColor("green")); - addColor((int)MessageLevel::Warning, QColor("orange")); - addColor((int)MessageLevel::Error, QColor("red")); - addColor((int)MessageLevel::Fatal, QColor("red")); - addColor((int)MessageLevel::Message, front); - } + LogColorCache(QColor front, QColor back) + : ColorCache(front, back, 1.0) + { + addColor((int)MessageLevel::MultiMC, QColor("purple")); + addColor((int)MessageLevel::Debug, QColor("green")); + addColor((int)MessageLevel::Warning, QColor("orange")); + addColor((int)MessageLevel::Error, QColor("red")); + addColor((int)MessageLevel::Fatal, QColor("red")); + addColor((int)MessageLevel::Message, front); + } - QColor getFront(MessageLevel::Enum level) - { - if(!m_colors.contains((int) level)) - { - return ColorCache::getFront((int)MessageLevel::Message); - } - return ColorCache::getFront((int)level); - } + QColor getFront(MessageLevel::Enum level) + { + if(!m_colors.contains((int) level)) + { + return ColorCache::getFront((int)MessageLevel::Message); + } + return ColorCache::getFront((int)level); + } - QColor getBack(MessageLevel::Enum level) - { - if(level == MessageLevel::Fatal) - { - return QColor(Qt::black); - } - return QColor(Qt::transparent); - } + QColor getBack(MessageLevel::Enum level) + { + if(level == MessageLevel::Fatal) + { + return QColor(Qt::black); + } + return QColor(Qt::transparent); + } }; diff --git a/application/ColumnResizer.cpp b/application/ColumnResizer.cpp index ee99bf40..fe415067 100644 --- a/application/ColumnResizer.cpp +++ b/application/ColumnResizer.cpp @@ -14,67 +14,67 @@ class FormLayoutWidgetItem : public QWidgetItem { public: - FormLayoutWidgetItem(QWidget* widget, QFormLayout* formLayout, QFormLayout::ItemRole itemRole) - : QWidgetItem(widget) - , m_width(-1) - , m_formLayout(formLayout) - , m_itemRole(itemRole) - {} - - QSize sizeHint() const - { - QSize size = QWidgetItem::sizeHint(); - if (m_width != -1) { - size.setWidth(m_width); - } - return size; - } - - QSize minimumSize() const - { - QSize size = QWidgetItem::minimumSize(); - if (m_width != -1) { - size.setWidth(m_width); - } - return size; - } - - QSize maximumSize() const - { - QSize size = QWidgetItem::maximumSize(); - if (m_width != -1) { - size.setWidth(m_width); - } - return size; - } - - void setWidth(int width) - { - if (width != m_width) { - m_width = width; - invalidate(); - } - } - - void setGeometry(const QRect& _rect) - { - QRect rect = _rect; - int width = widget()->sizeHint().width(); - if (m_itemRole == QFormLayout::LabelRole && m_formLayout->labelAlignment() & Qt::AlignRight) { - rect.setLeft(rect.right() - width); - } - QWidgetItem::setGeometry(rect); - } - - QFormLayout* formLayout() const - { - return m_formLayout; - } + FormLayoutWidgetItem(QWidget* widget, QFormLayout* formLayout, QFormLayout::ItemRole itemRole) + : QWidgetItem(widget) + , m_width(-1) + , m_formLayout(formLayout) + , m_itemRole(itemRole) + {} + + QSize sizeHint() const + { + QSize size = QWidgetItem::sizeHint(); + if (m_width != -1) { + size.setWidth(m_width); + } + return size; + } + + QSize minimumSize() const + { + QSize size = QWidgetItem::minimumSize(); + if (m_width != -1) { + size.setWidth(m_width); + } + return size; + } + + QSize maximumSize() const + { + QSize size = QWidgetItem::maximumSize(); + if (m_width != -1) { + size.setWidth(m_width); + } + return size; + } + + void setWidth(int width) + { + if (width != m_width) { + m_width = width; + invalidate(); + } + } + + void setGeometry(const QRect& _rect) + { + QRect rect = _rect; + int width = widget()->sizeHint().width(); + if (m_itemRole == QFormLayout::LabelRole && m_formLayout->labelAlignment() & Qt::AlignRight) { + rect.setLeft(rect.right() - width); + } + QWidgetItem::setGeometry(rect); + } + + QFormLayout* formLayout() const + { + return m_formLayout; + } private: - int m_width; - QFormLayout* m_formLayout; - QFormLayout::ItemRole m_itemRole; + int m_width; + QFormLayout* m_formLayout; + QFormLayout::ItemRole m_itemRole; }; typedef QPair<QGridLayout*, int> GridColumnInfo; @@ -82,25 +82,25 @@ typedef QPair<QGridLayout*, int> GridColumnInfo; class ColumnResizerPrivate { public: - ColumnResizerPrivate(ColumnResizer* q_ptr) - : q(q_ptr) - , m_updateTimer(new QTimer(q)) - { - m_updateTimer->setSingleShot(true); - m_updateTimer->setInterval(0); - QObject::connect(m_updateTimer, SIGNAL(timeout()), q, SLOT(updateWidth())); - } - - void scheduleWidthUpdate() - { - m_updateTimer->start(); - } - - ColumnResizer* q; - QTimer* m_updateTimer; - QList<QWidget*> m_widgets; - QList<FormLayoutWidgetItem*> m_wrWidgetItemList; - QList<GridColumnInfo> m_gridColumnInfoList; + ColumnResizerPrivate(ColumnResizer* q_ptr) + : q(q_ptr) + , m_updateTimer(new QTimer(q)) + { + m_updateTimer->setSingleShot(true); + m_updateTimer->setInterval(0); + QObject::connect(m_updateTimer, SIGNAL(timeout()), q, SLOT(updateWidth())); + } + + void scheduleWidthUpdate() + { + m_updateTimer->start(); + } + + ColumnResizer* q; + QTimer* m_updateTimer; + QList<QWidget*> m_widgets; + QList<FormLayoutWidgetItem*> m_wrWidgetItemList; + QList<GridColumnInfo> m_gridColumnInfoList; }; ColumnResizer::ColumnResizer(QObject* parent) @@ -110,90 +110,90 @@ ColumnResizer::ColumnResizer(QObject* parent) ColumnResizer::~ColumnResizer() { - delete d; + delete d; } void ColumnResizer::addWidget(QWidget* widget) { - d->m_widgets.append(widget); - widget->installEventFilter(this); - d->scheduleWidthUpdate(); + d->m_widgets.append(widget); + widget->installEventFilter(this); + d->scheduleWidthUpdate(); } void ColumnResizer::updateWidth() { - int width = 0; - Q_FOREACH(QWidget* widget, d->m_widgets) { - width = qMax(widget->sizeHint().width(), width); - } - Q_FOREACH(FormLayoutWidgetItem* item, d->m_wrWidgetItemList) { - item->setWidth(width); - item->formLayout()->update(); - } - Q_FOREACH(GridColumnInfo info, d->m_gridColumnInfoList) { - info.first->setColumnMinimumWidth(info.second, width); - } + int width = 0; + Q_FOREACH(QWidget* widget, d->m_widgets) { + width = qMax(widget->sizeHint().width(), width); + } + Q_FOREACH(FormLayoutWidgetItem* item, d->m_wrWidgetItemList) { + item->setWidth(width); + item->formLayout()->update(); + } + Q_FOREACH(GridColumnInfo info, d->m_gridColumnInfoList) { + info.first->setColumnMinimumWidth(info.second, width); + } } bool ColumnResizer::eventFilter(QObject*, QEvent* event) { - if (event->type() == QEvent::Resize) { - d->scheduleWidthUpdate(); - } - return false; + if (event->type() == QEvent::Resize) { + d->scheduleWidthUpdate(); + } + return false; } void ColumnResizer::addWidgetsFromLayout(QLayout* layout, int column) { - Q_ASSERT(column >= 0); - QGridLayout* gridLayout = qobject_cast<QGridLayout*>(layout); - QFormLayout* formLayout = qobject_cast<QFormLayout*>(layout); - if (gridLayout) { - addWidgetsFromGridLayout(gridLayout, column); - } else if (formLayout) { - if (column > QFormLayout::SpanningRole) { - qCritical() << "column should not be more than" << QFormLayout::SpanningRole << "for QFormLayout"; - return; - } - QFormLayout::ItemRole role = static_cast<QFormLayout::ItemRole>(column); - addWidgetsFromFormLayout(formLayout, role); - } else { - qCritical() << "Don't know how to handle layout" << layout; - } + Q_ASSERT(column >= 0); + QGridLayout* gridLayout = qobject_cast<QGridLayout*>(layout); + QFormLayout* formLayout = qobject_cast<QFormLayout*>(layout); + if (gridLayout) { + addWidgetsFromGridLayout(gridLayout, column); + } else if (formLayout) { + if (column > QFormLayout::SpanningRole) { + qCritical() << "column should not be more than" << QFormLayout::SpanningRole << "for QFormLayout"; + return; + } + QFormLayout::ItemRole role = static_cast<QFormLayout::ItemRole>(column); + addWidgetsFromFormLayout(formLayout, role); + } else { + qCritical() << "Don't know how to handle layout" << layout; + } } void ColumnResizer::addWidgetsFromGridLayout(QGridLayout* layout, int column) { - for (int row = 0; row < layout->rowCount(); ++row) { - QLayoutItem* item = layout->itemAtPosition(row, column); - if (!item) { - continue; - } - QWidget* widget = item->widget(); - if (!widget) { - continue; - } - addWidget(widget); - } - d->m_gridColumnInfoList << GridColumnInfo(layout, column); + for (int row = 0; row < layout->rowCount(); ++row) { + QLayoutItem* item = layout->itemAtPosition(row, column); + if (!item) { + continue; + } + QWidget* widget = item->widget(); + if (!widget) { + continue; + } + addWidget(widget); + } + d->m_gridColumnInfoList << GridColumnInfo(layout, column); } void ColumnResizer::addWidgetsFromFormLayout(QFormLayout* layout, QFormLayout::ItemRole role) { - for (int row = 0; row < layout->rowCount(); ++row) { - QLayoutItem* item = layout->itemAt(row, role); - if (!item) { - continue; - } - QWidget* widget = item->widget(); - if (!widget) { - continue; - } - layout->removeItem(item); - delete item; - FormLayoutWidgetItem* newItem = new FormLayoutWidgetItem(widget, layout, role); - layout->setItem(row, role, newItem); - addWidget(widget); - d->m_wrWidgetItemList << newItem; - } + for (int row = 0; row < layout->rowCount(); ++row) { + QLayoutItem* item = layout->itemAt(row, role); + if (!item) { + continue; + } + QWidget* widget = item->widget(); + if (!widget) { + continue; + } + layout->removeItem(item); + delete item; + FormLayoutWidgetItem* newItem = new FormLayoutWidgetItem(widget, layout, role); + layout->setItem(row, role, newItem); + addWidget(widget); + d->m_wrWidgetItemList << newItem; + } } diff --git a/application/ColumnResizer.h b/application/ColumnResizer.h index 78966a7e..8c920f01 100644 --- a/application/ColumnResizer.h +++ b/application/ColumnResizer.h @@ -18,24 +18,24 @@ class QWidget; class ColumnResizerPrivate; class ColumnResizer : public QObject { - Q_OBJECT + Q_OBJECT public: - ColumnResizer(QObject* parent = 0); - ~ColumnResizer(); + ColumnResizer(QObject* parent = 0); + ~ColumnResizer(); - void addWidget(QWidget* widget); - void addWidgetsFromLayout(QLayout*, int column); - void addWidgetsFromGridLayout(QGridLayout*, int column); - void addWidgetsFromFormLayout(QFormLayout*, QFormLayout::ItemRole role); + void addWidget(QWidget* widget); + void addWidgetsFromLayout(QLayout*, int column); + void addWidgetsFromGridLayout(QGridLayout*, int column); + void addWidgetsFromFormLayout(QFormLayout*, QFormLayout::ItemRole role); private Q_SLOTS: - void updateWidth(); + void updateWidth(); protected: - bool eventFilter(QObject*, QEvent* event); + bool eventFilter(QObject*, QEvent* event); private: - ColumnResizerPrivate* const d; + ColumnResizerPrivate* const d; }; #endif /* COLUMNRESIZER_H */ diff --git a/application/GuiUtil.cpp b/application/GuiUtil.cpp index b05fc57c..302206f5 100644 --- a/application/GuiUtil.cpp +++ b/application/GuiUtil.cpp @@ -15,117 +15,117 @@ QString GuiUtil::uploadPaste(const QString &text, QWidget *parentWidget) { - ProgressDialog dialog(parentWidget); - auto APIKeySetting = MMC->settings()->get("PasteEEAPIKey").toString(); - if(APIKeySetting == "multimc") - { - APIKeySetting = BuildConfig.PASTE_EE_KEY; - } - std::unique_ptr<PasteUpload> paste(new PasteUpload(parentWidget, text, APIKeySetting)); - - if (!paste->validateText()) - { - CustomMessageBox::selectable( - parentWidget, QObject::tr("Upload failed"), - QObject::tr("The log file is too big. You'll have to upload it manually."), - QMessageBox::Warning)->exec(); - return QString(); - } - - dialog.execWithTask(paste.get()); - if (!paste->wasSuccessful()) - { - CustomMessageBox::selectable(parentWidget, QObject::tr("Upload failed"), - paste->failReason(), QMessageBox::Critical)->exec(); - return QString(); - } - else - { - const QString link = paste->pasteLink(); - setClipboardText(link); - CustomMessageBox::selectable( - parentWidget, QObject::tr("Upload finished"), - QObject::tr("The <a href=\"%1\">link to the uploaded log</a> has been placed in your clipboard.").arg(link), - QMessageBox::Information)->exec(); - return link; - } + ProgressDialog dialog(parentWidget); + auto APIKeySetting = MMC->settings()->get("PasteEEAPIKey").toString(); + if(APIKeySetting == "multimc") + { + APIKeySetting = BuildConfig.PASTE_EE_KEY; + } + std::unique_ptr<PasteUpload> paste(new PasteUpload(parentWidget, text, APIKeySetting)); + + if (!paste->validateText()) + { + CustomMessageBox::selectable( + parentWidget, QObject::tr("Upload failed"), + QObject::tr("The log file is too big. You'll have to upload it manually."), + QMessageBox::Warning)->exec(); + return QString(); + } + + dialog.execWithTask(paste.get()); + if (!paste->wasSuccessful()) + { + CustomMessageBox::selectable(parentWidget, QObject::tr("Upload failed"), + paste->failReason(), QMessageBox::Critical)->exec(); + return QString(); + } + else + { + const QString link = paste->pasteLink(); + setClipboardText(link); + CustomMessageBox::selectable( + parentWidget, QObject::tr("Upload finished"), + QObject::tr("The <a href=\"%1\">link to the uploaded log</a> has been placed in your clipboard.").arg(link), + QMessageBox::Information)->exec(); + return link; + } } void GuiUtil::setClipboardText(const QString &text) { - QApplication::clipboard()->setText(text); + QApplication::clipboard()->setText(text); } static QStringList BrowseForFileInternal(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget, bool single) { - static QMap<QString, QString> savedPaths; - - QFileDialog w(parentWidget, caption); - QSet<QString> locations; - auto f = [&](QStandardPaths::StandardLocation l) - { - QString location = QStandardPaths::writableLocation(l); - QFileInfo finfo(location); - if (!finfo.exists()) - return; - locations.insert(location); - }; - f(QStandardPaths::DesktopLocation); - f(QStandardPaths::DocumentsLocation); - f(QStandardPaths::DownloadLocation); - f(QStandardPaths::HomeLocation); - QList<QUrl> urls; - for (auto location : locations) - { - urls.append(QUrl::fromLocalFile(location)); - } - urls.append(QUrl::fromLocalFile(defaultPath)); - - w.setFileMode(single ? QFileDialog::ExistingFile : QFileDialog::ExistingFiles); - w.setAcceptMode(QFileDialog::AcceptOpen); - w.setNameFilter(filter); - - QString pathToOpen; - if(savedPaths.contains(context)) - { - pathToOpen = savedPaths[context]; - } - else - { - pathToOpen = defaultPath; - } - if(!pathToOpen.isEmpty()) - { - QFileInfo finfo(pathToOpen); - if(finfo.exists() && finfo.isDir()) - { - w.setDirectory(finfo.absoluteFilePath()); - } - } - - w.setSidebarUrls(urls); - - if (w.exec()) - { - savedPaths[context] = w.directory().absolutePath(); - return w.selectedFiles(); - } - savedPaths[context] = w.directory().absolutePath(); - return {}; + static QMap<QString, QString> savedPaths; + + QFileDialog w(parentWidget, caption); + QSet<QString> locations; + auto f = [&](QStandardPaths::StandardLocation l) + { + QString location = QStandardPaths::writableLocation(l); + QFileInfo finfo(location); + if (!finfo.exists()) + return; + locations.insert(location); + }; + f(QStandardPaths::DesktopLocation); + f(QStandardPaths::DocumentsLocation); + f(QStandardPaths::DownloadLocation); + f(QStandardPaths::HomeLocation); + QList<QUrl> urls; + for (auto location : locations) + { + urls.append(QUrl::fromLocalFile(location)); + } + urls.append(QUrl::fromLocalFile(defaultPath)); + + w.setFileMode(single ? QFileDialog::ExistingFile : QFileDialog::ExistingFiles); + w.setAcceptMode(QFileDialog::AcceptOpen); + w.setNameFilter(filter); + + QString pathToOpen; + if(savedPaths.contains(context)) + { + pathToOpen = savedPaths[context]; + } + else + { + pathToOpen = defaultPath; + } + if(!pathToOpen.isEmpty()) + { + QFileInfo finfo(pathToOpen); + if(finfo.exists() && finfo.isDir()) + { + w.setDirectory(finfo.absoluteFilePath()); + } + } + + w.setSidebarUrls(urls); + + if (w.exec()) + { + savedPaths[context] = w.directory().absolutePath(); + return w.selectedFiles(); + } + savedPaths[context] = w.directory().absolutePath(); + return {}; } QString GuiUtil::BrowseForFile(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget) { - auto resultList = BrowseForFileInternal(context, caption, filter, defaultPath, parentWidget, true); - if(resultList.size()) - { - return resultList[0]; - } - return QString(); + auto resultList = BrowseForFileInternal(context, caption, filter, defaultPath, parentWidget, true); + if(resultList.size()) + { + return resultList[0]; + } + return QString(); } QStringList GuiUtil::BrowseForFiles(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget) { - return BrowseForFileInternal(context, caption, filter, defaultPath, parentWidget, false); + return BrowseForFileInternal(context, caption, filter, defaultPath, parentWidget, false); } diff --git a/application/HoeDown.h b/application/HoeDown.h index 18c315c6..c94a312f 100644 --- a/application/HoeDown.h +++ b/application/HoeDown.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,52 +25,52 @@ class HoeDown { public: - class buffer - { - public: - buffer(size_t unit = 4096) - { - buf = hoedown_buffer_new(unit); - } - ~buffer() - { - hoedown_buffer_free(buf); - } - const char * cstr() - { - return hoedown_buffer_cstr(buf); - } - void put(QByteArray input) - { - hoedown_buffer_put(buf, (uint8_t *) input.data(), input.size()); - } - const uint8_t * data() const - { - return buf->data; - } - size_t size() const - { - return buf->size; - } - hoedown_buffer * buf; - } ib, ob; - HoeDown() - { - renderer = hoedown_html_renderer_new((hoedown_html_flags) 0,0); - document = hoedown_document_new(renderer, (hoedown_extensions) 0, 8); - } - ~HoeDown() - { - hoedown_document_free(document); - hoedown_html_renderer_free(renderer); - } - QString process(QByteArray input) - { - ib.put(input); - hoedown_document_render(document, ob.buf, ib.data(), ib.size()); - return ob.cstr(); - } + class buffer + { + public: + buffer(size_t unit = 4096) + { + buf = hoedown_buffer_new(unit); + } + ~buffer() + { + hoedown_buffer_free(buf); + } + const char * cstr() + { + return hoedown_buffer_cstr(buf); + } + void put(QByteArray input) + { + hoedown_buffer_put(buf, (uint8_t *) input.data(), input.size()); + } + const uint8_t * data() const + { + return buf->data; + } + size_t size() const + { + return buf->size; + } + hoedown_buffer * buf; + } ib, ob; + HoeDown() + { + renderer = hoedown_html_renderer_new((hoedown_html_flags) 0,0); + document = hoedown_document_new(renderer, (hoedown_extensions) 0, 8); + } + ~HoeDown() + { + hoedown_document_free(document); + hoedown_html_renderer_free(renderer); + } + QString process(QByteArray input) + { + ib.put(input); + hoedown_document_render(document, ob.buf, ib.data(), ib.size()); + return ob.cstr(); + } private: - hoedown_document * document; - hoedown_renderer * renderer; + hoedown_document * document; + hoedown_renderer * renderer; }; diff --git a/application/InstancePageProvider.h b/application/InstancePageProvider.h index 9dda7859..dde36aef 100644 --- a/application/InstancePageProvider.h +++ b/application/InstancePageProvider.h @@ -15,57 +15,62 @@ #include "pages/instance/OtherLogsPage.h" #include "pages/instance/LegacyUpgradePage.h" #include "pages/instance/WorldListPage.h" +#include "pages/instance/ServersPage.h" +#include "pages/instance/GameOptionsPage.h" +#include "Env.h" class InstancePageProvider : public QObject, public BasePageProvider { - Q_OBJECT + Q_OBJECT public: - explicit InstancePageProvider(InstancePtr parent) - { - inst = parent; - } + explicit InstancePageProvider(InstancePtr parent) + { + inst = parent; + } - virtual ~InstancePageProvider() {}; - virtual QList<BasePage *> getPages() override - { - QList<BasePage *> values; - values.append(new LogPage(inst)); - std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst); - if(onesix) - { - values.append(new VersionPage(onesix.get())); - auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Loader mods"), "Loader-mods"); - modsPage->setFilter("%1 (*.zip *.jar *.litemod)"); - values.append(modsPage); - values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList(), "coremods", "coremods", tr("Core mods"), "Core-mods")); - values.append(new ResourcePackPage(onesix.get())); - values.append(new TexturePackPage(onesix.get())); - values.append(new NotesPage(onesix.get())); - values.append(new WorldListPage(onesix.get(), onesix->worldList(), "worlds", "worlds", tr("Worlds"), "Worlds")); - values.append(new ScreenshotsPage(FS::PathCombine(onesix->minecraftRoot(), "screenshots"))); - values.append(new InstanceSettingsPage(onesix.get())); - } - std::shared_ptr<LegacyInstance> legacy = std::dynamic_pointer_cast<LegacyInstance>(inst); - if(legacy) - { - values.append(new LegacyUpgradePage(legacy)); - values.append(new NotesPage(legacy.get())); - values.append(new WorldListPage(legacy.get(), legacy->worldList(), "worlds", "worlds", tr("Worlds"), "Worlds")); - values.append(new ScreenshotsPage(FS::PathCombine(legacy->minecraftRoot(), "screenshots"))); - } - auto logMatcher = inst->getLogFileMatcher(); - if(logMatcher) - { - values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher)); - } - return values; - } + virtual ~InstancePageProvider() {}; + virtual QList<BasePage *> getPages() override + { + QList<BasePage *> values; + values.append(new LogPage(inst)); + std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst); + if(onesix) + { + values.append(new VersionPage(onesix.get())); + auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Loader mods"), "Loader-mods"); + modsPage->setFilter("%1 (*.zip *.jar *.litemod)"); + values.append(modsPage); + values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList(), "coremods", "coremods", tr("Core mods"), "Core-mods")); + values.append(new ResourcePackPage(onesix.get())); + values.append(new TexturePackPage(onesix.get())); + values.append(new NotesPage(onesix.get())); + values.append(new WorldListPage(onesix.get(), onesix->worldList())); + values.append(new ServersPage(onesix.get())); + // values.append(new GameOptionsPage(onesix.get())); + values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots"))); + values.append(new InstanceSettingsPage(onesix.get())); + } + std::shared_ptr<LegacyInstance> legacy = std::dynamic_pointer_cast<LegacyInstance>(inst); + if(legacy) + { + values.append(new LegacyUpgradePage(legacy)); + values.append(new NotesPage(legacy.get())); + values.append(new WorldListPage(legacy.get(), legacy->worldList())); + values.append(new ScreenshotsPage(FS::PathCombine(legacy->gameRoot(), "screenshots"))); + } + auto logMatcher = inst->getLogFileMatcher(); + if(logMatcher) + { + values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher)); + } + return values; + } - virtual QString dialogTitle() override - { - return tr("Edit Instance (%1)").arg(inst->name()); - } + virtual QString dialogTitle() override + { + return tr("Edit Instance (%1)").arg(inst->name()); + } protected: - InstancePtr inst; + InstancePtr inst; }; diff --git a/application/InstanceProxyModel.cpp b/application/InstanceProxyModel.cpp index d0e9e10d..5317f60c 100644 --- a/application/InstanceProxyModel.cpp +++ b/application/InstanceProxyModel.cpp @@ -9,26 +9,26 @@ InstanceProxyModel::InstanceProxyModel(QObject *parent) : GroupedProxyModel(pare QVariant InstanceProxyModel::data(const QModelIndex & index, int role) const { - QVariant data = QSortFilterProxyModel::data(index, role); - if(role == Qt::DecorationRole) - { - return QVariant(MMC->icons()->getIcon(data.toString())); - } - return data; + QVariant data = QSortFilterProxyModel::data(index, role); + if(role == Qt::DecorationRole) + { + return QVariant(MMC->icons()->getIcon(data.toString())); + } + return data; } bool InstanceProxyModel::subSortLessThan(const QModelIndex &left, - const QModelIndex &right) const + const QModelIndex &right) const { - BaseInstance *pdataLeft = static_cast<BaseInstance *>(left.internalPointer()); - BaseInstance *pdataRight = static_cast<BaseInstance *>(right.internalPointer()); - QString sortMode = MMC->settings()->get("InstSortMode").toString(); - if (sortMode == "LastLaunch") - { - return pdataLeft->lastLaunch() > pdataRight->lastLaunch(); - } - else - { - return QString::localeAwareCompare(pdataLeft->name(), pdataRight->name()) < 0; - } + BaseInstance *pdataLeft = static_cast<BaseInstance *>(left.internalPointer()); + BaseInstance *pdataRight = static_cast<BaseInstance *>(right.internalPointer()); + QString sortMode = MMC->settings()->get("InstSortMode").toString(); + if (sortMode == "LastLaunch") + { + return pdataLeft->lastLaunch() > pdataRight->lastLaunch(); + } + else + { + return QString::localeAwareCompare(pdataLeft->name(), pdataRight->name()) < 0; + } } diff --git a/application/InstanceProxyModel.h b/application/InstanceProxyModel.h index c063f526..fab6f834 100644 --- a/application/InstanceProxyModel.h +++ b/application/InstanceProxyModel.h @@ -8,9 +8,9 @@ class InstanceProxyModel : public GroupedProxyModel { public: - explicit InstanceProxyModel(QObject *parent = 0); - QVariant data(const QModelIndex & index, int role) const override; + explicit InstanceProxyModel(QObject *parent = 0); + QVariant data(const QModelIndex & index, int role) const override; protected: - virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const override; + virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const override; }; diff --git a/application/InstanceWindow.cpp b/application/InstanceWindow.cpp index 5895ca3a..02c1bb23 100644 --- a/application/InstanceWindow.cpp +++ b/application/InstanceWindow.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,184 +31,188 @@ #include "icons/IconList.h" InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent) - : QMainWindow(parent), m_instance(instance) + : QMainWindow(parent), m_instance(instance) { - setAttribute(Qt::WA_DeleteOnClose); - - auto icon = MMC->icons()->getIcon(m_instance->iconKey()); - QString windowTitle = tr("Console window for ") + m_instance->name(); - - // Set window properties - { - setWindowIcon(icon); - setWindowTitle(windowTitle); - } - - // Add page container - { - auto provider = std::make_shared<InstancePageProvider>(m_instance); - m_container = new PageContainer(provider.get(), "console", this); - m_container->setParentContainer(this); - setCentralWidget(m_container); - } - - // Add custom buttons to the page container layout. - { - auto horizontalLayout = new QHBoxLayout(); - horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); - horizontalLayout->setContentsMargins(6, -1, 6, -1); - - auto btnHelp = new QPushButton(); - btnHelp->setText(tr("Help")); - horizontalLayout->addWidget(btnHelp); - connect(btnHelp, SIGNAL(clicked(bool)), m_container, SLOT(help())); - - auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout->addSpacerItem(spacer); - - m_killButton = new QPushButton(); - horizontalLayout->addWidget(m_killButton); - connect(m_killButton, SIGNAL(clicked(bool)), SLOT(on_btnKillMinecraft_clicked())); - - m_launchOfflineButton = new QPushButton(); - horizontalLayout->addWidget(m_launchOfflineButton); - m_launchOfflineButton->setText(tr("Launch Offline")); - updateLaunchButtons(); - connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked())); - - m_closeButton = new QPushButton(); - m_closeButton->setText(tr("Close")); - horizontalLayout->addWidget(m_closeButton); - connect(m_closeButton, SIGNAL(clicked(bool)), SLOT(on_closeButton_clicked())); - - m_container->addButtons(horizontalLayout); - } - - // restore window state - { - auto base64State = MMC->settings()->get("ConsoleWindowState").toByteArray(); - restoreState(QByteArray::fromBase64(base64State)); - auto base64Geometry = MMC->settings()->get("ConsoleWindowGeometry").toByteArray(); - restoreGeometry(QByteArray::fromBase64(base64Geometry)); - } - - // set up instance and launch process recognition - { - auto launchTask = m_instance->getLaunchTask(); - on_InstanceLaunchTask_changed(launchTask); - connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &InstanceWindow::on_InstanceLaunchTask_changed); - connect(m_instance.get(), &BaseInstance::runningStatusChanged, this, &InstanceWindow::on_RunningState_changed); - } - - // set up instance destruction detection - { - connect(m_instance.get(), &BaseInstance::statusChanged, this, &InstanceWindow::on_instanceStatusChanged); - } - show(); + setAttribute(Qt::WA_DeleteOnClose); + + auto icon = MMC->icons()->getIcon(m_instance->iconKey()); + QString windowTitle = tr("Console window for ") + m_instance->name(); + + // Set window properties + { + setWindowIcon(icon); + setWindowTitle(windowTitle); + } + + // Add page container + { + auto provider = std::make_shared<InstancePageProvider>(m_instance); + m_container = new PageContainer(provider.get(), "console", this); + m_container->setParentContainer(this); + setCentralWidget(m_container); + setContentsMargins(0, 0, 0, 0); + } + + // Add custom buttons to the page container layout. + { + auto horizontalLayout = new QHBoxLayout(); + horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + horizontalLayout->setContentsMargins(6, -1, 6, -1); + + auto btnHelp = new QPushButton(); + btnHelp->setText(tr("Help")); + horizontalLayout->addWidget(btnHelp); + connect(btnHelp, SIGNAL(clicked(bool)), m_container, SLOT(help())); + + auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout->addSpacerItem(spacer); + + m_killButton = new QPushButton(); + horizontalLayout->addWidget(m_killButton); + connect(m_killButton, SIGNAL(clicked(bool)), SLOT(on_btnKillMinecraft_clicked())); + + m_launchOfflineButton = new QPushButton(); + horizontalLayout->addWidget(m_launchOfflineButton); + m_launchOfflineButton->setText(tr("Launch Offline")); + updateLaunchButtons(); + connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked())); + + m_closeButton = new QPushButton(); + m_closeButton->setText(tr("Close")); + horizontalLayout->addWidget(m_closeButton); + connect(m_closeButton, SIGNAL(clicked(bool)), SLOT(on_closeButton_clicked())); + + m_container->addButtons(horizontalLayout); + } + + // restore window state + { + auto base64State = MMC->settings()->get("ConsoleWindowState").toByteArray(); + restoreState(QByteArray::fromBase64(base64State)); + auto base64Geometry = MMC->settings()->get("ConsoleWindowGeometry").toByteArray(); + restoreGeometry(QByteArray::fromBase64(base64Geometry)); + } + + // set up instance and launch process recognition + { + auto launchTask = m_instance->getLaunchTask(); + on_InstanceLaunchTask_changed(launchTask); + connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &InstanceWindow::on_InstanceLaunchTask_changed); + connect(m_instance.get(), &BaseInstance::runningStatusChanged, this, &InstanceWindow::on_RunningState_changed); + } + + // set up instance destruction detection + { + connect(m_instance.get(), &BaseInstance::statusChanged, this, &InstanceWindow::on_instanceStatusChanged); + } + show(); } void InstanceWindow::on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus) { - if(newStatus == BaseInstance::Status::Gone) - { - m_doNotSave = true; - close(); - } + if(newStatus == BaseInstance::Status::Gone) + { + m_doNotSave = true; + close(); + } } void InstanceWindow::updateLaunchButtons() { - if(m_instance->isRunning()) - { - m_launchOfflineButton->setEnabled(false); - m_killButton->setText(tr("Kill")); - m_killButton->setToolTip(tr("Kill the running instance")); - } - else if(!m_instance->canLaunch()) - { - m_launchOfflineButton->setEnabled(false); - m_killButton->setText(tr("Launch")); - m_killButton->setToolTip(tr("Launch the instance")); - m_killButton->setEnabled(false); - } - else - { - m_launchOfflineButton->setEnabled(true); - m_killButton->setText(tr("Launch")); - m_killButton->setToolTip(tr("Launch the instance")); - } + if(m_instance->isRunning()) + { + m_launchOfflineButton->setEnabled(false); + m_killButton->setText(tr("Kill")); + m_killButton->setToolTip(tr("Kill the running instance")); + } + else if(!m_instance->canLaunch()) + { + m_launchOfflineButton->setEnabled(false); + m_killButton->setText(tr("Launch")); + m_killButton->setToolTip(tr("Launch the instance")); + m_killButton->setEnabled(false); + } + else + { + m_launchOfflineButton->setEnabled(true); + m_killButton->setText(tr("Launch")); + m_killButton->setToolTip(tr("Launch the instance")); + } } void InstanceWindow::on_btnLaunchMinecraftOffline_clicked() { - MMC->launch(m_instance, false, nullptr); + MMC->launch(m_instance, false, nullptr); } -void InstanceWindow::on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc) +void InstanceWindow::on_InstanceLaunchTask_changed(shared_qobject_ptr<LaunchTask> proc) { - m_proc = proc; + m_proc = proc; } -void InstanceWindow::on_RunningState_changed(bool) +void InstanceWindow::on_RunningState_changed(bool running) { - updateLaunchButtons(); - m_container->refreshContainer(); + updateLaunchButtons(); + m_container->refreshContainer(); + if(running) { + selectPage("log"); + } } void InstanceWindow::on_closeButton_clicked() { - close(); + close(); } void InstanceWindow::closeEvent(QCloseEvent *event) { - bool proceed = true; - if(!m_doNotSave) - { - proceed &= m_container->prepareToClose(); - } - - if(!proceed) - { - return; - } - - MMC->settings()->set("ConsoleWindowState", saveState().toBase64()); - MMC->settings()->set("ConsoleWindowGeometry", saveGeometry().toBase64()); - emit isClosing(); - event->accept(); + bool proceed = true; + if(!m_doNotSave) + { + proceed &= m_container->prepareToClose(); + } + + if(!proceed) + { + return; + } + + MMC->settings()->set("ConsoleWindowState", saveState().toBase64()); + MMC->settings()->set("ConsoleWindowGeometry", saveGeometry().toBase64()); + emit isClosing(); + event->accept(); } bool InstanceWindow::saveAll() { - return m_container->prepareToClose(); + return m_container->saveAll(); } void InstanceWindow::on_btnKillMinecraft_clicked() { - if(m_instance->isRunning()) - { - MMC->kill(m_instance); - } - else - { - MMC->launch(m_instance, true, nullptr); - } + if(m_instance->isRunning()) + { + MMC->kill(m_instance); + } + else + { + MMC->launch(m_instance, true, nullptr); + } } QString InstanceWindow::instanceId() { - return m_instance->id(); + return m_instance->id(); } bool InstanceWindow::selectPage(QString pageId) { - return m_container->selectPage(pageId); + return m_container->selectPage(pageId); } void InstanceWindow::refreshContainer() { - m_container->refreshContainer(); + m_container->refreshContainer(); } InstanceWindow::~InstanceWindow() @@ -217,10 +221,10 @@ InstanceWindow::~InstanceWindow() bool InstanceWindow::requestClose() { - if(m_container->prepareToClose()) - { - close(); - return true; - } - return false; + if(m_container->prepareToClose()) + { + close(); + return true; + } + return false; } diff --git a/application/InstanceWindow.h b/application/InstanceWindow.h index 2b08644e..e5bd4464 100644 --- a/application/InstanceWindow.h +++ b/application/InstanceWindow.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,48 +26,48 @@ class QPushButton; class PageContainer; class InstanceWindow : public QMainWindow, public BasePageContainer { - Q_OBJECT + Q_OBJECT public: - explicit InstanceWindow(InstancePtr proc, QWidget *parent = 0); - virtual ~InstanceWindow(); + explicit InstanceWindow(InstancePtr proc, QWidget *parent = 0); + virtual ~InstanceWindow(); - bool selectPage(QString pageId) override; - void refreshContainer() override; + bool selectPage(QString pageId) override; + void refreshContainer() override; - QString instanceId(); + QString instanceId(); - // save all settings and changes (prepare for launch) - bool saveAll(); + // save all settings and changes (prepare for launch) + bool saveAll(); - // request closing the window (from a page) - bool requestClose() override; + // request closing the window (from a page) + bool requestClose() override; signals: - void isClosing(); + void isClosing(); private slots: - void on_closeButton_clicked(); - void on_btnKillMinecraft_clicked(); - void on_btnLaunchMinecraftOffline_clicked(); + void on_closeButton_clicked(); + void on_btnKillMinecraft_clicked(); + void on_btnLaunchMinecraftOffline_clicked(); - void on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc); - void on_RunningState_changed(bool running); - void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus); + void on_InstanceLaunchTask_changed(shared_qobject_ptr<LaunchTask> proc); + void on_RunningState_changed(bool running); + void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus); protected: - void closeEvent(QCloseEvent *) override; + void closeEvent(QCloseEvent *) override; private: - void updateLaunchButtons(); + void updateLaunchButtons(); private: - std::shared_ptr<LaunchTask> m_proc; - InstancePtr m_instance; - bool m_doNotSave = false; - PageContainer *m_container = nullptr; - QPushButton *m_closeButton = nullptr; - QPushButton *m_killButton = nullptr; - QPushButton *m_launchOfflineButton = nullptr; + shared_qobject_ptr<LaunchTask> m_proc; + InstancePtr m_instance; + bool m_doNotSave = false; + PageContainer *m_container = nullptr; + QPushButton *m_closeButton = nullptr; + QPushButton *m_killButton = nullptr; + QPushButton *m_launchOfflineButton = nullptr; }; diff --git a/application/JavaCommon.cpp b/application/JavaCommon.cpp index 0008fc04..563dfb35 100644 --- a/application/JavaCommon.cpp +++ b/application/JavaCommon.cpp @@ -4,100 +4,100 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent) { - if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegExp("-Xm[sx]")) - || jvmargs.contains("-XX-MaxHeapSize") || jvmargs.contains("-XX:InitialHeapSize")) - { - auto warnStr = QObject::tr( - "You tried to manually set a JVM memory option (using \"-XX:PermSize\", \"-XX-MaxHeapSize\", \"-XX:InitialHeapSize\", \"-Xmx\" or \"-Xms\").\n" - "There are dedicated boxes for these in the settings (Java tab, in the Memory group at the top).\n" - "This message will be displayed until you remove them from the JVM arguments."); - CustomMessageBox::selectable( - parent, QObject::tr("JVM arguments warning"), - warnStr, - QMessageBox::Warning)->exec(); - return false; - } - return true; + if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegExp("-Xm[sx]")) + || jvmargs.contains("-XX-MaxHeapSize") || jvmargs.contains("-XX:InitialHeapSize")) + { + auto warnStr = QObject::tr( + "You tried to manually set a JVM memory option (using \"-XX:PermSize\", \"-XX-MaxHeapSize\", \"-XX:InitialHeapSize\", \"-Xmx\" or \"-Xms\").\n" + "There are dedicated boxes for these in the settings (Java tab, in the Memory group at the top).\n" + "This message will be displayed until you remove them from the JVM arguments."); + CustomMessageBox::selectable( + parent, QObject::tr("JVM arguments warning"), + warnStr, + QMessageBox::Warning)->exec(); + return false; + } + return true; } void JavaCommon::javaWasOk(QWidget *parent, JavaCheckResult result) { - QString text; - text += QObject::tr("Java test succeeded!<br />Platform reported: %1<br />Java version " - "reported: %2<br />").arg(result.realPlatform, result.javaVersion.toString()); - if (result.errorLog.size()) - { - auto htmlError = result.errorLog; - htmlError.replace('\n', "<br />"); - text += QObject::tr("<br />Warnings:<br /><font color=\"orange\">%1</font>").arg(htmlError); - } - CustomMessageBox::selectable(parent, QObject::tr("Java test success"), text, QMessageBox::Information)->show(); + QString text; + text += QObject::tr("Java test succeeded!<br />Platform reported: %1<br />Java version " + "reported: %2<br />").arg(result.realPlatform, result.javaVersion.toString()); + if (result.errorLog.size()) + { + auto htmlError = result.errorLog; + htmlError.replace('\n', "<br />"); + text += QObject::tr("<br />Warnings:<br /><font color=\"orange\">%1</font>").arg(htmlError); + } + CustomMessageBox::selectable(parent, QObject::tr("Java test success"), text, QMessageBox::Information)->show(); } void JavaCommon::javaArgsWereBad(QWidget *parent, JavaCheckResult result) { - auto htmlError = result.errorLog; - QString text; - htmlError.replace('\n', "<br />"); - text += QObject::tr("The specified java binary didn't work with the arguments you provided:<br />"); - text += QString("<font color=\"red\">%1</font>").arg(htmlError); - CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); + auto htmlError = result.errorLog; + QString text; + htmlError.replace('\n', "<br />"); + text += QObject::tr("The specified java binary didn't work with the arguments you provided:<br />"); + text += QString("<font color=\"red\">%1</font>").arg(htmlError); + CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); } void JavaCommon::javaBinaryWasBad(QWidget *parent, JavaCheckResult result) { - QString text; - text += QObject::tr( - "The specified java binary didn't work.<br />You should use the auto-detect feature, " - "or set the path to the java executable.<br />"); - CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); + QString text; + text += QObject::tr( + "The specified java binary didn't work.<br />You should use the auto-detect feature, " + "or set the path to the java executable.<br />"); + CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); } void JavaCommon::TestCheck::run() { - if (!JavaCommon::checkJVMArgs(m_args, m_parent)) - { - emit finished(); - return; - } - checker.reset(new JavaChecker()); - connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, - SLOT(checkFinished(JavaCheckResult))); - checker->m_path = m_path; - checker->performCheck(); + if (!JavaCommon::checkJVMArgs(m_args, m_parent)) + { + emit finished(); + return; + } + checker.reset(new JavaChecker()); + connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, + SLOT(checkFinished(JavaCheckResult))); + checker->m_path = m_path; + checker->performCheck(); } void JavaCommon::TestCheck::checkFinished(JavaCheckResult result) { - if (result.validity != JavaCheckResult::Validity::Valid) - { - javaBinaryWasBad(m_parent, result); - emit finished(); - return; - } - checker.reset(new JavaChecker()); - connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, - SLOT(checkFinishedWithArgs(JavaCheckResult))); - checker->m_path = m_path; - checker->m_args = m_args; - checker->m_minMem = m_minMem; - checker->m_maxMem = m_maxMem; - if (result.javaVersion.requiresPermGen()) - { - checker->m_permGen = m_permGen; - } - checker->performCheck(); + if (result.validity != JavaCheckResult::Validity::Valid) + { + javaBinaryWasBad(m_parent, result); + emit finished(); + return; + } + checker.reset(new JavaChecker()); + connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, + SLOT(checkFinishedWithArgs(JavaCheckResult))); + checker->m_path = m_path; + checker->m_args = m_args; + checker->m_minMem = m_minMem; + checker->m_maxMem = m_maxMem; + if (result.javaVersion.requiresPermGen()) + { + checker->m_permGen = m_permGen; + } + checker->performCheck(); } void JavaCommon::TestCheck::checkFinishedWithArgs(JavaCheckResult result) { - if (result.validity == JavaCheckResult::Validity::Valid) - { - javaWasOk(m_parent, result); - emit finished(); - return; - } - javaArgsWereBad(m_parent, result); - emit finished(); + if (result.validity == JavaCheckResult::Validity::Valid) + { + javaWasOk(m_parent, result); + emit finished(); + return; + } + javaArgsWereBad(m_parent, result); + emit finished(); } diff --git a/application/JavaCommon.h b/application/JavaCommon.h index 4e4cd633..ca98145c 100644 --- a/application/JavaCommon.h +++ b/application/JavaCommon.h @@ -8,41 +8,41 @@ class QWidget; */ namespace JavaCommon { - bool checkJVMArgs(QString args, QWidget *parent); - - // Show a dialog saying that the Java binary was not usable - void javaBinaryWasBad(QWidget *parent, JavaCheckResult result); - // Show a dialog saying that the Java binary was not usable because of bad options - void javaArgsWereBad(QWidget *parent, JavaCheckResult result); - // Show a dialog saying that the Java binary was usable - void javaWasOk(QWidget *parent, JavaCheckResult result); - - class TestCheck : public QObject - { - Q_OBJECT - public: - TestCheck(QWidget *parent, QString path, QString args, int minMem, int maxMem, int permGen) - :m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen) - { - } - virtual ~TestCheck() {}; - - void run(); - - signals: - void finished(); - - private slots: - void checkFinished(JavaCheckResult result); - void checkFinishedWithArgs(JavaCheckResult result); - - private: - std::shared_ptr<JavaChecker> checker; - QWidget *m_parent = nullptr; - QString m_path; - QString m_args; - int m_minMem = 0; - int m_maxMem = 0; - int m_permGen = 64; - }; + bool checkJVMArgs(QString args, QWidget *parent); + + // Show a dialog saying that the Java binary was not usable + void javaBinaryWasBad(QWidget *parent, JavaCheckResult result); + // Show a dialog saying that the Java binary was not usable because of bad options + void javaArgsWereBad(QWidget *parent, JavaCheckResult result); + // Show a dialog saying that the Java binary was usable + void javaWasOk(QWidget *parent, JavaCheckResult result); + + class TestCheck : public QObject + { + Q_OBJECT + public: + TestCheck(QWidget *parent, QString path, QString args, int minMem, int maxMem, int permGen) + :m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen) + { + } + virtual ~TestCheck() {}; + + void run(); + + signals: + void finished(); + + private slots: + void checkFinished(JavaCheckResult result); + void checkFinishedWithArgs(JavaCheckResult result); + + private: + std::shared_ptr<JavaChecker> checker; + QWidget *m_parent = nullptr; + QString m_path; + QString m_args; + int m_minMem = 0; + int m_maxMem = 0; + int m_permGen = 64; + }; } diff --git a/application/KonamiCode.cpp b/application/KonamiCode.cpp index 07c285bc..4c5af837 100644 --- a/application/KonamiCode.cpp +++ b/application/KonamiCode.cpp @@ -6,11 +6,13 @@ namespace { const std::array<Qt::Key, 10> konamiCode = { - Qt::Key_Up, Qt::Key_Up, - Qt::Key_Down, Qt::Key_Down, - Qt::Key_Left, Qt::Key_Right, - Qt::Key_Left, Qt::Key_Right, - Qt::Key_B, Qt::Key_A + { + Qt::Key_Up, Qt::Key_Up, + Qt::Key_Down, Qt::Key_Down, + Qt::Key_Left, Qt::Key_Right, + Qt::Key_Left, Qt::Key_Right, + Qt::Key_B, Qt::Key_A + } }; } @@ -21,22 +23,22 @@ KonamiCode::KonamiCode(QObject* parent) : QObject(parent) void KonamiCode::input(QEvent* event) { - if( event->type() == QEvent::KeyPress ) - { - QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event ); - auto key = Qt::Key(keyEvent->key()); - if(key == konamiCode[m_progress]) - { - m_progress ++; - } - else - { - m_progress = 0; - } - if(m_progress == konamiCode.size()) - { - m_progress = 0; - emit triggered(); - } - } + if( event->type() == QEvent::KeyPress ) + { + QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event ); + auto key = Qt::Key(keyEvent->key()); + if(key == konamiCode[m_progress]) + { + m_progress ++; + } + else + { + m_progress = 0; + } + if(m_progress == konamiCode.size()) + { + m_progress = 0; + emit triggered(); + } + } } diff --git a/application/KonamiCode.h b/application/KonamiCode.h index ad17f8be..3d320ae7 100644 --- a/application/KonamiCode.h +++ b/application/KonamiCode.h @@ -4,14 +4,14 @@ class KonamiCode : public QObject { - Q_OBJECT + Q_OBJECT public: - KonamiCode(QObject *parent = 0); - void input(QEvent *event); + KonamiCode(QObject *parent = 0); + void input(QEvent *event); signals: - void triggered(); + void triggered(); private: - int m_progress = 0; + int m_progress = 0; }; diff --git a/application/LaunchController.cpp b/application/LaunchController.cpp index 2e711933..e39048f1 100644 --- a/application/LaunchController.cpp +++ b/application/LaunchController.cpp @@ -9,7 +9,6 @@ #include "InstanceWindow.h" #include "BuildConfig.h" #include "JavaCommon.h" -#include "SettingsUI.h" #include <QLineEdit> #include <QInputDialog> #include <tasks/Task.h> @@ -23,291 +22,291 @@ LaunchController::LaunchController(QObject *parent) : Task(parent) void LaunchController::executeTask() { - if (!m_instance) - { - emitFailed(tr("No instance specified")); - return; - } + if (!m_instance) + { + emitFailed(tr("No instance specified")); + return; + } - login(); + login(); } // FIXME: minecraft specific void LaunchController::login() { - JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget); + JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget); - // Find an account to use. - std::shared_ptr<MojangAccountList> accounts = MMC->accounts(); - MojangAccountPtr account = accounts->activeAccount(); - if (accounts->count() <= 0) - { - // Tell the user they need to log in at least one account in order to play. - auto reply = CustomMessageBox::selectable( - m_parentWidget, tr("No Accounts"), - tr("In order to play Minecraft, you must have at least one Mojang or Minecraft " - "account logged in to MultiMC." - "Would you like to open the account manager to add an account now?"), - QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)->exec(); + // Find an account to use. + std::shared_ptr<MojangAccountList> accounts = MMC->accounts(); + MojangAccountPtr account = accounts->activeAccount(); + if (accounts->count() <= 0) + { + // Tell the user they need to log in at least one account in order to play. + auto reply = CustomMessageBox::selectable( + m_parentWidget, tr("No Accounts"), + tr("In order to play Minecraft, you must have at least one Mojang or Minecraft " + "account logged in to MultiMC." + "Would you like to open the account manager to add an account now?"), + QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)->exec(); - if (reply == QMessageBox::Yes) - { - // Open the account manager. - SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), m_parentWidget, "accounts"); - } - } - else if (account.get() == nullptr) - { - // If no default account is set, ask the user which one to use. - ProfileSelectDialog selectDialog(tr("Which profile would you like to use?"), - ProfileSelectDialog::GlobalDefaultCheckbox, m_parentWidget); + if (reply == QMessageBox::Yes) + { + // Open the account manager. + MMC->ShowGlobalSettings(m_parentWidget, "accounts"); + } + } + else if (account.get() == nullptr) + { + // If no default account is set, ask the user which one to use. + ProfileSelectDialog selectDialog(tr("Which profile would you like to use?"), + ProfileSelectDialog::GlobalDefaultCheckbox, m_parentWidget); - selectDialog.exec(); + selectDialog.exec(); - // Launch the instance with the selected account. - account = selectDialog.selectedAccount(); + // Launch the instance with the selected account. + account = selectDialog.selectedAccount(); - // If the user said to use the account as default, do that. - if (selectDialog.useAsGlobalDefault() && account.get() != nullptr) - accounts->setActiveAccount(account->username()); - } + // If the user said to use the account as default, do that. + if (selectDialog.useAsGlobalDefault() && account.get() != nullptr) + accounts->setActiveAccount(account->username()); + } - // if no account is selected, we bail - if (!account.get()) - { - emitFailed(tr("No account selected for launch")); - return; - } + // if no account is selected, we bail + if (!account.get()) + { + emitFailed(tr("No account selected for launch")); + return; + } - // we try empty password first :) - QString password; - // we loop until the user succeeds in logging in or gives up - bool tryagain = true; - // the failure. the default failure. - const QString needLoginAgain = tr("Your account is currently not logged in. Please enter " - "your password to log in again."); - QString failReason = needLoginAgain; + // we try empty password first :) + QString password; + // we loop until the user succeeds in logging in or gives up + bool tryagain = true; + // the failure. the default failure. + const QString needLoginAgain = tr("Your account is currently not logged in. Please enter " + "your password to log in again."); + QString failReason = needLoginAgain; - while (tryagain) - { - m_session = std::make_shared<AuthSession>(); - m_session->wants_online = m_online; - auto task = account->login(m_session, password); - if (task) - { - // We'll need to validate the access token to make sure the account - // is still logged in. - ProgressDialog progDialog(m_parentWidget); - if (m_online) - { - progDialog.setSkipButton(true, tr("Play Offline")); - } - progDialog.execWithTask(task.get()); - if (!task->wasSuccessful()) - { - auto failReasonNew = task->failReason(); - if(failReasonNew == "Invalid token.") - { - account->invalidateClientToken(); - failReason = needLoginAgain; - } - else failReason = failReasonNew; - } - } - switch (m_session->status) - { - case AuthSession::Undetermined: - { - qCritical() << "Received undetermined session status during login. Bye."; - tryagain = false; - emitFailed(tr("Received undetermined session status during login.")); - break; - } - case AuthSession::RequiresPassword: - { - EditAccountDialog passDialog(failReason, m_parentWidget, EditAccountDialog::PasswordField); - auto username = m_session->username; - auto chopN = [](QString toChop, int N) -> QString - { - if(toChop.size() > N) - { - auto left = toChop.left(N); - left += QString("\u25CF").repeated(toChop.size() - N); - return left; - } - return toChop; - }; + while (tryagain) + { + m_session = std::make_shared<AuthSession>(); + m_session->wants_online = m_online; + auto task = account->login(m_session, password); + if (task) + { + // We'll need to validate the access token to make sure the account + // is still logged in. + ProgressDialog progDialog(m_parentWidget); + if (m_online) + { + progDialog.setSkipButton(true, tr("Play Offline")); + } + progDialog.execWithTask(task.get()); + if (!task->wasSuccessful()) + { + auto failReasonNew = task->failReason(); + if(failReasonNew == "Invalid token.") + { + account->invalidateClientToken(); + failReason = needLoginAgain; + } + else failReason = failReasonNew; + } + } + switch (m_session->status) + { + case AuthSession::Undetermined: + { + qCritical() << "Received undetermined session status during login. Bye."; + tryagain = false; + emitFailed(tr("Received undetermined session status during login.")); + break; + } + case AuthSession::RequiresPassword: + { + EditAccountDialog passDialog(failReason, m_parentWidget, EditAccountDialog::PasswordField); + auto username = m_session->username; + auto chopN = [](QString toChop, int N) -> QString + { + if(toChop.size() > N) + { + auto left = toChop.left(N); + left += QString("\u25CF").repeated(toChop.size() - N); + return left; + } + return toChop; + }; - if(username.contains('@')) - { - auto parts = username.split('@'); - auto mailbox = chopN(parts[0],3); - QString domain = chopN(parts[1], 3); - username = mailbox + '@' + domain; - } - passDialog.setUsername(username); - if (passDialog.exec() == QDialog::Accepted) - { - password = passDialog.password(); - } - else - { - tryagain = false; - } - break; - } - case AuthSession::PlayableOffline: - { - // we ask the user for a player name - bool ok = false; - QString usedname = m_session->player_name; - QString name = QInputDialog::getText(m_parentWidget, tr("Player name"), - tr("Choose your offline mode player name."), - QLineEdit::Normal, m_session->player_name, &ok); - if (!ok) - { - tryagain = false; - break; - } - if (name.length()) - { - usedname = name; - } - m_session->MakeOffline(usedname); - // offline flavored game from here :3 - } - case AuthSession::PlayableOnline: - { - launchInstance(); - tryagain = false; - return; - } - } - } - emitFailed(tr("Failed to launch.")); + if(username.contains('@')) + { + auto parts = username.split('@'); + auto mailbox = chopN(parts[0],3); + QString domain = chopN(parts[1], 3); + username = mailbox + '@' + domain; + } + passDialog.setUsername(username); + if (passDialog.exec() == QDialog::Accepted) + { + password = passDialog.password(); + } + else + { + tryagain = false; + } + break; + } + case AuthSession::PlayableOffline: + { + // we ask the user for a player name + bool ok = false; + QString usedname = m_session->player_name; + QString name = QInputDialog::getText(m_parentWidget, tr("Player name"), + tr("Choose your offline mode player name."), + QLineEdit::Normal, m_session->player_name, &ok); + if (!ok) + { + tryagain = false; + break; + } + if (name.length()) + { + usedname = name; + } + m_session->MakeOffline(usedname); + // offline flavored game from here :3 + } + case AuthSession::PlayableOnline: + { + launchInstance(); + tryagain = false; + return; + } + } + } + emitFailed(tr("Failed to launch.")); } void LaunchController::launchInstance() { - Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL"); - Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL"); + Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL"); + Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL"); - if(!m_instance->reloadSettings()) - { - QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't load the instance profile.")); - emitFailed(tr("Couldn't load the instance profile.")); - return; - } + if(!m_instance->reloadSettings()) + { + QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't load the instance profile.")); + emitFailed(tr("Couldn't load the instance profile.")); + return; + } - m_launcher = m_instance->createLaunchTask(m_session); - if (!m_launcher) - { - emitFailed(tr("Couldn't instantiate a launcher.")); - return; - } + m_launcher = m_instance->createLaunchTask(m_session); + if (!m_launcher) + { + emitFailed(tr("Couldn't instantiate a launcher.")); + return; + } - auto console = qobject_cast<InstanceWindow *>(m_parentWidget); - auto showConsole = m_instance->settings()->get("ShowConsole").toBool(); - if(!console && showConsole) - { - MMC->showInstanceWindow(m_instance); - } - connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch); - connect(m_launcher.get(), &LaunchTask::succeeded, this, &LaunchController::onSucceeded); - connect(m_launcher.get(), &LaunchTask::failed, this, &LaunchController::onFailed); - connect(m_launcher.get(), &LaunchTask::requestProgress, this, &LaunchController::onProgressRequested); + auto console = qobject_cast<InstanceWindow *>(m_parentWidget); + auto showConsole = m_instance->settings()->get("ShowConsole").toBool(); + if(!console && showConsole) + { + MMC->showInstanceWindow(m_instance); + } + connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch); + connect(m_launcher.get(), &LaunchTask::succeeded, this, &LaunchController::onSucceeded); + connect(m_launcher.get(), &LaunchTask::failed, this, &LaunchController::onFailed); + connect(m_launcher.get(), &LaunchTask::requestProgress, this, &LaunchController::onProgressRequested); - m_launcher->prependStep(std::make_shared<TextPrint>(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC)); - m_launcher->start(); + m_launcher->prependStep(new TextPrint(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC)); + m_launcher->start(); } void LaunchController::readyForLaunch() { - if (!m_profiler) - { - m_launcher->proceed(); - return; - } + if (!m_profiler) + { + m_launcher->proceed(); + return; + } - QString error; - if (!m_profiler->check(&error)) - { - m_launcher->abort(); - QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't start profiler: %1").arg(error)); - emitFailed("Profiler startup failed"); - return; - } - BaseProfiler *profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this); + QString error; + if (!m_profiler->check(&error)) + { + m_launcher->abort(); + QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't start profiler: %1").arg(error)); + emitFailed("Profiler startup failed"); + return; + } + BaseProfiler *profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this); - connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString & message) - { - QMessageBox msg; - msg.setText(tr("The game launch is delayed until you press the " - "button. This is the right time to setup the profiler, as the " - "profiler server is running now.\n\n%1").arg(message)); - msg.setWindowTitle(tr("Waiting")); - msg.setIcon(QMessageBox::Information); - msg.addButton(tr("Launch"), QMessageBox::AcceptRole); - msg.setModal(true); - msg.exec(); - m_launcher->proceed(); - }); - connect(profilerInstance, &BaseProfiler::abortLaunch, [this](const QString & message) - { - QMessageBox msg; - msg.setText(tr("Couldn't start the profiler: %1").arg(message)); - msg.setWindowTitle(tr("Error")); - msg.setIcon(QMessageBox::Critical); - msg.addButton(QMessageBox::Ok); - msg.setModal(true); - msg.exec(); - m_launcher->abort(); - emitFailed("Profiler startup failed"); - }); - profilerInstance->beginProfiling(m_launcher); + connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString & message) + { + QMessageBox msg; + msg.setText(tr("The game launch is delayed until you press the " + "button. This is the right time to setup the profiler, as the " + "profiler server is running now.\n\n%1").arg(message)); + msg.setWindowTitle(tr("Waiting")); + msg.setIcon(QMessageBox::Information); + msg.addButton(tr("Launch"), QMessageBox::AcceptRole); + msg.setModal(true); + msg.exec(); + m_launcher->proceed(); + }); + connect(profilerInstance, &BaseProfiler::abortLaunch, [this](const QString & message) + { + QMessageBox msg; + msg.setText(tr("Couldn't start the profiler: %1").arg(message)); + msg.setWindowTitle(tr("Error")); + msg.setIcon(QMessageBox::Critical); + msg.addButton(QMessageBox::Ok); + msg.setModal(true); + msg.exec(); + m_launcher->abort(); + emitFailed("Profiler startup failed"); + }); + profilerInstance->beginProfiling(m_launcher); } void LaunchController::onSucceeded() { - emitSucceeded(); + emitSucceeded(); } void LaunchController::onFailed(QString reason) { - if(m_instance->settings()->get("ShowConsoleOnError").toBool()) - { - MMC->showInstanceWindow(m_instance, "console"); - } - emitFailed(reason); + if(m_instance->settings()->get("ShowConsoleOnError").toBool()) + { + MMC->showInstanceWindow(m_instance, "console"); + } + emitFailed(reason); } void LaunchController::onProgressRequested(Task* task) { - ProgressDialog progDialog(m_parentWidget); - progDialog.setSkipButton(true, tr("Abort")); - m_launcher->proceed(); - progDialog.execWithTask(task); + ProgressDialog progDialog(m_parentWidget); + progDialog.setSkipButton(true, tr("Abort")); + m_launcher->proceed(); + progDialog.execWithTask(task); } bool LaunchController::abort() { - if(!m_launcher) - { - return true; - } - if(!m_launcher->canAbort()) - { - return false; - } - auto response = CustomMessageBox::selectable( - m_parentWidget, tr("Kill Minecraft?"), - tr("This can cause the instance to get corrupted and should only be used if Minecraft " - "is frozen for some reason"), - QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec(); - if (response == QMessageBox::Yes) - { - return m_launcher->abort(); - } - return false; + if(!m_launcher) + { + return true; + } + if(!m_launcher->canAbort()) + { + return false; + } + auto response = CustomMessageBox::selectable( + m_parentWidget, tr("Kill Minecraft?"), + tr("This can cause the instance to get corrupted and should only be used if Minecraft " + "is frozen for some reason"), + QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec(); + if (response == QMessageBox::Yes) + { + return m_launcher->abort(); + } + return false; } diff --git a/application/LaunchController.h b/application/LaunchController.h index 84c65743..1d879028 100644 --- a/application/LaunchController.h +++ b/application/LaunchController.h @@ -6,56 +6,56 @@ class InstanceWindow; class LaunchController: public Task { - Q_OBJECT + Q_OBJECT public: - void executeTask() override; + void executeTask() override; - LaunchController(QObject * parent = nullptr); - virtual ~LaunchController(){}; + LaunchController(QObject * parent = nullptr); + virtual ~LaunchController(){}; - void setInstance(InstancePtr instance) - { - m_instance = instance; - } - InstancePtr instance() - { - return m_instance; - } - void setOnline(bool online) - { - m_online = online; - } - void setProfiler(BaseProfilerFactory *profiler) - { - m_profiler = profiler; - } - void setParentWidget(QWidget * widget) - { - m_parentWidget = widget; - } - QString id() - { - return m_instance->id(); - } - bool abort() override; + void setInstance(InstancePtr instance) + { + m_instance = instance; + } + InstancePtr instance() + { + return m_instance; + } + void setOnline(bool online) + { + m_online = online; + } + void setProfiler(BaseProfilerFactory *profiler) + { + m_profiler = profiler; + } + void setParentWidget(QWidget * widget) + { + m_parentWidget = widget; + } + QString id() + { + return m_instance->id(); + } + bool abort() override; private: - void login(); - void launchInstance(); + void login(); + void launchInstance(); private slots: - void readyForLaunch(); + void readyForLaunch(); - void onSucceeded(); - void onFailed(QString reason); - void onProgressRequested(Task *task); + void onSucceeded(); + void onFailed(QString reason); + void onProgressRequested(Task *task); private: - BaseProfilerFactory *m_profiler = nullptr; - bool m_online = true; - InstancePtr m_instance; - QWidget * m_parentWidget = nullptr; - InstanceWindow *m_console = nullptr; - AuthSessionPtr m_session; - std::shared_ptr <LaunchTask> m_launcher; + BaseProfilerFactory *m_profiler = nullptr; + bool m_online = true; + InstancePtr m_instance; + QWidget * m_parentWidget = nullptr; + InstanceWindow *m_console = nullptr; + AuthSessionPtr m_session; + shared_qobject_ptr<LaunchTask> m_launcher; }; diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp index 60742412..1db09c6c 100644 --- a/application/MainWindow.cpp +++ b/application/MainWindow.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Andrew Okin * Peterix @@ -70,7 +70,6 @@ #include "InstanceProxyModel.h" #include "JavaCommon.h" #include "LaunchController.h" -#include "SettingsUI.h" #include "groupview/GroupView.h" #include "groupview/InstanceDelegate.h" #include "widgets/LabeledToolButton.h" @@ -86,7 +85,6 @@ #include "dialogs/EditAccountDialog.h" #include "dialogs/NotificationDialog.h" #include "dialogs/ExportInstanceDialog.h" -#include <FolderInstanceProvider.h> #include <InstanceImportTask.h> #include "UpdateController.h" #include "KonamiCode.h" @@ -97,42 +95,42 @@ template <typename T> class Translated { public: - Translated(){} - Translated(QWidget *parent) - { - m_contained = new T(parent); - } - void setTooltipId(const char * tooltip) - { - m_tooltip = tooltip; - } - void setTextId(const char * text) - { - m_text = text; - } - operator T*() - { - return m_contained; - } - T * operator->() - { - return m_contained; - } - void retranslate() - { - if(m_text) - { - m_contained->setText(QApplication::translate("MainWindow", m_text)); - } - if(m_tooltip) - { - m_contained->setToolTip(QApplication::translate("MainWindow", m_tooltip)); - } - } + Translated(){} + Translated(QWidget *parent) + { + m_contained = new T(parent); + } + void setTooltipId(const char * tooltip) + { + m_tooltip = tooltip; + } + void setTextId(const char * text) + { + m_text = text; + } + operator T*() + { + return m_contained; + } + T * operator->() + { + return m_contained; + } + void retranslate() + { + if(m_text) + { + m_contained->setText(QApplication::translate("MainWindow", m_text)); + } + if(m_tooltip) + { + m_contained->setToolTip(QApplication::translate("MainWindow", m_tooltip)); + } + } private: - T * m_contained = nullptr; - const char * m_text = nullptr; - const char * m_tooltip = nullptr; + T * m_contained = nullptr; + const char * m_text = nullptr; + const char * m_tooltip = nullptr; }; using TranslatedAction = Translated<QAction>; using TranslatedToolButton = Translated<QToolButton>; @@ -140,889 +138,944 @@ using TranslatedToolButton = Translated<QToolButton>; class TranslatedToolbar { public: - TranslatedToolbar(){} - TranslatedToolbar(QWidget *parent) - { - m_contained = new QToolBar(parent); - } - void setWindowTitleId(const char * title) - { - m_title = title; - } - operator QToolBar*() - { - return m_contained; - } - QToolBar * operator->() - { - return m_contained; - } - void retranslate() - { - if(m_title) - { - m_contained->setWindowTitle(QApplication::translate("MainWindow", m_title)); - } - } + TranslatedToolbar(){} + TranslatedToolbar(QWidget *parent) + { + m_contained = new QToolBar(parent); + } + void setWindowTitleId(const char * title) + { + m_title = title; + } + operator QToolBar*() + { + return m_contained; + } + QToolBar * operator->() + { + return m_contained; + } + void retranslate() + { + if(m_title) + { + m_contained->setWindowTitle(QApplication::translate("MainWindow", m_title)); + } + } private: - QToolBar * m_contained = nullptr; - const char * m_title = nullptr; + QToolBar * m_contained = nullptr; + const char * m_title = nullptr; }; class MainWindow::Ui { public: - TranslatedAction actionAddInstance; - //TranslatedAction actionRefresh; - TranslatedAction actionCheckUpdate; - TranslatedAction actionSettings; - TranslatedAction actionPatreon; - TranslatedAction actionMoreNews; - TranslatedAction actionManageAccounts; - TranslatedAction actionLaunchInstance; - TranslatedAction actionRenameInstance; - TranslatedAction actionChangeInstGroup; - TranslatedAction actionChangeInstIcon; - TranslatedAction actionEditInstNotes; - TranslatedAction actionEditInstance; - TranslatedAction actionWorlds; - TranslatedAction actionViewSelectedInstFolder; - TranslatedAction actionDeleteInstance; - TranslatedAction actionConfig_Folder; - TranslatedAction actionCAT; - TranslatedAction actionCopyInstance; - TranslatedAction actionLaunchInstanceOffline; - TranslatedAction actionScreenshots; - TranslatedAction actionExportInstance; - QVector<TranslatedAction *> all_actions; - - LabeledToolButton *renameButton = nullptr; - LabeledToolButton *changeIconButton = nullptr; - - QMenu * foldersMenu = nullptr; - TranslatedToolButton foldersMenuButton; - TranslatedAction actionViewInstanceFolder; - TranslatedAction actionViewCentralModsFolder; - - QMenu * helpMenu = nullptr; - TranslatedToolButton helpMenuButton; - TranslatedAction actionReportBug; - TranslatedAction actionDISCORD; - TranslatedAction actionREDDIT; - TranslatedAction actionAbout; - - QVector<TranslatedToolButton *> all_toolbuttons; - - QWidget *centralWidget = nullptr; - QHBoxLayout *horizontalLayout = nullptr; - QStatusBar *statusBar = nullptr; - - TranslatedToolbar mainToolBar; - TranslatedToolbar instanceToolBar; - TranslatedToolbar newsToolBar; - QVector<TranslatedToolbar *> all_toolbars; - bool m_kill = false; - - void updateLaunchAction() - { - if(m_kill) - { - actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Kill")); - actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance")); - } - else - { - actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch")); - actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance.")); - } - actionLaunchInstance.retranslate(); - } - void setLaunchAction(bool kill) - { - m_kill = kill; - updateLaunchAction(); - } - - void createMainToolbar(QMainWindow *MainWindow) - { - mainToolBar = TranslatedToolbar(MainWindow); - mainToolBar->setObjectName(QStringLiteral("mainToolBar")); - mainToolBar->setMovable(false); - mainToolBar->setAllowedAreas(Qt::TopToolBarArea); - mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - mainToolBar->setFloatable(false); - mainToolBar.setWindowTitleId(QT_TRANSLATE_NOOP("MainWindow", "Main Toolbar")); - - actionAddInstance = TranslatedAction(MainWindow); - actionAddInstance->setObjectName(QStringLiteral("actionAddInstance")); - actionAddInstance->setIcon(MMC->getThemedIcon("new")); - actionAddInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Add Instance")); - actionAddInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Add a new instance.")); - all_actions.append(&actionAddInstance); - mainToolBar->addAction(actionAddInstance); - - mainToolBar->addSeparator(); - - foldersMenu = new QMenu(MainWindow); - foldersMenu->setToolTipsVisible(true); - - actionViewInstanceFolder = TranslatedAction(MainWindow); - actionViewInstanceFolder->setObjectName(QStringLiteral("actionViewInstanceFolder")); - actionViewInstanceFolder->setIcon(MMC->getThemedIcon("viewfolder")); - actionViewInstanceFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Instance Folder")); - actionViewInstanceFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance folder in a file browser.")); - all_actions.append(&actionViewInstanceFolder); - foldersMenu->addAction(actionViewInstanceFolder); - - actionViewCentralModsFolder = TranslatedAction(MainWindow); - actionViewCentralModsFolder->setObjectName(QStringLiteral("actionViewCentralModsFolder")); - actionViewCentralModsFolder->setIcon(MMC->getThemedIcon("centralmods")); - actionViewCentralModsFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Central Mods Folder")); - actionViewCentralModsFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the central mods folder in a file browser.")); - all_actions.append(&actionViewCentralModsFolder); - foldersMenu->addAction(actionViewCentralModsFolder); - - foldersMenuButton = TranslatedToolButton(MainWindow); - foldersMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Folders")); - foldersMenuButton.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open one of the folders shared between instances.")); - foldersMenuButton->setMenu(foldersMenu); - foldersMenuButton->setPopupMode(QToolButton::InstantPopup); - foldersMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - foldersMenuButton->setIcon(MMC->getThemedIcon("viewfolder")); - all_toolbuttons.append(&foldersMenuButton); - QWidgetAction* foldersButtonAction = new QWidgetAction(MainWindow); - foldersButtonAction->setDefaultWidget(foldersMenuButton); - mainToolBar->addAction(foldersButtonAction); - - actionSettings = TranslatedAction(MainWindow); - actionSettings->setObjectName(QStringLiteral("actionSettings")); - actionSettings->setIcon(MMC->getThemedIcon("settings")); - actionSettings->setMenuRole(QAction::PreferencesRole); - actionSettings.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Settings")); - actionSettings.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change settings.")); - all_actions.append(&actionSettings); - mainToolBar->addAction(actionSettings); - - helpMenu = new QMenu(MainWindow); - helpMenu->setToolTipsVisible(true); - - actionReportBug = TranslatedAction(MainWindow); - actionReportBug->setObjectName(QStringLiteral("actionReportBug")); - actionReportBug->setIcon(MMC->getThemedIcon("bug")); - actionReportBug.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Report a Bug")); - actionReportBug.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the bug tracker to report a bug with MultiMC.")); - all_actions.append(&actionReportBug); - helpMenu->addAction(actionReportBug); - - actionDISCORD = TranslatedAction(MainWindow); - actionDISCORD->setObjectName(QStringLiteral("actionDISCORD")); - actionDISCORD->setIcon(MMC->getThemedIcon("discord")); - actionDISCORD.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Discord")); - actionDISCORD.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC discord voice chat.")); - all_actions.append(&actionDISCORD); - helpMenu->addAction(actionDISCORD); - - actionREDDIT = TranslatedAction(MainWindow); - actionREDDIT->setObjectName(QStringLiteral("actionREDDIT")); - actionREDDIT->setIcon(MMC->getThemedIcon("reddit-alien")); - actionREDDIT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Reddit")); - actionREDDIT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC subreddit.")); - all_actions.append(&actionREDDIT); - helpMenu->addAction(actionREDDIT); - - actionAbout = TranslatedAction(MainWindow); - actionAbout->setObjectName(QStringLiteral("actionAbout")); - actionAbout->setIcon(MMC->getThemedIcon("about")); - actionAbout->setMenuRole(QAction::AboutRole); - actionAbout.setTextId(QT_TRANSLATE_NOOP("MainWindow", "About MultiMC")); - actionAbout.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View information about MultiMC.")); - all_actions.append(&actionAbout); - helpMenu->addAction(actionAbout); - - helpMenuButton = TranslatedToolButton(MainWindow); - helpMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Help")); - helpMenuButton.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Get help with MultiMC or Minecraft.")); - helpMenuButton->setMenu(helpMenu); - helpMenuButton->setPopupMode(QToolButton::InstantPopup); - helpMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - helpMenuButton->setIcon(MMC->getThemedIcon("help")); - all_toolbuttons.append(&helpMenuButton); - QWidgetAction* helpButtonAction = new QWidgetAction(MainWindow); - helpButtonAction->setDefaultWidget(helpMenuButton); - mainToolBar->addAction(helpButtonAction); - - if(BuildConfig.UPDATER_ENABLED) - { - actionCheckUpdate = TranslatedAction(MainWindow); - actionCheckUpdate->setObjectName(QStringLiteral("actionCheckUpdate")); - actionCheckUpdate->setIcon(MMC->getThemedIcon("checkupdate")); - actionCheckUpdate.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Update")); - actionCheckUpdate.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Check for new updates for MultiMC.")); - all_actions.append(&actionCheckUpdate); - mainToolBar->addAction(actionCheckUpdate); - } - - mainToolBar->addSeparator(); - - actionPatreon = TranslatedAction(MainWindow); - actionPatreon->setObjectName(QStringLiteral("actionPatreon")); - actionPatreon->setIcon(MMC->getThemedIcon("patreon")); - actionPatreon.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Support MultiMC")); - actionPatreon.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the MultiMC Patreon page.")); - all_actions.append(&actionPatreon); - mainToolBar->addAction(actionPatreon); - - actionCAT = TranslatedAction(MainWindow); - actionCAT->setObjectName(QStringLiteral("actionCAT")); - actionCAT->setCheckable(true); - actionCAT->setIcon(MMC->getThemedIcon("cat")); - actionCAT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Meow")); - actionCAT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "It's a fluffy kitty :3")); - actionCAT->setPriority(QAction::LowPriority); - all_actions.append(&actionCAT); - mainToolBar->addAction(actionCAT); - - // profile menu and its actions - actionManageAccounts = TranslatedAction(MainWindow); - actionManageAccounts->setObjectName(QStringLiteral("actionManageAccounts")); - actionManageAccounts.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage Accounts")); - // FIXME: no tooltip! - actionManageAccounts->setCheckable(false); - actionManageAccounts->setIcon(MMC->getThemedIcon("accounts")); - all_actions.append(&actionManageAccounts); - - all_toolbars.append(&mainToolBar); - MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar); - } - - void createStatusBar(QMainWindow *MainWindow) - { - statusBar = new QStatusBar(MainWindow); - statusBar->setObjectName(QStringLiteral("statusBar")); - MainWindow->setStatusBar(statusBar); - } - - void createNewsToolbar(QMainWindow *MainWindow) - { - newsToolBar = TranslatedToolbar(MainWindow); - newsToolBar->setObjectName(QStringLiteral("newsToolBar")); - newsToolBar->setMovable(false); - newsToolBar->setAllowedAreas(Qt::BottomToolBarArea); - newsToolBar->setIconSize(QSize(16, 16)); - newsToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - newsToolBar->setFloatable(false); - newsToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "News Toolbar")); - - actionMoreNews = TranslatedAction(MainWindow); - actionMoreNews->setObjectName(QStringLiteral("actionMoreNews")); - actionMoreNews->setIcon(MMC->getThemedIcon("news")); - actionMoreNews.setTextId(QT_TRANSLATE_NOOP("MainWindow", "More news...")); - actionMoreNews.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the MultiMC development blog to read more news about MultiMC.")); - all_actions.append(&actionMoreNews); - newsToolBar->addAction(actionMoreNews); - - all_toolbars.append(&newsToolBar); - MainWindow->addToolBar(Qt::BottomToolBarArea, newsToolBar); - } - - void createInstanceToolbar(QMainWindow *MainWindow) - { - instanceToolBar = TranslatedToolbar(MainWindow); - instanceToolBar->setObjectName(QStringLiteral("instanceToolBar")); - // disabled until we have an instance selected - instanceToolBar->setEnabled(false); - instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea); - instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextOnly); - instanceToolBar->setFloatable(false); - instanceToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "Instance Toolbar")); - - // NOTE: not added to toolbar, but used for instance context menu (right click) - actionChangeInstIcon = TranslatedAction(MainWindow); - actionChangeInstIcon->setObjectName(QStringLiteral("actionChangeInstIcon")); - actionChangeInstIcon->setIcon(QIcon(":/icons/instances/infinity")); - actionChangeInstIcon->setIconVisibleInMenu(true); - actionChangeInstIcon.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Change Icon")); - actionChangeInstIcon.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the selected instance's icon.")); - all_actions.append(&actionChangeInstIcon); - - changeIconButton = new LabeledToolButton(MainWindow); - changeIconButton->setObjectName(QStringLiteral("changeIconButton")); - changeIconButton->setIcon(MMC->getThemedIcon("news")); - changeIconButton->setToolTip(actionChangeInstIcon->toolTip()); - changeIconButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - instanceToolBar->addWidget(changeIconButton); - - // NOTE: not added to toolbar, but used for instance context menu (right click) - actionRenameInstance = TranslatedAction(MainWindow); - actionRenameInstance->setObjectName(QStringLiteral("actionRenameInstance")); - actionRenameInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Rename")); - actionRenameInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Rename the selected instance.")); - all_actions.append(&actionRenameInstance); - - // the rename label is inside the rename tool button - renameButton = new LabeledToolButton(MainWindow); - renameButton->setObjectName(QStringLiteral("renameButton")); - renameButton->setToolTip(actionRenameInstance->toolTip()); - renameButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - instanceToolBar->addWidget(renameButton); - - instanceToolBar->addSeparator(); - - actionLaunchInstance = TranslatedAction(MainWindow); - actionLaunchInstance->setObjectName(QStringLiteral("actionLaunchInstance")); - all_actions.append(&actionLaunchInstance); - instanceToolBar->addAction(actionLaunchInstance); - - actionLaunchInstanceOffline = TranslatedAction(MainWindow); - actionLaunchInstanceOffline->setObjectName(QStringLiteral("actionLaunchInstanceOffline")); - actionLaunchInstanceOffline.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch Offline")); - actionLaunchInstanceOffline.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in offline mode.")); - all_actions.append(&actionLaunchInstanceOffline); - instanceToolBar->addAction(actionLaunchInstanceOffline); - - instanceToolBar->addSeparator(); - - actionEditInstance = TranslatedAction(MainWindow); - actionEditInstance->setObjectName(QStringLiteral("actionEditInstance")); - actionEditInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Instance")); - actionEditInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the instance settings, mods and versions.")); - all_actions.append(&actionEditInstance); - instanceToolBar->addAction(actionEditInstance); - - actionEditInstNotes = TranslatedAction(MainWindow); - actionEditInstNotes->setObjectName(QStringLiteral("actionEditInstNotes")); - actionEditInstNotes.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Notes")); - actionEditInstNotes.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Edit the notes for the selected instance.")); - all_actions.append(&actionEditInstNotes); - instanceToolBar->addAction(actionEditInstNotes); - - actionWorlds = TranslatedAction(MainWindow); - actionWorlds->setObjectName(QStringLiteral("actionWorlds")); - actionWorlds.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Worlds")); - actionWorlds.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View the worlds of this instance.")); - all_actions.append(&actionWorlds); - instanceToolBar->addAction(actionWorlds); - - actionScreenshots = TranslatedAction(MainWindow); - actionScreenshots->setObjectName(QStringLiteral("actionScreenshots")); - actionScreenshots.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage Screenshots")); - actionScreenshots.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View and upload screenshots for this instance.")); - all_actions.append(&actionScreenshots); - instanceToolBar->addAction(actionScreenshots); - - actionChangeInstGroup = TranslatedAction(MainWindow); - actionChangeInstGroup->setObjectName(QStringLiteral("actionChangeInstGroup")); - actionChangeInstGroup.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Change Group")); - actionChangeInstGroup.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the selected instance's group.")); - all_actions.append(&actionChangeInstGroup); - instanceToolBar->addAction(actionChangeInstGroup); - - instanceToolBar->addSeparator(); - - actionViewSelectedInstFolder = TranslatedAction(MainWindow); - actionViewSelectedInstFolder->setObjectName(QStringLiteral("actionViewSelectedInstFolder")); - actionViewSelectedInstFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Instance Folder")); - actionViewSelectedInstFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's root folder in a file browser.")); - all_actions.append(&actionViewSelectedInstFolder); - instanceToolBar->addAction(actionViewSelectedInstFolder); - - actionConfig_Folder = TranslatedAction(MainWindow); - actionConfig_Folder->setObjectName(QStringLiteral("actionConfig_Folder")); - actionConfig_Folder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Config Folder")); - actionConfig_Folder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance's config folder.")); - all_actions.append(&actionConfig_Folder); - instanceToolBar->addAction(actionConfig_Folder); - - instanceToolBar->addSeparator(); - - actionExportInstance = TranslatedAction(MainWindow); - actionExportInstance->setObjectName(QStringLiteral("actionExportInstance")); - actionExportInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Export Instance")); - // FIXME: missing tooltip - all_actions.append(&actionExportInstance); - instanceToolBar->addAction(actionExportInstance); - - actionDeleteInstance = TranslatedAction(MainWindow); - actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance")); - actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Delete")); - actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance.")); - all_actions.append(&actionDeleteInstance); - instanceToolBar->addAction(actionDeleteInstance); - - actionCopyInstance = TranslatedAction(MainWindow); - actionCopyInstance->setObjectName(QStringLiteral("actionCopyInstance")); - actionCopyInstance->setIcon(MMC->getThemedIcon("copy")); - actionCopyInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Copy Instance")); - actionCopyInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Copy the selected instance.")); - all_actions.append(&actionCopyInstance); - instanceToolBar->addAction(actionCopyInstance); - - all_toolbars.append(&instanceToolBar); - MainWindow->addToolBar(Qt::RightToolBarArea, instanceToolBar); - } - - void setupUi(QMainWindow *MainWindow) - { - if (MainWindow->objectName().isEmpty()) - { - MainWindow->setObjectName(QStringLiteral("MainWindow")); - } - MainWindow->resize(694, 563); - MainWindow->setWindowIcon(MMC->getThemedIcon("logo")); - MainWindow->setWindowTitle("MultiMC 5"); - - createMainToolbar(MainWindow); - - centralWidget = new QWidget(MainWindow); - centralWidget->setObjectName(QStringLiteral("centralWidget")); - horizontalLayout = new QHBoxLayout(centralWidget); - horizontalLayout->setSpacing(0); - horizontalLayout->setContentsMargins(11, 11, 11, 11); - horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); - horizontalLayout->setSizeConstraint(QLayout::SetDefaultConstraint); - horizontalLayout->setContentsMargins(0, 0, 0, 0); - MainWindow->setCentralWidget(centralWidget); - - createStatusBar(MainWindow); - createNewsToolbar(MainWindow); - createInstanceToolbar(MainWindow); - - retranslateUi(MainWindow); - - QMetaObject::connectSlotsByName(MainWindow); - } // setupUi - - void retranslateUi(QMainWindow *MainWindow) - { - QString winTitle = tr("MultiMC 5 - Version %1").arg(BuildConfig.printableVersionString()); - if (!BuildConfig.BUILD_PLATFORM.isEmpty()) - { - winTitle += tr(" on %1", "on platform, as in operating system").arg(BuildConfig.BUILD_PLATFORM); - } - MainWindow->setWindowTitle(winTitle); - // all the actions - for(auto * item: all_actions) - { - item->retranslate(); - } - for(auto * item: all_toolbars) - { - item->retranslate(); - } - for(auto * item: all_toolbuttons) - { - item->retranslate(); - } - // submenu buttons - foldersMenuButton->setText(tr("Folders")); - helpMenuButton->setText(tr("Help")); - } // retranslateUi + TranslatedAction actionAddInstance; + //TranslatedAction actionRefresh; + TranslatedAction actionCheckUpdate; + TranslatedAction actionSettings; + TranslatedAction actionPatreon; + TranslatedAction actionMoreNews; + TranslatedAction actionManageAccounts; + TranslatedAction actionLaunchInstance; + TranslatedAction actionRenameInstance; + TranslatedAction actionChangeInstGroup; + TranslatedAction actionChangeInstIcon; + TranslatedAction actionEditInstNotes; + TranslatedAction actionEditInstance; + TranslatedAction actionWorlds; + TranslatedAction actionViewSelectedInstFolder; + TranslatedAction actionViewSelectedMCFolder; + TranslatedAction actionDeleteInstance; + TranslatedAction actionConfig_Folder; + TranslatedAction actionCAT; + TranslatedAction actionCopyInstance; + TranslatedAction actionLaunchInstanceOffline; + TranslatedAction actionScreenshots; + TranslatedAction actionExportInstance; + QVector<TranslatedAction *> all_actions; + + LabeledToolButton *renameButton = nullptr; + LabeledToolButton *changeIconButton = nullptr; + + QMenu * foldersMenu = nullptr; + TranslatedToolButton foldersMenuButton; + TranslatedAction actionViewInstanceFolder; + TranslatedAction actionViewCentralModsFolder; + + QMenu * helpMenu = nullptr; + TranslatedToolButton helpMenuButton; + TranslatedAction actionReportBug; + TranslatedAction actionDISCORD; + TranslatedAction actionREDDIT; + TranslatedAction actionAbout; + + QVector<TranslatedToolButton *> all_toolbuttons; + + QWidget *centralWidget = nullptr; + QHBoxLayout *horizontalLayout = nullptr; + QStatusBar *statusBar = nullptr; + + TranslatedToolbar mainToolBar; + TranslatedToolbar instanceToolBar; + TranslatedToolbar newsToolBar; + QVector<TranslatedToolbar *> all_toolbars; + bool m_kill = false; + + void updateLaunchAction() + { + if(m_kill) + { + actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Kill")); + actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance")); + } + else + { + actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch")); + actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance.")); + } + actionLaunchInstance.retranslate(); + } + void setLaunchAction(bool kill) + { + m_kill = kill; + updateLaunchAction(); + } + + void createMainToolbar(QMainWindow *MainWindow) + { + mainToolBar = TranslatedToolbar(MainWindow); + mainToolBar->setObjectName(QStringLiteral("mainToolBar")); + mainToolBar->setMovable(false); + mainToolBar->setAllowedAreas(Qt::TopToolBarArea); + mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + mainToolBar->setFloatable(false); + mainToolBar.setWindowTitleId(QT_TRANSLATE_NOOP("MainWindow", "Main Toolbar")); + + actionAddInstance = TranslatedAction(MainWindow); + actionAddInstance->setObjectName(QStringLiteral("actionAddInstance")); + actionAddInstance->setIcon(MMC->getThemedIcon("new")); + actionAddInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Add Instance")); + actionAddInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Add a new instance.")); + all_actions.append(&actionAddInstance); + mainToolBar->addAction(actionAddInstance); + + mainToolBar->addSeparator(); + + foldersMenu = new QMenu(MainWindow); + foldersMenu->setToolTipsVisible(true); + + actionViewInstanceFolder = TranslatedAction(MainWindow); + actionViewInstanceFolder->setObjectName(QStringLiteral("actionViewInstanceFolder")); + actionViewInstanceFolder->setIcon(MMC->getThemedIcon("viewfolder")); + actionViewInstanceFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Instance Folder")); + actionViewInstanceFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance folder in a file browser.")); + all_actions.append(&actionViewInstanceFolder); + foldersMenu->addAction(actionViewInstanceFolder); + + actionViewCentralModsFolder = TranslatedAction(MainWindow); + actionViewCentralModsFolder->setObjectName(QStringLiteral("actionViewCentralModsFolder")); + actionViewCentralModsFolder->setIcon(MMC->getThemedIcon("centralmods")); + actionViewCentralModsFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Central Mods Folder")); + actionViewCentralModsFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the central mods folder in a file browser.")); + all_actions.append(&actionViewCentralModsFolder); + foldersMenu->addAction(actionViewCentralModsFolder); + + foldersMenuButton = TranslatedToolButton(MainWindow); + foldersMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Folders")); + foldersMenuButton.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open one of the folders shared between instances.")); + foldersMenuButton->setMenu(foldersMenu); + foldersMenuButton->setPopupMode(QToolButton::InstantPopup); + foldersMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + foldersMenuButton->setIcon(MMC->getThemedIcon("viewfolder")); + foldersMenuButton->setFocusPolicy(Qt::NoFocus); + all_toolbuttons.append(&foldersMenuButton); + QWidgetAction* foldersButtonAction = new QWidgetAction(MainWindow); + foldersButtonAction->setDefaultWidget(foldersMenuButton); + mainToolBar->addAction(foldersButtonAction); + + actionSettings = TranslatedAction(MainWindow); + actionSettings->setObjectName(QStringLiteral("actionSettings")); + actionSettings->setIcon(MMC->getThemedIcon("settings")); + actionSettings->setMenuRole(QAction::PreferencesRole); + actionSettings.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Settings")); + actionSettings.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change settings.")); + all_actions.append(&actionSettings); + mainToolBar->addAction(actionSettings); + + helpMenu = new QMenu(MainWindow); + helpMenu->setToolTipsVisible(true); + + actionReportBug = TranslatedAction(MainWindow); + actionReportBug->setObjectName(QStringLiteral("actionReportBug")); + actionReportBug->setIcon(MMC->getThemedIcon("bug")); + actionReportBug.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Report a Bug")); + actionReportBug.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the bug tracker to report a bug with MultiMC.")); + all_actions.append(&actionReportBug); + helpMenu->addAction(actionReportBug); + + actionDISCORD = TranslatedAction(MainWindow); + actionDISCORD->setObjectName(QStringLiteral("actionDISCORD")); + actionDISCORD->setIcon(MMC->getThemedIcon("discord")); + actionDISCORD.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Discord")); + actionDISCORD.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC discord voice chat.")); + all_actions.append(&actionDISCORD); + helpMenu->addAction(actionDISCORD); + + actionREDDIT = TranslatedAction(MainWindow); + actionREDDIT->setObjectName(QStringLiteral("actionREDDIT")); + actionREDDIT->setIcon(MMC->getThemedIcon("reddit-alien")); + actionREDDIT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Reddit")); + actionREDDIT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC subreddit.")); + all_actions.append(&actionREDDIT); + helpMenu->addAction(actionREDDIT); + + actionAbout = TranslatedAction(MainWindow); + actionAbout->setObjectName(QStringLiteral("actionAbout")); + actionAbout->setIcon(MMC->getThemedIcon("about")); + actionAbout->setMenuRole(QAction::AboutRole); + actionAbout.setTextId(QT_TRANSLATE_NOOP("MainWindow", "About MultiMC")); + actionAbout.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View information about MultiMC.")); + all_actions.append(&actionAbout); + helpMenu->addAction(actionAbout); + + helpMenuButton = TranslatedToolButton(MainWindow); + helpMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Help")); + helpMenuButton.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Get help with MultiMC or Minecraft.")); + helpMenuButton->setMenu(helpMenu); + helpMenuButton->setPopupMode(QToolButton::InstantPopup); + helpMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + helpMenuButton->setIcon(MMC->getThemedIcon("help")); + helpMenuButton->setFocusPolicy(Qt::NoFocus); + all_toolbuttons.append(&helpMenuButton); + QWidgetAction* helpButtonAction = new QWidgetAction(MainWindow); + helpButtonAction->setDefaultWidget(helpMenuButton); + mainToolBar->addAction(helpButtonAction); + + if(BuildConfig.UPDATER_ENABLED) + { + actionCheckUpdate = TranslatedAction(MainWindow); + actionCheckUpdate->setObjectName(QStringLiteral("actionCheckUpdate")); + actionCheckUpdate->setIcon(MMC->getThemedIcon("checkupdate")); + actionCheckUpdate.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Update")); + actionCheckUpdate.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Check for new updates for MultiMC.")); + all_actions.append(&actionCheckUpdate); + mainToolBar->addAction(actionCheckUpdate); + } + + mainToolBar->addSeparator(); + + actionPatreon = TranslatedAction(MainWindow); + actionPatreon->setObjectName(QStringLiteral("actionPatreon")); + actionPatreon->setIcon(MMC->getThemedIcon("patreon")); + actionPatreon.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Support MultiMC")); + actionPatreon.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the MultiMC Patreon page.")); + all_actions.append(&actionPatreon); + mainToolBar->addAction(actionPatreon); + + actionCAT = TranslatedAction(MainWindow); + actionCAT->setObjectName(QStringLiteral("actionCAT")); + actionCAT->setCheckable(true); + actionCAT->setIcon(MMC->getThemedIcon("cat")); + actionCAT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Meow")); + actionCAT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "It's a fluffy kitty :3")); + actionCAT->setPriority(QAction::LowPriority); + all_actions.append(&actionCAT); + mainToolBar->addAction(actionCAT); + + // profile menu and its actions + actionManageAccounts = TranslatedAction(MainWindow); + actionManageAccounts->setObjectName(QStringLiteral("actionManageAccounts")); + actionManageAccounts.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage Accounts")); + // FIXME: no tooltip! + actionManageAccounts->setCheckable(false); + actionManageAccounts->setIcon(MMC->getThemedIcon("accounts")); + all_actions.append(&actionManageAccounts); + + all_toolbars.append(&mainToolBar); + MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar); + } + + void createStatusBar(QMainWindow *MainWindow) + { + statusBar = new QStatusBar(MainWindow); + statusBar->setObjectName(QStringLiteral("statusBar")); + MainWindow->setStatusBar(statusBar); + } + + void createNewsToolbar(QMainWindow *MainWindow) + { + newsToolBar = TranslatedToolbar(MainWindow); + newsToolBar->setObjectName(QStringLiteral("newsToolBar")); + newsToolBar->setMovable(false); + newsToolBar->setAllowedAreas(Qt::BottomToolBarArea); + newsToolBar->setIconSize(QSize(16, 16)); + newsToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + newsToolBar->setFloatable(false); + newsToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "News Toolbar")); + + actionMoreNews = TranslatedAction(MainWindow); + actionMoreNews->setObjectName(QStringLiteral("actionMoreNews")); + actionMoreNews->setIcon(MMC->getThemedIcon("news")); + actionMoreNews.setTextId(QT_TRANSLATE_NOOP("MainWindow", "More news...")); + actionMoreNews.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the MultiMC development blog to read more news about MultiMC.")); + all_actions.append(&actionMoreNews); + newsToolBar->addAction(actionMoreNews); + + all_toolbars.append(&newsToolBar); + MainWindow->addToolBar(Qt::BottomToolBarArea, newsToolBar); + } + + void createInstanceToolbar(QMainWindow *MainWindow) + { + instanceToolBar = TranslatedToolbar(MainWindow); + instanceToolBar->setObjectName(QStringLiteral("instanceToolBar")); + // disabled until we have an instance selected + instanceToolBar->setEnabled(false); + instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea); + instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextOnly); + instanceToolBar->setFloatable(false); + instanceToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "Instance Toolbar")); + + // NOTE: not added to toolbar, but used for instance context menu (right click) + actionChangeInstIcon = TranslatedAction(MainWindow); + actionChangeInstIcon->setObjectName(QStringLiteral("actionChangeInstIcon")); + actionChangeInstIcon->setIcon(QIcon(":/icons/instances/infinity")); + actionChangeInstIcon->setIconVisibleInMenu(true); + actionChangeInstIcon.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Change Icon")); + actionChangeInstIcon.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the selected instance's icon.")); + all_actions.append(&actionChangeInstIcon); + + changeIconButton = new LabeledToolButton(MainWindow); + changeIconButton->setObjectName(QStringLiteral("changeIconButton")); + changeIconButton->setIcon(MMC->getThemedIcon("news")); + changeIconButton->setToolTip(actionChangeInstIcon->toolTip()); + changeIconButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + instanceToolBar->addWidget(changeIconButton); + + // NOTE: not added to toolbar, but used for instance context menu (right click) + actionRenameInstance = TranslatedAction(MainWindow); + actionRenameInstance->setObjectName(QStringLiteral("actionRenameInstance")); + actionRenameInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Rename")); + actionRenameInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Rename the selected instance.")); + all_actions.append(&actionRenameInstance); + + // the rename label is inside the rename tool button + renameButton = new LabeledToolButton(MainWindow); + renameButton->setObjectName(QStringLiteral("renameButton")); + renameButton->setToolTip(actionRenameInstance->toolTip()); + renameButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + instanceToolBar->addWidget(renameButton); + + instanceToolBar->addSeparator(); + + actionLaunchInstance = TranslatedAction(MainWindow); + actionLaunchInstance->setObjectName(QStringLiteral("actionLaunchInstance")); + all_actions.append(&actionLaunchInstance); + instanceToolBar->addAction(actionLaunchInstance); + + actionLaunchInstanceOffline = TranslatedAction(MainWindow); + actionLaunchInstanceOffline->setObjectName(QStringLiteral("actionLaunchInstanceOffline")); + actionLaunchInstanceOffline.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch Offline")); + actionLaunchInstanceOffline.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in offline mode.")); + all_actions.append(&actionLaunchInstanceOffline); + instanceToolBar->addAction(actionLaunchInstanceOffline); + + instanceToolBar->addSeparator(); + + actionEditInstance = TranslatedAction(MainWindow); + actionEditInstance->setObjectName(QStringLiteral("actionEditInstance")); + actionEditInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Instance")); + actionEditInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the instance settings, mods and versions.")); + all_actions.append(&actionEditInstance); + instanceToolBar->addAction(actionEditInstance); + + actionEditInstNotes = TranslatedAction(MainWindow); + actionEditInstNotes->setObjectName(QStringLiteral("actionEditInstNotes")); + actionEditInstNotes.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Notes")); + actionEditInstNotes.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Edit the notes for the selected instance.")); + all_actions.append(&actionEditInstNotes); + instanceToolBar->addAction(actionEditInstNotes); + + actionWorlds = TranslatedAction(MainWindow); + actionWorlds->setObjectName(QStringLiteral("actionWorlds")); + actionWorlds.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Worlds")); + actionWorlds.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View the worlds of this instance.")); + all_actions.append(&actionWorlds); + instanceToolBar->addAction(actionWorlds); + + actionScreenshots = TranslatedAction(MainWindow); + actionScreenshots->setObjectName(QStringLiteral("actionScreenshots")); + actionScreenshots.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage Screenshots")); + actionScreenshots.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View and upload screenshots for this instance.")); + all_actions.append(&actionScreenshots); + instanceToolBar->addAction(actionScreenshots); + + actionChangeInstGroup = TranslatedAction(MainWindow); + actionChangeInstGroup->setObjectName(QStringLiteral("actionChangeInstGroup")); + actionChangeInstGroup.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Change Group")); + actionChangeInstGroup.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the selected instance's group.")); + all_actions.append(&actionChangeInstGroup); + instanceToolBar->addAction(actionChangeInstGroup); + + instanceToolBar->addSeparator(); + + actionViewSelectedMCFolder = TranslatedAction(MainWindow); + actionViewSelectedMCFolder->setObjectName(QStringLiteral("actionViewSelectedMCFolder")); + actionViewSelectedMCFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Minecraft Folder")); + actionViewSelectedMCFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's minecraft folder in a file browser.")); + all_actions.append(&actionViewSelectedMCFolder); + instanceToolBar->addAction(actionViewSelectedMCFolder); + + actionConfig_Folder = TranslatedAction(MainWindow); + actionConfig_Folder->setObjectName(QStringLiteral("actionConfig_Folder")); + actionConfig_Folder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Config Folder")); + actionConfig_Folder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance's config folder.")); + all_actions.append(&actionConfig_Folder); + instanceToolBar->addAction(actionConfig_Folder); + + actionViewSelectedInstFolder = TranslatedAction(MainWindow); + actionViewSelectedInstFolder->setObjectName(QStringLiteral("actionViewSelectedInstFolder")); + actionViewSelectedInstFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Instance Folder")); + actionViewSelectedInstFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's root folder in a file browser.")); + all_actions.append(&actionViewSelectedInstFolder); + instanceToolBar->addAction(actionViewSelectedInstFolder); + + instanceToolBar->addSeparator(); + + actionExportInstance = TranslatedAction(MainWindow); + actionExportInstance->setObjectName(QStringLiteral("actionExportInstance")); + actionExportInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Export Instance")); + // FIXME: missing tooltip + all_actions.append(&actionExportInstance); + instanceToolBar->addAction(actionExportInstance); + + actionDeleteInstance = TranslatedAction(MainWindow); + actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance")); + actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Delete")); + actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance.")); + all_actions.append(&actionDeleteInstance); + instanceToolBar->addAction(actionDeleteInstance); + + actionCopyInstance = TranslatedAction(MainWindow); + actionCopyInstance->setObjectName(QStringLiteral("actionCopyInstance")); + actionCopyInstance->setIcon(MMC->getThemedIcon("copy")); + actionCopyInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Copy Instance")); + actionCopyInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Copy the selected instance.")); + all_actions.append(&actionCopyInstance); + instanceToolBar->addAction(actionCopyInstance); + + all_toolbars.append(&instanceToolBar); + MainWindow->addToolBar(Qt::RightToolBarArea, instanceToolBar); + } + + void setupUi(QMainWindow *MainWindow) + { + if (MainWindow->objectName().isEmpty()) + { + MainWindow->setObjectName(QStringLiteral("MainWindow")); + } + MainWindow->resize(694, 563); + MainWindow->setWindowIcon(MMC->getThemedIcon("logo")); + MainWindow->setWindowTitle("MultiMC 5"); + + createMainToolbar(MainWindow); + + centralWidget = new QWidget(MainWindow); + centralWidget->setObjectName(QStringLiteral("centralWidget")); + horizontalLayout = new QHBoxLayout(centralWidget); + horizontalLayout->setSpacing(0); + horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + horizontalLayout->setSizeConstraint(QLayout::SetDefaultConstraint); + horizontalLayout->setContentsMargins(0, 0, 0, 0); + MainWindow->setCentralWidget(centralWidget); + + createStatusBar(MainWindow); + createNewsToolbar(MainWindow); + createInstanceToolbar(MainWindow); + + retranslateUi(MainWindow); + + QMetaObject::connectSlotsByName(MainWindow); + } // setupUi + + void retranslateUi(QMainWindow *MainWindow) + { + QString winTitle = tr("MultiMC 5 - Version %1").arg(BuildConfig.printableVersionString()); + if (!BuildConfig.BUILD_PLATFORM.isEmpty()) + { + winTitle += tr(" on %1", "on platform, as in operating system").arg(BuildConfig.BUILD_PLATFORM); + } + MainWindow->setWindowTitle(winTitle); + // all the actions + for(auto * item: all_actions) + { + item->retranslate(); + } + for(auto * item: all_toolbars) + { + item->retranslate(); + } + for(auto * item: all_toolbuttons) + { + item->retranslate(); + } + // submenu buttons + foldersMenuButton->setText(tr("Folders")); + helpMenuButton->setText(tr("Help")); + } // retranslateUi }; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow::Ui) { - ui->setupUi(this); - - // OSX magic. - setUnifiedTitleAndToolBarOnMac(true); - - // Global shortcuts - { - // FIXME: This is kinda weird. and bad. We need some kind of managed shutdown. - auto q = new QShortcut(QKeySequence::Quit, this); - connect(q, SIGNAL(activated()), qApp, SLOT(quit())); - } - - // Konami Code - { - secretEventFilter = new KonamiCode(this); - connect(secretEventFilter, &KonamiCode::triggered, this, &MainWindow::konamiTriggered); - } - - // Add the news label to the news toolbar. - { - m_newsChecker.reset(new NewsChecker(BuildConfig.NEWS_RSS_URL)); - newsLabel = new QToolButton(); - newsLabel->setIcon(MMC->getThemedIcon("news")); - newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel); - QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked); - QObject::connect(m_newsChecker.get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel); - updateNewsLabel(); - } - - // Create the instance list widget - { - view = new GroupView(ui->centralWidget); - - view->setSelectionMode(QAbstractItemView::SingleSelection); - // FIXME: leaks ListViewDelegate - view->setItemDelegate(new ListViewDelegate(this)); - view->setFrameShape(QFrame::NoFrame); - // do not show ugly blue border on the mac - view->setAttribute(Qt::WA_MacShowFocusRect, false); - - view->installEventFilter(this); - view->setContextMenuPolicy(Qt::CustomContextMenu); - connect(view, &QWidget::customContextMenuRequested, this, &MainWindow::showInstanceContextMenu); - connect(view, &GroupView::droppedURLs, this, &MainWindow::droppedURLs); - - proxymodel = new InstanceProxyModel(this); - proxymodel->setSourceModel(MMC->instances().get()); - proxymodel->sort(0); - connect(proxymodel, &InstanceProxyModel::dataChanged, this, &MainWindow::instanceDataChanged); - - view->setModel(proxymodel); - ui->horizontalLayout->addWidget(view); - } - // The cat background - { - bool cat_enable = MMC->settings()->get("TheCat").toBool(); - ui->actionCAT->setChecked(cat_enable); - connect(ui->actionCAT, SIGNAL(toggled(bool)), SLOT(onCatToggled(bool))); - setCatBackground(cat_enable); - } - // start instance when double-clicked - connect(view, &GroupView::doubleClicked, this, &MainWindow::instanceActivated); - - // track the selection -- update the instance toolbar - connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this, &MainWindow::instanceChanged); - - // track icon changes and update the toolbar! - connect(MMC->icons().get(), &IconList::iconUpdated, this, &MainWindow::iconUpdated); - - // model reset -> selection is invalid. All the instance pointers are wrong. - connect(MMC->instances().get(), &InstanceList::dataIsInvalid, this, &MainWindow::selectionBad); - - m_statusLeft = new QLabel(tr("No instance selected"), this); - m_statusRight = new ServerStatus(this); - statusBar()->addPermanentWidget(m_statusLeft, 1); - statusBar()->addPermanentWidget(m_statusRight, 0); - - // Add "manage accounts" button, right align - QWidget *spacer = new QWidget(); - spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - ui->mainToolBar->addWidget(spacer); - - accountMenu = new QMenu(this); - - repopulateAccountsMenu(); - - accountMenuButton = new QToolButton(this); - accountMenuButton->setText(tr("Profiles")); - accountMenuButton->setMenu(accountMenu); - accountMenuButton->setPopupMode(QToolButton::InstantPopup); - accountMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - accountMenuButton->setIcon(MMC->getThemedIcon("noaccount")); - - QWidgetAction *accountMenuButtonAction = new QWidgetAction(this); - accountMenuButtonAction->setDefaultWidget(accountMenuButton); - - ui->mainToolBar->addAction(accountMenuButtonAction); - - // Update the menu when the active account changes. - // Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit. - // Template hell sucks... - connect(MMC->accounts().get(), &MojangAccountList::activeAccountChanged, [this] - { - activeAccountChanged(); - }); - connect(MMC->accounts().get(), &MojangAccountList::listChanged, [this] - { - repopulateAccountsMenu(); - }); - - // Show initial account - activeAccountChanged(); - - auto accounts = MMC->accounts(); - - QList<Net::Download::Ptr> skin_dls; - for (int i = 0; i < accounts->count(); i++) - { - auto account = accounts->at(i); - if (!account) - { - qWarning() << "Null account at index" << i; - continue; - } - for (auto profile : account->profiles()) - { - auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png"); - auto action = Net::Download::makeCached(QUrl("https://" + URLConstants::SKINS_BASE + profile.id + ".png"), meta); - skin_dls.append(action); - meta->setStale(true); - } - } - if (!skin_dls.isEmpty()) - { - auto job = new NetJob("Startup player skins download"); - connect(job, &NetJob::succeeded, this, &MainWindow::skinJobFinished); - connect(job, &NetJob::failed, this, &MainWindow::skinJobFinished); - for (auto action : skin_dls) - { - job->addNetAction(action); - } - skin_download_job.reset(job); - job->start(); - } - - // load the news - { - m_newsChecker->reloadNews(); - updateNewsLabel(); - } - - - if(BuildConfig.UPDATER_ENABLED) - { - bool updatesAllowed = MMC->updatesAreAllowed(); - updatesAllowedChanged(updatesAllowed); - - connect(ui->actionCheckUpdate, &QAction::triggered, this, &MainWindow::checkForUpdates); - - // set up the updater object. - auto updater = MMC->updateChecker(); - connect(updater.get(), &UpdateChecker::updateAvailable, this, &MainWindow::updateAvailable); - connect(updater.get(), &UpdateChecker::noUpdateFound, this, &MainWindow::updateNotAvailable); - // if automatic update checks are allowed, start one. - if (MMC->settings()->get("AutoUpdate").toBool() && updatesAllowed) - { - updater->checkForUpdate(MMC->settings()->get("UpdateChannel").toString(), false); - } - } - - { - auto checker = new NotificationChecker(); - checker->setNotificationsUrl(QUrl(BuildConfig.NOTIFICATION_URL)); - checker->setApplicationChannel(BuildConfig.VERSION_CHANNEL); - checker->setApplicationPlatform(BuildConfig.BUILD_PLATFORM); - checker->setApplicationFullVersion(BuildConfig.FULL_VERSION_STR); - m_notificationChecker.reset(checker); - connect(m_notificationChecker.get(), &NotificationChecker::notificationCheckFinished, this, &MainWindow::notificationsChanged); - checker->checkForNotifications(); - } - - setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString()); - - // removing this looks stupid - view->setFocus(); + ui->setupUi(this); + + // OSX magic. + setUnifiedTitleAndToolBarOnMac(true); + + // Global shortcuts + { + // FIXME: This is kinda weird. and bad. We need some kind of managed shutdown. + auto q = new QShortcut(QKeySequence::Quit, this); + connect(q, SIGNAL(activated()), qApp, SLOT(quit())); + } + + // Konami Code + { + secretEventFilter = new KonamiCode(this); + connect(secretEventFilter, &KonamiCode::triggered, this, &MainWindow::konamiTriggered); + } + + // Add the news label to the news toolbar. + { + m_newsChecker.reset(new NewsChecker(BuildConfig.NEWS_RSS_URL)); + newsLabel = new QToolButton(); + newsLabel->setIcon(MMC->getThemedIcon("news")); + newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + newsLabel->setFocusPolicy(Qt::NoFocus); + ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel); + QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked); + QObject::connect(m_newsChecker.get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel); + updateNewsLabel(); + } + + // Create the instance list widget + { + view = new GroupView(ui->centralWidget); + + view->setSelectionMode(QAbstractItemView::SingleSelection); + // FIXME: leaks ListViewDelegate + view->setItemDelegate(new ListViewDelegate(this)); + view->setFrameShape(QFrame::NoFrame); + // do not show ugly blue border on the mac + view->setAttribute(Qt::WA_MacShowFocusRect, false); + + view->installEventFilter(this); + view->setContextMenuPolicy(Qt::CustomContextMenu); + connect(view, &QWidget::customContextMenuRequested, this, &MainWindow::showInstanceContextMenu); + connect(view, &GroupView::droppedURLs, this, &MainWindow::droppedURLs, Qt::QueuedConnection); + + proxymodel = new InstanceProxyModel(this); + proxymodel->setSourceModel(MMC->instances().get()); + proxymodel->sort(0); + connect(proxymodel, &InstanceProxyModel::dataChanged, this, &MainWindow::instanceDataChanged); + + view->setModel(proxymodel); + ui->horizontalLayout->addWidget(view); + } + // The cat background + { + bool cat_enable = MMC->settings()->get("TheCat").toBool(); + ui->actionCAT->setChecked(cat_enable); + // NOTE: calling the operator like that is an ugly hack to appease ancient gcc... + connect(ui->actionCAT.operator->(), SIGNAL(toggled(bool)), SLOT(onCatToggled(bool))); + setCatBackground(cat_enable); + } + // start instance when double-clicked + connect(view, &GroupView::activated, this, &MainWindow::instanceActivated); + + // track the selection -- update the instance toolbar + connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this, &MainWindow::instanceChanged); + + // track icon changes and update the toolbar! + connect(MMC->icons().get(), &IconList::iconUpdated, this, &MainWindow::iconUpdated); + + // model reset -> selection is invalid. All the instance pointers are wrong. + connect(MMC->instances().get(), &InstanceList::dataIsInvalid, this, &MainWindow::selectionBad); + + // handle newly added instances + connect(MMC->instances().get(), &InstanceList::instanceSelectRequest, this, &MainWindow::instanceSelectRequest); + + // When the global settings page closes, we want to know about it and update our state + connect(MMC, &MultiMC::globalSettingsClosed, this, &MainWindow::globalSettingsClosed); + + m_statusLeft = new QLabel(tr("No instance selected"), this); + m_statusRight = new ServerStatus(this); + statusBar()->addPermanentWidget(m_statusLeft, 1); + statusBar()->addPermanentWidget(m_statusRight, 0); + + // Add "manage accounts" button, right align + QWidget *spacer = new QWidget(); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + ui->mainToolBar->addWidget(spacer); + + accountMenu = new QMenu(this); + + repopulateAccountsMenu(); + + accountMenuButton = new QToolButton(this); + accountMenuButton->setText(tr("Profiles")); + accountMenuButton->setMenu(accountMenu); + accountMenuButton->setPopupMode(QToolButton::InstantPopup); + accountMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + accountMenuButton->setIcon(MMC->getThemedIcon("noaccount")); + + QWidgetAction *accountMenuButtonAction = new QWidgetAction(this); + accountMenuButtonAction->setDefaultWidget(accountMenuButton); + + ui->mainToolBar->addAction(accountMenuButtonAction); + + // Update the menu when the active account changes. + // Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit. + // Template hell sucks... + connect(MMC->accounts().get(), &MojangAccountList::activeAccountChanged, [this] + { + activeAccountChanged(); + }); + connect(MMC->accounts().get(), &MojangAccountList::listChanged, [this] + { + repopulateAccountsMenu(); + }); + + // Show initial account + activeAccountChanged(); + + auto accounts = MMC->accounts(); + + QList<Net::Download::Ptr> skin_dls; + for (int i = 0; i < accounts->count(); i++) + { + auto account = accounts->at(i); + if (!account) + { + qWarning() << "Null account at index" << i; + continue; + } + for (auto profile : account->profiles()) + { + auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png"); + auto action = Net::Download::makeCached(QUrl(URLConstants::SKINS_BASE + profile.id + ".png"), meta); + skin_dls.append(action); + meta->setStale(true); + } + } + if (!skin_dls.isEmpty()) + { + auto job = new NetJob("Startup player skins download"); + connect(job, &NetJob::succeeded, this, &MainWindow::skinJobFinished); + connect(job, &NetJob::failed, this, &MainWindow::skinJobFinished); + for (auto action : skin_dls) + { + job->addNetAction(action); + } + skin_download_job.reset(job); + job->start(); + } + + // load the news + { + m_newsChecker->reloadNews(); + updateNewsLabel(); + } + + + if(BuildConfig.UPDATER_ENABLED) + { + bool updatesAllowed = MMC->updatesAreAllowed(); + updatesAllowedChanged(updatesAllowed); + + // NOTE: calling the operator like that is an ugly hack to appease ancient gcc... + connect(ui->actionCheckUpdate.operator->(), &QAction::triggered, this, &MainWindow::checkForUpdates); + + // set up the updater object. + auto updater = MMC->updateChecker(); + connect(updater.get(), &UpdateChecker::updateAvailable, this, &MainWindow::updateAvailable); + connect(updater.get(), &UpdateChecker::noUpdateFound, this, &MainWindow::updateNotAvailable); + // if automatic update checks are allowed, start one. + if (MMC->settings()->get("AutoUpdate").toBool() && updatesAllowed) + { + updater->checkForUpdate(MMC->settings()->get("UpdateChannel").toString(), false); + } + } + + { + auto checker = new NotificationChecker(); + checker->setNotificationsUrl(QUrl(BuildConfig.NOTIFICATION_URL)); + checker->setApplicationChannel(BuildConfig.VERSION_CHANNEL); + checker->setApplicationPlatform(BuildConfig.BUILD_PLATFORM); + checker->setApplicationFullVersion(BuildConfig.FULL_VERSION_STR); + m_notificationChecker.reset(checker); + connect(m_notificationChecker.get(), &NotificationChecker::notificationCheckFinished, this, &MainWindow::notificationsChanged); + checker->checkForNotifications(); + } + + setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString()); + + // removing this looks stupid + view->setFocus(); } MainWindow::~MainWindow() { } +QMenu * MainWindow::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->mainToolBar->toggleViewAction() ); + return filteredMenu; +} + void MainWindow::konamiTriggered() { - qDebug() << "Super Secret Mode ACTIVATED!"; + // ENV.enableFeature("NewModsPage"); + qDebug() << "Super Secret Mode ACTIVATED!"; } void MainWindow::skinJobFinished() { - activeAccountChanged(); - skin_download_job.reset(); + activeAccountChanged(); + skin_download_job.reset(); } void MainWindow::showInstanceContextMenu(const QPoint &pos) { - QList<QAction *> actions; - - QAction *actionSep = new QAction("", this); - actionSep->setSeparator(true); - - bool onInstance = view->indexAt(pos).isValid(); - if (onInstance) - { - actions = ui->instanceToolBar->actions(); - - // replace the change icon widget with an actual action - actions.replace(0, ui->actionChangeInstIcon); - - // replace the rename widget with an actual action - actions.replace(1, ui->actionRenameInstance); - - // add header - actions.prepend(actionSep); - QAction *actionVoid = new QAction(m_selectedInstance->name(), this); - actionVoid->setEnabled(false); - actions.prepend(actionVoid); - } - else - { - auto group = view->groupNameAt(pos); - - QAction *actionVoid = new QAction("MultiMC", this); - actionVoid->setEnabled(false); - - QAction *actionCreateInstance = new QAction(tr("Create instance"), this); - actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip()); - if(!group.isNull()) - { - QVariantMap data; - data["group"] = group; - actionCreateInstance->setData(data); - } - - connect(actionCreateInstance, SIGNAL(triggered(bool)), SLOT(on_actionAddInstance_triggered())); - - actions.prepend(actionSep); - actions.prepend(actionVoid); - actions.append(actionCreateInstance); - if(!group.isNull()) - { - QAction *actionDeleteGroup = new QAction(tr("Delete group '%1'").arg(group), this); - QVariantMap data; - data["group"] = group; - actionDeleteGroup->setData(data); - connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup())); - actions.append(actionDeleteGroup); - } - } - QMenu myMenu; - myMenu.addActions(actions); - /* - if (onInstance) - myMenu.setEnabled(m_selectedInstance->canLaunch()); - */ - myMenu.exec(view->mapToGlobal(pos)); + QList<QAction *> actions; + + QAction *actionSep = new QAction("", this); + actionSep->setSeparator(true); + + bool onInstance = view->indexAt(pos).isValid(); + if (onInstance) + { + actions = ui->instanceToolBar->actions(); + + // replace the change icon widget with an actual action + actions.replace(0, ui->actionChangeInstIcon); + + // replace the rename widget with an actual action + actions.replace(1, ui->actionRenameInstance); + + // add header + actions.prepend(actionSep); + QAction *actionVoid = new QAction(m_selectedInstance->name(), this); + actionVoid->setEnabled(false); + actions.prepend(actionVoid); + } + else + { + auto group = view->groupNameAt(pos); + + QAction *actionVoid = new QAction("MultiMC", this); + actionVoid->setEnabled(false); + + QAction *actionCreateInstance = new QAction(tr("Create instance"), this); + actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip()); + if(!group.isNull()) + { + QVariantMap data; + data["group"] = group; + actionCreateInstance->setData(data); + } + + connect(actionCreateInstance, SIGNAL(triggered(bool)), SLOT(on_actionAddInstance_triggered())); + + actions.prepend(actionSep); + actions.prepend(actionVoid); + actions.append(actionCreateInstance); + if(!group.isNull()) + { + QAction *actionDeleteGroup = new QAction(tr("Delete group '%1'").arg(group), this); + QVariantMap data; + data["group"] = group; + actionDeleteGroup->setData(data); + connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup())); + actions.append(actionDeleteGroup); + } + } + QMenu myMenu; + myMenu.addActions(actions); + /* + if (onInstance) + myMenu.setEnabled(m_selectedInstance->canLaunch()); + */ + myMenu.exec(view->mapToGlobal(pos)); } void MainWindow::updateToolsMenu() { - QToolButton *launchButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); - if(!m_selectedInstance || m_selectedInstance->isRunning()) - { - ui->actionLaunchInstance->setMenu(nullptr); - launchButton->setPopupMode(QToolButton::InstantPopup); - return; - } - - QMenu *launchMenu = ui->actionLaunchInstance->menu(); - launchButton->setPopupMode(QToolButton::MenuButtonPopup); - if (launchMenu) - { - launchMenu->clear(); - } - else - { - launchMenu = new QMenu(this); - } - - QAction *normalLaunch = launchMenu->addAction(tr("Launch")); - connect(normalLaunch, &QAction::triggered, [this]() - { - MMC->launch(m_selectedInstance); - }); - launchMenu->addSeparator()->setText(tr("Profilers")); - for (auto profiler : MMC->profilers().values()) - { - QAction *profilerAction = launchMenu->addAction(profiler->name()); - QString error; - if (!profiler->check(&error)) - { - profilerAction->setDisabled(true); - profilerAction->setToolTip(tr("Profiler not setup correctly. Go into settings, \"External Tools\".")); - } - else - { - connect(profilerAction, &QAction::triggered, [this, profiler]() - { - MMC->launch(m_selectedInstance, true, profiler.get()); - }); - } - } - ui->actionLaunchInstance->setMenu(launchMenu); + QToolButton *launchButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); + QToolButton *launchOfflineButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline)); + + if(!m_selectedInstance || m_selectedInstance->isRunning()) + { + ui->actionLaunchInstance->setMenu(nullptr); + ui->actionLaunchInstanceOffline->setMenu(nullptr); + launchButton->setPopupMode(QToolButton::InstantPopup); + launchOfflineButton->setPopupMode(QToolButton::InstantPopup); + return; + } + + QMenu *launchMenu = ui->actionLaunchInstance->menu(); + QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu(); + launchButton->setPopupMode(QToolButton::MenuButtonPopup); + launchOfflineButton->setPopupMode(QToolButton::MenuButtonPopup); + if (launchMenu) + { + launchMenu->clear(); + } + else + { + launchMenu = new QMenu(this); + } + if (launchOfflineMenu) { + launchOfflineMenu->clear(); + } + else + { + launchOfflineMenu = new QMenu(this); + } + + QAction *normalLaunch = launchMenu->addAction(tr("Launch")); + QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline")); + connect(normalLaunch, &QAction::triggered, [this]() + { + MMC->launch(m_selectedInstance, true); + }); + connect(normalLaunchOffline, &QAction::triggered, [this]() + { + MMC->launch(m_selectedInstance, false); + }); + QString profilersTitle = tr("Profilers"); + launchMenu->addSeparator()->setText(profilersTitle); + launchOfflineMenu->addSeparator()->setText(profilersTitle); + for (auto profiler : MMC->profilers().values()) + { + QAction *profilerAction = launchMenu->addAction(profiler->name()); + QAction *profilerOfflineAction = launchOfflineMenu->addAction(profiler->name()); + QString error; + if (!profiler->check(&error)) + { + profilerAction->setDisabled(true); + profilerOfflineAction->setDisabled(true); + QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\"."); + profilerAction->setToolTip(profilerToolTip); + profilerOfflineAction->setToolTip(profilerToolTip); + } + else + { + connect(profilerAction, &QAction::triggered, [this, profiler]() + { + MMC->launch(m_selectedInstance, true, profiler.get()); + }); + connect(profilerOfflineAction, &QAction::triggered, [this, profiler]() + { + MMC->launch(m_selectedInstance, false, profiler.get()); + }); + } + } + ui->actionLaunchInstance->setMenu(launchMenu); + ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu); } QString profileInUseFilter(const QString & profile, bool used) { - if(used) - { - return profile + QObject::tr(" (in use)"); - } - else - { - return profile; - } + if(used) + { + return profile + QObject::tr(" (in use)"); + } + else + { + return profile; + } } void MainWindow::repopulateAccountsMenu() { - accountMenu->clear(); - - std::shared_ptr<MojangAccountList> accounts = MMC->accounts(); - MojangAccountPtr active_account = accounts->activeAccount(); - - QString active_username = ""; - if (active_account != nullptr) - { - active_username = active_account->username(); - const AccountProfile *profile = active_account->currentProfile(); - // this can be called before accountMenuButton exists - if (profile != nullptr && accountMenuButton) - { - auto profileLabel = profileInUseFilter(profile->name, active_account->isInUse()); - accountMenuButton->setText(profileLabel); - } - } - - if (accounts->count() <= 0) - { - QAction *action = new QAction(tr("No accounts added!"), this); - action->setEnabled(false); - accountMenu->addAction(action); - } - else - { - // TODO: Nicer way to iterate? - for (int i = 0; i < accounts->count(); i++) - { - MojangAccountPtr account = accounts->at(i); - for (auto profile : account->profiles()) - { - auto profileLabel = profileInUseFilter(profile.name, account->isInUse()); - QAction *action = new QAction(profileLabel, this); - action->setData(account->username()); - action->setCheckable(true); - if (active_username == account->username()) - { - action->setChecked(true); - } - - action->setIcon(SkinUtils::getFaceFromCache(profile.id)); - accountMenu->addAction(action); - connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); - } - } - } - - accountMenu->addSeparator(); - - QAction *action = new QAction(tr("No Default Account"), this); - action->setCheckable(true); - action->setIcon(MMC->getThemedIcon("noaccount")); - action->setData(""); - if (active_username.isEmpty()) - { - action->setChecked(true); - } - - accountMenu->addAction(action); - connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); - - accountMenu->addSeparator(); - accountMenu->addAction(ui->actionManageAccounts); + accountMenu->clear(); + + std::shared_ptr<MojangAccountList> accounts = MMC->accounts(); + MojangAccountPtr active_account = accounts->activeAccount(); + + QString active_username = ""; + if (active_account != nullptr) + { + active_username = active_account->username(); + const AccountProfile *profile = active_account->currentProfile(); + // this can be called before accountMenuButton exists + if (profile != nullptr && accountMenuButton) + { + auto profileLabel = profileInUseFilter(profile->name, active_account->isInUse()); + accountMenuButton->setText(profileLabel); + } + } + + if (accounts->count() <= 0) + { + QAction *action = new QAction(tr("No accounts added!"), this); + action->setEnabled(false); + accountMenu->addAction(action); + } + else + { + // TODO: Nicer way to iterate? + for (int i = 0; i < accounts->count(); i++) + { + MojangAccountPtr account = accounts->at(i); + for (auto profile : account->profiles()) + { + auto profileLabel = profileInUseFilter(profile.name, account->isInUse()); + QAction *action = new QAction(profileLabel, this); + action->setData(account->username()); + action->setCheckable(true); + if (active_username == account->username()) + { + action->setChecked(true); + } + + action->setIcon(SkinUtils::getFaceFromCache(profile.id)); + accountMenu->addAction(action); + connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); + } + } + } + + accountMenu->addSeparator(); + + QAction *action = new QAction(tr("No Default Account"), this); + action->setCheckable(true); + action->setIcon(MMC->getThemedIcon("noaccount")); + action->setData(""); + if (active_username.isEmpty()) + { + action->setChecked(true); + } + + accountMenu->addAction(action); + connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); + + accountMenu->addSeparator(); + accountMenu->addAction(ui->actionManageAccounts); } void MainWindow::updatesAllowedChanged(bool allowed) { - if(!BuildConfig.UPDATER_ENABLED) - { - return; - } - ui->actionCheckUpdate->setEnabled(allowed); + if(!BuildConfig.UPDATER_ENABLED) + { + return; + } + ui->actionCheckUpdate->setEnabled(allowed); } /* @@ -1030,799 +1083,825 @@ void MainWindow::updatesAllowedChanged(bool allowed) */ void MainWindow::changeActiveAccount() { - QAction *sAction = (QAction *)sender(); - // Profile's associated Mojang username - // Will need to change when profiles are properly implemented - if (sAction->data().type() != QVariant::Type::String) - return; + QAction *sAction = (QAction *)sender(); + // Profile's associated Mojang username + // Will need to change when profiles are properly implemented + if (sAction->data().type() != QVariant::Type::String) + return; - QVariant data = sAction->data(); - QString id = ""; - if (!data.isNull()) - { - id = data.toString(); - } + QVariant data = sAction->data(); + QString id = ""; + if (!data.isNull()) + { + id = data.toString(); + } - MMC->accounts()->setActiveAccount(id); + MMC->accounts()->setActiveAccount(id); - activeAccountChanged(); + activeAccountChanged(); } void MainWindow::activeAccountChanged() { - repopulateAccountsMenu(); + repopulateAccountsMenu(); - MojangAccountPtr account = MMC->accounts()->activeAccount(); + MojangAccountPtr account = MMC->accounts()->activeAccount(); - if (account != nullptr && account->username() != "") - { - const AccountProfile *profile = account->currentProfile(); - if (profile != nullptr) - { - auto profileLabel = profileInUseFilter(profile->name, account->isInUse()); - accountMenuButton->setIcon(SkinUtils::getFaceFromCache(profile->id)); - accountMenuButton->setText(profileLabel); - return; - } - } + if (account != nullptr && account->username() != "") + { + const AccountProfile *profile = account->currentProfile(); + if (profile != nullptr) + { + auto profileLabel = profileInUseFilter(profile->name, account->isInUse()); + accountMenuButton->setIcon(SkinUtils::getFaceFromCache(profile->id)); + accountMenuButton->setText(profileLabel); + return; + } + } - // Set the icon to the "no account" icon. - accountMenuButton->setIcon(MMC->getThemedIcon("noaccount")); - accountMenuButton->setText(tr("Profiles")); + // Set the icon to the "no account" icon. + accountMenuButton->setIcon(MMC->getThemedIcon("noaccount")); + accountMenuButton->setText(tr("Profiles")); } bool MainWindow::eventFilter(QObject *obj, QEvent *ev) { - if (obj == view) - { - if (ev->type() == QEvent::KeyPress) - { - secretEventFilter->input(ev); - QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); - switch (keyEvent->key()) - { - case Qt::Key_Enter: - case Qt::Key_Return: - activateInstance(m_selectedInstance); - return true; - case Qt::Key_Delete: - on_actionDeleteInstance_triggered(); - return true; - case Qt::Key_F5: - refreshInstances(); - return true; - case Qt::Key_F2: - on_actionRenameInstance_triggered(); - return true; - default: - break; - } - } - } - return QMainWindow::eventFilter(obj, ev); + if (obj == view) + { + if (ev->type() == QEvent::KeyPress) + { + secretEventFilter->input(ev); + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); + switch (keyEvent->key()) + { + /* + case Qt::Key_Enter: + case Qt::Key_Return: + activateInstance(m_selectedInstance); + return true; + */ + case Qt::Key_Delete: + on_actionDeleteInstance_triggered(); + return true; + case Qt::Key_F5: + refreshInstances(); + return true; + case Qt::Key_F2: + on_actionRenameInstance_triggered(); + return true; + default: + break; + } + } + } + return QMainWindow::eventFilter(obj, ev); } void MainWindow::updateNewsLabel() { - if (m_newsChecker->isLoadingNews()) - { - newsLabel->setText(tr("Loading news...")); - newsLabel->setEnabled(false); - } - else - { - QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries(); - if (entries.length() > 0) - { - newsLabel->setText(entries[0]->title); - newsLabel->setEnabled(true); - } - else - { - newsLabel->setText(tr("No news available.")); - newsLabel->setEnabled(false); - } - } + if (m_newsChecker->isLoadingNews()) + { + newsLabel->setText(tr("Loading news...")); + newsLabel->setEnabled(false); + } + else + { + QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries(); + if (entries.length() > 0) + { + newsLabel->setText(entries[0]->title); + newsLabel->setEnabled(true); + } + else + { + newsLabel->setText(tr("No news available.")); + newsLabel->setEnabled(false); + } + } } void MainWindow::updateAvailable(GoUpdate::Status status) { - if(!MMC->updatesAreAllowed()) - { - updateNotAvailable(); - return; - } - UpdateDialog dlg(true, this); - UpdateAction action = (UpdateAction)dlg.exec(); - switch (action) - { - case UPDATE_LATER: - qDebug() << "Update will be installed later."; - break; - case UPDATE_NOW: - downloadUpdates(status); - break; - } + if(!MMC->updatesAreAllowed()) + { + updateNotAvailable(); + return; + } + UpdateDialog dlg(true, this); + UpdateAction action = (UpdateAction)dlg.exec(); + switch (action) + { + case UPDATE_LATER: + qDebug() << "Update will be installed later."; + break; + case UPDATE_NOW: + downloadUpdates(status); + break; + } } void MainWindow::updateNotAvailable() { - UpdateDialog dlg(false, this); - dlg.exec(); + UpdateDialog dlg(false, this); + dlg.exec(); } QList<int> stringToIntList(const QString &string) { - QStringList split = string.split(',', QString::SkipEmptyParts); - QList<int> out; - for (int i = 0; i < split.size(); ++i) - { - out.append(split.at(i).toInt()); - } - return out; + QStringList split = string.split(',', QString::SkipEmptyParts); + QList<int> out; + for (int i = 0; i < split.size(); ++i) + { + out.append(split.at(i).toInt()); + } + return out; } QString intListToString(const QList<int> &list) { - QStringList slist; - for (int i = 0; i < list.size(); ++i) - { - slist.append(QString::number(list.at(i))); - } - return slist.join(','); + QStringList slist; + for (int i = 0; i < list.size(); ++i) + { + slist.append(QString::number(list.at(i))); + } + return slist.join(','); } void MainWindow::notificationsChanged() { - QList<NotificationChecker::NotificationEntry> entries = m_notificationChecker->notificationEntries(); - QList<int> shownNotifications = stringToIntList(MMC->settings()->get("ShownNotifications").toString()); - for (auto it = entries.begin(); it != entries.end(); ++it) - { - NotificationChecker::NotificationEntry entry = *it; - if (!shownNotifications.contains(entry.id)) - { - NotificationDialog dialog(entry, this); - if (dialog.exec() == NotificationDialog::DontShowAgain) - { - shownNotifications.append(entry.id); - } - } - } - MMC->settings()->set("ShownNotifications", intListToString(shownNotifications)); + QList<NotificationChecker::NotificationEntry> entries = m_notificationChecker->notificationEntries(); + QList<int> shownNotifications = stringToIntList(MMC->settings()->get("ShownNotifications").toString()); + for (auto it = entries.begin(); it != entries.end(); ++it) + { + NotificationChecker::NotificationEntry entry = *it; + if (!shownNotifications.contains(entry.id)) + { + NotificationDialog dialog(entry, this); + if (dialog.exec() == NotificationDialog::DontShowAgain) + { + shownNotifications.append(entry.id); + } + } + } + MMC->settings()->set("ShownNotifications", intListToString(shownNotifications)); } void MainWindow::downloadUpdates(GoUpdate::Status status) { - if(!MMC->updatesAreAllowed()) - { - return; - } - qDebug() << "Downloading updates."; - ProgressDialog updateDlg(this); - status.rootPath = MMC->root(); - - auto dlPath = FS::PathCombine(MMC->root(), "update", "XXXXXX"); - if (!FS::ensureFilePathExists(dlPath)) - { - CustomMessageBox::selectable(this, tr("Error"), tr("Couldn't create folder for update downloads:\n%1").arg(dlPath), QMessageBox::Warning)->show(); - } - GoUpdate::DownloadTask updateTask(status, dlPath, &updateDlg); - // If the task succeeds, install the updates. - if (updateDlg.execWithTask(&updateTask)) - { - /** - * NOTE: This disables launching instances until the update either succeeds (and this process exits) - * or the update fails (and the control leaves this scope). - */ - MMC->updateIsRunning(true); - UpdateController update(this, MMC->root(), updateTask.updateFilesDir(), updateTask.operations()); - update.installUpdates(); - MMC->updateIsRunning(false); - } - else - { - CustomMessageBox::selectable(this, tr("Error"), updateTask.failReason(), QMessageBox::Warning)->show(); - } + if(!MMC->updatesAreAllowed()) + { + return; + } + qDebug() << "Downloading updates."; + ProgressDialog updateDlg(this); + status.rootPath = MMC->root(); + + auto dlPath = FS::PathCombine(MMC->root(), "update", "XXXXXX"); + if (!FS::ensureFilePathExists(dlPath)) + { + CustomMessageBox::selectable(this, tr("Error"), tr("Couldn't create folder for update downloads:\n%1").arg(dlPath), QMessageBox::Warning)->show(); + } + GoUpdate::DownloadTask updateTask(status, dlPath, &updateDlg); + // If the task succeeds, install the updates. + if (updateDlg.execWithTask(&updateTask)) + { + /** + * NOTE: This disables launching instances until the update either succeeds (and this process exits) + * or the update fails (and the control leaves this scope). + */ + MMC->updateIsRunning(true); + UpdateController update(this, MMC->root(), updateTask.updateFilesDir(), updateTask.operations()); + update.installUpdates(); + MMC->updateIsRunning(false); + } + else + { + CustomMessageBox::selectable(this, tr("Error"), updateTask.failReason(), QMessageBox::Warning)->show(); + } } void MainWindow::onCatToggled(bool state) { - setCatBackground(state); - MMC->settings()->set("TheCat", state); + setCatBackground(state); + MMC->settings()->set("TheCat", state); +} + +namespace { +template <typename T> +T non_stupid_abs(T in) +{ + if (in < 0) + return -in; + return in; +} } void MainWindow::setCatBackground(bool enabled) { - if (enabled) - { - view->setStyleSheet("GroupView" - "{" - "background-image: url(:/backgrounds/kitteh);" - "background-attachment: fixed;" - "background-clip: padding;" - "background-position: top right;" - "background-repeat: none;" - "background-color:palette(base);" - "}"); - } - else - { - view->setStyleSheet(QString()); - } + if (enabled) + { + QDateTime now = QDateTime::currentDateTime(); + QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0)); + ; + QString cat = (non_stupid_abs(now.daysTo(xmas)) <= 4) ? "catmas" : "kitteh"; + view->setStyleSheet(QString(R"( +GroupView +{ + background-image: url(:/backgrounds/%1); + background-attachment: fixed; + background-clip: padding; + background-position: top right; + background-repeat: none; + background-color:palette(base); +})").arg(cat)); + } + else + { + view->setStyleSheet(QString()); + } } void MainWindow::runModalTask(Task *task) { - connect(task, &Task::failed, [this](QString reason) - { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - }); - connect(task, &Task::succeeded, [this, task]() - { - QStringList warnings = task->warnings(); - if(warnings.count()) - { - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); - } - }); - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(task); + connect(task, &Task::failed, [this](QString reason) + { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + }); + connect(task, &Task::succeeded, [this, task]() + { + QStringList warnings = task->warnings(); + if(warnings.count()) + { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } + }); + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(task); } void MainWindow::instanceFromInstanceTask(InstanceTask *rawTask) { - std::unique_ptr<Task> task(MMC->folderProvider()->wrapInstanceTask(rawTask)); - runModalTask(task.get()); - - // FIXME: handle instance selection after creation - // finalizeInstance(newInstance); + unique_qobject_ptr<Task> task(MMC->instances()->wrapInstanceTask(rawTask)); + runModalTask(task.get()); } void MainWindow::on_actionCopyInstance_triggered() { - if (!m_selectedInstance) - return; + if (!m_selectedInstance) + return; - CopyInstanceDialog copyInstDlg(m_selectedInstance, this); - if (!copyInstDlg.exec()) - return; + CopyInstanceDialog copyInstDlg(m_selectedInstance, this); + if (!copyInstDlg.exec()) + return; - auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves()); - copyTask->setName(copyInstDlg.instName()); - copyTask->setGroup(copyInstDlg.instGroup()); - copyTask->setIcon(copyInstDlg.iconKey()); - std::unique_ptr<Task> task(MMC->folderProvider()->wrapInstanceTask(copyTask)); - runModalTask(task.get()); - - // FIXME: handle instance selection after creation - // finalizeInstance(newInstance); + auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves()); + copyTask->setName(copyInstDlg.instName()); + copyTask->setGroup(copyInstDlg.instGroup()); + copyTask->setIcon(copyInstDlg.iconKey()); + unique_qobject_ptr<Task> task(MMC->instances()->wrapInstanceTask(copyTask)); + runModalTask(task.get()); } void MainWindow::finalizeInstance(InstancePtr inst) { - view->updateGeometries(); - setSelectedInstanceById(inst->id()); - if (MMC->accounts()->anyAccountIsValid()) - { - ProgressDialog loadDialog(this); - auto update = inst->createUpdateTask(Net::Mode::Online); - connect(update.get(), &Task::failed, [this](QString reason) - { - QString error = QString("Instance load failed: %1").arg(reason); - CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show(); - }); - if(update) - { - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(update.get()); - } - } - else - { - CustomMessageBox::selectable(this, tr("Error"), tr("MultiMC cannot download Minecraft or update instances unless you have at least " - "one account added.\nPlease add your Mojang or Minecraft account."), - QMessageBox::Warning) - ->show(); - } + view->updateGeometries(); + setSelectedInstanceById(inst->id()); + if (MMC->accounts()->anyAccountIsValid()) + { + ProgressDialog loadDialog(this); + auto update = inst->createUpdateTask(Net::Mode::Online); + connect(update.get(), &Task::failed, [this](QString reason) + { + QString error = QString("Instance load failed: %1").arg(reason); + CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show(); + }); + if(update) + { + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(update.get()); + } + } + else + { + CustomMessageBox::selectable(this, tr("Error"), tr("MultiMC cannot download Minecraft or update instances unless you have at least " + "one account added.\nPlease add your Mojang or Minecraft account."), + QMessageBox::Warning) + ->show(); + } } void MainWindow::addInstance(QString url) { - QString groupName; - do - { - QObject* obj = sender(); - if(!obj) - break; - QAction *action = qobject_cast<QAction *>(obj); - if(!action) - break; - auto map = action->data().toMap(); - if(!map.contains("group")) - break; - groupName = map["group"].toString(); - } while(0); - - if(groupName.isEmpty()) - { - groupName = MMC->settings()->get("LastUsedGroupForNewInstance").toString(); - } - - NewInstanceDialog newInstDlg(groupName, url, this); - if (!newInstDlg.exec()) - return; - - MMC->settings()->set("LastUsedGroupForNewInstance", newInstDlg.instGroup()); - - InstanceTask * creationTask = newInstDlg.extractTask(); - if(creationTask) - { - instanceFromInstanceTask(creationTask); - } + QString groupName; + do + { + QObject* obj = sender(); + if(!obj) + break; + QAction *action = qobject_cast<QAction *>(obj); + if(!action) + break; + auto map = action->data().toMap(); + if(!map.contains("group")) + break; + groupName = map["group"].toString(); + } while(0); + + if(groupName.isEmpty()) + { + groupName = MMC->settings()->get("LastUsedGroupForNewInstance").toString(); + } + + NewInstanceDialog newInstDlg(groupName, url, this); + if (!newInstDlg.exec()) + return; + + MMC->settings()->set("LastUsedGroupForNewInstance", newInstDlg.instGroup()); + + InstanceTask * creationTask = newInstDlg.extractTask(); + if(creationTask) + { + instanceFromInstanceTask(creationTask); + } } void MainWindow::on_actionAddInstance_triggered() { - addInstance(); + addInstance(); } void MainWindow::droppedURLs(QList<QUrl> urls) { - for(auto & url:urls) - { - if(url.isLocalFile()) - { - addInstance(url.toLocalFile()); - } - else - { - addInstance(url.toString()); - } - // Only process one dropped file... - break; - } + for(auto & url:urls) + { + if(url.isLocalFile()) + { + addInstance(url.toLocalFile()); + } + else + { + addInstance(url.toString()); + } + // Only process one dropped file... + break; + } } void MainWindow::on_actionREDDIT_triggered() { - DesktopServices::openUrl(QUrl("https://www.reddit.com/r/MultiMC/")); + DesktopServices::openUrl(QUrl("https://www.reddit.com/r/MultiMC/")); } void MainWindow::on_actionDISCORD_triggered() { - DesktopServices::openUrl(QUrl("https://discord.gg/0k2zsXGNHs0fE4Wm")); + DesktopServices::openUrl(QUrl("https://discord.gg/0k2zsXGNHs0fE4Wm")); } void MainWindow::on_actionChangeInstIcon_triggered() { - if (!m_selectedInstance) - return; + if (!m_selectedInstance) + return; - IconPickerDialog dlg(this); - dlg.execWithSelection(m_selectedInstance->iconKey()); - if (dlg.result() == QDialog::Accepted) - { - m_selectedInstance->setIconKey(dlg.selectedIconKey); - auto icon = MMC->icons()->getIcon(dlg.selectedIconKey); - ui->actionChangeInstIcon->setIcon(icon); - ui->changeIconButton->setIcon(icon); - } + IconPickerDialog dlg(this); + dlg.execWithSelection(m_selectedInstance->iconKey()); + if (dlg.result() == QDialog::Accepted) + { + m_selectedInstance->setIconKey(dlg.selectedIconKey); + auto icon = MMC->icons()->getIcon(dlg.selectedIconKey); + ui->actionChangeInstIcon->setIcon(icon); + ui->changeIconButton->setIcon(icon); + } } void MainWindow::iconUpdated(QString icon) { - if (icon == m_currentInstIcon) - { - auto icon = MMC->icons()->getIcon(m_currentInstIcon); - ui->actionChangeInstIcon->setIcon(icon); - ui->changeIconButton->setIcon(icon); - } + if (icon == m_currentInstIcon) + { + auto icon = MMC->icons()->getIcon(m_currentInstIcon); + ui->actionChangeInstIcon->setIcon(icon); + ui->changeIconButton->setIcon(icon); + } } void MainWindow::updateInstanceToolIcon(QString new_icon) { - m_currentInstIcon = new_icon; - auto icon = MMC->icons()->getIcon(m_currentInstIcon); - ui->actionChangeInstIcon->setIcon(icon); - ui->changeIconButton->setIcon(icon); + m_currentInstIcon = new_icon; + auto icon = MMC->icons()->getIcon(m_currentInstIcon); + ui->actionChangeInstIcon->setIcon(icon); + ui->changeIconButton->setIcon(icon); } void MainWindow::setSelectedInstanceById(const QString &id) { - if (id.isNull()) - return; - const QModelIndex index = MMC->instances()->getInstanceIndexById(id); - if (index.isValid()) - { - QModelIndex selectionIndex = proxymodel->mapFromSource(index); - view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect); - } + if (id.isNull()) + return; + const QModelIndex index = MMC->instances()->getInstanceIndexById(id); + if (index.isValid()) + { + QModelIndex selectionIndex = proxymodel->mapFromSource(index); + view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect); + } } void MainWindow::on_actionChangeInstGroup_triggered() { - if (!m_selectedInstance) - return; + if (!m_selectedInstance) + return; - bool ok = false; - QString name(m_selectedInstance->group()); - auto groups = MMC->instances()->getGroups(); - groups.insert(0, ""); - groups.sort(Qt::CaseInsensitive); - int foo = groups.indexOf(name); + bool ok = false; + InstanceId instId = m_selectedInstance->id(); + QString name(MMC->instances()->getInstanceGroup(instId)); + auto groups = MMC->instances()->getGroups(); + groups.insert(0, ""); + groups.sort(Qt::CaseInsensitive); + int foo = groups.indexOf(name); - name = QInputDialog::getItem(this, tr("Group name"), tr("Enter a new group name."), groups, foo, true, &ok); - name = name.simplified(); - if (ok) - m_selectedInstance->setGroupPost(name); + name = QInputDialog::getItem(this, tr("Group name"), tr("Enter a new group name."), groups, foo, true, &ok); + name = name.simplified(); + if (ok) + { + MMC->instances()->setInstanceGroup(instId, name); + } } void MainWindow::deleteGroup() { - QObject* obj = sender(); - if(!obj) - return; - QAction *action = qobject_cast<QAction *>(obj); - if(!action) - return; - auto map = action->data().toMap(); - if(!map.contains("group")) - return; - QString groupName = map["group"].toString(); - if(!groupName.isEmpty()) - { - MMC->instances()->deleteGroup(groupName); - } + QObject* obj = sender(); + if(!obj) + return; + QAction *action = qobject_cast<QAction *>(obj); + if(!action) + return; + auto map = action->data().toMap(); + if(!map.contains("group")) + return; + QString groupName = map["group"].toString(); + if(!groupName.isEmpty()) + { + auto reply = QMessageBox::question(this, tr("Delete group"), tr("Are you sure you want to delete the group %1") + .arg(groupName), QMessageBox::Yes | QMessageBox::No); + if(reply == QMessageBox::Yes) + { + MMC->instances()->deleteGroup(groupName); + } + } } void MainWindow::on_actionViewInstanceFolder_triggered() { - QString str = MMC->settings()->get("InstanceDir").toString(); - DesktopServices::openDirectory(str); + QString str = MMC->settings()->get("InstanceDir").toString(); + DesktopServices::openDirectory(str); } void MainWindow::refreshInstances() { - MMC->instances()->loadList(true); + MMC->instances()->loadList(); } void MainWindow::on_actionViewCentralModsFolder_triggered() { - DesktopServices::openDirectory(MMC->settings()->get("CentralModsDir").toString(), true); + DesktopServices::openDirectory(MMC->settings()->get("CentralModsDir").toString(), true); } void MainWindow::on_actionConfig_Folder_triggered() { - if (m_selectedInstance) - { - QString str = m_selectedInstance->instanceConfigFolder(); - DesktopServices::openDirectory(QDir(str).absolutePath()); - } + if (m_selectedInstance) + { + QString str = m_selectedInstance->instanceConfigFolder(); + DesktopServices::openDirectory(QDir(str).absolutePath()); + } } void MainWindow::checkForUpdates() { - if(BuildConfig.UPDATER_ENABLED) - { - auto updater = MMC->updateChecker(); - updater->checkForUpdate(MMC->settings()->get("UpdateChannel").toString(), true); - } - else - { - qWarning() << "Updater not set up. Cannot check for updates."; - } + if(BuildConfig.UPDATER_ENABLED) + { + auto updater = MMC->updateChecker(); + updater->checkForUpdate(MMC->settings()->get("UpdateChannel").toString(), true); + } + else + { + qWarning() << "Updater not set up. Cannot check for updates."; + } } void MainWindow::on_actionSettings_triggered() { - SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), this, "global-settings"); - // FIXME: quick HACK to make this work. improve, optimize. - MMC->instances()->loadList(true); - proxymodel->invalidate(); - proxymodel->sort(0); - updateToolsMenu(); - update(); + MMC->ShowGlobalSettings(this, "global-settings"); +} + +void MainWindow::globalSettingsClosed() +{ + // FIXME: quick HACK to make this work. improve, optimize. + MMC->instances()->loadList(); + proxymodel->invalidate(); + proxymodel->sort(0); + updateToolsMenu(); + update(); } void MainWindow::on_actionInstanceSettings_triggered() { - MMC->showInstanceWindow(m_selectedInstance, "settings"); + MMC->showInstanceWindow(m_selectedInstance, "settings"); } void MainWindow::on_actionEditInstNotes_triggered() { - MMC->showInstanceWindow(m_selectedInstance, "notes"); + MMC->showInstanceWindow(m_selectedInstance, "notes"); } void MainWindow::on_actionWorlds_triggered() { - MMC->showInstanceWindow(m_selectedInstance, "worlds"); + MMC->showInstanceWindow(m_selectedInstance, "worlds"); } void MainWindow::on_actionEditInstance_triggered() { - MMC->showInstanceWindow(m_selectedInstance); + MMC->showInstanceWindow(m_selectedInstance); } void MainWindow::on_actionScreenshots_triggered() { - MMC->showInstanceWindow(m_selectedInstance, "screenshots"); + MMC->showInstanceWindow(m_selectedInstance, "screenshots"); } void MainWindow::on_actionManageAccounts_triggered() { - SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), this, "accounts"); + MMC->ShowGlobalSettings(this, "accounts"); } void MainWindow::on_actionReportBug_triggered() { - DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/issues")); + DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/issues")); } void MainWindow::on_actionPatreon_triggered() { - DesktopServices::openUrl(QUrl("http://www.patreon.com/multimc")); + DesktopServices::openUrl(QUrl("https://www.patreon.com/multimc")); } void MainWindow::on_actionMoreNews_triggered() { - DesktopServices::openUrl(QUrl("http://multimc.org/posts.html")); + DesktopServices::openUrl(QUrl("https://multimc.org/posts.html")); } void MainWindow::newsButtonClicked() { - QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries(); - if (entries.count() > 0) - { - DesktopServices::openUrl(QUrl(entries[0]->link)); - } - else - { - DesktopServices::openUrl(QUrl("http://multimc.org/posts.html")); - } + QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries(); + if (entries.count() > 0) + { + DesktopServices::openUrl(QUrl(entries[0]->link)); + } + else + { + DesktopServices::openUrl(QUrl("https://multimc.org/posts.html")); + } } void MainWindow::on_actionAbout_triggered() { - AboutDialog dialog(this); - dialog.exec(); -} - -void MainWindow::on_mainToolBar_visibilityChanged(bool) -{ - // Don't allow hiding the main toolbar. - // This is the only way I could find to prevent it... :/ - ui->mainToolBar->setVisible(true); + AboutDialog dialog(this); + dialog.exec(); } void MainWindow::on_actionDeleteInstance_triggered() { - if (!m_selectedInstance) - { - return; - } - auto response = CustomMessageBox::selectable( - this, - tr("CAREFUL!"), - tr("About to delete: %1\nThis is permanent and will completely delete the instance.\n\nAre you sure?").arg(m_selectedInstance->name()), - QMessageBox::Warning, - QMessageBox::Yes | QMessageBox::No - )->exec(); - if (response == QMessageBox::Yes) - { - m_selectedInstance->nuke(); - } + if (!m_selectedInstance) + { + return; + } + auto id = m_selectedInstance->id(); + auto response = CustomMessageBox::selectable( + this, + tr("CAREFUL!"), + tr("About to delete: %1\nThis is permanent and will completely delete the instance.\n\nAre you sure?").arg(m_selectedInstance->name()), + QMessageBox::Warning, + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No + )->exec(); + if (response == QMessageBox::Yes) + { + MMC->instances()->deleteInstance(id); + } } void MainWindow::on_actionExportInstance_triggered() { - if (m_selectedInstance) - { - ExportInstanceDialog dlg(m_selectedInstance, this); - dlg.exec(); - } + if (m_selectedInstance) + { + ExportInstanceDialog dlg(m_selectedInstance, this); + dlg.exec(); + } } void MainWindow::on_actionRenameInstance_triggered() { - if (m_selectedInstance) - { - bool ok = false; - QString name(m_selectedInstance->name()); - name = QInputDialog::getText(this, tr("Instance name"), tr("Enter a new instance name."), QLineEdit::Normal, name, &ok); - - name = name.trimmed(); - if (name.length() > 0) - { - if (ok && name.length()) - { - m_selectedInstance->setName(name); - ui->renameButton->setText(name); - } - } - } + if (m_selectedInstance) + { + view->edit(view->currentIndex()); + } } void MainWindow::on_actionViewSelectedInstFolder_triggered() { - if (m_selectedInstance) - { - QString str = m_selectedInstance->instanceRoot(); - DesktopServices::openDirectory(QDir(str).absolutePath()); - } + if (m_selectedInstance) + { + QString str = m_selectedInstance->instanceRoot(); + DesktopServices::openDirectory(QDir(str).absolutePath()); + } } +void MainWindow::on_actionViewSelectedMCFolder_triggered() +{ + if (m_selectedInstance) + { + QString str = m_selectedInstance->gameRoot(); + if (!FS::ensureFilePathExists(str)) + { + // TODO: report error + return; + } + DesktopServices::openDirectory(QDir(str).absolutePath()); + } +} + + void MainWindow::closeEvent(QCloseEvent *event) { - // Save the window state and geometry. - MMC->settings()->set("MainWindowState", saveState().toBase64()); - MMC->settings()->set("MainWindowGeometry", saveGeometry().toBase64()); - event->accept(); - emit isClosing(); + // Save the window state and geometry. + MMC->settings()->set("MainWindowState", saveState().toBase64()); + MMC->settings()->set("MainWindowGeometry", saveGeometry().toBase64()); + event->accept(); + emit isClosing(); } void MainWindow::changeEvent(QEvent* event) { - if (event->type() == QEvent::LanguageChange) - { - ui->retranslateUi(this); - } - QMainWindow::changeEvent(event); + if (event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } + QMainWindow::changeEvent(event); } void MainWindow::instanceActivated(QModelIndex index) { - if (!index.isValid()) - return; - QString id = index.data(InstanceList::InstanceIDRole).toString(); - InstancePtr inst = MMC->instances()->getInstanceById(id); - if (!inst) - return; + if (!index.isValid()) + return; + QString id = index.data(InstanceList::InstanceIDRole).toString(); + InstancePtr inst = MMC->instances()->getInstanceById(id); + if (!inst) + return; - activateInstance(inst); + activateInstance(inst); } void MainWindow::on_actionLaunchInstance_triggered() { - if (!m_selectedInstance) - { - return; - } - if(m_selectedInstance->isRunning()) - { - MMC->kill(m_selectedInstance); - } - else - { - MMC->launch(m_selectedInstance); - } + if (!m_selectedInstance) + { + return; + } + if(m_selectedInstance->isRunning()) + { + MMC->kill(m_selectedInstance); + } + else + { + MMC->launch(m_selectedInstance); + } } void MainWindow::activateInstance(InstancePtr instance) { - MMC->launch(instance); + MMC->launch(instance); } void MainWindow::on_actionLaunchInstanceOffline_triggered() { - if (m_selectedInstance) - { - MMC->launch(m_selectedInstance, false); - } + if (m_selectedInstance) + { + MMC->launch(m_selectedInstance, false); + } } void MainWindow::taskEnd() { - QObject *sender = QObject::sender(); - if (sender == m_versionLoadTask) - m_versionLoadTask = NULL; + QObject *sender = QObject::sender(); + if (sender == m_versionLoadTask) + m_versionLoadTask = NULL; - sender->deleteLater(); + sender->deleteLater(); } void MainWindow::startTask(Task *task) { - connect(task, SIGNAL(succeeded()), SLOT(taskEnd())); - connect(task, SIGNAL(failed(QString)), SLOT(taskEnd())); - task->start(); + connect(task, SIGNAL(succeeded()), SLOT(taskEnd())); + connect(task, SIGNAL(failed(QString)), SLOT(taskEnd())); + task->start(); } void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &previous) { - if (!current.isValid()) - { - MMC->settings()->set("SelectedInstance", QString()); - selectionBad(); - return; - } - QString id = current.data(InstanceList::InstanceIDRole).toString(); - m_selectedInstance = MMC->instances()->getInstanceById(id); - if (m_selectedInstance) - { - ui->instanceToolBar->setEnabled(true); - if(m_selectedInstance->isRunning()) - { - ui->actionLaunchInstance->setEnabled(true); - ui->setLaunchAction(true); - } - else - { - ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); - ui->setLaunchAction(false); - } - ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch()); - ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); - ui->renameButton->setText(m_selectedInstance->name()); - m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); - updateInstanceToolIcon(m_selectedInstance->iconKey()); - - updateToolsMenu(); - - MMC->settings()->set("SelectedInstance", m_selectedInstance->id()); - } - else - { - ui->instanceToolBar->setEnabled(false); - MMC->settings()->set("SelectedInstance", QString()); - selectionBad(); - return; - } + if (!current.isValid()) + { + MMC->settings()->set("SelectedInstance", QString()); + selectionBad(); + return; + } + QString id = current.data(InstanceList::InstanceIDRole).toString(); + m_selectedInstance = MMC->instances()->getInstanceById(id); + if (m_selectedInstance) + { + ui->instanceToolBar->setEnabled(true); + if(m_selectedInstance->isRunning()) + { + ui->actionLaunchInstance->setEnabled(true); + ui->setLaunchAction(true); + } + else + { + ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); + ui->setLaunchAction(false); + } + ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch()); + ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); + ui->renameButton->setText(m_selectedInstance->name()); + m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); + updateInstanceToolIcon(m_selectedInstance->iconKey()); + + updateToolsMenu(); + + MMC->settings()->set("SelectedInstance", m_selectedInstance->id()); + } + else + { + ui->instanceToolBar->setEnabled(false); + MMC->settings()->set("SelectedInstance", QString()); + selectionBad(); + return; + } +} + +void MainWindow::instanceSelectRequest(QString id) +{ + setSelectedInstanceById(id); } void MainWindow::instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { - auto current = view->selectionModel()->currentIndex(); - QItemSelection test(topLeft, bottomRight); - if (test.contains(current)) - { - instanceChanged(current, current); - } + auto current = view->selectionModel()->currentIndex(); + QItemSelection test(topLeft, bottomRight); + if (test.contains(current)) + { + instanceChanged(current, current); + } } void MainWindow::selectionBad() { - // start by reseting everything... - m_selectedInstance = nullptr; + // start by reseting everything... + m_selectedInstance = nullptr; - statusBar()->clearMessage(); - ui->instanceToolBar->setEnabled(false); - ui->renameButton->setText(tr("Rename Instance")); - updateInstanceToolIcon("infinity"); + statusBar()->clearMessage(); + ui->instanceToolBar->setEnabled(false); + ui->renameButton->setText(tr("Rename Instance")); + updateInstanceToolIcon("infinity"); - // ...and then see if we can enable the previously selected instance - setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString()); + // ...and then see if we can enable the previously selected instance + setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString()); } void MainWindow::checkInstancePathForProblems() { - QString instanceFolder = MMC->settings()->get("InstanceDir").toString(); - if (FS::checkProblemticPathJava(QDir(instanceFolder))) - { - QMessageBox warning(this); - warning.setText(tr("Your instance folder contains \'!\' and this is known to cause Java problems!")); - warning.setInformativeText(tr("You have now two options: <br/>" - " - change the instance folder in the settings <br/>" - " - move this installation of MultiMC5 to a different folder")); - warning.setDefaultButton(QMessageBox::Ok); - warning.exec(); - } - auto tempFolderText = tr("This is a problem: <br/>" - " - MultiMC will likely be deleted without warning by the operating system <br/>" - " - close MultiMC now and extract it to a real location, not a temporary folder"); - QString pathfoldername = QDir(instanceFolder).absolutePath(); - if (pathfoldername.contains("Rar$", Qt::CaseInsensitive)) - { - QMessageBox warning(this); - warning.setText(tr("Your instance folder contains \'Rar$\' - that means you haven't extracted the MultiMC zip!")); - warning.setInformativeText(tempFolderText); - warning.setDefaultButton(QMessageBox::Ok); - warning.exec(); - } - else if (pathfoldername.contains(QDir::tempPath())) - { - QMessageBox warning(this); - warning.setText(tr("Your instance folder is in a temporary folder: \'%1\'!").arg(QDir::tempPath())); - warning.setInformativeText(tempFolderText); - warning.setDefaultButton(QMessageBox::Ok); - warning.exec(); - } + QString instanceFolder = MMC->settings()->get("InstanceDir").toString(); + if (FS::checkProblemticPathJava(QDir(instanceFolder))) + { + QMessageBox warning(this); + warning.setText(tr("Your instance folder contains \'!\' and this is known to cause Java problems!")); + warning.setInformativeText(tr("You have now two options: <br/>" + " - change the instance folder in the settings <br/>" + " - move this installation of MultiMC5 to a different folder")); + warning.setDefaultButton(QMessageBox::Ok); + warning.exec(); + } + auto tempFolderText = tr("This is a problem: <br/>" + " - MultiMC will likely be deleted without warning by the operating system <br/>" + " - close MultiMC now and extract it to a real location, not a temporary folder"); + QString pathfoldername = QDir(instanceFolder).absolutePath(); + if (pathfoldername.contains("Rar$", Qt::CaseInsensitive)) + { + QMessageBox warning(this); + warning.setText(tr("Your instance folder contains \'Rar$\' - that means you haven't extracted the MultiMC zip!")); + warning.setInformativeText(tempFolderText); + warning.setDefaultButton(QMessageBox::Ok); + warning.exec(); + } + else if (pathfoldername.startsWith(QDir::tempPath()) || pathfoldername.contains("/TempState/")) + { + QMessageBox warning(this); + warning.setText(tr("Your instance folder is in a temporary folder: \'%1\'!").arg(QDir::tempPath())); + warning.setInformativeText(tempFolderText); + warning.setDefaultButton(QMessageBox::Ok); + warning.exec(); + } } diff --git a/application/MainWindow.h b/application/MainWindow.h index 8f756412..a415b5e8 100644 --- a/application/MainWindow.h +++ b/application/MainWindow.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,174 +42,181 @@ class InstanceTask; class MainWindow : public QMainWindow { - Q_OBJECT + Q_OBJECT - class Ui; + class Ui; public: - explicit MainWindow(QWidget *parent = 0); - ~MainWindow(); + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); - bool eventFilter(QObject *obj, QEvent *ev) override; - void closeEvent(QCloseEvent *event) override; - void changeEvent(QEvent * event) override; + bool eventFilter(QObject *obj, QEvent *ev) override; + void closeEvent(QCloseEvent *event) override; + void changeEvent(QEvent * event) override; - void checkInstancePathForProblems(); + void checkInstancePathForProblems(); - void updatesAllowedChanged(bool allowed); + void updatesAllowedChanged(bool allowed); signals: - void isClosing(); + void isClosing(); + +protected: + QMenu * createPopupMenu() override; private slots: - void onCatToggled(bool); + void onCatToggled(bool); + + void on_actionAbout_triggered(); + + void on_actionAddInstance_triggered(); - void on_actionAbout_triggered(); + void on_actionREDDIT_triggered(); - void on_actionAddInstance_triggered(); + void on_actionDISCORD_triggered(); - void on_actionREDDIT_triggered(); + void on_actionCopyInstance_triggered(); - void on_actionDISCORD_triggered(); + void on_actionChangeInstGroup_triggered(); - void on_actionCopyInstance_triggered(); + void on_actionChangeInstIcon_triggered(); + void on_changeIconButton_clicked(bool) + { + on_actionChangeInstIcon_triggered(); + } - void on_actionChangeInstGroup_triggered(); + void on_actionViewInstanceFolder_triggered(); - void on_actionChangeInstIcon_triggered(); - void on_changeIconButton_clicked(bool) - { - on_actionChangeInstIcon_triggered(); - } + void on_actionConfig_Folder_triggered(); - void on_actionViewInstanceFolder_triggered(); + void on_actionViewSelectedInstFolder_triggered(); - void on_actionConfig_Folder_triggered(); + void on_actionViewSelectedMCFolder_triggered(); - void on_actionViewSelectedInstFolder_triggered(); + void refreshInstances(); - void refreshInstances(); + void on_actionViewCentralModsFolder_triggered(); - void on_actionViewCentralModsFolder_triggered(); + void checkForUpdates(); - void checkForUpdates(); + void on_actionSettings_triggered(); - void on_actionSettings_triggered(); + void on_actionInstanceSettings_triggered(); - void on_actionInstanceSettings_triggered(); + void on_actionManageAccounts_triggered(); - void on_actionManageAccounts_triggered(); + void on_actionReportBug_triggered(); - void on_actionReportBug_triggered(); + void on_actionPatreon_triggered(); - void on_actionPatreon_triggered(); + void on_actionMoreNews_triggered(); - void on_actionMoreNews_triggered(); + void newsButtonClicked(); - void newsButtonClicked(); + void on_actionLaunchInstance_triggered(); - void on_mainToolBar_visibilityChanged(bool); + void on_actionLaunchInstanceOffline_triggered(); - void on_actionLaunchInstance_triggered(); + void on_actionDeleteInstance_triggered(); - void on_actionLaunchInstanceOffline_triggered(); + void deleteGroup(); - void on_actionDeleteInstance_triggered(); + void on_actionExportInstance_triggered(); - void deleteGroup(); + void on_actionRenameInstance_triggered(); + void on_renameButton_clicked(bool) + { + on_actionRenameInstance_triggered(); + } - void on_actionExportInstance_triggered(); + void on_actionEditInstance_triggered(); - void on_actionRenameInstance_triggered(); - void on_renameButton_clicked(bool) - { - on_actionRenameInstance_triggered(); - } + void on_actionEditInstNotes_triggered(); - void on_actionEditInstance_triggered(); + void on_actionWorlds_triggered(); - void on_actionEditInstNotes_triggered(); + void on_actionScreenshots_triggered(); - void on_actionWorlds_triggered(); + void taskEnd(); - void on_actionScreenshots_triggered(); + /** + * called when an icon is changed in the icon model. + */ + void iconUpdated(QString); - void taskEnd(); + void showInstanceContextMenu(const QPoint &); - /** - * called when an icon is changed in the icon model. - */ - void iconUpdated(QString); + void updateToolsMenu(); - void showInstanceContextMenu(const QPoint &); + void skinJobFinished(); - void updateToolsMenu(); + void instanceActivated(QModelIndex); - void skinJobFinished(); + void instanceChanged(const QModelIndex ¤t, const QModelIndex &previous); - void instanceActivated(QModelIndex); + void instanceSelectRequest(QString id); - void instanceChanged(const QModelIndex ¤t, const QModelIndex &previous); + void instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); - void instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void selectionBad(); - void selectionBad(); + void startTask(Task *task); - void startTask(Task *task); + void updateAvailable(GoUpdate::Status status); - void updateAvailable(GoUpdate::Status status); + void updateNotAvailable(); - void updateNotAvailable(); + void notificationsChanged(); - void notificationsChanged(); + void activeAccountChanged(); - void activeAccountChanged(); + void changeActiveAccount(); - void changeActiveAccount(); + void repopulateAccountsMenu(); - void repopulateAccountsMenu(); + void updateNewsLabel(); - void updateNewsLabel(); + /*! + * Runs the DownloadTask and installs updates. + */ + void downloadUpdates(GoUpdate::Status status); - /*! - * Runs the DownloadTask and installs updates. - */ - void downloadUpdates(GoUpdate::Status status); + void droppedURLs(QList<QUrl> urls); - void droppedURLs(QList<QUrl> urls); + void konamiTriggered(); - void konamiTriggered(); + void globalSettingsClosed(); private: - void addInstance(QString url = QString()); - void activateInstance(InstancePtr instance); - void setCatBackground(bool enabled); - void updateInstanceToolIcon(QString new_icon); - void setSelectedInstanceById(const QString &id); + void addInstance(QString url = QString()); + void activateInstance(InstancePtr instance); + void setCatBackground(bool enabled); + void updateInstanceToolIcon(QString new_icon); + void setSelectedInstanceById(const QString &id); - void runModalTask(Task *task); - void instanceFromInstanceTask(InstanceTask *task); - void finalizeInstance(InstancePtr inst); + void runModalTask(Task *task); + void instanceFromInstanceTask(InstanceTask *task); + void finalizeInstance(InstancePtr inst); private: - std::unique_ptr<Ui> ui; - - // these are managed by Qt's memory management model! - GroupView *view = nullptr; - InstanceProxyModel *proxymodel = nullptr; - QToolButton *newsLabel = nullptr; - QLabel *m_statusLeft = nullptr; - ServerStatus *m_statusRight = nullptr; - QMenu *accountMenu = nullptr; - QToolButton *accountMenuButton = nullptr; - KonamiCode * secretEventFilter = nullptr; - - unique_qobject_ptr<NetJob> skin_download_job; - unique_qobject_ptr<NewsChecker> m_newsChecker; - unique_qobject_ptr<NotificationChecker> m_notificationChecker; - - InstancePtr m_selectedInstance; - QString m_currentInstIcon; - - // managed by the application object - Task *m_versionLoadTask = nullptr; + std::unique_ptr<Ui> ui; + + // these are managed by Qt's memory management model! + GroupView *view = nullptr; + InstanceProxyModel *proxymodel = nullptr; + QToolButton *newsLabel = nullptr; + QLabel *m_statusLeft = nullptr; + ServerStatus *m_statusRight = nullptr; + QMenu *accountMenu = nullptr; + QToolButton *accountMenuButton = nullptr; + KonamiCode * secretEventFilter = nullptr; + + unique_qobject_ptr<NetJob> skin_download_job; + unique_qobject_ptr<NewsChecker> m_newsChecker; + unique_qobject_ptr<NotificationChecker> m_notificationChecker; + + InstancePtr m_selectedInstance; + QString m_currentInstIcon; + + // managed by the application object + Task *m_versionLoadTask = nullptr; }; diff --git a/application/MultiMC.cpp b/application/MultiMC.cpp index 9e35717a..5626b672 100644 --- a/application/MultiMC.cpp +++ b/application/MultiMC.cpp @@ -2,10 +2,15 @@ #include "BuildConfig.h" #include "MainWindow.h" #include "InstanceWindow.h" + +#include "groupview/AccessibleGroupView.h" +#include <QAccessible> + #include "pages/BasePageProvider.h" #include "pages/global/MultiMCPage.h" #include "pages/global/MinecraftPage.h" #include "pages/global/JavaPage.h" +#include "pages/global/LanguagePage.h" #include "pages/global/ProxyPage.h" #include "pages/global/ExternalToolsPage.h" #include "pages/global/AccountListPage.h" @@ -36,7 +41,6 @@ #include "dialogs/CustomMessageBox.h" #include "InstanceList.h" -#include "FolderInstanceProvider.h" #include <minecraft/auth/MojangAccountList.h> #include "icons/IconList.h" @@ -65,6 +69,8 @@ #include <sys.h> +#include "pagedialog/PageDialog.h" + #if defined Q_OS_WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -82,1148 +88,1041 @@ static const QLatin1String liveCheckFile("live.check"); using namespace Commandline; #define MACOS_HINT "If you are on macOS Sierra, you might have to move MultiMC.app to your /Applications or ~/Applications folder. "\ - "This usually fixes the problem and you can move the application elsewhere afterwards.\n"\ - "\n" + "This usually fixes the problem and you can move the application elsewhere afterwards.\n"\ + "\n" static void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - const char *levels = "DWCFIS"; - const QString format("%1 %2 %3\n"); - - qint64 msecstotal = MMC->timeSinceStart(); - qint64 seconds = msecstotal / 1000; - qint64 msecs = msecstotal % 1000; - QString foo; - char buf[1025] = {0}; - ::snprintf(buf, 1024, "%5lld.%03lld", seconds, msecs); - - QString out = format.arg(buf).arg(levels[type]).arg(msg); - - MMC->logFile->write(out.toUtf8()); - MMC->logFile->flush(); - QTextStream(stderr) << out.toLocal8Bit(); - fflush(stderr); + const char *levels = "DWCFIS"; + const QString format("%1 %2 %3\n"); + + qint64 msecstotal = MMC->timeSinceStart(); + qint64 seconds = msecstotal / 1000; + qint64 msecs = msecstotal % 1000; + QString foo; + char buf[1025] = {0}; + ::snprintf(buf, 1024, "%5lld.%03lld", seconds, msecs); + + QString out = format.arg(buf).arg(levels[type]).arg(msg); + + MMC->logFile->write(out.toUtf8()); + MMC->logFile->flush(); + QTextStream(stderr) << out.toLocal8Bit(); + fflush(stderr); } MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) { #if defined Q_OS_WIN32 - // attach the parent console - if(AttachConsole(ATTACH_PARENT_PROCESS)) - { - // if attach succeeds, reopen and sync all the i/o - if(freopen("CON", "w", stdout)) - { - std::cout.sync_with_stdio(); - } - if(freopen("CON", "w", stderr)) - { - std::cerr.sync_with_stdio(); - } - if(freopen("CON", "r", stdin)) - { - std::cin.sync_with_stdio(); - } - auto out = GetStdHandle (STD_OUTPUT_HANDLE); - DWORD written; - const char * endline = "\n"; - WriteConsole(out, endline, strlen(endline), &written, NULL); - consoleAttached = true; - } + // attach the parent console + if(AttachConsole(ATTACH_PARENT_PROCESS)) + { + // if attach succeeds, reopen and sync all the i/o + if(freopen("CON", "w", stdout)) + { + std::cout.sync_with_stdio(); + } + if(freopen("CON", "w", stderr)) + { + std::cerr.sync_with_stdio(); + } + if(freopen("CON", "r", stdin)) + { + std::cin.sync_with_stdio(); + } + auto out = GetStdHandle (STD_OUTPUT_HANDLE); + DWORD written; + const char * endline = "\n"; + WriteConsole(out, endline, strlen(endline), &written, NULL); + consoleAttached = true; + } #endif - setOrganizationName("MultiMC"); - setOrganizationDomain("multimc.org"); - setApplicationName("MultiMC5"); - setApplicationDisplayName("MultiMC 5"); - setApplicationVersion(BuildConfig.printableVersionString()); - - startTime = QDateTime::currentDateTime(); - - // Don't quit on hiding the last window - this->setQuitOnLastWindowClosed(false); - - // Commandline parsing - QHash<QString, QVariant> args; - { - Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals); - - // --help - parser.addSwitch("help"); - parser.addShortOpt("help", 'h'); - parser.addDocumentation("help", "display this help and exit."); - // --version - parser.addSwitch("version"); - parser.addShortOpt("version", 'V'); - parser.addDocumentation("version", "display program version and exit."); - // --dir - parser.addOption("dir"); - parser.addShortOpt("dir", 'd'); - parser.addDocumentation("dir", "use the supplied folder as MultiMC root instead of " - "the binary location (use '.' for current)"); - // --launch - parser.addOption("launch"); - parser.addShortOpt("launch", 'l'); - parser.addDocumentation("launch", "launch the specified instance (by instance ID)"); - // --alive - parser.addSwitch("alive"); - parser.addDocumentation("alive", "write a small '" + liveCheckFile + "' file after MultiMC starts"); - - // parse the arguments - try - { - args = parser.parse(arguments()); - } - catch (ParsingError e) - { - std::cerr << "CommandLineError: " << e.what() << std::endl; - std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters." - << std::endl; - m_status = MultiMC::Failed; - return; - } - - // display help and exit - if (args["help"].toBool()) - { - std::cout << qPrintable(parser.compileHelp(arguments()[0])); - m_status = MultiMC::Succeeded; - return; - } - - // display version and exit - if (args["version"].toBool()) - { - std::cout << "Version " << BuildConfig.printableVersionString().toStdString() << std::endl; - std::cout << "Git " << BuildConfig.GIT_COMMIT.toStdString() << std::endl; - m_status = MultiMC::Succeeded; - return; - } - } - m_instanceIdToLaunch = args["launch"].toString(); - m_liveCheck = args["alive"].toBool(); - - QString origcwdPath = QDir::currentPath(); - QString binPath = applicationDirPath(); - QString adjustedBy; - QString dataPath; - // change folder - QString dirParam = args["dir"].toString(); - if (!dirParam.isEmpty()) - { - // the dir param. it makes multimc data path point to whatever the user specified - // on command line - adjustedBy += "Command line " + dirParam; - dataPath = dirParam; - } - else - { + setOrganizationName("MultiMC"); + setOrganizationDomain("multimc.org"); + setApplicationName("MultiMC5"); + setApplicationDisplayName("MultiMC 5"); + setApplicationVersion(BuildConfig.printableVersionString()); + + startTime = QDateTime::currentDateTime(); + + // Don't quit on hiding the last window + this->setQuitOnLastWindowClosed(false); + + // Commandline parsing + QHash<QString, QVariant> args; + { + Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals); + + // --help + parser.addSwitch("help"); + parser.addShortOpt("help", 'h'); + parser.addDocumentation("help", "display this help and exit."); + // --version + parser.addSwitch("version"); + parser.addShortOpt("version", 'V'); + parser.addDocumentation("version", "display program version and exit."); + // --dir + parser.addOption("dir"); + parser.addShortOpt("dir", 'd'); + parser.addDocumentation("dir", "use the supplied folder as MultiMC root instead of " + "the binary location (use '.' for current)"); + // --launch + parser.addOption("launch"); + parser.addShortOpt("launch", 'l'); + parser.addDocumentation("launch", "launch the specified instance (by instance ID)"); + // --alive + parser.addSwitch("alive"); + parser.addDocumentation("alive", "write a small '" + liveCheckFile + "' file after MultiMC starts"); + + // parse the arguments + try + { + args = parser.parse(arguments()); + } + catch (const ParsingError &e) + { + std::cerr << "CommandLineError: " << e.what() << std::endl; + std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters." + << std::endl; + m_status = MultiMC::Failed; + return; + } + + // display help and exit + if (args["help"].toBool()) + { + std::cout << qPrintable(parser.compileHelp(arguments()[0])); + m_status = MultiMC::Succeeded; + return; + } + + // display version and exit + if (args["version"].toBool()) + { + std::cout << "Version " << BuildConfig.printableVersionString().toStdString() << std::endl; + std::cout << "Git " << BuildConfig.GIT_COMMIT.toStdString() << std::endl; + m_status = MultiMC::Succeeded; + return; + } + } + m_instanceIdToLaunch = args["launch"].toString(); + m_liveCheck = args["alive"].toBool(); + + QString origcwdPath = QDir::currentPath(); + QString binPath = applicationDirPath(); + QString adjustedBy; + QString dataPath; + // change folder + QString dirParam = args["dir"].toString(); + if (!dirParam.isEmpty()) + { + // the dir param. it makes multimc data path point to whatever the user specified + // on command line + adjustedBy += "Command line " + dirParam; + dataPath = dirParam; + } + else + { #ifdef MULTIMC_LINUX_DATADIR - QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME")); - if (xdgDataHome.isEmpty()) - xdgDataHome = QDir::homePath() + QLatin1String("/.local/share"); - dataPath = xdgDataHome + "/multimc"; - adjustedBy += "XDG standard " + dataPath; + QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME")); + if (xdgDataHome.isEmpty()) + xdgDataHome = QDir::homePath() + QLatin1String("/.local/share"); + dataPath = xdgDataHome + "/multimc"; + adjustedBy += "XDG standard " + dataPath; #else - dataPath = applicationDirPath(); - adjustedBy += "Fallback to binary path " + dataPath; + dataPath = applicationDirPath(); + adjustedBy += "Fallback to binary path " + dataPath; #endif - } - - if (!FS::ensureFolderPathExists(dataPath)) - { - showFatalErrorMessage( - "MultiMC data folder could not be created.", - "MultiMC data folder could not be created.\n" - "\n" + } + + if (!FS::ensureFolderPathExists(dataPath)) + { + showFatalErrorMessage( + "MultiMC data folder could not be created.", + "MultiMC data folder could not be created.\n" + "\n" #if defined(Q_OS_MAC) - MACOS_HINT + MACOS_HINT #endif - "Make sure you have the right permissions to the MultiMC data folder and any folder needed to access it.\n" - "\n" - "MultiMC cannot continue until you fix this problem." - ); - return; - } - if (!QDir::setCurrent(dataPath)) - { - showFatalErrorMessage( - "MultiMC data folder could not be opened.", - "MultiMC data folder could not be opened.\n" - "\n" + "Make sure you have the right permissions to the MultiMC data folder and any folder needed to access it.\n" + "\n" + "MultiMC cannot continue until you fix this problem." + ); + return; + } + if (!QDir::setCurrent(dataPath)) + { + showFatalErrorMessage( + "MultiMC data folder could not be opened.", + "MultiMC data folder could not be opened.\n" + "\n" #if defined(Q_OS_MAC) - MACOS_HINT + MACOS_HINT #endif - "Make sure you have the right permissions to the MultiMC data folder.\n" - "\n" - "MultiMC cannot continue until you fix this problem." - ); - return; - } - - /* - * Establish the mechanism for communication with an already running MultiMC that uses the same data path. - * If there is one, tell it what the user actually wanted to do and exit. - * We want to initialize this before logging to avoid messing with the log of a potential already running copy. - */ - auto appID = ApplicationId::fromPathAndVersion(QDir::currentPath(), BuildConfig.printableVersionString()); - { - // FIXME: you can run the same binaries with multiple data dirs and they won't clash. This could cause issues for updates. - m_peerInstance = new LocalPeer(this, appID); - connect(m_peerInstance, &LocalPeer::messageReceived, this, &MultiMC::messageReceived); - if(m_peerInstance->isClient()) - { - if(m_instanceIdToLaunch.isEmpty()) - { - m_peerInstance->sendMessage("activate", 2000); - } - else - { - m_peerInstance->sendMessage(m_instanceIdToLaunch, 2000); - } - m_status = MultiMC::Succeeded; - return; - } - } - - // init the logger - { - static const QString logBase = "MultiMC-%0.log"; - auto moveFile = [](const QString &oldName, const QString &newName) - { - QFile::remove(newName); - QFile::copy(oldName, newName); - QFile::remove(oldName); - }; - - moveFile(logBase.arg(3), logBase.arg(4)); - moveFile(logBase.arg(2), logBase.arg(3)); - moveFile(logBase.arg(1), logBase.arg(2)); - moveFile(logBase.arg(0), logBase.arg(1)); - - logFile = std::unique_ptr<QFile>(new QFile(logBase.arg(0))); - if(!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) - { - showFatalErrorMessage( - "MultiMC data folder is not writable!", - "MultiMC couldn't create a log file - the MultiMC data folder is not writable.\n" - "\n" - #if defined(Q_OS_MAC) - MACOS_HINT - #endif - "Make sure you have write permissions to the MultiMC data folder.\n" - "\n" - "MultiMC cannot continue until you fix this problem." - ); - return; - } - qInstallMessageHandler(appDebugOutput); - qDebug() << "<> Log initialized."; - } - - // Set up paths - { - // Root path is used for updates. + "Make sure you have the right permissions to the MultiMC data folder.\n" + "\n" + "MultiMC cannot continue until you fix this problem." + ); + return; + } + + /* + * Establish the mechanism for communication with an already running MultiMC that uses the same data path. + * If there is one, tell it what the user actually wanted to do and exit. + * We want to initialize this before logging to avoid messing with the log of a potential already running copy. + */ + auto appID = ApplicationId::fromPathAndVersion(QDir::currentPath(), BuildConfig.printableVersionString()); + { + // FIXME: you can run the same binaries with multiple data dirs and they won't clash. This could cause issues for updates. + m_peerInstance = new LocalPeer(this, appID); + connect(m_peerInstance, &LocalPeer::messageReceived, this, &MultiMC::messageReceived); + if(m_peerInstance->isClient()) + { + if(m_instanceIdToLaunch.isEmpty()) + { + m_peerInstance->sendMessage("activate", 2000); + } + else + { + m_peerInstance->sendMessage(m_instanceIdToLaunch, 2000); + } + m_status = MultiMC::Succeeded; + return; + } + } + + // init the logger + { + static const QString logBase = "MultiMC-%0.log"; + auto moveFile = [](const QString &oldName, const QString &newName) + { + QFile::remove(newName); + QFile::copy(oldName, newName); + QFile::remove(oldName); + }; + + moveFile(logBase.arg(3), logBase.arg(4)); + moveFile(logBase.arg(2), logBase.arg(3)); + moveFile(logBase.arg(1), logBase.arg(2)); + moveFile(logBase.arg(0), logBase.arg(1)); + + logFile = std::unique_ptr<QFile>(new QFile(logBase.arg(0))); + if(!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + { + showFatalErrorMessage( + "MultiMC data folder is not writable!", + "MultiMC couldn't create a log file - the MultiMC data folder is not writable.\n" + "\n" + #if defined(Q_OS_MAC) + MACOS_HINT + #endif + "Make sure you have write permissions to the MultiMC data folder.\n" + "\n" + "MultiMC cannot continue until you fix this problem." + ); + return; + } + qInstallMessageHandler(appDebugOutput); + qDebug() << "<> Log initialized."; + } + + // Set up paths + { + // Root path is used for updates. #ifdef Q_OS_LINUX - QDir foo(FS::PathCombine(binPath, "..")); - m_rootPath = foo.absolutePath(); + QDir foo(FS::PathCombine(binPath, "..")); + m_rootPath = foo.absolutePath(); #elif defined(Q_OS_WIN32) - m_rootPath = binPath; + m_rootPath = binPath; #elif defined(Q_OS_MAC) - QDir foo(FS::PathCombine(binPath, "../..")); - m_rootPath = foo.absolutePath(); - // on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues) - FS::updateTimestamp(m_rootPath); + QDir foo(FS::PathCombine(binPath, "../..")); + m_rootPath = foo.absolutePath(); + // on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues) + FS::updateTimestamp(m_rootPath); #endif #ifdef MULTIMC_JARS_LOCATION - ENV.setJarsPath( TOSTRING(MULTIMC_JARS_LOCATION) ); + ENV.setJarsPath( TOSTRING(MULTIMC_JARS_LOCATION) ); #endif - qDebug() << "MultiMC 5, (c) 2013-2018 MultiMC Contributors"; - qDebug() << "Version : " << BuildConfig.printableVersionString(); - qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; - qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; - if (adjustedBy.size()) - { - qDebug() << "Work dir before adjustment : " << origcwdPath; - qDebug() << "Work dir after adjustment : " << QDir::currentPath(); - qDebug() << "Adjusted by : " << adjustedBy; - } - else - { - qDebug() << "Work dir : " << QDir::currentPath(); - } - qDebug() << "Binary path : " << binPath; - qDebug() << "Application root path : " << m_rootPath; - if(!m_instanceIdToLaunch.isEmpty()) - { - qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch; - } - qDebug() << "<> Paths set."; - } - - do // once - { - if(m_liveCheck) - { - QFile check(liveCheckFile); - if(!check.open(QIODevice::WriteOnly | QIODevice::Truncate)) - { - qWarning() << "Could not open" << liveCheckFile << "for writing!"; - break; - } - auto payload = appID.toString().toUtf8(); - if(check.write(payload) != payload.size()) - { - qWarning() << "Could not write into" << liveCheckFile; - check.remove(); - break; - } - check.close(); - } - } while(false); - - // Initialize application settings - { - m_settings.reset(new INISettingsObject("multimc.cfg", this)); - // Updates - m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL); - m_settings->registerSetting("AutoUpdate", true); - - // Theming - m_settings->registerSetting("IconTheme", QString("multimc")); - m_settings->registerSetting("ApplicationTheme", QString("system")); - - // Notifications - m_settings->registerSetting("ShownNotifications", QString()); - - // Remembered state - m_settings->registerSetting("LastUsedGroupForNewInstance", QString()); - - QString defaultMonospace; - int defaultSize = 11; + qDebug() << "MultiMC 5, (c) 2013-2019 MultiMC Contributors"; + qDebug() << "Version : " << BuildConfig.printableVersionString(); + qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; + qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; + if (adjustedBy.size()) + { + qDebug() << "Work dir before adjustment : " << origcwdPath; + qDebug() << "Work dir after adjustment : " << QDir::currentPath(); + qDebug() << "Adjusted by : " << adjustedBy; + } + else + { + qDebug() << "Work dir : " << QDir::currentPath(); + } + qDebug() << "Binary path : " << binPath; + qDebug() << "Application root path : " << m_rootPath; + if(!m_instanceIdToLaunch.isEmpty()) + { + qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch; + } + qDebug() << "<> Paths set."; + } + + do // once + { + if(m_liveCheck) + { + QFile check(liveCheckFile); + if(!check.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + qWarning() << "Could not open" << liveCheckFile << "for writing!"; + break; + } + auto payload = appID.toString().toUtf8(); + if(check.write(payload) != payload.size()) + { + qWarning() << "Could not write into" << liveCheckFile; + check.remove(); + break; + } + check.close(); + } + } while(false); + + // Initialize application settings + { + m_settings.reset(new INISettingsObject("multimc.cfg", this)); + // Updates + m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL); + m_settings->registerSetting("AutoUpdate", true); + + // Theming + m_settings->registerSetting("IconTheme", QString("multimc")); + m_settings->registerSetting("ApplicationTheme", QString("system")); + + // Notifications + m_settings->registerSetting("ShownNotifications", QString()); + + // Remembered state + m_settings->registerSetting("LastUsedGroupForNewInstance", QString()); + + QString defaultMonospace; + int defaultSize = 11; #ifdef Q_OS_WIN32 - defaultMonospace = "Courier"; - defaultSize = 10; + defaultMonospace = "Courier"; + defaultSize = 10; #elif defined(Q_OS_MAC) - defaultMonospace = "Menlo"; + defaultMonospace = "Menlo"; #else - defaultMonospace = "Monospace"; + defaultMonospace = "Monospace"; #endif - // resolve the font so the default actually matches - QFont consoleFont; - consoleFont.setFamily(defaultMonospace); - consoleFont.setStyleHint(QFont::Monospace); - consoleFont.setFixedPitch(true); - QFontInfo consoleFontInfo(consoleFont); - QString resolvedDefaultMonospace = consoleFontInfo.family(); - QFont resolvedFont(resolvedDefaultMonospace); - qDebug() << "Detected default console font:" << resolvedDefaultMonospace - << ", substitutions:" << resolvedFont.substitutions().join(','); - - m_settings->registerSetting("ConsoleFont", resolvedDefaultMonospace); - m_settings->registerSetting("ConsoleFontSize", defaultSize); - m_settings->registerSetting("ConsoleMaxLines", 100000); - m_settings->registerSetting("ConsoleOverflowStop", true); - - // Folders - m_settings->registerSetting("InstanceDir", "instances"); - m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods"); - m_settings->registerSetting("IconsDir", "icons"); - - // Editors - m_settings->registerSetting("JsonEditor", QString()); - - // Language - m_settings->registerSetting("Language", QString()); - - // Console - m_settings->registerSetting("ShowConsole", false); - m_settings->registerSetting("AutoCloseConsole", false); - m_settings->registerSetting("ShowConsoleOnError", true); - m_settings->registerSetting("LogPrePostOutput", true); - - // Window Size - m_settings->registerSetting({"LaunchMaximized", "MCWindowMaximize"}, false); - m_settings->registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854); - m_settings->registerSetting({"MinecraftWinHeight", "MCWindowHeight"}, 480); - - // Proxy Settings - m_settings->registerSetting("ProxyType", "None"); - m_settings->registerSetting({"ProxyAddr", "ProxyHostName"}, "127.0.0.1"); - m_settings->registerSetting("ProxyPort", 8080); - m_settings->registerSetting({"ProxyUser", "ProxyUsername"}, ""); - m_settings->registerSetting({"ProxyPass", "ProxyPassword"}, ""); - - // Memory - m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512); - m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 1024); - m_settings->registerSetting("PermGen", 128); - - // Java Settings - m_settings->registerSetting("JavaPath", ""); - m_settings->registerSetting("JavaTimestamp", 0); - m_settings->registerSetting("JavaArchitecture", ""); - m_settings->registerSetting("JavaVersion", ""); - m_settings->registerSetting("LastHostname", ""); - m_settings->registerSetting("JvmArgs", ""); - - // Minecraft launch method - m_settings->registerSetting("MCLaunchMethod", "LauncherPart"); - - // Wrapper command for launch - m_settings->registerSetting("WrapperCommand", ""); - - // Custom Commands - m_settings->registerSetting({"PreLaunchCommand", "PreLaunchCmd"}, ""); - m_settings->registerSetting({"PostExitCommand", "PostExitCmd"}, ""); - - // The cat - m_settings->registerSetting("TheCat", false); - - m_settings->registerSetting("InstSortMode", "Name"); - m_settings->registerSetting("SelectedInstance", QString()); - - // Window state and geometry - m_settings->registerSetting("MainWindowState", ""); - m_settings->registerSetting("MainWindowGeometry", ""); - - m_settings->registerSetting("ConsoleWindowState", ""); - m_settings->registerSetting("ConsoleWindowGeometry", ""); - - m_settings->registerSetting("SettingsGeometry", ""); - - m_settings->registerSetting("PagedGeometry", ""); - - m_settings->registerSetting("NewInstanceGeometry", ""); - - m_settings->registerSetting("UpdateDialogGeometry", ""); - - // paste.ee API key - m_settings->registerSetting("PasteEEAPIKey", "multimc"); - -/* if(!BuildConfig.ANALYTICS_ID.isEmpty()) - { - // Analytics - m_settings->registerSetting("Analytics", true); - m_settings->registerSetting("AnalyticsSeen", 0); - m_settings->registerSetting("AnalyticsClientID", QString()); - } -*/ - // Init page provider - { - m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings")); - m_globalSettingsProvider->addPage<MultiMCPage>(); - m_globalSettingsProvider->addPage<MinecraftPage>(); - m_globalSettingsProvider->addPage<JavaPage>(); - m_globalSettingsProvider->addPage<CustomCommandsPage>(); - m_globalSettingsProvider->addPage<ProxyPage>(); - // m_globalSettingsProvider->addPage<PackagesPage>(); - m_globalSettingsProvider->addPage<ExternalToolsPage>(); - m_globalSettingsProvider->addPage<AccountListPage>(); - m_globalSettingsProvider->addPage<PasteEEPage>(); - } - qDebug() << "<> Settings loaded."; - } - - // load translations - { - m_translations.reset(new TranslationsModel("translations")); - auto bcp47Name = m_settings->get("Language").toString(); - m_translations->selectLanguage(bcp47Name); - qDebug() << "Your language is" << bcp47Name; - qDebug() << "<> Translations loaded."; - } - - // initialize the updater - if(BuildConfig.UPDATER_ENABLED) - { - m_updateChecker.reset(new UpdateChecker(BuildConfig.CHANLIST_URL, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD)); - qDebug() << "<> Updater started."; - } - - // Instance icons - { - auto setting = MMC->settings()->getSetting("IconsDir"); - QStringList instFolders = - { - ":/icons/multimc/32x32/instances/", - ":/icons/multimc/50x50/instances/", - ":/icons/multimc/128x128/instances/" - }; - m_icons.reset(new IconList(instFolders, setting->get().toString())); - connect(setting.get(), &Setting::SettingChanged,[&](const Setting &, QVariant value) - { - m_icons->directoryChanged(value.toString()); - }); - ENV.registerIconList(m_icons); - qDebug() << "<> Instance icons intialized."; - } - - // Icon themes - { - // TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies! - // set icon theme search path! - auto searchPaths = QIcon::themeSearchPaths(); - searchPaths.append("iconthemes"); - QIcon::setThemeSearchPaths(searchPaths); - qDebug() << "<> Icon themes initialized."; - } - - // Initialize widget themes - { - auto insertTheme = [this](ITheme * theme) - { - m_themes.insert(std::make_pair(theme->id(), std::unique_ptr<ITheme>(theme))); - }; - auto darkTheme = new DarkTheme(); - insertTheme(new SystemTheme()); - insertTheme(darkTheme); - insertTheme(new BrightTheme()); - insertTheme(new CustomTheme(darkTheme, "custom")); - qDebug() << "<> Widget themes initialized."; - } - - // initialize and load all instances - { - auto InstDirSetting = m_settings->getSetting("InstanceDir"); - // instance path: check for problems with '!' in instance path and warn the user in the log - // and rememer that we have to show him a dialog when the gui starts (if it does so) - QString instDir = InstDirSetting->get().toString(); - qDebug() << "Instance path : " << instDir; - if (FS::checkProblemticPathJava(QDir(instDir))) - { - qWarning() << "Your instance path contains \'!\' and this is known to cause java problems"; - } - m_instances.reset(new InstanceList(this)); - m_instanceFolder = new FolderInstanceProvider(m_settings, instDir); - connect(InstDirSetting.get(), &Setting::SettingChanged, m_instanceFolder, &FolderInstanceProvider::on_InstFolderChanged); - m_instances->addInstanceProvider(m_instanceFolder); - qDebug() << "Loading Instances..."; - m_instances->loadList(true); - qDebug() << "<> Instances loaded."; - } - - // and accounts - { - m_accounts.reset(new MojangAccountList(this)); - qDebug() << "Loading accounts..."; - m_accounts->setListFilePath("accounts.json", true); - m_accounts->loadList(); - qDebug() << "<> Accounts loaded."; - } - - // init the http meta cache - { - ENV.initHttpMetaCache(); - qDebug() << "<> Cache initialized."; - } - - // init proxy settings - { - QString proxyTypeStr = settings()->get("ProxyType").toString(); - QString addr = settings()->get("ProxyAddr").toString(); - int port = settings()->get("ProxyPort").value<qint16>(); - QString user = settings()->get("ProxyUser").toString(); - QString pass = settings()->get("ProxyPass").toString(); - ENV.updateProxySettings(proxyTypeStr, addr, port, user, pass); - qDebug() << "<> Proxy settings done."; - } - - // now we have network, download translation updates - m_translations->downloadIndex(); - - //FIXME: what to do with these? - m_profilers.insert("jprofiler", std::shared_ptr<BaseProfilerFactory>(new JProfilerFactory())); - m_profilers.insert("jvisualvm", std::shared_ptr<BaseProfilerFactory>(new JVisualVMFactory())); - for (auto profiler : m_profilers.values()) - { - profiler->registerSettings(m_settings); - } - - // Create the MCEdit thing... why is this here? - { - m_mcedit.reset(new MCEditTool(m_settings)); - } - - connect(this, &MultiMC::aboutToQuit, [this](){ - if(m_instances) - { - // save any remaining instance state - m_instances->saveNow(); - } - if(logFile) - { - logFile->flush(); - logFile->close(); - } - }); - - { - setIconTheme(settings()->get("IconTheme").toString()); - qDebug() << "<> Icon theme set."; - setApplicationTheme(settings()->get("ApplicationTheme").toString(), true); - qDebug() << "<> Application theme set."; - } - - // Initialize analytics -/* [this]() - { - const int analyticsVersion = 2; - if(BuildConfig.ANALYTICS_ID.isEmpty()) - { - return; - } - - auto analyticsSetting = m_settings->getSetting("Analytics"); - connect(analyticsSetting.get(), &Setting::SettingChanged, this, &MultiMC::analyticsSettingChanged); - QString clientID = m_settings->get("AnalyticsClientID").toString(); - if(clientID.isEmpty()) - { - clientID = QUuid::createUuid().toString(); - clientID.remove(QLatin1Char('{')); - clientID.remove(QLatin1Char('}')); - m_settings->set("AnalyticsClientID", clientID); - } - m_analytics = new GAnalytics(BuildConfig.ANALYTICS_ID, clientID, analyticsVersion, this); - m_analytics->setLogLevel(GAnalytics::Debug); - m_analytics->setAnonymizeIPs(true); - m_analytics->setNetworkAccessManager(&ENV.qnam()); - - if(m_settings->get("AnalyticsSeen").toInt() < m_analytics->version()) - { - qDebug() << "Analytics info not seen by user yet (or old version)."; - return; - } - if(!m_settings->get("Analytics").toBool()) - { - qDebug() << "Analytics disabled by user."; - return; - } - - m_analytics->enable(); - qDebug() << "<> Initialized analytics with tid" << BuildConfig.ANALYTICS_ID; - }(); */ - - if(createSetupWizard()) - { - return; - } - performMainStartupAction(); + // resolve the font so the default actually matches + QFont consoleFont; + consoleFont.setFamily(defaultMonospace); + consoleFont.setStyleHint(QFont::Monospace); + consoleFont.setFixedPitch(true); + QFontInfo consoleFontInfo(consoleFont); + QString resolvedDefaultMonospace = consoleFontInfo.family(); + QFont resolvedFont(resolvedDefaultMonospace); + qDebug() << "Detected default console font:" << resolvedDefaultMonospace + << ", substitutions:" << resolvedFont.substitutions().join(','); + + m_settings->registerSetting("ConsoleFont", resolvedDefaultMonospace); + m_settings->registerSetting("ConsoleFontSize", defaultSize); + m_settings->registerSetting("ConsoleMaxLines", 100000); + m_settings->registerSetting("ConsoleOverflowStop", true); + + // Folders + m_settings->registerSetting("InstanceDir", "instances"); + m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods"); + m_settings->registerSetting("IconsDir", "icons"); + + // Editors + m_settings->registerSetting("JsonEditor", QString()); + + // Language + m_settings->registerSetting("Language", QString()); + + // Console + m_settings->registerSetting("ShowConsole", false); + m_settings->registerSetting("AutoCloseConsole", false); + m_settings->registerSetting("ShowConsoleOnError", true); + m_settings->registerSetting("LogPrePostOutput", true); + + // Window Size + m_settings->registerSetting({"LaunchMaximized", "MCWindowMaximize"}, false); + m_settings->registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854); + m_settings->registerSetting({"MinecraftWinHeight", "MCWindowHeight"}, 480); + + // Proxy Settings + m_settings->registerSetting("ProxyType", "None"); + m_settings->registerSetting({"ProxyAddr", "ProxyHostName"}, "127.0.0.1"); + m_settings->registerSetting("ProxyPort", 8080); + m_settings->registerSetting({"ProxyUser", "ProxyUsername"}, ""); + m_settings->registerSetting({"ProxyPass", "ProxyPassword"}, ""); + + // Memory + m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512); + m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 1024); + m_settings->registerSetting("PermGen", 128); + + // Java Settings + m_settings->registerSetting("JavaPath", ""); + m_settings->registerSetting("JavaTimestamp", 0); + m_settings->registerSetting("JavaArchitecture", ""); + m_settings->registerSetting("JavaVersion", ""); + m_settings->registerSetting("LastHostname", ""); + m_settings->registerSetting("JvmArgs", ""); + + // Minecraft launch method + m_settings->registerSetting("MCLaunchMethod", "LauncherPart"); + + // Wrapper command for launch + m_settings->registerSetting("WrapperCommand", ""); + + // Custom Commands + m_settings->registerSetting({"PreLaunchCommand", "PreLaunchCmd"}, ""); + m_settings->registerSetting({"PostExitCommand", "PostExitCmd"}, ""); + + // The cat + m_settings->registerSetting("TheCat", false); + + m_settings->registerSetting("InstSortMode", "Name"); + m_settings->registerSetting("SelectedInstance", QString()); + + // Window state and geometry + m_settings->registerSetting("MainWindowState", ""); + m_settings->registerSetting("MainWindowGeometry", ""); + + m_settings->registerSetting("ConsoleWindowState", ""); + m_settings->registerSetting("ConsoleWindowGeometry", ""); + + m_settings->registerSetting("SettingsGeometry", ""); + + m_settings->registerSetting("PagedGeometry", ""); + + m_settings->registerSetting("NewInstanceGeometry", ""); + + m_settings->registerSetting("UpdateDialogGeometry", ""); + + // paste.ee API key + m_settings->registerSetting("PasteEEAPIKey", "multimc"); + + // Init page provider + { + m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings")); + m_globalSettingsProvider->addPage<MultiMCPage>(); + m_globalSettingsProvider->addPage<MinecraftPage>(); + m_globalSettingsProvider->addPage<JavaPage>(); + m_globalSettingsProvider->addPage<LanguagePage>(); + m_globalSettingsProvider->addPage<CustomCommandsPage>(); + m_globalSettingsProvider->addPage<ProxyPage>(); + // m_globalSettingsProvider->addPage<PackagesPage>(); + m_globalSettingsProvider->addPage<ExternalToolsPage>(); + m_globalSettingsProvider->addPage<AccountListPage>(); + m_globalSettingsProvider->addPage<PasteEEPage>(); + } + qDebug() << "<> Settings loaded."; + } + + QAccessible::installFactory(groupViewAccessibleFactory); + + // load translations + { + m_translations.reset(new TranslationsModel("translations")); + auto bcp47Name = m_settings->get("Language").toString(); + m_translations->selectLanguage(bcp47Name); + qDebug() << "Your language is" << bcp47Name; + qDebug() << "<> Translations loaded."; + } + + // initialize the updater + if(BuildConfig.UPDATER_ENABLED) + { + m_updateChecker.reset(new UpdateChecker(BuildConfig.CHANLIST_URL, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD)); + qDebug() << "<> Updater started."; + } + + // Instance icons + { + auto setting = MMC->settings()->getSetting("IconsDir"); + QStringList instFolders = + { + ":/icons/multimc/32x32/instances/", + ":/icons/multimc/50x50/instances/", + ":/icons/multimc/128x128/instances/", + ":/icons/multimc/scalable/instances/" + }; + m_icons.reset(new IconList(instFolders, setting->get().toString())); + connect(setting.get(), &Setting::SettingChanged,[&](const Setting &, QVariant value) + { + m_icons->directoryChanged(value.toString()); + }); + ENV.registerIconList(m_icons); + qDebug() << "<> Instance icons intialized."; + } + + // Icon themes + { + // TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies! + // set icon theme search path! + auto searchPaths = QIcon::themeSearchPaths(); + searchPaths.append("iconthemes"); + QIcon::setThemeSearchPaths(searchPaths); + qDebug() << "<> Icon themes initialized."; + } + + // Initialize widget themes + { + auto insertTheme = [this](ITheme * theme) + { + m_themes.insert(std::make_pair(theme->id(), std::unique_ptr<ITheme>(theme))); + }; + auto darkTheme = new DarkTheme(); + insertTheme(new SystemTheme()); + insertTheme(darkTheme); + insertTheme(new BrightTheme()); + insertTheme(new CustomTheme(darkTheme, "custom")); + qDebug() << "<> Widget themes initialized."; + } + + // initialize and load all instances + { + auto InstDirSetting = m_settings->getSetting("InstanceDir"); + // instance path: check for problems with '!' in instance path and warn the user in the log + // and rememer that we have to show him a dialog when the gui starts (if it does so) + QString instDir = InstDirSetting->get().toString(); + qDebug() << "Instance path : " << instDir; + if (FS::checkProblemticPathJava(QDir(instDir))) + { + qWarning() << "Your instance path contains \'!\' and this is known to cause java problems"; + } + m_instances.reset(new InstanceList(m_settings, instDir, this)); + connect(InstDirSetting.get(), &Setting::SettingChanged, m_instances.get(), &InstanceList::on_InstFolderChanged); + qDebug() << "Loading Instances..."; + m_instances->loadList(); + qDebug() << "<> Instances loaded."; + } + + // and accounts + { + m_accounts.reset(new MojangAccountList(this)); + qDebug() << "Loading accounts..."; + m_accounts->setListFilePath("accounts.json", true); + m_accounts->loadList(); + qDebug() << "<> Accounts loaded."; + } + + // init the http meta cache + { + ENV.initHttpMetaCache(); + qDebug() << "<> Cache initialized."; + } + + // init proxy settings + { + QString proxyTypeStr = settings()->get("ProxyType").toString(); + QString addr = settings()->get("ProxyAddr").toString(); + int port = settings()->get("ProxyPort").value<qint16>(); + QString user = settings()->get("ProxyUser").toString(); + QString pass = settings()->get("ProxyPass").toString(); + ENV.updateProxySettings(proxyTypeStr, addr, port, user, pass); + qDebug() << "<> Proxy settings done."; + } + + // now we have network, download translation updates + m_translations->downloadIndex(); + + //FIXME: what to do with these? + m_profilers.insert("jprofiler", std::shared_ptr<BaseProfilerFactory>(new JProfilerFactory())); + m_profilers.insert("jvisualvm", std::shared_ptr<BaseProfilerFactory>(new JVisualVMFactory())); + for (auto profiler : m_profilers.values()) + { + profiler->registerSettings(m_settings); + } + + // Create the MCEdit thing... why is this here? + { + m_mcedit.reset(new MCEditTool(m_settings)); + } + + connect(this, &MultiMC::aboutToQuit, [this](){ + if(m_instances) + { + // save any remaining instance state + m_instances->saveNow(); + } + if(logFile) + { + logFile->flush(); + logFile->close(); + } + }); + + { + setIconTheme(settings()->get("IconTheme").toString()); + qDebug() << "<> Icon theme set."; + setApplicationTheme(settings()->get("ApplicationTheme").toString(), true); + qDebug() << "<> Application theme set."; + } + + if(createSetupWizard()) + { + return; + } + performMainStartupAction(); } bool MultiMC::createSetupWizard() { - bool javaRequired = [&]() - { - QString currentHostName = QHostInfo::localHostName(); - QString oldHostName = settings()->get("LastHostname").toString(); - if (currentHostName != oldHostName) - { - settings()->set("LastHostname", currentHostName); - return true; - } - QString currentJavaPath = settings()->get("JavaPath").toString(); - QString actualPath = FS::ResolveExecutable(currentJavaPath); - if (actualPath.isNull()) - { - return true; - } - return false; - }/*(); - bool analyticsRequired = [&]() - { - if(BuildConfig.ANALYTICS_ID.isEmpty()) - { - return false; - } - if (!settings()->get("Analytics").toBool()) - { - return false; - } - if (settings()->get("AnalyticsSeen").toInt() < analytics()->version()) - { - return true; - } - return false; - }*/(); - bool languageRequired = [&]() - { - if (settings()->get("Language").toString().isEmpty()) - return true; - return false; - }(); -/* bool wizardRequired = javaRequired || analyticsRequired || languageRequired; -*/ bool wizardRequired = javaRequired || languageRequired; - - if(wizardRequired) - { - m_setupWizard = new SetupWizard(nullptr); - if (languageRequired) - { - m_setupWizard->addPage(new LanguageWizardPage(m_setupWizard)); - } - if (javaRequired) - { - m_setupWizard->addPage(new JavaWizardPage(m_setupWizard)); - } -/* if(analyticsRequired) - { - m_setupWizard->addPage(new AnalyticsWizardPage(m_setupWizard)); - } -*/ connect(m_setupWizard, &QDialog::finished, this, &MultiMC::setupWizardFinished); - m_setupWizard->show(); - return true; - } - return false; + bool javaRequired = [&]() + { + QString currentHostName = QHostInfo::localHostName(); + QString oldHostName = settings()->get("LastHostname").toString(); + if (currentHostName != oldHostName) + { + settings()->set("LastHostname", currentHostName); + return true; + } + QString currentJavaPath = settings()->get("JavaPath").toString(); + QString actualPath = FS::ResolveExecutable(currentJavaPath); + if (actualPath.isNull()) + { + return true; + } + return false; + }(); + bool languageRequired = [&]() + { + if (settings()->get("Language").toString().isEmpty()) + return true; + return false; + }(); + bool wizardRequired = javaRequired || languageRequired; + + if(wizardRequired) + { + m_setupWizard = new SetupWizard(nullptr); + if (languageRequired) + { + m_setupWizard->addPage(new LanguageWizardPage(m_setupWizard)); + } + if (javaRequired) + { + m_setupWizard->addPage(new JavaWizardPage(m_setupWizard)); + } + connect(m_setupWizard, &QDialog::finished, this, &MultiMC::setupWizardFinished); + m_setupWizard->show(); + return true; + } + return false; } void MultiMC::setupWizardFinished(int status) { - qDebug() << "Wizard result =" << status; - performMainStartupAction(); + qDebug() << "Wizard result =" << status; + performMainStartupAction(); } void MultiMC::performMainStartupAction() { - m_status = MultiMC::Initialized; - if(!m_instanceIdToLaunch.isEmpty()) - { - auto inst = instances()->getInstanceById(m_instanceIdToLaunch); - if(inst) - { - qDebug() << "<> Instance launching:" << m_instanceIdToLaunch; - launch(inst, true, nullptr); - return; - } - } - if(!m_mainWindow) - { - // normal main window - showMainWindow(false); - qDebug() << "<> Main window shown."; - } + m_status = MultiMC::Initialized; + if(!m_instanceIdToLaunch.isEmpty()) + { + auto inst = instances()->getInstanceById(m_instanceIdToLaunch); + if(inst) + { + qDebug() << "<> Instance launching:" << m_instanceIdToLaunch; + launch(inst, true, nullptr); + return; + } + } + if(!m_mainWindow) + { + // normal main window + showMainWindow(false); + qDebug() << "<> Main window shown."; + } } void MultiMC::showFatalErrorMessage(const QString& title, const QString& content) { - m_status = MultiMC::Failed; - auto dialog = CustomMessageBox::selectable(nullptr, title, content, QMessageBox::Critical); - dialog->exec(); + m_status = MultiMC::Failed; + auto dialog = CustomMessageBox::selectable(nullptr, title, content, QMessageBox::Critical); + dialog->exec(); } MultiMC::~MultiMC() { - // kill the other globals. - Env::dispose(); + // kill the other globals. + Env::dispose(); - // Shut down logger by setting the logger function to nothing - qInstallMessageHandler(nullptr); + // Shut down logger by setting the logger function to nothing + qInstallMessageHandler(nullptr); #if defined Q_OS_WIN32 - // Detach from Windows console - if(consoleAttached) - { - fclose(stdout); - fclose(stdin); - fclose(stderr); - FreeConsole(); - } + // Detach from Windows console + if(consoleAttached) + { + fclose(stdout); + fclose(stdin); + fclose(stderr); + FreeConsole(); + } #endif } void MultiMC::messageReceived(const QString& message) { - if(status() != Initialized) - { - qDebug() << "Received message" << message << "while still initializing. It will be ignored."; - return; - } - if(message == "activate") - { - showMainWindow(); - } - else - { - auto inst = instances()->getInstanceById(message); - if(inst) - { - launch(inst, true, nullptr); - } - } + if(status() != Initialized) + { + qDebug() << "Received message" << message << "while still initializing. It will be ignored."; + return; + } + if(message == "activate") + { + showMainWindow(); + } + else + { + auto inst = instances()->getInstanceById(message); + if(inst) + { + launch(inst, true, nullptr); + } + } } /* void MultiMC::analyticsSettingChanged(const Setting&, QVariant value) { - if(!m_analytics) - return; - bool enabled = value.toBool(); - if(enabled) - { - qDebug() << "Analytics enabled by user."; - } - else - { - qDebug() << "Analytics disabled by user."; - } - m_analytics->enable(enabled); + if(!m_analytics) + return; + bool enabled = value.toBool(); + if(enabled) + { + qDebug() << "Analytics enabled by user."; + } + else + { + qDebug() << "Analytics disabled by user."; + } + m_analytics->enable(enabled); } */ std::shared_ptr<TranslationsModel> MultiMC::translations() { - return m_translations; + return m_translations; } std::shared_ptr<JavaInstallList> MultiMC::javalist() { - if (!m_javalist) - { - m_javalist.reset(new JavaInstallList()); - } - return m_javalist; + if (!m_javalist) + { + m_javalist.reset(new JavaInstallList()); + } + return m_javalist; } std::vector<ITheme *> MultiMC::getValidApplicationThemes() { - std::vector<ITheme *> ret; - auto iter = m_themes.cbegin(); - while (iter != m_themes.cend()) - { - ret.push_back((*iter).second.get()); - iter++; - } - return ret; + std::vector<ITheme *> ret; + auto iter = m_themes.cbegin(); + while (iter != m_themes.cend()) + { + ret.push_back((*iter).second.get()); + iter++; + } + return ret; } void MultiMC::setApplicationTheme(const QString& name, bool initial) { - auto systemPalette = qApp->palette(); - auto themeIter = m_themes.find(name); - if(themeIter != m_themes.end()) - { - auto & theme = (*themeIter).second; - theme->apply(initial); - } - else - { - qWarning() << "Tried to set invalid theme:" << name; - } + auto systemPalette = qApp->palette(); + auto themeIter = m_themes.find(name); + if(themeIter != m_themes.end()) + { + auto & theme = (*themeIter).second; + theme->apply(initial); + } + else + { + qWarning() << "Tried to set invalid theme:" << name; + } } void MultiMC::setIconTheme(const QString& name) { - XdgIcon::setThemeName(name); + XdgIcon::setThemeName(name); } QIcon MultiMC::getThemedIcon(const QString& name) { - return XdgIcon::fromTheme(name); + return XdgIcon::fromTheme(name); } bool MultiMC::openJsonEditor(const QString &filename) { - const QString file = QDir::current().absoluteFilePath(filename); - if (m_settings->get("JsonEditor").toString().isEmpty()) - { - return DesktopServices::openUrl(QUrl::fromLocalFile(file)); - } - else - { - //return DesktopServices::openFile(m_settings->get("JsonEditor").toString(), file); - return DesktopServices::run(m_settings->get("JsonEditor").toString(), {file}); - } + const QString file = QDir::current().absoluteFilePath(filename); + if (m_settings->get("JsonEditor").toString().isEmpty()) + { + return DesktopServices::openUrl(QUrl::fromLocalFile(file)); + } + else + { + //return DesktopServices::openFile(m_settings->get("JsonEditor").toString(), file); + return DesktopServices::run(m_settings->get("JsonEditor").toString(), {file}); + } } bool MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *profiler) { - if(m_updateRunning) - { - qDebug() << "Cannot launch instances while an update is running."; - } - else if(instance->canLaunch()) - { - auto & extras = m_instanceExtras[instance->id()]; - auto & window = extras.window; - if(window) - { - if(!window->saveAll()) - { - return false; - } - } - auto & controller = extras.controller; - controller.reset(new LaunchController()); - controller->setInstance(instance); - controller->setOnline(online); - controller->setProfiler(profiler); - if(window) - { - controller->setParentWidget(window); - } - else if(m_mainWindow) - { - controller->setParentWidget(m_mainWindow); - } - connect(controller.get(), &LaunchController::succeeded, this, &MultiMC::controllerSucceeded); - connect(controller.get(), &LaunchController::failed, this, &MultiMC::controllerFailed); - addRunningInstance(); - controller->start(); - return true; - } - else if (instance->isRunning()) - { - showInstanceWindow(instance, "console"); - return true; - } - else if (instance->canEdit()) - { - showInstanceWindow(instance); - return true; - } - return false; + if(m_updateRunning) + { + qDebug() << "Cannot launch instances while an update is running."; + } + else if(instance->canLaunch()) + { + auto & extras = m_instanceExtras[instance->id()]; + auto & window = extras.window; + if(window) + { + if(!window->saveAll()) + { + return false; + } + } + auto & controller = extras.controller; + controller.reset(new LaunchController()); + controller->setInstance(instance); + controller->setOnline(online); + controller->setProfiler(profiler); + if(window) + { + controller->setParentWidget(window); + } + else if(m_mainWindow) + { + controller->setParentWidget(m_mainWindow); + } + connect(controller.get(), &LaunchController::succeeded, this, &MultiMC::controllerSucceeded); + connect(controller.get(), &LaunchController::failed, this, &MultiMC::controllerFailed); + addRunningInstance(); + controller->start(); + return true; + } + else if (instance->isRunning()) + { + showInstanceWindow(instance, "console"); + return true; + } + else if (instance->canEdit()) + { + showInstanceWindow(instance); + return true; + } + return false; } bool MultiMC::kill(InstancePtr instance) { - if (!instance->isRunning()) - { - qWarning() << "Attempted to kill instance" << instance->id() << "which isn't running."; - return false; - } - auto & extras = m_instanceExtras[instance->id()]; - // NOTE: copy of the shared pointer keeps it alive - auto controller = extras.controller; - if(controller) - { - return controller->abort(); - } - return true; + if (!instance->isRunning()) + { + qWarning() << "Attempted to kill instance" << instance->id() << "which isn't running."; + return false; + } + auto & extras = m_instanceExtras[instance->id()]; + // NOTE: copy of the shared pointer keeps it alive + auto controller = extras.controller; + if(controller) + { + return controller->abort(); + } + return true; } void MultiMC::addRunningInstance() { - m_runningInstances ++; - if(m_runningInstances == 1) - { - emit updateAllowedChanged(false); - } + m_runningInstances ++; + if(m_runningInstances == 1) + { + emit updateAllowedChanged(false); + } } void MultiMC::subRunningInstance() { - if(m_runningInstances == 0) - { - qCritical() << "Something went really wrong and we now have less than 0 running instances... WTF"; - return; - } - m_runningInstances --; - if(m_runningInstances == 0) - { - emit updateAllowedChanged(true); - } + if(m_runningInstances == 0) + { + qCritical() << "Something went really wrong and we now have less than 0 running instances... WTF"; + return; + } + m_runningInstances --; + if(m_runningInstances == 0) + { + emit updateAllowedChanged(true); + } } bool MultiMC::shouldExitNow() const { - return m_runningInstances == 0 && m_openWindows == 0; + return m_runningInstances == 0 && m_openWindows == 0; } bool MultiMC::updatesAreAllowed() { - return m_runningInstances == 0; + return m_runningInstances == 0; } void MultiMC::updateIsRunning(bool running) { - m_updateRunning = running; + m_updateRunning = running; } void MultiMC::controllerSucceeded() { - auto controller = qobject_cast<LaunchController *>(QObject::sender()); - if(!controller) - return; - auto id = controller->id(); - auto & extras = m_instanceExtras[id]; - - // on success, do... - if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) - { - if(extras.window) - { - extras.window->close(); - } - } - extras.controller.reset(); - subRunningInstance(); - - // quit when there are no more windows. - if(shouldExitNow()) - { - m_status = Status::Succeeded; - exit(0); - } + auto controller = qobject_cast<LaunchController *>(QObject::sender()); + if(!controller) + return; + auto id = controller->id(); + auto & extras = m_instanceExtras[id]; + + // on success, do... + if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) + { + if(extras.window) + { + extras.window->close(); + } + } + extras.controller.reset(); + subRunningInstance(); + + // quit when there are no more windows. + if(shouldExitNow()) + { + m_status = Status::Succeeded; + exit(0); + } } void MultiMC::controllerFailed(const QString& error) { - Q_UNUSED(error); - auto controller = qobject_cast<LaunchController *>(QObject::sender()); - if(!controller) - return; - auto id = controller->id(); - auto & extras = m_instanceExtras[id]; - - // on failure, do... nothing - extras.controller.reset(); - subRunningInstance(); - - // quit when there are no more windows. - if(shouldExitNow()) - { - m_status = Status::Failed; - exit(1); - } + Q_UNUSED(error); + auto controller = qobject_cast<LaunchController *>(QObject::sender()); + if(!controller) + return; + auto id = controller->id(); + auto & extras = m_instanceExtras[id]; + + // on failure, do... nothing + extras.controller.reset(); + subRunningInstance(); + + // quit when there are no more windows. + if(shouldExitNow()) + { + m_status = Status::Failed; + exit(1); + } +} + +void MultiMC::ShowGlobalSettings(class QWidget* parent, QString open_page) +{ + if(!m_globalSettingsProvider) { + return; + } + emit globalSettingsAboutToOpen(); + { + SettingsObject::Lock lock(MMC->settings()); + PageDialog dlg(m_globalSettingsProvider.get(), open_page, parent); + dlg.exec(); + } + emit globalSettingsClosed(); } MainWindow* MultiMC::showMainWindow(bool minimized) { - if(m_mainWindow) - { - m_mainWindow->setWindowState(m_mainWindow->windowState() & ~Qt::WindowMinimized); - m_mainWindow->raise(); - m_mainWindow->activateWindow(); - } - else - { - m_mainWindow = new MainWindow(); - m_mainWindow->restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray())); - m_mainWindow->restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray())); - if(minimized) - { - m_mainWindow->showMinimized(); - } - else - { - m_mainWindow->show(); - } - - m_mainWindow->checkInstancePathForProblems(); - connect(this, &MultiMC::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged); - connect(m_mainWindow, &MainWindow::isClosing, this, &MultiMC::on_windowClose); - m_openWindows++; - } -/* // FIXME: move this somewhere else... - if(m_analytics) - { - auto windowSize = m_mainWindow->size(); - auto sizeString = QString("%1x%2").arg(windowSize.width()).arg(windowSize.height()); - qDebug() << "Viewport size" << sizeString; - m_analytics->setViewportSize(sizeString); -*/ /* - * cm1 = java min heap [MB] - * cm2 = java max heap [MB] - * cm3 = system RAM [MB] - * - * cd1 = java version - * cd2 = java architecture - * cd3 = system architecture - * cd4 = CPU architecture - */ -/* QVariantMap customValues; - int min = m_settings->get("MinMemAlloc").toInt(); - int max = m_settings->get("MaxMemAlloc").toInt(); - if(min < max) - { - customValues["cm1"] = min; - customValues["cm2"] = max; - } - else - { - customValues["cm1"] = max; - customValues["cm2"] = min; - } - - constexpr uint64_t Mega = 1024ull * 1024ull; - int ramSize = int(Sys::getSystemRam() / Mega); - qDebug() << "RAM size is" << ramSize << "MB"; - customValues["cm3"] = ramSize; - - customValues["cd1"] = m_settings->get("JavaVersion"); - customValues["cd2"] = m_settings->get("JavaArchitecture"); - customValues["cd3"] = Sys::isSystem64bit() ? "64":"32"; - customValues["cd4"] = Sys::isCPU64bit() ? "64":"32"; - auto kernelInfo = Sys::getKernelInfo(); - customValues["cd5"] = kernelInfo.kernelName; - customValues["cd6"] = kernelInfo.kernelVersion; - auto distInfo = Sys::getDistributionInfo(); - if(!distInfo.distributionName.isEmpty()) - { - customValues["cd7"] = distInfo.distributionName; - } - if(!distInfo.distributionVersion.isEmpty()) - { - customValues["cd8"] = distInfo.distributionVersion; - } - m_analytics->sendScreenView("Main Window", customValues); - } -*/ - return m_mainWindow; + if(m_mainWindow) + { + m_mainWindow->setWindowState(m_mainWindow->windowState() & ~Qt::WindowMinimized); + m_mainWindow->raise(); + m_mainWindow->activateWindow(); + } + else + { + m_mainWindow = new MainWindow(); + m_mainWindow->restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray())); + m_mainWindow->restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray())); + if(minimized) + { + m_mainWindow->showMinimized(); + } + else + { + m_mainWindow->show(); + } + + m_mainWindow->checkInstancePathForProblems(); + connect(this, &MultiMC::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged); + connect(m_mainWindow, &MainWindow::isClosing, this, &MultiMC::on_windowClose); + m_openWindows++; + } + return m_mainWindow; } InstanceWindow *MultiMC::showInstanceWindow(InstancePtr instance, QString page) { - if(!instance) - return nullptr; - auto id = instance->id(); - auto & extras = m_instanceExtras[id]; - auto & window = extras.window; - - if(window) - { - window->raise(); - window->activateWindow(); - } - else - { - window = new InstanceWindow(instance); - m_openWindows ++; - connect(window, &InstanceWindow::isClosing, this, &MultiMC::on_windowClose); - } - if(!page.isEmpty()) - { - window->selectPage(page); - } - if(extras.controller) - { - extras.controller->setParentWidget(window); - } - return window; + if(!instance) + return nullptr; + auto id = instance->id(); + auto & extras = m_instanceExtras[id]; + auto & window = extras.window; + + if(window) + { + window->raise(); + window->activateWindow(); + } + else + { + window = new InstanceWindow(instance); + m_openWindows ++; + connect(window, &InstanceWindow::isClosing, this, &MultiMC::on_windowClose); + } + if(!page.isEmpty()) + { + window->selectPage(page); + } + if(extras.controller) + { + extras.controller->setParentWidget(window); + } + return window; } void MultiMC::on_windowClose() { - m_openWindows--; - auto instWindow = qobject_cast<InstanceWindow *>(QObject::sender()); - if(instWindow) - { - auto & extras = m_instanceExtras[instWindow->instanceId()]; - extras.window = nullptr; - if(extras.controller) - { - extras.controller->setParentWidget(m_mainWindow); - } - } - auto mainWindow = qobject_cast<MainWindow *>(QObject::sender()); - if(mainWindow) - { - m_mainWindow = nullptr; - } - // quit when there are no more windows. - if(shouldExitNow()) - { - exit(0); - } + m_openWindows--; + auto instWindow = qobject_cast<InstanceWindow *>(QObject::sender()); + if(instWindow) + { + auto & extras = m_instanceExtras[instWindow->instanceId()]; + extras.window = nullptr; + if(extras.controller) + { + extras.controller->setParentWidget(m_mainWindow); + } + } + auto mainWindow = qobject_cast<MainWindow *>(QObject::sender()); + if(mainWindow) + { + m_mainWindow = nullptr; + } + // quit when there are no more windows. + if(shouldExitNow()) + { + exit(0); + } } diff --git a/application/MultiMC.h b/application/MultiMC.h index 9c91d06d..056fca88 100644 --- a/application/MultiMC.h +++ b/application/MultiMC.h @@ -39,182 +39,189 @@ class MCEditTool; class MultiMC : public QApplication { - // friends for the purpose of limiting access to deprecated stuff - Q_OBJECT + // friends for the purpose of limiting access to deprecated stuff + Q_OBJECT public: - enum Status - { - StartingUp, - Failed, - Succeeded, - Initialized - }; + enum Status + { + StartingUp, + Failed, + Succeeded, + Initialized + }; public: - MultiMC(int &argc, char **argv); - virtual ~MultiMC(); + MultiMC(int &argc, char **argv); + virtual ~MultiMC(); - std::shared_ptr<SettingsObject> settings() const - { - return m_settings; - } + std::shared_ptr<SettingsObject> settings() const + { + return m_settings; + } - std::shared_ptr<GenericPageProvider> globalSettingsPages() const - { - return m_globalSettingsProvider; - } + qint64 timeSinceStart() const + { + return startTime.msecsTo(QDateTime::currentDateTime()); + } - qint64 timeSinceStart() const - { - return startTime.msecsTo(QDateTime::currentDateTime()); - } + QIcon getThemedIcon(const QString& name); - QIcon getThemedIcon(const QString& name); + void setIconTheme(const QString& name); - void setIconTheme(const QString& name); + std::vector<ITheme *> getValidApplicationThemes(); - std::vector<ITheme *> getValidApplicationThemes(); + void setApplicationTheme(const QString& name, bool initial); - void setApplicationTheme(const QString& name, bool initial); + // DownloadUpdateTask + std::shared_ptr<UpdateChecker> updateChecker() + { + return m_updateChecker; + } - // DownloadUpdateTask - std::shared_ptr<UpdateChecker> updateChecker() - { - return m_updateChecker; - } + std::shared_ptr<TranslationsModel> translations(); - std::shared_ptr<TranslationsModel> translations(); + std::shared_ptr<JavaInstallList> javalist(); - std::shared_ptr<JavaInstallList> javalist(); + std::shared_ptr<InstanceList> instances() const + { + return m_instances; + } - std::shared_ptr<InstanceList> instances() const - { - return m_instances; - } + FolderInstanceProvider * folderProvider() const + { + return m_instanceFolder; + } - FolderInstanceProvider * folderProvider() const - { - return m_instanceFolder; - } + std::shared_ptr<IconList> icons() const + { + return m_icons; + } - std::shared_ptr<IconList> icons() const - { - return m_icons; - } + MCEditTool *mcedit() const + { + return m_mcedit.get(); + } - MCEditTool *mcedit() const - { - return m_mcedit.get(); - } + std::shared_ptr<MojangAccountList> accounts() const + { + return m_accounts; + } - std::shared_ptr<MojangAccountList> accounts() const - { - return m_accounts; - } + Status status() const + { + return m_status; + } - Status status() const - { - return m_status; - } + const QMap<QString, std::shared_ptr<BaseProfilerFactory>> &profilers() const + { + return m_profilers; + } - const QMap<QString, std::shared_ptr<BaseProfilerFactory>> &profilers() const - { - return m_profilers; - } + /// this is the root of the 'installation'. Used for automatic updates + const QString &root() + { + return m_rootPath; + } - /// this is the root of the 'installation'. Used for automatic updates - const QString &root() - { - return m_rootPath; - } + /*! + * Opens a json file using either a system default editor, or, if not empty, the editor + * specified in the settings + */ + bool openJsonEditor(const QString &filename); - /*! - * Opens a json file using either a system default editor, or, if not empty, the editor - * specified in the settings - */ - bool openJsonEditor(const QString &filename); + InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString()); + MainWindow *showMainWindow(bool minimized = false); - InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString()); - MainWindow *showMainWindow(bool minimized = false); + void updateIsRunning(bool running); + bool updatesAreAllowed(); - void updateIsRunning(bool running); - bool updatesAreAllowed(); + void ShowGlobalSettings(class QWidget * parent, QString open_page = QString()); signals: - void updateAllowedChanged(bool status); + void updateAllowedChanged(bool status); + void globalSettingsAboutToOpen(); + void globalSettingsClosed(); public slots: - bool launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr); - bool kill(InstancePtr instance); + bool launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr); + bool kill(InstancePtr instance); private slots: +<<<<<<< HEAD void on_windowClose(); void messageReceived(const QString & message); void controllerSucceeded(); void controllerFailed(const QString & error); /* void analyticsSettingChanged(const Setting &setting, QVariant value); */ void setupWizardFinished(int status); +======= + void on_windowClose(); + void messageReceived(const QString & message); + void controllerSucceeded(); + void controllerFailed(const QString & error); + void analyticsSettingChanged(const Setting &setting, QVariant value); + void setupWizardFinished(int status); +>>>>>>> origin/stable private: - bool createSetupWizard(); - void performMainStartupAction(); + bool createSetupWizard(); + void performMainStartupAction(); - // sets the fatal error message and m_status to Failed. - void showFatalErrorMessage(const QString & title, const QString & content); + // sets the fatal error message and m_status to Failed. + void showFatalErrorMessage(const QString & title, const QString & content); private: - void addRunningInstance(); - void subRunningInstance(); - bool shouldExitNow() const; + void addRunningInstance(); + void subRunningInstance(); + bool shouldExitNow() const; private: - QDateTime startTime; + QDateTime startTime; - std::shared_ptr<SettingsObject> m_settings; - std::shared_ptr<InstanceList> m_instances; - FolderInstanceProvider * m_instanceFolder = nullptr; - std::shared_ptr<IconList> m_icons; - std::shared_ptr<UpdateChecker> m_updateChecker; - std::shared_ptr<MojangAccountList> m_accounts; - std::shared_ptr<JavaInstallList> m_javalist; - std::shared_ptr<TranslationsModel> m_translations; - std::shared_ptr<GenericPageProvider> m_globalSettingsProvider; - std::map<QString, std::unique_ptr<ITheme>> m_themes; - std::unique_ptr<MCEditTool> m_mcedit; + std::shared_ptr<SettingsObject> m_settings; + std::shared_ptr<InstanceList> m_instances; + FolderInstanceProvider * m_instanceFolder = nullptr; + std::shared_ptr<IconList> m_icons; + std::shared_ptr<UpdateChecker> m_updateChecker; + std::shared_ptr<MojangAccountList> m_accounts; + std::shared_ptr<JavaInstallList> m_javalist; + std::shared_ptr<TranslationsModel> m_translations; + std::shared_ptr<GenericPageProvider> m_globalSettingsProvider; + std::map<QString, std::unique_ptr<ITheme>> m_themes; + std::unique_ptr<MCEditTool> m_mcedit; - QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers; + QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers; - QString m_rootPath; - Status m_status = MultiMC::StartingUp; + QString m_rootPath; + Status m_status = MultiMC::StartingUp; #if defined Q_OS_WIN32 - // used on Windows to attach the standard IO streams - bool consoleAttached = false; + // used on Windows to attach the standard IO streams + bool consoleAttached = false; #endif - // FIXME: attach to instances instead. - struct InstanceXtras - { - InstanceWindow * window = nullptr; - shared_qobject_ptr<LaunchController> controller; - }; - std::map<QString, InstanceXtras> m_instanceExtras; + // FIXME: attach to instances instead. + struct InstanceXtras + { + InstanceWindow * window = nullptr; + shared_qobject_ptr<LaunchController> controller; + }; + std::map<QString, InstanceXtras> m_instanceExtras; - // main state variables - size_t m_openWindows = 0; - size_t m_runningInstances = 0; - bool m_updateRunning = false; + // main state variables + size_t m_openWindows = 0; + size_t m_runningInstances = 0; + bool m_updateRunning = false; - // main window, if any - MainWindow * m_mainWindow = nullptr; + // main window, if any + MainWindow * m_mainWindow = nullptr; - // peer MultiMC instance connector - used to implement single instance MultiMC and signalling - LocalPeer * m_peerInstance = nullptr; + // peer MultiMC instance connector - used to implement single instance MultiMC and signalling + LocalPeer * m_peerInstance = nullptr; -/* GAnalytics * m_analytics = nullptr; */ - SetupWizard * m_setupWizard = nullptr; + SetupWizard * m_setupWizard = nullptr; public: - QString m_instanceIdToLaunch; - bool m_liveCheck = false; - std::unique_ptr<QFile> logFile; + QString m_instanceIdToLaunch; + bool m_liveCheck = false; + std::unique_ptr<QFile> logFile; }; diff --git a/application/SettingsUI.h b/application/SettingsUI.h deleted file mode 100644 index 0d14fbee..00000000 --- a/application/SettingsUI.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "pages/BasePageProvider.h" -#include "MultiMC.h" -#include "pagedialog/PageDialog.h" -#include "InstancePageProvider.h" -#include <settings/SettingsObject.h> -#include <BaseInstance.h> - -/* - * FIXME: this is a fragment. find a better place for it. - */ -namespace SettingsUI -{ -template <typename T> -void ShowPageDialog(T raw_provider, QWidget * parent, QString open_page = QString()) -{ - auto provider = std::dynamic_pointer_cast<BasePageProvider>(raw_provider); - if(!provider) - return; - { - SettingsObject::Lock lock(MMC->settings()); - PageDialog dlg(provider.get(), open_page, parent); - dlg.exec(); - } -} -} diff --git a/application/UpdateController.cpp b/application/UpdateController.cpp index 8df5b3fc..0309ad93 100644 --- a/application/UpdateController.cpp +++ b/application/UpdateController.cpp @@ -27,423 +27,423 @@ #endif static QFile::Permissions unixModeToPermissions(const int mode) { - QFile::Permissions perms; + QFile::Permissions perms; - if (mode & S_IRUSR) - { - perms |= QFile::ReadUser; - } - if (mode & S_IWUSR) - { - perms |= QFile::WriteUser; - } - if (mode & S_IXUSR) - { - perms |= QFile::ExeUser; - } + if (mode & S_IRUSR) + { + perms |= QFile::ReadUser; + } + if (mode & S_IWUSR) + { + perms |= QFile::WriteUser; + } + if (mode & S_IXUSR) + { + perms |= QFile::ExeUser; + } - if (mode & S_IRGRP) - { - perms |= QFile::ReadGroup; - } - if (mode & S_IWGRP) - { - perms |= QFile::WriteGroup; - } - if (mode & S_IXGRP) - { - perms |= QFile::ExeGroup; - } + if (mode & S_IRGRP) + { + perms |= QFile::ReadGroup; + } + if (mode & S_IWGRP) + { + perms |= QFile::WriteGroup; + } + if (mode & S_IXGRP) + { + perms |= QFile::ExeGroup; + } - if (mode & S_IROTH) - { - perms |= QFile::ReadOther; - } - if (mode & S_IWOTH) - { - perms |= QFile::WriteOther; - } - if (mode & S_IXOTH) - { - perms |= QFile::ExeOther; - } - return perms; + if (mode & S_IROTH) + { + perms |= QFile::ReadOther; + } + if (mode & S_IWOTH) + { + perms |= QFile::WriteOther; + } + if (mode & S_IXOTH) + { + perms |= QFile::ExeOther; + } + return perms; } static const QLatin1String liveCheckFile("live.check"); UpdateController::UpdateController(QWidget * parent, const QString& root, const QString updateFilesDir, GoUpdate::OperationList operations) { - m_parent = parent; - m_root = root; - m_updateFilesDir = updateFilesDir; - m_operations = operations; + m_parent = parent; + m_root = root; + m_updateFilesDir = updateFilesDir; + m_operations = operations; } void UpdateController::installUpdates() { - qint64 pid = -1; - QStringList args; - bool started = false; + qint64 pid = -1; + QStringList args; + bool started = false; - qDebug() << "Installing updates."; + qDebug() << "Installing updates."; #ifdef Q_OS_WIN - QString finishCmd = QApplication::applicationFilePath(); + QString finishCmd = QApplication::applicationFilePath(); #elif defined Q_OS_LINUX - QString finishCmd = FS::PathCombine(m_root, "MultiMC"); + QString finishCmd = FS::PathCombine(m_root, "MultiMC"); #elif defined Q_OS_MAC - QString finishCmd = QApplication::applicationFilePath(); + QString finishCmd = QApplication::applicationFilePath(); #else #error Unsupported operating system. #endif - QString backupPath = FS::PathCombine(m_root, "update", "backup"); - QDir origin(m_root); + QString backupPath = FS::PathCombine(m_root, "update", "backup"); + QDir origin(m_root); - // clean up the backup folder. it should be empty before we start - if(!FS::deletePath(backupPath)) - { - qWarning() << "couldn't remove previous backup folder" << backupPath; - } - // and it should exist. - if(!FS::ensureFolderPathExists(backupPath)) - { - qWarning() << "couldn't create folder" << backupPath; - return; - } + // clean up the backup folder. it should be empty before we start + if(!FS::deletePath(backupPath)) + { + qWarning() << "couldn't remove previous backup folder" << backupPath; + } + // and it should exist. + if(!FS::ensureFolderPathExists(backupPath)) + { + qWarning() << "couldn't create folder" << backupPath; + return; + } - bool useXPHack = false; - QString exePath; - QString exeOrigin; - QString exeBackup; + bool useXPHack = false; + QString exePath; + QString exeOrigin; + QString exeBackup; - // perform the update operations - for(auto op: m_operations) - { - switch(op.type) - { - // replace = move original out to backup, if it exists, move the new file in its place - case GoUpdate::Operation::OP_REPLACE: - { + // perform the update operations + for(auto op: m_operations) + { + switch(op.type) + { + // replace = move original out to backup, if it exists, move the new file in its place + case GoUpdate::Operation::OP_REPLACE: + { #ifdef Q_OS_WIN32 - // hack for people renaming the .exe because ... reasons :) - if(op.destination == "MultiMC.exe") - { - op.destination = QFileInfo(QApplication::applicationFilePath()).fileName(); - } + // hack for people renaming the .exe because ... reasons :) + if(op.destination == "MultiMC.exe") + { + op.destination = QFileInfo(QApplication::applicationFilePath()).fileName(); + } #endif - QFileInfo destination (FS::PathCombine(m_root, op.destination)); + QFileInfo destination (FS::PathCombine(m_root, op.destination)); #ifdef Q_OS_WIN32 - if(QSysInfo::windowsVersion() < QSysInfo::WV_VISTA) - { - if(destination.fileName() == "MultiMC.exe") - { - QDir rootDir(m_root); - exeOrigin = rootDir.relativeFilePath(op.source); - exePath = rootDir.relativeFilePath(op.destination); - exeBackup = rootDir.relativeFilePath(FS::PathCombine(backupPath, destination.fileName())); - useXPHack = true; - continue; - } - } + if(QSysInfo::windowsVersion() < QSysInfo::WV_VISTA) + { + if(destination.fileName() == "MultiMC.exe") + { + QDir rootDir(m_root); + exeOrigin = rootDir.relativeFilePath(op.source); + exePath = rootDir.relativeFilePath(op.destination); + exeBackup = rootDir.relativeFilePath(FS::PathCombine(backupPath, destination.fileName())); + useXPHack = true; + continue; + } + } #endif - if(destination.exists()) - { - QString backupName = op.destination; - backupName.replace('/', '_'); - QString backupFilePath = FS::PathCombine(backupPath, backupName); - if(!QFile::rename(destination.absoluteFilePath(), backupFilePath)) - { - qWarning() << "Couldn't move:" << destination.absoluteFilePath() << "to" << backupFilePath; - m_failedOperationType = Replace; - m_failedFile = op.destination; - fail(); - return; - } - BackupEntry be; - be.original = destination.absoluteFilePath(); - be.backup = backupFilePath; - be.update = op.source; - m_replace_backups.append(be); - } - // make sure the folder we are putting this into exists - if(!FS::ensureFilePathExists(destination.absoluteFilePath())) - { - qWarning() << "REPLACE: Couldn't create folder:" << destination.absoluteFilePath(); - m_failedOperationType = Replace; - m_failedFile = op.destination; - fail(); - return; - } - // now move the new file in - if(!QFile::rename(op.source, destination.absoluteFilePath())) - { - qWarning() << "REPLACE: Couldn't move:" << op.source << "to" << destination.absoluteFilePath(); - m_failedOperationType = Replace; - m_failedFile = op.destination; - fail(); - return; - } - QFile::setPermissions(destination.absoluteFilePath(), unixModeToPermissions(op.destinationMode)); - } - break; - // delete = move original to backup - case GoUpdate::Operation::OP_DELETE: - { - QString destFilePath = FS::PathCombine(m_root, op.destination); - if(QFile::exists(destFilePath)) - { - QString backupName = op.destination; - backupName.replace('/', '_'); - QString trashFilePath = FS::PathCombine(backupPath, backupName); + if(destination.exists()) + { + QString backupName = op.destination; + backupName.replace('/', '_'); + QString backupFilePath = FS::PathCombine(backupPath, backupName); + if(!QFile::rename(destination.absoluteFilePath(), backupFilePath)) + { + qWarning() << "Couldn't move:" << destination.absoluteFilePath() << "to" << backupFilePath; + m_failedOperationType = Replace; + m_failedFile = op.destination; + fail(); + return; + } + BackupEntry be; + be.original = destination.absoluteFilePath(); + be.backup = backupFilePath; + be.update = op.source; + m_replace_backups.append(be); + } + // make sure the folder we are putting this into exists + if(!FS::ensureFilePathExists(destination.absoluteFilePath())) + { + qWarning() << "REPLACE: Couldn't create folder:" << destination.absoluteFilePath(); + m_failedOperationType = Replace; + m_failedFile = op.destination; + fail(); + return; + } + // now move the new file in + if(!QFile::rename(op.source, destination.absoluteFilePath())) + { + qWarning() << "REPLACE: Couldn't move:" << op.source << "to" << destination.absoluteFilePath(); + m_failedOperationType = Replace; + m_failedFile = op.destination; + fail(); + return; + } + QFile::setPermissions(destination.absoluteFilePath(), unixModeToPermissions(op.destinationMode)); + } + break; + // delete = move original to backup + case GoUpdate::Operation::OP_DELETE: + { + QString destFilePath = FS::PathCombine(m_root, op.destination); + if(QFile::exists(destFilePath)) + { + QString backupName = op.destination; + backupName.replace('/', '_'); + QString trashFilePath = FS::PathCombine(backupPath, backupName); - if(!QFile::rename(destFilePath, trashFilePath)) - { - qWarning() << "DELETE: Couldn't move:" << op.destination << "to" << trashFilePath; - m_failedFile = op.destination; - m_failedOperationType = Delete; - fail(); - return; - } - BackupEntry be; - be.original = destFilePath; - be.backup = trashFilePath; - m_delete_backups.append(be); - } - } - break; - } - } + if(!QFile::rename(destFilePath, trashFilePath)) + { + qWarning() << "DELETE: Couldn't move:" << op.destination << "to" << trashFilePath; + m_failedFile = op.destination; + m_failedOperationType = Delete; + fail(); + return; + } + BackupEntry be; + be.original = destFilePath; + be.backup = trashFilePath; + m_delete_backups.append(be); + } + } + break; + } + } - // try to start the new binary - args = qApp->arguments(); - args.removeFirst(); + // try to start the new binary + args = qApp->arguments(); + args.removeFirst(); - // on old Windows, do insane things... no error checking here, this is just to have something. - if(useXPHack) - { - QString script; - auto nativePath = QDir::toNativeSeparators(exePath); - auto nativeOriginPath = QDir::toNativeSeparators(exeOrigin); - auto nativeBackupPath = QDir::toNativeSeparators(exeBackup); + // on old Windows, do insane things... no error checking here, this is just to have something. + if(useXPHack) + { + QString script; + auto nativePath = QDir::toNativeSeparators(exePath); + auto nativeOriginPath = QDir::toNativeSeparators(exeOrigin); + auto nativeBackupPath = QDir::toNativeSeparators(exeBackup); - // so we write this vbscript thing... - QTextStream out(&script); - out << "WScript.Sleep 1000\n"; - out << "Set fso=CreateObject(\"Scripting.FileSystemObject\")\n"; - out << "Set shell=CreateObject(\"WScript.Shell\")\n"; - out << "fso.MoveFile \"" << nativePath << "\", \"" << nativeBackupPath << "\"\n"; - out << "fso.MoveFile \"" << nativeOriginPath << "\", \"" << nativePath << "\"\n"; - out << "shell.Run \"" << nativePath << "\"\n"; + // so we write this vbscript thing... + QTextStream out(&script); + out << "WScript.Sleep 1000\n"; + out << "Set fso=CreateObject(\"Scripting.FileSystemObject\")\n"; + out << "Set shell=CreateObject(\"WScript.Shell\")\n"; + out << "fso.MoveFile \"" << nativePath << "\", \"" << nativeBackupPath << "\"\n"; + out << "fso.MoveFile \"" << nativeOriginPath << "\", \"" << nativePath << "\"\n"; + out << "shell.Run \"" << nativePath << "\"\n"; - QString scriptPath = FS::PathCombine(m_root, "update", "update.vbs"); + QString scriptPath = FS::PathCombine(m_root, "update", "update.vbs"); - // we save it - QFile scriptFile(scriptPath); - scriptFile.open(QIODevice::WriteOnly); - scriptFile.write(script.toLocal8Bit().replace("\n", "\r\n")); - scriptFile.close(); + // we save it + QFile scriptFile(scriptPath); + scriptFile.open(QIODevice::WriteOnly); + scriptFile.write(script.toLocal8Bit().replace("\n", "\r\n")); + scriptFile.close(); - // we run it - started = QProcess::startDetached("wscript", {scriptPath}, m_root); + // we run it + started = QProcess::startDetached("wscript", {scriptPath}, m_root); - // and we quit. conscious thought. - qApp->quit(); - return; - } - bool doLiveCheck = true; - bool startFailed = false; + // and we quit. conscious thought. + qApp->quit(); + return; + } + bool doLiveCheck = true; + bool startFailed = false; - // remove live check file, if any - if(QFile::exists(liveCheckFile)) - { - if(!QFile::remove(liveCheckFile)) - { - qWarning() << "Couldn't remove the" << liveCheckFile << "file! We will proceed without :("; - doLiveCheck = false; - } - } + // remove live check file, if any + if(QFile::exists(liveCheckFile)) + { + if(!QFile::remove(liveCheckFile)) + { + qWarning() << "Couldn't remove the" << liveCheckFile << "file! We will proceed without :("; + doLiveCheck = false; + } + } - if(doLiveCheck) - { - if(!args.contains("--alive")) - { - args.append("--alive"); - } - } + if(doLiveCheck) + { + if(!args.contains("--alive")) + { + args.append("--alive"); + } + } - // FIXME: reparse args and construct a safe variant from scratch. This is a workaround for GH-1874: - QStringList realargs; - int skip = 0; - for(auto & arg: args) - { - if(skip) - { - skip--; - continue; - } - if(arg == "-l") - { - skip = 1; - continue; - } - realargs.append(arg); - } + // FIXME: reparse args and construct a safe variant from scratch. This is a workaround for GH-1874: + QStringList realargs; + int skip = 0; + for(auto & arg: args) + { + if(skip) + { + skip--; + continue; + } + if(arg == "-l") + { + skip = 1; + continue; + } + realargs.append(arg); + } - // start the updated application - started = QProcess::startDetached(finishCmd, realargs, QDir::currentPath(), &pid); - // much dumber check - just find out if the call - if(!started || pid == -1) - { - qWarning() << "Couldn't start new process properly!"; - startFailed = true; - } - if(!startFailed && doLiveCheck) - { - int attempts = 0; - while(attempts < 10) - { - attempts++; - QString key; - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - if(!QFile::exists(liveCheckFile)) - { - qWarning() << "Couldn't find the" << liveCheckFile << "file!"; - startFailed = true; - continue; - } - try - { - key = QString::fromUtf8(FS::read(liveCheckFile)); - auto id = ApplicationId::fromRawString(key); - LocalPeer peer(nullptr, id); - if(peer.isClient()) - { - startFailed = false; - qDebug() << "Found process started with key " << key; - break; - } - else - { - startFailed = true; - qDebug() << "Process started with key " << key << "apparently died or is not reponding..."; - break; - } - } - catch(Exception e) - { - qWarning() << "Couldn't read the" << liveCheckFile << "file!"; - startFailed = true; - continue; - } - } - } - if(startFailed) - { - m_failedOperationType = Start; - fail(); - return; - } - else - { - origin.rmdir(m_updateFilesDir); - qApp->quit(); - return; - } + // start the updated application + started = QProcess::startDetached(finishCmd, realargs, QDir::currentPath(), &pid); + // much dumber check - just find out if the call + if(!started || pid == -1) + { + qWarning() << "Couldn't start new process properly!"; + startFailed = true; + } + if(!startFailed && doLiveCheck) + { + int attempts = 0; + while(attempts < 10) + { + attempts++; + QString key; + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + if(!QFile::exists(liveCheckFile)) + { + qWarning() << "Couldn't find the" << liveCheckFile << "file!"; + startFailed = true; + continue; + } + try + { + key = QString::fromUtf8(FS::read(liveCheckFile)); + auto id = ApplicationId::fromRawString(key); + LocalPeer peer(nullptr, id); + if(peer.isClient()) + { + startFailed = false; + qDebug() << "Found process started with key " << key; + break; + } + else + { + startFailed = true; + qDebug() << "Process started with key " << key << "apparently died or is not reponding..."; + break; + } + } + catch (const Exception &e) + { + qWarning() << "Couldn't read the" << liveCheckFile << "file!"; + startFailed = true; + continue; + } + } + } + if(startFailed) + { + m_failedOperationType = Start; + fail(); + return; + } + else + { + origin.rmdir(m_updateFilesDir); + qApp->quit(); + return; + } } void UpdateController::fail() { - qWarning() << "Update failed!"; + qWarning() << "Update failed!"; - QString msg; - bool doRollback = false; - QString failTitle = QObject::tr("Update failed!"); - QString rollFailTitle = QObject::tr("Rollback failed!"); - switch (m_failedOperationType) - { - case Replace: - { - msg = QObject::tr("Couldn't replace file %1. Changes will be reverted.\n" - "See the MultiMC log file for details.").arg(m_failedFile); - doRollback = true; - QMessageBox::critical(m_parent, failTitle, msg); - break; - } - case Delete: - { - msg = QObject::tr("Couldn't remove file %1. Changes will be reverted.\n" - "See the MultiMC log file for details.").arg(m_failedFile); - doRollback = true; - QMessageBox::critical(m_parent, failTitle, msg); - break; - } - case Start: - { - msg = QObject::tr("The new version didn't start or is too old and doesn't respond to startup checks.\n" - "\n" - "Roll back to previous version?"); - auto result = QMessageBox::critical( - m_parent, - failTitle, - msg, - QMessageBox::Yes | QMessageBox::No, - QMessageBox::Yes - ); - doRollback = (result == QMessageBox::Yes); - break; - } - case Nothing: - default: - return; - } - if(doRollback) - { - auto rollbackOK = rollback(); - if(!rollbackOK) - { - msg = QObject::tr("The rollback failed too.\n" - "You will have to repair MultiMC manually.\n" - "Please let us know why and how this happened.").arg(m_failedFile); - QMessageBox::critical(m_parent, rollFailTitle, msg); - qApp->quit(); - } - } - else - { - qApp->quit(); - } + QString msg; + bool doRollback = false; + QString failTitle = QObject::tr("Update failed!"); + QString rollFailTitle = QObject::tr("Rollback failed!"); + switch (m_failedOperationType) + { + case Replace: + { + msg = QObject::tr("Couldn't replace file %1. Changes will be reverted.\n" + "See the MultiMC log file for details.").arg(m_failedFile); + doRollback = true; + QMessageBox::critical(m_parent, failTitle, msg); + break; + } + case Delete: + { + msg = QObject::tr("Couldn't remove file %1. Changes will be reverted.\n" + "See the MultiMC log file for details.").arg(m_failedFile); + doRollback = true; + QMessageBox::critical(m_parent, failTitle, msg); + break; + } + case Start: + { + msg = QObject::tr("The new version didn't start or is too old and doesn't respond to startup checks.\n" + "\n" + "Roll back to previous version?"); + auto result = QMessageBox::critical( + m_parent, + failTitle, + msg, + QMessageBox::Yes | QMessageBox::No, + QMessageBox::Yes + ); + doRollback = (result == QMessageBox::Yes); + break; + } + case Nothing: + default: + return; + } + if(doRollback) + { + auto rollbackOK = rollback(); + if(!rollbackOK) + { + msg = QObject::tr("The rollback failed too.\n" + "You will have to repair MultiMC manually.\n" + "Please let us know why and how this happened.").arg(m_failedFile); + QMessageBox::critical(m_parent, rollFailTitle, msg); + qApp->quit(); + } + } + else + { + qApp->quit(); + } } bool UpdateController::rollback() { - bool revertOK = true; - // if the above failed, roll back changes - for(auto backup:m_replace_backups) - { - qWarning() << "restoring" << backup.original << "from" << backup.backup; - if(!QFile::rename(backup.original, backup.update)) - { - revertOK = false; - qWarning() << "moving new" << backup.original << "back to" << backup.update << "failed!"; - continue; - } + bool revertOK = true; + // if the above failed, roll back changes + for(auto backup:m_replace_backups) + { + qWarning() << "restoring" << backup.original << "from" << backup.backup; + if(!QFile::rename(backup.original, backup.update)) + { + revertOK = false; + qWarning() << "moving new" << backup.original << "back to" << backup.update << "failed!"; + continue; + } - if(!QFile::rename(backup.backup, backup.original)) - { - revertOK = false; - qWarning() << "restoring" << backup.original << "failed!"; - } - } - for(auto backup:m_delete_backups) - { - qWarning() << "restoring" << backup.original << "from" << backup.backup; - if(!QFile::rename(backup.backup, backup.original)) - { - revertOK = false; - qWarning() << "restoring" << backup.original << "failed!"; - } - } - return revertOK; + if(!QFile::rename(backup.backup, backup.original)) + { + revertOK = false; + qWarning() << "restoring" << backup.original << "failed!"; + } + } + for(auto backup:m_delete_backups) + { + qWarning() << "restoring" << backup.original << "from" << backup.backup; + if(!QFile::rename(backup.backup, backup.original)) + { + revertOK = false; + qWarning() << "restoring" << backup.original << "failed!"; + } + } + return revertOK; } diff --git a/application/UpdateController.h b/application/UpdateController.h index 2b258701..715554e5 100644 --- a/application/UpdateController.h +++ b/application/UpdateController.h @@ -9,36 +9,36 @@ class QWidget; class UpdateController { public: - UpdateController(QWidget * parent, const QString &root, const QString updateFilesDir, GoUpdate::OperationList operations); - void installUpdates(); + UpdateController(QWidget * parent, const QString &root, const QString updateFilesDir, GoUpdate::OperationList operations); + void installUpdates(); private: - void fail(); - bool rollback(); + void fail(); + bool rollback(); private: - QString m_root; - QString m_updateFilesDir; - GoUpdate::OperationList m_operations; - QWidget * m_parent; + QString m_root; + QString m_updateFilesDir; + GoUpdate::OperationList m_operations; + QWidget * m_parent; - struct BackupEntry - { - // path where we got the new file from - QString update; - // path of what is being actually updated - QString original; - // path where the backup of the updated file was placed - QString backup; - }; - QList <BackupEntry> m_replace_backups; - QList <BackupEntry> m_delete_backups; - enum Failure - { - Replace, - Delete, - Start, - Nothing - } m_failedOperationType = Nothing; - QString m_failedFile; + struct BackupEntry + { + // path where we got the new file from + QString update; + // path of what is being actually updated + QString original; + // path where the backup of the updated file was placed + QString backup; + }; + QList <BackupEntry> m_replace_backups; + QList <BackupEntry> m_delete_backups; + enum Failure + { + Replace, + Delete, + Start, + Nothing + } m_failedOperationType = Nothing; + QString m_failedFile; }; diff --git a/application/VersionProxyModel.cpp b/application/VersionProxyModel.cpp index d59fceee..5587136f 100644 --- a/application/VersionProxyModel.cpp +++ b/application/VersionProxyModel.cpp @@ -7,430 +7,441 @@ class VersionFilterModel : public QSortFilterProxyModel { - Q_OBJECT + Q_OBJECT public: - VersionFilterModel(VersionProxyModel *parent) : QSortFilterProxyModel(parent) - { - m_parent = parent; - setSortRole(BaseVersionList::SortRole); - sort(0, Qt::DescendingOrder); - } - - bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const - { - const auto &filters = m_parent->filters(); - for (auto it = filters.begin(); it != filters.end(); ++it) - { - auto idx = sourceModel()->index(source_row, 0, source_parent); - auto data = sourceModel()->data(idx, it.key()); - auto match = data.toString(); - if(!it.value()->accepts(match)) - { - return false; - } - } - return true; - } - - void filterChanged() - { - invalidateFilter(); - } + VersionFilterModel(VersionProxyModel *parent) : QSortFilterProxyModel(parent) + { + m_parent = parent; + setSortRole(BaseVersionList::SortRole); + sort(0, Qt::DescendingOrder); + } + + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const + { + const auto &filters = m_parent->filters(); + for (auto it = filters.begin(); it != filters.end(); ++it) + { + auto idx = sourceModel()->index(source_row, 0, source_parent); + auto data = sourceModel()->data(idx, it.key()); + auto match = data.toString(); + if(!it.value()->accepts(match)) + { + return false; + } + } + return true; + } + + void filterChanged() + { + invalidateFilter(); + } private: - VersionProxyModel *m_parent; + VersionProxyModel *m_parent; }; VersionProxyModel::VersionProxyModel(QObject *parent) : QAbstractProxyModel(parent) { - filterModel = new VersionFilterModel(this); - connect(filterModel, &QAbstractItemModel::dataChanged, this, &VersionProxyModel::sourceDataChanged); - connect(filterModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &VersionProxyModel::sourceRowsAboutToBeInserted); - connect(filterModel, &QAbstractItemModel::rowsInserted, this, &VersionProxyModel::sourceRowsInserted); - connect(filterModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &VersionProxyModel::sourceRowsAboutToBeRemoved); - connect(filterModel, &QAbstractItemModel::rowsRemoved, this, &VersionProxyModel::sourceRowsRemoved); - // FIXME: implement when needed - /* - connect(replacing, &QAbstractItemModel::rowsAboutToBeMoved, this, &VersionProxyModel::sourceRowsAboutToBeMoved); - connect(replacing, &QAbstractItemModel::rowsMoved, this, &VersionProxyModel::sourceRowsMoved); - connect(replacing, &QAbstractItemModel::layoutAboutToBeChanged, this, &VersionProxyModel::sourceLayoutAboutToBeChanged); - connect(replacing, &QAbstractItemModel::layoutChanged, this, &VersionProxyModel::sourceLayoutChanged); - */ - connect(filterModel, &QAbstractItemModel::modelAboutToBeReset, this, &VersionProxyModel::sourceAboutToBeReset); - connect(filterModel, &QAbstractItemModel::modelReset, this, &VersionProxyModel::sourceReset); - - QAbstractProxyModel::setSourceModel(filterModel); + filterModel = new VersionFilterModel(this); + connect(filterModel, &QAbstractItemModel::dataChanged, this, &VersionProxyModel::sourceDataChanged); + connect(filterModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &VersionProxyModel::sourceRowsAboutToBeInserted); + connect(filterModel, &QAbstractItemModel::rowsInserted, this, &VersionProxyModel::sourceRowsInserted); + connect(filterModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &VersionProxyModel::sourceRowsAboutToBeRemoved); + connect(filterModel, &QAbstractItemModel::rowsRemoved, this, &VersionProxyModel::sourceRowsRemoved); + // FIXME: implement when needed + /* + connect(replacing, &QAbstractItemModel::rowsAboutToBeMoved, this, &VersionProxyModel::sourceRowsAboutToBeMoved); + connect(replacing, &QAbstractItemModel::rowsMoved, this, &VersionProxyModel::sourceRowsMoved); + connect(replacing, &QAbstractItemModel::layoutAboutToBeChanged, this, &VersionProxyModel::sourceLayoutAboutToBeChanged); + connect(replacing, &QAbstractItemModel::layoutChanged, this, &VersionProxyModel::sourceLayoutChanged); + */ + connect(filterModel, &QAbstractItemModel::modelAboutToBeReset, this, &VersionProxyModel::sourceAboutToBeReset); + connect(filterModel, &QAbstractItemModel::modelReset, this, &VersionProxyModel::sourceReset); + + QAbstractProxyModel::setSourceModel(filterModel); } QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { - if(section < 0 || section >= m_columns.size()) - return QVariant(); - if(orientation != Qt::Horizontal) - return QVariant(); - auto column = m_columns[section]; - if(role == Qt::DisplayRole) - { - switch(column) - { - case Name: - return tr("Version"); - case ParentVersion: - return tr("Minecraft"); //FIXME: this should come from metadata - case Branch: - return tr("Branch"); - case Type: - return tr("Type"); - case Architecture: - return tr("Architecture"); - case Path: - return tr("Path"); - case Time: - return tr("Released"); - } - } - else if(role == Qt::ToolTipRole) - { - switch(column) - { - case Name: - return tr("The name of the version."); - case ParentVersion: - return tr("Minecraft version"); //FIXME: this should come from metadata - case Branch: - return tr("The version's branch"); - case Type: - return tr("The version's type"); - case Architecture: - return tr("CPU Architecture"); - case Path: - return tr("Filesystem path to this version"); - case Time: - return tr("Release date of this version"); - } - } - return QVariant(); + if(section < 0 || section >= m_columns.size()) + return QVariant(); + if(orientation != Qt::Horizontal) + return QVariant(); + auto column = m_columns[section]; + if(role == Qt::DisplayRole) + { + switch(column) + { + case Name: + return tr("Version"); + case ParentVersion: + return tr("Minecraft"); //FIXME: this should come from metadata + case Branch: + return tr("Branch"); + case Type: + return tr("Type"); + case Architecture: + return tr("Architecture"); + case Path: + return tr("Path"); + case Time: + return tr("Released"); + } + } + else if(role == Qt::ToolTipRole) + { + switch(column) + { + case Name: + return tr("The name of the version."); + case ParentVersion: + return tr("Minecraft version"); //FIXME: this should come from metadata + case Branch: + return tr("The version's branch"); + case Type: + return tr("The version's type"); + case Architecture: + return tr("CPU Architecture"); + case Path: + return tr("Filesystem path to this version"); + case Time: + return tr("Release date of this version"); + } + } + return QVariant(); } QVariant VersionProxyModel::data(const QModelIndex &index, int role) const { - if(!index.isValid()) - { - return QVariant(); - } - auto column = m_columns[index.column()]; - auto parentIndex = mapToSource(index); - switch(role) - { - case Qt::DisplayRole: - { - switch(column) - { - case Name: - return sourceModel()->data(parentIndex, BaseVersionList::VersionRole); - case ParentVersion: - return sourceModel()->data(parentIndex, BaseVersionList::ParentVersionRole); - case Branch: - return sourceModel()->data(parentIndex, BaseVersionList::BranchRole); - case Type: - return sourceModel()->data(parentIndex, BaseVersionList::TypeRole); - case Architecture: - return sourceModel()->data(parentIndex, BaseVersionList::ArchitectureRole); - case Path: - return sourceModel()->data(parentIndex, BaseVersionList::PathRole); - case Time: - return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate(); - default: - return QVariant(); - } - } - case Qt::ToolTipRole: - { - switch(column) - { - case Name: - { - if(hasRecommended) - { - auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); - if(value.toBool()) - { - return tr("Recommended"); - } - else if(hasLatest) - { - auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); - if(value.toBool()) - { - return tr("Latest"); - } - } - else if(index.row() == 0) - { - return tr("Latest"); - } - } - } - default: - { - return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole); - } - } - } - case Qt::DecorationRole: - { - switch(column) - { - case Name: - { - if(hasRecommended) - { - auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); - if(value.toBool()) - { - return MMC->getThemedIcon("star"); - } - else if(hasLatest) - { - auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); - if(value.toBool()) - { - return MMC->getThemedIcon("bug"); - } - } - else if(index.row() == 0) - { - return MMC->getThemedIcon("bug"); - } - auto pixmap = QPixmapCache::find("placeholder"); - if(!pixmap) - { - QPixmap px(16,16); - px.fill(Qt::transparent); - QPixmapCache::insert("placeholder", px); - return px; - } - return *pixmap; - } - } - default: - { - return QVariant(); - } - } - } - default: - { - if(roles.contains((BaseVersionList::ModelRoles)role)) - { - return sourceModel()->data(parentIndex, role); - } - return QVariant(); - } - } -}; + if(!index.isValid()) + { + return QVariant(); + } + auto column = m_columns[index.column()]; + auto parentIndex = mapToSource(index); + switch(role) + { + case Qt::DisplayRole: + { + switch(column) + { + case Name: + { + QString version = sourceModel()->data(parentIndex, BaseVersionList::VersionRole).toString(); + if(version == m_currentVersion) + { + return tr("%1 (installed)").arg(version); + } + return version; + } + case ParentVersion: + return sourceModel()->data(parentIndex, BaseVersionList::ParentVersionRole); + case Branch: + return sourceModel()->data(parentIndex, BaseVersionList::BranchRole); + case Type: + return sourceModel()->data(parentIndex, BaseVersionList::TypeRole); + case Architecture: + return sourceModel()->data(parentIndex, BaseVersionList::ArchitectureRole); + case Path: + return sourceModel()->data(parentIndex, BaseVersionList::PathRole); + case Time: + return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate(); + default: + return QVariant(); + } + } + case Qt::ToolTipRole: + { + switch(column) + { + case Name: + { + if(hasRecommended) + { + auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); + if(value.toBool()) + { + return tr("Recommended"); + } + else if(hasLatest) + { + auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); + if(value.toBool()) + { + return tr("Latest"); + } + } + else if(index.row() == 0) + { + return tr("Latest"); + } + } + } + default: + { + return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole); + } + } + } + case Qt::DecorationRole: + { + switch(column) + { + case Name: + { + if(hasRecommended) + { + auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); + if(value.toBool()) + { + return MMC->getThemedIcon("star"); + } + else if(hasLatest) + { + auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); + if(value.toBool()) + { + return MMC->getThemedIcon("bug"); + } + } + else if(index.row() == 0) + { + return MMC->getThemedIcon("bug"); + } + auto pixmap = QPixmapCache::find("placeholder"); + if(!pixmap) + { + QPixmap px(16,16); + px.fill(Qt::transparent); + QPixmapCache::insert("placeholder", px); + return px; + } + return *pixmap; + } + } + default: + { + return QVariant(); + } + } + } + default: + { + if(roles.contains((BaseVersionList::ModelRoles)role)) + { + return sourceModel()->data(parentIndex, role); + } + return QVariant(); + } + } +} QModelIndex VersionProxyModel::parent(const QModelIndex &child) const { - return QModelIndex(); + return QModelIndex(); } QModelIndex VersionProxyModel::mapFromSource(const QModelIndex &sourceIndex) const { - if(sourceIndex.isValid()) - { - return index(sourceIndex.row(), 0); - } - return QModelIndex(); + if(sourceIndex.isValid()) + { + return index(sourceIndex.row(), 0); + } + return QModelIndex(); } QModelIndex VersionProxyModel::mapToSource(const QModelIndex &proxyIndex) const { - if(proxyIndex.isValid()) - { - return sourceModel()->index(proxyIndex.row(), 0); - } - return QModelIndex(); + if(proxyIndex.isValid()) + { + return sourceModel()->index(proxyIndex.row(), 0); + } + return QModelIndex(); } QModelIndex VersionProxyModel::index(int row, int column, const QModelIndex &parent) const { - // no trees here... shoo - if(parent.isValid()) - { - return QModelIndex(); - } - if(row < 0 || row >= sourceModel()->rowCount()) - return QModelIndex(); - if(column < 0 || column >= columnCount()) - return QModelIndex(); - return QAbstractItemModel::createIndex(row, column); + // no trees here... shoo + if(parent.isValid()) + { + return QModelIndex(); + } + if(row < 0 || row >= sourceModel()->rowCount()) + return QModelIndex(); + if(column < 0 || column >= columnCount()) + return QModelIndex(); + return QAbstractItemModel::createIndex(row, column); } int VersionProxyModel::columnCount(const QModelIndex &parent) const { - return m_columns.size(); + return m_columns.size(); } int VersionProxyModel::rowCount(const QModelIndex &parent) const { - if(sourceModel()) - { - return sourceModel()->rowCount(); - } - return 0; + if(sourceModel()) + { + return sourceModel()->rowCount(); + } + return 0; } void VersionProxyModel::sourceDataChanged(const QModelIndex &source_top_left, - const QModelIndex &source_bottom_right) + const QModelIndex &source_bottom_right) { - if(source_top_left.parent() != source_bottom_right.parent()) - return; + if(source_top_left.parent() != source_bottom_right.parent()) + return; - // whole row is getting changed - auto topLeft = createIndex(source_top_left.row(), 0); - auto bottomRight = createIndex(source_bottom_right.row(), columnCount() - 1); - emit dataChanged(topLeft, bottomRight); + // whole row is getting changed + auto topLeft = createIndex(source_top_left.row(), 0); + auto bottomRight = createIndex(source_bottom_right.row(), columnCount() - 1); + emit dataChanged(topLeft, bottomRight); } void VersionProxyModel::setSourceModel(QAbstractItemModel *replacingRaw) { - auto replacing = dynamic_cast<BaseVersionList *>(replacingRaw); - beginResetModel(); - - m_columns.clear(); - if(!replacing) - { - roles.clear(); - filterModel->setSourceModel(replacing); - return; - } - - roles = replacing->providesRoles(); - if(roles.contains(BaseVersionList::VersionRole)) - { - m_columns.push_back(Name); - } - /* - if(roles.contains(BaseVersionList::ParentVersionRole)) - { - m_columns.push_back(ParentVersion); - } - */ - if(roles.contains(BaseVersionList::ArchitectureRole)) - { - m_columns.push_back(Architecture); - } - if(roles.contains(BaseVersionList::PathRole)) - { - m_columns.push_back(Path); - } - if(roles.contains(Meta::VersionList::TimeRole)) - { - m_columns.push_back(Time); - } - if(roles.contains(BaseVersionList::BranchRole)) - { - m_columns.push_back(Branch); - } - if(roles.contains(BaseVersionList::TypeRole)) - { - m_columns.push_back(Type); - } - if(roles.contains(BaseVersionList::RecommendedRole)) - { - hasRecommended = true; - } - if(roles.contains(BaseVersionList::LatestRole)) - { - hasLatest = true; - } - filterModel->setSourceModel(replacing); - - endResetModel(); + auto replacing = dynamic_cast<BaseVersionList *>(replacingRaw); + beginResetModel(); + + m_columns.clear(); + if(!replacing) + { + roles.clear(); + filterModel->setSourceModel(replacing); + return; + } + + roles = replacing->providesRoles(); + if(roles.contains(BaseVersionList::VersionRole)) + { + m_columns.push_back(Name); + } + /* + if(roles.contains(BaseVersionList::ParentVersionRole)) + { + m_columns.push_back(ParentVersion); + } + */ + if(roles.contains(BaseVersionList::ArchitectureRole)) + { + m_columns.push_back(Architecture); + } + if(roles.contains(BaseVersionList::PathRole)) + { + m_columns.push_back(Path); + } + if(roles.contains(Meta::VersionList::TimeRole)) + { + m_columns.push_back(Time); + } + if(roles.contains(BaseVersionList::BranchRole)) + { + m_columns.push_back(Branch); + } + if(roles.contains(BaseVersionList::TypeRole)) + { + m_columns.push_back(Type); + } + if(roles.contains(BaseVersionList::RecommendedRole)) + { + hasRecommended = true; + } + if(roles.contains(BaseVersionList::LatestRole)) + { + hasLatest = true; + } + filterModel->setSourceModel(replacing); + + endResetModel(); } QModelIndex VersionProxyModel::getRecommended() const { - if(!roles.contains(BaseVersionList::RecommendedRole)) - { - return index(0, 0); - } - int recommended = 0; - for (int i = 0; i < rowCount(); i++) - { - auto value = sourceModel()->data(mapToSource(index(i, 0)), BaseVersionList::RecommendedRole); - if (value.toBool()) - { - recommended = i; - } - } - return index(recommended, 0); + if(!roles.contains(BaseVersionList::RecommendedRole)) + { + return index(0, 0); + } + int recommended = 0; + for (int i = 0; i < rowCount(); i++) + { + auto value = sourceModel()->data(mapToSource(index(i, 0)), BaseVersionList::RecommendedRole); + if (value.toBool()) + { + recommended = i; + } + } + return index(recommended, 0); } QModelIndex VersionProxyModel::getVersion(const QString& version) const { - int found = -1; - for (int i = 0; i < rowCount(); i++) - { - auto value = sourceModel()->data(mapToSource(index(i, 0)), BaseVersionList::VersionRole); - if (value.toString() == version) - { - found = i; - } - } - if(found == -1) - { - return QModelIndex(); - } - return index(found, 0); + int found = -1; + for (int i = 0; i < rowCount(); i++) + { + auto value = sourceModel()->data(mapToSource(index(i, 0)), BaseVersionList::VersionRole); + if (value.toString() == version) + { + found = i; + } + } + if(found == -1) + { + return QModelIndex(); + } + return index(found, 0); } void VersionProxyModel::clearFilters() { - m_filters.clear(); - filterModel->filterChanged(); + m_filters.clear(); + filterModel->filterChanged(); } void VersionProxyModel::setFilter(const BaseVersionList::ModelRoles column, Filter * f) { - m_filters[column].reset(f); - filterModel->filterChanged(); + m_filters[column].reset(f); + filterModel->filterChanged(); } const VersionProxyModel::FilterMap &VersionProxyModel::filters() const { - return m_filters; + return m_filters; } void VersionProxyModel::sourceAboutToBeReset() { - beginResetModel(); + beginResetModel(); } void VersionProxyModel::sourceReset() { - endResetModel(); + endResetModel(); } void VersionProxyModel::sourceRowsAboutToBeInserted(const QModelIndex& parent, int first, int last) { - beginInsertRows(parent, first, last); + beginInsertRows(parent, first, last); } void VersionProxyModel::sourceRowsInserted(const QModelIndex& parent, int first, int last) { - endInsertRows(); + endInsertRows(); } void VersionProxyModel::sourceRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last) { - beginRemoveRows(parent, first, last); + beginRemoveRows(parent, first, last); } void VersionProxyModel::sourceRowsRemoved(const QModelIndex& parent, int first, int last) { - endRemoveRows(); + endRemoveRows(); } +void VersionProxyModel::setCurrentVersion(const QString &version) +{ + m_currentVersion = version; +} #include "VersionProxyModel.moc" diff --git a/application/VersionProxyModel.h b/application/VersionProxyModel.h index 91d1ba23..8991c31b 100644 --- a/application/VersionProxyModel.h +++ b/application/VersionProxyModel.h @@ -8,58 +8,60 @@ class VersionFilterModel; class VersionProxyModel: public QAbstractProxyModel { - Q_OBJECT + Q_OBJECT public: - enum Column - { - Name, - ParentVersion, - Branch, - Type, - Architecture, - Path, - Time - }; - typedef QHash<BaseVersionList::ModelRoles, std::shared_ptr<Filter>> FilterMap; + enum Column + { + Name, + ParentVersion, + Branch, + Type, + Architecture, + Path, + Time + }; + typedef QHash<BaseVersionList::ModelRoles, std::shared_ptr<Filter>> FilterMap; public: VersionProxyModel ( QObject* parent = 0 ); virtual ~VersionProxyModel() {}; - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; - virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; - virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; - virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - virtual QModelIndex parent(const QModelIndex &child) const override; - virtual void setSourceModel(QAbstractItemModel *sourceModel) override; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; + virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; + virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + virtual QModelIndex parent(const QModelIndex &child) const override; + virtual void setSourceModel(QAbstractItemModel *sourceModel) override; - const FilterMap &filters() const; - void setFilter(const BaseVersionList::ModelRoles column, Filter * filter); - void clearFilters(); - QModelIndex getRecommended() const; - QModelIndex getVersion(const QString & version) const; + const FilterMap &filters() const; + void setFilter(const BaseVersionList::ModelRoles column, Filter * filter); + void clearFilters(); + QModelIndex getRecommended() const; + QModelIndex getVersion(const QString & version) const; + void setCurrentVersion(const QString &version); private slots: - void sourceDataChanged(const QModelIndex &source_top_left,const QModelIndex &source_bottom_right); + void sourceDataChanged(const QModelIndex &source_top_left,const QModelIndex &source_bottom_right); - void sourceAboutToBeReset(); - void sourceReset(); + void sourceAboutToBeReset(); + void sourceReset(); - void sourceRowsAboutToBeInserted(const QModelIndex &parent, int first, int last); - void sourceRowsInserted(const QModelIndex &parent, int first, int last); + void sourceRowsAboutToBeInserted(const QModelIndex &parent, int first, int last); + void sourceRowsInserted(const QModelIndex &parent, int first, int last); - void sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); - void sourceRowsRemoved(const QModelIndex &parent, int first, int last); + void sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); + void sourceRowsRemoved(const QModelIndex &parent, int first, int last); private: - QList<Column> m_columns; - FilterMap m_filters; - BaseVersionList::RoleList roles; - VersionFilterModel * filterModel; - bool hasRecommended = false; - bool hasLatest = false; + QList<Column> m_columns; + FilterMap m_filters; + BaseVersionList::RoleList roles; + VersionFilterModel * filterModel; + bool hasRecommended = false; + bool hasLatest = false; + QString m_currentVersion; }; diff --git a/application/dialogs/AboutDialog.cpp b/application/dialogs/AboutDialog.cpp index 8f2e72c2..ca1dfd94 100644 --- a/application/dialogs/AboutDialog.cpp +++ b/application/dialogs/AboutDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,113 +27,113 @@ // This is a hack, but I can't think of a better way to do this easily without screwing with QTextDocument... static QString getCreditsHtml(QStringList patrons) { - QString creditsHtml = QObject::tr( - "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0//EN' 'http://www.w3.org/TR/REC-html40/strict.dtd'>" - "<html>" - "" - "<head>" - "<meta name='qrichtext' content='1' />" - "<style type='text/css'>" - "p { white-space: pre-wrap; margin-top:2px; margin-bottom:2px; }" - "</style>" - "</head>" - "" - "<body style=' font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;'>" - "" - "<h3>MultiMC Developers</h3>" - "<p>Andrew Okin <<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>></p>" - "<p>Petr Mrázek <<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>></p>" - "<p>Sky Welch <<a href='mailto:multimc@bunnies.io'>multimc@bunnies.io</a>></p>" - "<p>Jan (02JanDal) <<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>></p>" - "<p>RoboSky <<a href='https://twitter.com/RoboSky_'>@RoboSky_</a>></p>" - "" - "<h3>With thanks to</h3>" - "<p>Orochimarufan <<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>></p>" - "<p>TakSuyu <<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>></p>" - "<p>Kilobyte <<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>></p>" - "<p>Rootbear75 <<a href='https://twitter.com/rootbear75'>@rootbear75</a>></p>" - "" - "<h3>Patrons</h3>" - "%1" - "" - "</body>" - "</html>"); - if (patrons.isEmpty()) - return creditsHtml.arg(QObject::tr("<p>Loading...</p>")); - else - { - QString patronsStr; - for (QString patron : patrons) - { - patronsStr.append(QString("<p>%1</p>").arg(patron)); - } - - return creditsHtml.arg(patronsStr); - } + QString creditsHtml = QObject::tr( + "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0//EN' 'http://www.w3.org/TR/REC-html40/strict.dtd'>" + "<html>" + "" + "<head>" + "<meta name='qrichtext' content='1' />" + "<style type='text/css'>" + "p { white-space: pre-wrap; margin-top:2px; margin-bottom:2px; }" + "</style>" + "</head>" + "" + "<body style=' font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;'>" + "" + "<h3>MultiMC Developers</h3>" + "<p>Andrew Okin <<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>></p>" + "<p>Petr Mrázek <<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>></p>" + "<p>Sky Welch <<a href='mailto:multimc@bunnies.io'>multimc@bunnies.io</a>></p>" + "<p>Jan (02JanDal) <<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>></p>" + "<p>RoboSky <<a href='https://twitter.com/RoboSky_'>@RoboSky_</a>></p>" + "" + "<h3>With thanks to</h3>" + "<p>Orochimarufan <<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>></p>" + "<p>TakSuyu <<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>></p>" + "<p>Kilobyte <<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>></p>" + "<p>Rootbear75 <<a href='https://twitter.com/rootbear75'>@rootbear75</a>></p>" + "" + "<h3>Patrons</h3>" + "%1" + "" + "</body>" + "</html>"); + if (patrons.isEmpty()) + return creditsHtml.arg(QObject::tr("<p>Loading...</p>")); + else + { + QString patronsStr; + for (QString patron : patrons) + { + patronsStr.append(QString("<p>%1</p>").arg(patron)); + } + + return creditsHtml.arg(patronsStr); + } } static QString getLicenseHtml() { - HoeDown hoedown; - QFile dataFile(":/documents/COPYING.md"); - dataFile.open(QIODevice::ReadOnly); - QString output = hoedown.process(dataFile.readAll()); - return output; + HoeDown hoedown; + QFile dataFile(":/documents/COPYING.md"); + dataFile.open(QIODevice::ReadOnly); + QString output = hoedown.process(dataFile.readAll()); + return output; } AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog) { - ui->setupUi(this); + ui->setupUi(this); - QString chtml = getCreditsHtml(QStringList()); - ui->creditsText->setHtml(chtml); + QString chtml = getCreditsHtml(QStringList()); + ui->creditsText->setHtml(chtml); - QString lhtml = getLicenseHtml(); - ui->licenseText->setHtml(lhtml); + QString lhtml = getLicenseHtml(); + ui->licenseText->setHtml(lhtml); - ui->urlLabel->setOpenExternalLinks(true); + ui->urlLabel->setOpenExternalLinks(true); - ui->icon->setPixmap(MMC->getThemedIcon("logo").pixmap(64)); - ui->title->setText("MultiMC 5"); + ui->icon->setPixmap(MMC->getThemedIcon("logo").pixmap(64)); + ui->title->setText("MultiMC 5"); - ui->versionLabel->setText(tr("Version") +": " + BuildConfig.printableVersionString()); - ui->platformLabel->setText(tr("Platform") +": " + BuildConfig.BUILD_PLATFORM); + ui->versionLabel->setText(tr("Version") +": " + BuildConfig.printableVersionString()); + ui->platformLabel->setText(tr("Platform") +": " + BuildConfig.BUILD_PLATFORM); - if (BuildConfig.VERSION_BUILD >= 0) - ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(BuildConfig.VERSION_BUILD)); - else - ui->buildNumLabel->setVisible(false); + if (BuildConfig.VERSION_BUILD >= 0) + ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(BuildConfig.VERSION_BUILD)); + else + ui->buildNumLabel->setVisible(false); - if (!BuildConfig.VERSION_CHANNEL.isEmpty()) - ui->channelLabel->setText(tr("Channel") +": " + BuildConfig.VERSION_CHANNEL); - else - ui->channelLabel->setVisible(false); + if (!BuildConfig.VERSION_CHANNEL.isEmpty()) + ui->channelLabel->setText(tr("Channel") +": " + BuildConfig.VERSION_CHANNEL); + else + ui->channelLabel->setVisible(false); - connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); + connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); - connect(ui->aboutQt, &QPushButton::clicked, &QApplication::aboutQt); + connect(ui->aboutQt, &QPushButton::clicked, &QApplication::aboutQt); - loadPatronList(); + loadPatronList(); } AboutDialog::~AboutDialog() { - delete ui; + delete ui; } void AboutDialog::loadPatronList() { - netJob.reset(new NetJob("Patreon Patron List")); - netJob->addNetAction(Net::Download::makeByteArray(QUrl("http://files.multimc.org/patrons.txt"), &dataSink)); - connect(netJob.get(), &NetJob::succeeded, this, &AboutDialog::patronListLoaded); - netJob->start(); + netJob.reset(new NetJob("Patreon Patron List")); + netJob->addNetAction(Net::Download::makeByteArray(QUrl("https://files.multimc.org/patrons.txt"), &dataSink)); + connect(netJob.get(), &NetJob::succeeded, this, &AboutDialog::patronListLoaded); + netJob->start(); } void AboutDialog::patronListLoaded() { - QString patronListStr(dataSink); - dataSink.clear(); - QString html = getCreditsHtml(patronListStr.split("\n", QString::SkipEmptyParts)); - ui->creditsText->setHtml(html); + QString patronListStr(dataSink); + dataSink.clear(); + QString html = getCreditsHtml(patronListStr.split("\n", QString::SkipEmptyParts)); + ui->creditsText->setHtml(html); } diff --git a/application/dialogs/AboutDialog.h b/application/dialogs/AboutDialog.h index 9768b866..21001c07 100644 --- a/application/dialogs/AboutDialog.h +++ b/application/dialogs/AboutDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,23 +25,23 @@ class AboutDialog; class AboutDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit AboutDialog(QWidget *parent = 0); - ~AboutDialog(); + explicit AboutDialog(QWidget *parent = 0); + ~AboutDialog(); public slots: - /// Starts loading a list of Patreon patrons. - void loadPatronList(); - - /// Slot for when the patron list loads successfully. - void patronListLoaded(); + /// Starts loading a list of Patreon patrons. + void loadPatronList(); + + /// Slot for when the patron list loads successfully. + void patronListLoaded(); private: - Ui::AboutDialog *ui; + Ui::AboutDialog *ui; - NetJobPtr netJob; - QByteArray dataSink; + NetJobPtr netJob; + QByteArray dataSink; }; diff --git a/application/dialogs/AboutDialog.ui b/application/dialogs/AboutDialog.ui index 5e8e3e68..a312aa0c 100644 --- a/application/dialogs/AboutDialog.ui +++ b/application/dialogs/AboutDialog.ui @@ -165,7 +165,7 @@ </font> </property> <property name="text"> - <string>© 2012-2018 MultiMC Contributors</string> + <string>© 2012-2019 MultiMC Contributors</string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -180,7 +180,7 @@ </font> </property> <property name="text"> - <string notr="true"><html><head/><body><p><a href="http://github.com/MultiMC/MultiMC5">http://github.com/MultiMC/MultiMC5</a></p></body></html></string> + <string notr="true"><html><head/><body><p><a href="https://github.com/MultiMC/MultiMC5">https://github.com/MultiMC/MultiMC5</a></p></body></html></string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> diff --git a/application/dialogs/CopyInstanceDialog.cpp b/application/dialogs/CopyInstanceDialog.cpp index 72ef00fa..6100860c 100644 --- a/application/dialogs/CopyInstanceDialog.cpp +++ b/application/dialogs/CopyInstanceDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,87 +29,97 @@ #include "InstanceList.h" CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) - :QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original) + :QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original) { - ui->setupUi(this); - resize(minimumSizeHint()); - layout()->setSizeConstraint(QLayout::SetFixedSize); - - InstIconKey = original->iconKey(); - ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); - ui->instNameTextBox->setText(original->name()); - ui->instNameTextBox->setFocus(); - auto groups = MMC->instances()->getGroups().toSet(); - auto groupList = QStringList(groups.toList()); - groupList.sort(Qt::CaseInsensitive); - groupList.removeOne(""); - groupList.push_front(""); - ui->groupBox->addItems(groupList); - int index = groupList.indexOf(m_original->group()); - if(index == -1) - { - index = 0; - } - ui->groupBox->setCurrentIndex(index); - ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); - ui->copySavesCheckbox->setChecked(m_copySaves); + ui->setupUi(this); + resize(minimumSizeHint()); + layout()->setSizeConstraint(QLayout::SetFixedSize); + + InstIconKey = original->iconKey(); + ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); + ui->instNameTextBox->setText(original->name()); + ui->instNameTextBox->setFocus(); + auto groups = MMC->instances()->getGroups().toSet(); + auto groupList = QStringList(groups.toList()); + groupList.sort(Qt::CaseInsensitive); + groupList.removeOne(""); + groupList.push_front(""); + ui->groupBox->addItems(groupList); + int index = groupList.indexOf(MMC->instances()->getInstanceGroup(m_original->id())); + if(index == -1) + { + index = 0; + } + ui->groupBox->setCurrentIndex(index); + ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); + ui->copySavesCheckbox->setChecked(m_copySaves); } CopyInstanceDialog::~CopyInstanceDialog() { - delete ui; + delete ui; } void CopyInstanceDialog::updateDialogState() { - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!instName().isEmpty()); + auto allowOK = !instName().isEmpty(); + auto OkButton = ui->buttonBox->button(QDialogButtonBox::Ok); + if(OkButton->isEnabled() != allowOK) + { + OkButton->setEnabled(allowOK); + } } QString CopyInstanceDialog::instName() const { - return ui->instNameTextBox->text(); + auto result = ui->instNameTextBox->text().trimmed(); + if(result.size()) + { + return result; + } + return QString(); } QString CopyInstanceDialog::iconKey() const { - return InstIconKey; + return InstIconKey; } QString CopyInstanceDialog::instGroup() const { - return ui->groupBox->currentText(); + return ui->groupBox->currentText(); } void CopyInstanceDialog::on_iconButton_clicked() { - IconPickerDialog dlg(this); - dlg.execWithSelection(InstIconKey); - - if (dlg.result() == QDialog::Accepted) - { - InstIconKey = dlg.selectedIconKey; - ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); - } + IconPickerDialog dlg(this); + dlg.execWithSelection(InstIconKey); + + if (dlg.result() == QDialog::Accepted) + { + InstIconKey = dlg.selectedIconKey; + ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); + } } void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1) { - updateDialogState(); + updateDialogState(); } bool CopyInstanceDialog::shouldCopySaves() const { - return m_copySaves; + return m_copySaves; } void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state) { - if(state == Qt::Unchecked) - { - m_copySaves = false; - } - else if(state == Qt::Checked) - { - m_copySaves = true; - } + if(state == Qt::Unchecked) + { + m_copySaves = false; + } + else if(state == Qt::Checked) + { + m_copySaves = true; + } } diff --git a/application/dialogs/CopyInstanceDialog.h b/application/dialogs/CopyInstanceDialog.h index 809552eb..d46e647c 100644 --- a/application/dialogs/CopyInstanceDialog.h +++ b/application/dialogs/CopyInstanceDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,28 +28,28 @@ class CopyInstanceDialog; class CopyInstanceDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit CopyInstanceDialog(InstancePtr original, QWidget *parent = 0); - ~CopyInstanceDialog(); + explicit CopyInstanceDialog(InstancePtr original, QWidget *parent = 0); + ~CopyInstanceDialog(); - void updateDialogState(); + void updateDialogState(); - QString instName() const; - QString instGroup() const; - QString iconKey() const; - bool shouldCopySaves() const; + QString instName() const; + QString instGroup() const; + QString iconKey() const; + bool shouldCopySaves() const; private slots: - void on_iconButton_clicked(); - void on_instNameTextBox_textChanged(const QString &arg1); - void on_copySavesCheckbox_stateChanged(int state); + void on_iconButton_clicked(); + void on_instNameTextBox_textChanged(const QString &arg1); + void on_copySavesCheckbox_stateChanged(int state); private: - Ui::CopyInstanceDialog *ui; - QString InstIconKey; - InstancePtr m_original; - bool m_copySaves = true; + Ui::CopyInstanceDialog *ui; + QString InstIconKey; + InstancePtr m_original; + bool m_copySaves = true; }; diff --git a/application/dialogs/CustomMessageBox.cpp b/application/dialogs/CustomMessageBox.cpp index a7d75263..db60b7f3 100644 --- a/application/dialogs/CustomMessageBox.cpp +++ b/application/dialogs/CustomMessageBox.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,18 +18,18 @@ namespace CustomMessageBox { QMessageBox *selectable(QWidget *parent, const QString &title, const QString &text, - QMessageBox::Icon icon, QMessageBox::StandardButtons buttons, - QMessageBox::StandardButton defaultButton) + QMessageBox::Icon icon, QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton) { - QMessageBox *messageBox = new QMessageBox(parent); - messageBox->setWindowTitle(title); - messageBox->setText(text); - messageBox->setStandardButtons(buttons); - messageBox->setDefaultButton(defaultButton); - messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); - messageBox->setIcon(icon); - messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); + QMessageBox *messageBox = new QMessageBox(parent); + messageBox->setWindowTitle(title); + messageBox->setText(text); + messageBox->setStandardButtons(buttons); + messageBox->setDefaultButton(defaultButton); + messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); + messageBox->setIcon(icon); + messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); - return messageBox; + return messageBox; } } diff --git a/application/dialogs/CustomMessageBox.h b/application/dialogs/CustomMessageBox.h index f9c0ad4e..92291e17 100644 --- a/application/dialogs/CustomMessageBox.h +++ b/application/dialogs/CustomMessageBox.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ namespace CustomMessageBox { QMessageBox *selectable(QWidget *parent, const QString &title, const QString &text, - QMessageBox::Icon icon = QMessageBox::NoIcon, - QMessageBox::StandardButtons buttons = QMessageBox::Ok, - QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); + QMessageBox::Icon icon = QMessageBox::NoIcon, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); } diff --git a/application/dialogs/EditAccountDialog.cpp b/application/dialogs/EditAccountDialog.cpp index e43be1d8..9ae0c745 100644 --- a/application/dialogs/EditAccountDialog.cpp +++ b/application/dialogs/EditAccountDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,43 +19,43 @@ #include <QUrl> EditAccountDialog::EditAccountDialog(const QString &text, QWidget *parent, int flags) - : QDialog(parent), ui(new Ui::EditAccountDialog) + : QDialog(parent), ui(new Ui::EditAccountDialog) { - ui->setupUi(this); + ui->setupUi(this); - ui->label->setText(text); - ui->label->setVisible(!text.isEmpty()); + ui->label->setText(text); + ui->label->setVisible(!text.isEmpty()); - ui->userTextBox->setEnabled(flags & UsernameField); - ui->passTextBox->setEnabled(flags & PasswordField); + ui->userTextBox->setEnabled(flags & UsernameField); + ui->passTextBox->setEnabled(flags & PasswordField); } EditAccountDialog::~EditAccountDialog() { - delete ui; + delete ui; } void EditAccountDialog::on_label_linkActivated(const QString &link) { - DesktopServices::openUrl(QUrl(link)); + DesktopServices::openUrl(QUrl(link)); } void EditAccountDialog::setUsername(const QString & user) const { - ui->userTextBox->setText(user); + ui->userTextBox->setText(user); } QString EditAccountDialog::username() const { - return ui->userTextBox->text(); + return ui->userTextBox->text(); } void EditAccountDialog::setPassword(const QString & pass) const { - ui->passTextBox->setText(pass); + ui->passTextBox->setText(pass); } QString EditAccountDialog::password() const { - return ui->passTextBox->text(); + return ui->passTextBox->text(); } diff --git a/application/dialogs/EditAccountDialog.h b/application/dialogs/EditAccountDialog.h index f121a111..4f80284a 100644 --- a/application/dialogs/EditAccountDialog.h +++ b/application/dialogs/EditAccountDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,33 +24,33 @@ class EditAccountDialog; class EditAccountDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit EditAccountDialog(const QString &text = "", QWidget *parent = 0, - int flags = UsernameField | PasswordField); - ~EditAccountDialog(); + explicit EditAccountDialog(const QString &text = "", QWidget *parent = 0, + int flags = UsernameField | PasswordField); + ~EditAccountDialog(); - void setUsername(const QString & user) const; - void setPassword(const QString & pass) const; + void setUsername(const QString & user) const; + void setPassword(const QString & pass) const; - QString username() const; - QString password() const; + QString username() const; + QString password() const; - enum Flags - { - NoFlags = 0, + enum Flags + { + NoFlags = 0, - //! Specifies that the dialog should have a username field. - UsernameField, + //! Specifies that the dialog should have a username field. + UsernameField, - //! Specifies that the dialog should have a password field. - PasswordField, - }; + //! Specifies that the dialog should have a password field. + PasswordField, + }; private slots: void on_label_linkActivated(const QString &link); private: - Ui::EditAccountDialog *ui; + Ui::EditAccountDialog *ui; }; diff --git a/application/dialogs/ExportInstanceDialog.cpp b/application/dialogs/ExportInstanceDialog.cpp index 0e19b758..49c082e9 100644 --- a/application/dialogs/ExportInstanceDialog.cpp +++ b/application/dialogs/ExportInstanceDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,456 +33,450 @@ class PackIgnoreProxy : public QSortFilterProxyModel { - Q_OBJECT + Q_OBJECT public: - PackIgnoreProxy(InstancePtr instance, QObject *parent) : QSortFilterProxyModel(parent) - { - m_instance = instance; - } - // NOTE: Sadly, we have to do sorting ourselves. - bool lessThan(const QModelIndex &left, const QModelIndex &right) const - { - QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel()); - if (!fsm) - { - return QSortFilterProxyModel::lessThan(left, right); - } - bool asc = sortOrder() == Qt::AscendingOrder ? true : false; - - QFileInfo leftFileInfo = fsm->fileInfo(left); - QFileInfo rightFileInfo = fsm->fileInfo(right); - - if (!leftFileInfo.isDir() && rightFileInfo.isDir()) - { - return !asc; - } - if (leftFileInfo.isDir() && !rightFileInfo.isDir()) - { - return asc; - } - - // sort and proxy model breaks the original model... - if (sortColumn() == 0) - { - return Strings::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), - Qt::CaseInsensitive) < 0; - } - if (sortColumn() == 1) - { - auto leftSize = leftFileInfo.size(); - auto rightSize = rightFileInfo.size(); - if ((leftSize == rightSize) || (leftFileInfo.isDir() && rightFileInfo.isDir())) - { - return Strings::naturalCompare(leftFileInfo.fileName(), - rightFileInfo.fileName(), - Qt::CaseInsensitive) < 0 - ? asc - : !asc; - } - return leftSize < rightSize; - } - return QSortFilterProxyModel::lessThan(left, right); - } - - virtual Qt::ItemFlags flags(const QModelIndex &index) const - { - if (!index.isValid()) - return Qt::NoItemFlags; - - auto sourceIndex = mapToSource(index); - Qt::ItemFlags flags = sourceIndex.flags(); - if (index.column() == 0) - { - flags |= Qt::ItemIsUserCheckable; - if (sourceIndex.model()->hasChildren(sourceIndex)) - { - flags |= Qt::ItemIsTristate; - } - } - - return flags; - } - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const - { - QModelIndex sourceIndex = mapToSource(index); - - if (index.column() == 0 && role == Qt::CheckStateRole) - { - QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel()); - auto blockedPath = relPath(fsm->filePath(sourceIndex)); - auto cover = blocked.cover(blockedPath); - if (!cover.isNull()) - { - return QVariant(Qt::Unchecked); - } - else if (blocked.exists(blockedPath)) - { - return QVariant(Qt::PartiallyChecked); - } - else - { - return QVariant(Qt::Checked); - } - } - - return sourceIndex.data(role); - } - - virtual bool setData(const QModelIndex &index, const QVariant &value, - int role = Qt::EditRole) - { - if (index.column() == 0 && role == Qt::CheckStateRole) - { - Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt()); - return setFilterState(index, state); - } - - QModelIndex sourceIndex = mapToSource(index); - return QSortFilterProxyModel::sourceModel()->setData(sourceIndex, value, role); - } - - QString relPath(const QString &path) const - { - QString prefix = QDir().absoluteFilePath(m_instance->instanceRoot()); - prefix += '/'; - if (!path.startsWith(prefix)) - { - return QString(); - } - return path.mid(prefix.size()); - } - - bool setFilterState(QModelIndex index, Qt::CheckState state) - { - QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel()); - - if (!fsm) - { - return false; - } - - QModelIndex sourceIndex = mapToSource(index); - auto blockedPath = relPath(fsm->filePath(sourceIndex)); - bool changed = false; - if (state == Qt::Unchecked) - { - // blocking a path - auto &node = blocked.insert(blockedPath); - // get rid of all blocked nodes below - node.clear(); - changed = true; - } - else if (state == Qt::Checked || state == Qt::PartiallyChecked) - { - if (!blocked.remove(blockedPath)) - { - auto cover = blocked.cover(blockedPath); - qDebug() << "Blocked by cover" << cover; - // uncover - blocked.remove(cover); - // block all contents, except for any cover - QModelIndex rootIndex = - fsm->index(FS::PathCombine(m_instance->instanceRoot(), cover)); - QModelIndex doing = rootIndex; - int row = 0; - QStack<QModelIndex> todo; - while (1) - { - auto node = doing.child(row, 0); - if (!node.isValid()) - { - if (!todo.size()) - { - break; - } - else - { - doing = todo.pop(); - row = 0; - continue; - } - } - auto relpath = relPath(fsm->filePath(node)); - if (blockedPath.startsWith(relpath)) // cover found? - { - // continue processing cover later - todo.push(node); - } - else - { - // or just block this one. - blocked.insert(relpath); - } - row++; - } - } - changed = true; - } - if (changed) - { - // update the thing - emit dataChanged(index, index, {Qt::CheckStateRole}); - // update everything above index - QModelIndex up = index.parent(); - while (1) - { - if (!up.isValid()) - break; - emit dataChanged(up, up, {Qt::CheckStateRole}); - up = up.parent(); - } - // and everything below the index - QModelIndex doing = index; - int row = 0; - QStack<QModelIndex> todo; - while (1) - { - auto node = doing.child(row, 0); - if (!node.isValid()) - { - if (!todo.size()) - { - break; - } - else - { - doing = todo.pop(); - row = 0; - continue; - } - } - emit dataChanged(node, node, {Qt::CheckStateRole}); - todo.push(node); - row++; - } - // siblings and unrelated nodes are ignored - } - return true; - } - - bool shouldExpand(QModelIndex index) - { - QModelIndex sourceIndex = mapToSource(index); - QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel()); - if (!fsm) - { - return false; - } - auto blockedPath = relPath(fsm->filePath(sourceIndex)); - auto found = blocked.find(blockedPath); - if(found) - { - return !found->leaf(); - } - return false; - } - - void setBlockedPaths(QStringList paths) - { - beginResetModel(); - blocked.clear(); - blocked.insert(paths); - endResetModel(); - } - - const SeparatorPrefixTree<'/'> & blockedPaths() const - { - return blocked; - } + PackIgnoreProxy(InstancePtr instance, QObject *parent) : QSortFilterProxyModel(parent) + { + m_instance = instance; + } + // NOTE: Sadly, we have to do sorting ourselves. + bool lessThan(const QModelIndex &left, const QModelIndex &right) const + { + QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel()); + if (!fsm) + { + return QSortFilterProxyModel::lessThan(left, right); + } + bool asc = sortOrder() == Qt::AscendingOrder ? true : false; + + QFileInfo leftFileInfo = fsm->fileInfo(left); + QFileInfo rightFileInfo = fsm->fileInfo(right); + + if (!leftFileInfo.isDir() && rightFileInfo.isDir()) + { + return !asc; + } + if (leftFileInfo.isDir() && !rightFileInfo.isDir()) + { + return asc; + } + + // sort and proxy model breaks the original model... + if (sortColumn() == 0) + { + return Strings::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), + Qt::CaseInsensitive) < 0; + } + if (sortColumn() == 1) + { + auto leftSize = leftFileInfo.size(); + auto rightSize = rightFileInfo.size(); + if ((leftSize == rightSize) || (leftFileInfo.isDir() && rightFileInfo.isDir())) + { + return Strings::naturalCompare(leftFileInfo.fileName(), + rightFileInfo.fileName(), + Qt::CaseInsensitive) < 0 + ? asc + : !asc; + } + return leftSize < rightSize; + } + return QSortFilterProxyModel::lessThan(left, right); + } + + virtual Qt::ItemFlags flags(const QModelIndex &index) const + { + if (!index.isValid()) + return Qt::NoItemFlags; + + auto sourceIndex = mapToSource(index); + Qt::ItemFlags flags = sourceIndex.flags(); + if (index.column() == 0) + { + flags |= Qt::ItemIsUserCheckable; + if (sourceIndex.model()->hasChildren(sourceIndex)) + { + flags |= Qt::ItemIsTristate; + } + } + + return flags; + } + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const + { + QModelIndex sourceIndex = mapToSource(index); + + if (index.column() == 0 && role == Qt::CheckStateRole) + { + QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel()); + auto blockedPath = relPath(fsm->filePath(sourceIndex)); + auto cover = blocked.cover(blockedPath); + if (!cover.isNull()) + { + return QVariant(Qt::Unchecked); + } + else if (blocked.exists(blockedPath)) + { + return QVariant(Qt::PartiallyChecked); + } + else + { + return QVariant(Qt::Checked); + } + } + + return sourceIndex.data(role); + } + + virtual bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) + { + if (index.column() == 0 && role == Qt::CheckStateRole) + { + Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt()); + return setFilterState(index, state); + } + + QModelIndex sourceIndex = mapToSource(index); + return QSortFilterProxyModel::sourceModel()->setData(sourceIndex, value, role); + } + + QString relPath(const QString &path) const + { + QString prefix = QDir().absoluteFilePath(m_instance->instanceRoot()); + prefix += '/'; + if (!path.startsWith(prefix)) + { + return QString(); + } + return path.mid(prefix.size()); + } + + bool setFilterState(QModelIndex index, Qt::CheckState state) + { + QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel()); + + if (!fsm) + { + return false; + } + + QModelIndex sourceIndex = mapToSource(index); + auto blockedPath = relPath(fsm->filePath(sourceIndex)); + bool changed = false; + if (state == Qt::Unchecked) + { + // blocking a path + auto &node = blocked.insert(blockedPath); + // get rid of all blocked nodes below + node.clear(); + changed = true; + } + else if (state == Qt::Checked || state == Qt::PartiallyChecked) + { + if (!blocked.remove(blockedPath)) + { + auto cover = blocked.cover(blockedPath); + qDebug() << "Blocked by cover" << cover; + // uncover + blocked.remove(cover); + // block all contents, except for any cover + QModelIndex rootIndex = + fsm->index(FS::PathCombine(m_instance->instanceRoot(), cover)); + QModelIndex doing = rootIndex; + int row = 0; + QStack<QModelIndex> todo; + while (1) + { + auto node = doing.child(row, 0); + if (!node.isValid()) + { + if (!todo.size()) + { + break; + } + else + { + doing = todo.pop(); + row = 0; + continue; + } + } + auto relpath = relPath(fsm->filePath(node)); + if (blockedPath.startsWith(relpath)) // cover found? + { + // continue processing cover later + todo.push(node); + } + else + { + // or just block this one. + blocked.insert(relpath); + } + row++; + } + } + changed = true; + } + if (changed) + { + // update the thing + emit dataChanged(index, index, {Qt::CheckStateRole}); + // update everything above index + QModelIndex up = index.parent(); + while (1) + { + if (!up.isValid()) + break; + emit dataChanged(up, up, {Qt::CheckStateRole}); + up = up.parent(); + } + // and everything below the index + QModelIndex doing = index; + int row = 0; + QStack<QModelIndex> todo; + while (1) + { + auto node = doing.child(row, 0); + if (!node.isValid()) + { + if (!todo.size()) + { + break; + } + else + { + doing = todo.pop(); + row = 0; + continue; + } + } + emit dataChanged(node, node, {Qt::CheckStateRole}); + todo.push(node); + row++; + } + // siblings and unrelated nodes are ignored + } + return true; + } + + bool shouldExpand(QModelIndex index) + { + QModelIndex sourceIndex = mapToSource(index); + QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel()); + if (!fsm) + { + return false; + } + auto blockedPath = relPath(fsm->filePath(sourceIndex)); + auto found = blocked.find(blockedPath); + if(found) + { + return !found->leaf(); + } + return false; + } + + void setBlockedPaths(QStringList paths) + { + beginResetModel(); + blocked.clear(); + blocked.insert(paths); + endResetModel(); + } + + const SeparatorPrefixTree<'/'> & blockedPaths() const + { + return blocked; + } protected: - bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const - { - Q_UNUSED(source_parent) + bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const + { + Q_UNUSED(source_parent) - // adjust the columns you want to filter out here - // return false for those that will be hidden - if (source_column == 2 || source_column == 3) - return false; + // adjust the columns you want to filter out here + // return false for those that will be hidden + if (source_column == 2 || source_column == 3) + return false; - return true; - } + return true; + } private: - InstancePtr m_instance; - SeparatorPrefixTree<'/'> blocked; + InstancePtr m_instance; + SeparatorPrefixTree<'/'> blocked; }; ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget *parent) - : QDialog(parent), ui(new Ui::ExportInstanceDialog), m_instance(instance) + : QDialog(parent), ui(new Ui::ExportInstanceDialog), m_instance(instance) { - ui->setupUi(this); - auto model = new QFileSystemModel(this); - proxyModel = new PackIgnoreProxy(m_instance, this); - loadPackIgnore(); - proxyModel->setSourceModel(model); - auto root = instance->instanceRoot(); - ui->treeView->setModel(proxyModel); - ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root))); - ui->treeView->sortByColumn(0, Qt::AscendingOrder); - - connect(proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int))); - - model->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); - model->setRootPath(root); - auto headerView = ui->treeView->header(); - headerView->setSectionResizeMode(QHeaderView::ResizeToContents); - headerView->setSectionResizeMode(0, QHeaderView::Stretch); + ui->setupUi(this); + auto model = new QFileSystemModel(this); + proxyModel = new PackIgnoreProxy(m_instance, this); + loadPackIgnore(); + proxyModel->setSourceModel(model); + auto root = instance->instanceRoot(); + ui->treeView->setModel(proxyModel); + ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root))); + ui->treeView->sortByColumn(0, Qt::AscendingOrder); + + connect(proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int))); + + model->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); + model->setRootPath(root); + auto headerView = ui->treeView->header(); + headerView->setSectionResizeMode(QHeaderView::ResizeToContents); + headerView->setSectionResizeMode(0, QHeaderView::Stretch); } ExportInstanceDialog::~ExportInstanceDialog() { - delete ui; + delete ui; } /// Save icon to instance's folder is needed void SaveIcon(InstancePtr m_instance) { - auto iconKey = m_instance->iconKey(); - auto iconList = MMC->icons(); - auto mmcIcon = iconList->icon(iconKey); - if(mmcIcon) - { - bool saveIcon = false; - switch(mmcIcon->type()) - { - case IconType::FileBased: - case IconType::Transient: - saveIcon = true; - default: - break; - } - if(saveIcon) - { - auto & image = mmcIcon->m_images[mmcIcon->type()]; - auto & icon = image.icon; - auto sizes = icon.availableSizes(); - if(sizes.size() == 0) - { - return; - } - auto areaOf = [](QSize size) - { - return size.width() * size.height(); - }; - QSize largest = sizes[0]; - // find variant with largest area - for(auto size: sizes) - { - if(areaOf(largest) < areaOf(size)) - { - largest = size; - } - } - auto pixmap = icon.pixmap(largest); - pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png")); - } - } + auto iconKey = m_instance->iconKey(); + auto iconList = MMC->icons(); + auto mmcIcon = iconList->icon(iconKey); + if(!mmcIcon || mmcIcon->isBuiltIn()) { + return; + } + auto path = mmcIcon->getFilePath(); + if(!path.isNull()) { + QFileInfo inInfo (path); + FS::copy(path, FS::PathCombine(m_instance->instanceRoot(), inInfo.fileName())) (); + return; + } + auto & image = mmcIcon->m_images[mmcIcon->type()]; + auto & icon = image.icon; + auto sizes = icon.availableSizes(); + if(sizes.size() == 0) + { + return; + } + auto areaOf = [](QSize size) + { + return size.width() * size.height(); + }; + QSize largest = sizes[0]; + // find variant with largest area + for(auto size: sizes) + { + if(areaOf(largest) < areaOf(size)) + { + largest = size; + } + } + auto pixmap = icon.pixmap(largest); + pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png")); } bool ExportInstanceDialog::doExport() { - auto name = FS::RemoveInvalidFilenameChars(m_instance->name()); - - const QString output = QFileDialog::getSaveFileName( - this, tr("Export %1").arg(m_instance->name()), - FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr, QFileDialog::DontConfirmOverwrite); - if (output.isEmpty()) - { - return false; - } - if (QFile::exists(output)) - { - int ret = - QMessageBox::question(this, tr("Overwrite?"), - tr("This file already exists. Do you want to overwrite it?"), - QMessageBox::No, QMessageBox::Yes); - if (ret == QMessageBox::No) - { - return false; - } - } - - SaveIcon(m_instance); - - auto & blocked = proxyModel->blockedPaths(); - using std::placeholders::_1; - if (!JlCompress::compressDir(output, m_instance->instanceRoot(), name, std::bind(&SeparatorPrefixTree<'/'>::covers, blocked, _1))) - { - QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); - return false; - } - return true; + auto name = FS::RemoveInvalidFilenameChars(m_instance->name()); + + const QString output = QFileDialog::getSaveFileName( + this, tr("Export %1").arg(m_instance->name()), + FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr, QFileDialog::DontConfirmOverwrite); + if (output.isEmpty()) + { + return false; + } + if (QFile::exists(output)) + { + int ret = + QMessageBox::question(this, tr("Overwrite?"), + tr("This file already exists. Do you want to overwrite it?"), + QMessageBox::No, QMessageBox::Yes); + if (ret == QMessageBox::No) + { + return false; + } + } + + SaveIcon(m_instance); + + auto & blocked = proxyModel->blockedPaths(); + using std::placeholders::_1; + if (!JlCompress::compressDir(output, m_instance->instanceRoot(), name, std::bind(&SeparatorPrefixTree<'/'>::covers, blocked, _1))) + { + QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); + return false; + } + return true; } void ExportInstanceDialog::done(int result) { - savePackIgnore(); - if (result == QDialog::Accepted) - { - if (doExport()) - { - QDialog::done(QDialog::Accepted); - return; - } - else - { - return; - } - } - QDialog::done(result); + savePackIgnore(); + if (result == QDialog::Accepted) + { + if (doExport()) + { + QDialog::done(QDialog::Accepted); + return; + } + else + { + return; + } + } + QDialog::done(result); } void ExportInstanceDialog::rowsInserted(QModelIndex parent, int top, int bottom) { - //WARNING: possible off-by-one? - for(int i = top; i < bottom; i++) - { - auto node = parent.child(i, 0); - if(proxyModel->shouldExpand(node)) - { - auto expNode = node.parent(); - if(!expNode.isValid()) - { - continue; - } - ui->treeView->expand(node); - } - } + //WARNING: possible off-by-one? + for(int i = top; i < bottom; i++) + { + auto node = parent.child(i, 0); + if(proxyModel->shouldExpand(node)) + { + auto expNode = node.parent(); + if(!expNode.isValid()) + { + continue; + } + ui->treeView->expand(node); + } + } } QString ExportInstanceDialog::ignoreFileName() { - return FS::PathCombine(m_instance->instanceRoot(), ".packignore"); + return FS::PathCombine(m_instance->instanceRoot(), ".packignore"); } void ExportInstanceDialog::loadPackIgnore() { - auto filename = ignoreFileName(); - QFile ignoreFile(filename); - if(!ignoreFile.open(QIODevice::ReadOnly)) - { - return; - } - auto data = ignoreFile.readAll(); - auto string = QString::fromUtf8(data); - proxyModel->setBlockedPaths(string.split('\n', QString::SkipEmptyParts)); + auto filename = ignoreFileName(); + QFile ignoreFile(filename); + if(!ignoreFile.open(QIODevice::ReadOnly)) + { + return; + } + auto data = ignoreFile.readAll(); + auto string = QString::fromUtf8(data); + proxyModel->setBlockedPaths(string.split('\n', QString::SkipEmptyParts)); } void ExportInstanceDialog::savePackIgnore() { - auto data = proxyModel->blockedPaths().toStringList().join('\n').toUtf8(); - auto filename = ignoreFileName(); - try - { - FS::write(filename, data); - } - catch (Exception & e) - { - qWarning() << e.cause(); - } + auto data = proxyModel->blockedPaths().toStringList().join('\n').toUtf8(); + auto filename = ignoreFileName(); + try + { + FS::write(filename, data); + } + catch (const Exception &e) + { + qWarning() << e.cause(); + } } #include "ExportInstanceDialog.moc" diff --git a/application/dialogs/ExportInstanceDialog.h b/application/dialogs/ExportInstanceDialog.h index 7b9c6726..aec4cbfb 100644 --- a/application/dialogs/ExportInstanceDialog.h +++ b/application/dialogs/ExportInstanceDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,25 +30,25 @@ class ExportInstanceDialog; class ExportInstanceDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit ExportInstanceDialog(InstancePtr instance, QWidget *parent = 0); - ~ExportInstanceDialog(); + explicit ExportInstanceDialog(InstancePtr instance, QWidget *parent = 0); + ~ExportInstanceDialog(); - virtual void done(int result); + virtual void done(int result); private: - bool doExport(); - void loadPackIgnore(); - void savePackIgnore(); - QString ignoreFileName(); + bool doExport(); + void loadPackIgnore(); + void savePackIgnore(); + QString ignoreFileName(); private: - Ui::ExportInstanceDialog *ui; - InstancePtr m_instance; - PackIgnoreProxy * proxyModel; + Ui::ExportInstanceDialog *ui; + InstancePtr m_instance; + PackIgnoreProxy * proxyModel; private slots: - void rowsInserted(QModelIndex parent, int top, int bottom); + void rowsInserted(QModelIndex parent, int top, int bottom); }; diff --git a/application/dialogs/IconPickerDialog.cpp b/application/dialogs/IconPickerDialog.cpp index 4ffd12bc..7f930717 100644 --- a/application/dialogs/IconPickerDialog.cpp +++ b/application/dialogs/IconPickerDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,138 +25,139 @@ #include "groupview/InstanceDelegate.h" #include "icons/IconList.h" +#include "icons/IconUtils.h" #include <DesktopServices.h> IconPickerDialog::IconPickerDialog(QWidget *parent) - : QDialog(parent), ui(new Ui::IconPickerDialog) + : QDialog(parent), ui(new Ui::IconPickerDialog) { - ui->setupUi(this); - setWindowModality(Qt::WindowModal); - - auto contentsWidget = ui->iconView; - contentsWidget->setViewMode(QListView::IconMode); - contentsWidget->setFlow(QListView::LeftToRight); - contentsWidget->setIconSize(QSize(48, 48)); - contentsWidget->setMovement(QListView::Static); - contentsWidget->setResizeMode(QListView::Adjust); - contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection); - contentsWidget->setSpacing(5); - contentsWidget->setWordWrap(false); - contentsWidget->setWrapping(true); - contentsWidget->setUniformItemSizes(true); - contentsWidget->setTextElideMode(Qt::ElideRight); - contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - contentsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - contentsWidget->setItemDelegate(new ListViewDelegate()); - - // contentsWidget->setAcceptDrops(true); - contentsWidget->setDropIndicatorShown(true); - contentsWidget->viewport()->setAcceptDrops(true); - contentsWidget->setDragDropMode(QAbstractItemView::DropOnly); - contentsWidget->setDefaultDropAction(Qt::CopyAction); - - contentsWidget->installEventFilter(this); - - contentsWidget->setModel(MMC->icons().get()); - - // NOTE: ResetRole forces the button to be on the left, while the OK/Cancel ones are on the right. We win. - auto buttonAdd = ui->buttonBox->addButton(tr("Add Icon"), QDialogButtonBox::ResetRole); - auto buttonRemove = ui->buttonBox->addButton(tr("Remove Icon"), QDialogButtonBox::ResetRole); - - connect(buttonAdd, SIGNAL(clicked(bool)), SLOT(addNewIcon())); - connect(buttonRemove, SIGNAL(clicked(bool)), SLOT(removeSelectedIcon())); - - connect(contentsWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(activated(QModelIndex))); - - connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(selectionChanged(QItemSelection, QItemSelection))); - - auto buttonFolder = ui->buttonBox->addButton(tr("Open Folder"), QDialogButtonBox::ResetRole); - connect(buttonFolder, &QPushButton::clicked, this, &IconPickerDialog::openFolder); + ui->setupUi(this); + setWindowModality(Qt::WindowModal); + + auto contentsWidget = ui->iconView; + contentsWidget->setViewMode(QListView::IconMode); + contentsWidget->setFlow(QListView::LeftToRight); + contentsWidget->setIconSize(QSize(48, 48)); + contentsWidget->setMovement(QListView::Static); + contentsWidget->setResizeMode(QListView::Adjust); + contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection); + contentsWidget->setSpacing(5); + contentsWidget->setWordWrap(false); + contentsWidget->setWrapping(true); + contentsWidget->setUniformItemSizes(true); + contentsWidget->setTextElideMode(Qt::ElideRight); + contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + contentsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + contentsWidget->setItemDelegate(new ListViewDelegate()); + + // contentsWidget->setAcceptDrops(true); + contentsWidget->setDropIndicatorShown(true); + contentsWidget->viewport()->setAcceptDrops(true); + contentsWidget->setDragDropMode(QAbstractItemView::DropOnly); + contentsWidget->setDefaultDropAction(Qt::CopyAction); + + contentsWidget->installEventFilter(this); + + contentsWidget->setModel(MMC->icons().get()); + + // NOTE: ResetRole forces the button to be on the left, while the OK/Cancel ones are on the right. We win. + auto buttonAdd = ui->buttonBox->addButton(tr("Add Icon"), QDialogButtonBox::ResetRole); + auto buttonRemove = ui->buttonBox->addButton(tr("Remove Icon"), QDialogButtonBox::ResetRole); + + connect(buttonAdd, SIGNAL(clicked(bool)), SLOT(addNewIcon())); + connect(buttonRemove, SIGNAL(clicked(bool)), SLOT(removeSelectedIcon())); + + connect(contentsWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(activated(QModelIndex))); + + connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(selectionChanged(QItemSelection, QItemSelection))); + + auto buttonFolder = ui->buttonBox->addButton(tr("Open Folder"), QDialogButtonBox::ResetRole); + connect(buttonFolder, &QPushButton::clicked, this, &IconPickerDialog::openFolder); } bool IconPickerDialog::eventFilter(QObject *obj, QEvent *evt) { - if (obj != ui->iconView) - return QDialog::eventFilter(obj, evt); - if (evt->type() != QEvent::KeyPress) - { - return QDialog::eventFilter(obj, evt); - } - QKeyEvent *keyEvent = static_cast<QKeyEvent *>(evt); - switch (keyEvent->key()) - { - case Qt::Key_Delete: - removeSelectedIcon(); - return true; - case Qt::Key_Plus: - addNewIcon(); - return true; - default: - break; - } - return QDialog::eventFilter(obj, evt); + if (obj != ui->iconView) + return QDialog::eventFilter(obj, evt); + if (evt->type() != QEvent::KeyPress) + { + return QDialog::eventFilter(obj, evt); + } + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(evt); + switch (keyEvent->key()) + { + case Qt::Key_Delete: + removeSelectedIcon(); + return true; + case Qt::Key_Plus: + addNewIcon(); + return true; + default: + break; + } + return QDialog::eventFilter(obj, evt); } void IconPickerDialog::addNewIcon() { - //: The title of the select icons open file dialog - QString selectIcons = tr("Select Icons"); - //: The type of icon files - QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(), - tr("Icons") + "(*.png *.jpg *.jpeg *.ico *.svg)"); - MMC->icons()->installIcons(fileNames); + //: The title of the select icons open file dialog + QString selectIcons = tr("Select Icons"); + //: The type of icon files + auto filter = IconUtils::getIconFilter(); + QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(), tr("Icons %1").arg(filter)); + MMC->icons()->installIcons(fileNames); } void IconPickerDialog::removeSelectedIcon() { - MMC->icons()->deleteIcon(selectedIconKey); + MMC->icons()->deleteIcon(selectedIconKey); } void IconPickerDialog::activated(QModelIndex index) { - selectedIconKey = index.data(Qt::UserRole).toString(); - accept(); + selectedIconKey = index.data(Qt::UserRole).toString(); + accept(); } void IconPickerDialog::selectionChanged(QItemSelection selected, QItemSelection deselected) { - if (selected.empty()) - return; + if (selected.empty()) + return; - QString key = selected.first().indexes().first().data(Qt::UserRole).toString(); - if (!key.isEmpty()) - selectedIconKey = key; + QString key = selected.first().indexes().first().data(Qt::UserRole).toString(); + if (!key.isEmpty()) + selectedIconKey = key; } int IconPickerDialog::execWithSelection(QString selection) { - auto list = MMC->icons(); - auto contentsWidget = ui->iconView; - selectedIconKey = selection; - - int index_nr = list->getIconIndex(selection); - auto model_index = list->index(index_nr); - contentsWidget->selectionModel()->select( - model_index, QItemSelectionModel::Current | QItemSelectionModel::Select); - - QMetaObject::invokeMethod(this, "delayed_scroll", Qt::QueuedConnection, - Q_ARG(QModelIndex, model_index)); - return QDialog::exec(); + auto list = MMC->icons(); + auto contentsWidget = ui->iconView; + selectedIconKey = selection; + + int index_nr = list->getIconIndex(selection); + auto model_index = list->index(index_nr); + contentsWidget->selectionModel()->select( + model_index, QItemSelectionModel::Current | QItemSelectionModel::Select); + + QMetaObject::invokeMethod(this, "delayed_scroll", Qt::QueuedConnection, + Q_ARG(QModelIndex, model_index)); + return QDialog::exec(); } void IconPickerDialog::delayed_scroll(QModelIndex model_index) { - auto contentsWidget = ui->iconView; - contentsWidget->scrollTo(model_index); + auto contentsWidget = ui->iconView; + contentsWidget->scrollTo(model_index); } IconPickerDialog::~IconPickerDialog() { - delete ui; + delete ui; } void IconPickerDialog::openFolder() { - DesktopServices::openDirectory(MMC->icons()->getDirectory(), true); + DesktopServices::openDirectory(MMC->icons()->getDirectory(), true); } diff --git a/application/dialogs/IconPickerDialog.h b/application/dialogs/IconPickerDialog.h index 9053ec61..cb870f44 100644 --- a/application/dialogs/IconPickerDialog.h +++ b/application/dialogs/IconPickerDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,26 +24,26 @@ class IconPickerDialog; class IconPickerDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit IconPickerDialog(QWidget *parent = 0); - ~IconPickerDialog(); - int execWithSelection(QString selection); - QString selectedIconKey; + explicit IconPickerDialog(QWidget *parent = 0); + ~IconPickerDialog(); + int execWithSelection(QString selection); + QString selectedIconKey; protected: - virtual bool eventFilter(QObject *, QEvent *); + virtual bool eventFilter(QObject *, QEvent *); private: - Ui::IconPickerDialog *ui; + Ui::IconPickerDialog *ui; private slots: - void selectionChanged(QItemSelection, QItemSelection); - void activated(QModelIndex); - void delayed_scroll(QModelIndex); - void addNewIcon(); - void removeSelectedIcon(); - void openFolder(); + void selectionChanged(QItemSelection, QItemSelection); + void activated(QModelIndex); + void delayed_scroll(QModelIndex); + void addNewIcon(); + void removeSelectedIcon(); + void openFolder(); }; diff --git a/application/dialogs/LoginDialog.cpp b/application/dialogs/LoginDialog.cpp index b2020372..484cb8e2 100644 --- a/application/dialogs/LoginDialog.cpp +++ b/application/dialogs/LoginDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,89 +22,89 @@ LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LoginDialog) { - ui->setupUi(this); - ui->progressBar->setVisible(false); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + ui->setupUi(this); + ui->progressBar->setVisible(false); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } LoginDialog::~LoginDialog() { - delete ui; + delete ui; } // Stage 1: User interaction void LoginDialog::accept() { - setUserInputsEnabled(false); - ui->progressBar->setVisible(true); - - // Setup the login task and start it - m_account = MojangAccount::createFromUsername(ui->userTextBox->text()); - m_loginTask = m_account->login(nullptr, ui->passTextBox->text()); - connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed); - connect(m_loginTask.get(), &Task::succeeded, this, - &LoginDialog::onTaskSucceeded); - connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus); - connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress); - m_loginTask->start(); + setUserInputsEnabled(false); + ui->progressBar->setVisible(true); + + // Setup the login task and start it + m_account = MojangAccount::createFromUsername(ui->userTextBox->text()); + m_loginTask = m_account->login(nullptr, ui->passTextBox->text()); + connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed); + connect(m_loginTask.get(), &Task::succeeded, this, + &LoginDialog::onTaskSucceeded); + connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus); + connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress); + m_loginTask->start(); } void LoginDialog::setUserInputsEnabled(bool enable) { - ui->userTextBox->setEnabled(enable); - ui->passTextBox->setEnabled(enable); - ui->buttonBox->setEnabled(enable); + ui->userTextBox->setEnabled(enable); + ui->passTextBox->setEnabled(enable); + ui->buttonBox->setEnabled(enable); } // Enable the OK button only when both textboxes contain something. void LoginDialog::on_userTextBox_textEdited(const QString &newText) { - ui->buttonBox->button(QDialogButtonBox::Ok) - ->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty()); + ui->buttonBox->button(QDialogButtonBox::Ok) + ->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty()); } void LoginDialog::on_passTextBox_textEdited(const QString &newText) { - ui->buttonBox->button(QDialogButtonBox::Ok) - ->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty()); + ui->buttonBox->button(QDialogButtonBox::Ok) + ->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty()); } void LoginDialog::onTaskFailed(const QString &reason) { - // Set message - ui->label->setText("<span style='color:red'>" + reason + "</span>"); + // Set message + ui->label->setText("<span style='color:red'>" + reason + "</span>"); - // Re-enable user-interaction - setUserInputsEnabled(true); - ui->progressBar->setVisible(false); + // Re-enable user-interaction + setUserInputsEnabled(true); + ui->progressBar->setVisible(false); } void LoginDialog::onTaskSucceeded() { - QDialog::accept(); + QDialog::accept(); } void LoginDialog::onTaskStatus(const QString &status) { - ui->label->setText(status); + ui->label->setText(status); } void LoginDialog::onTaskProgress(qint64 current, qint64 total) { - ui->progressBar->setMaximum(total); - ui->progressBar->setValue(current); + ui->progressBar->setMaximum(total); + ui->progressBar->setValue(current); } // Public interface MojangAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg) { - LoginDialog dlg(parent); - dlg.ui->label->setText(msg); - if (dlg.exec() == QDialog::Accepted) - { - return dlg.m_account; - } - return 0; + LoginDialog dlg(parent); + dlg.ui->label->setText(msg); + if (dlg.exec() == QDialog::Accepted) + { + return dlg.m_account; + } + return 0; } diff --git a/application/dialogs/LoginDialog.h b/application/dialogs/LoginDialog.h index 27b97cb0..30fcb760 100644 --- a/application/dialogs/LoginDialog.h +++ b/application/dialogs/LoginDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,32 +27,32 @@ class LoginDialog; class LoginDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - ~LoginDialog(); + ~LoginDialog(); - static MojangAccountPtr newAccount(QWidget *parent, QString message); + static MojangAccountPtr newAccount(QWidget *parent, QString message); private: - explicit LoginDialog(QWidget *parent = 0); + explicit LoginDialog(QWidget *parent = 0); - void setUserInputsEnabled(bool enable); + void setUserInputsEnabled(bool enable); protected slots: - void accept(); + void accept(); - void onTaskFailed(const QString &reason); - void onTaskSucceeded(); - void onTaskStatus(const QString &status); - void onTaskProgress(qint64 current, qint64 total); + void onTaskFailed(const QString &reason); + void onTaskSucceeded(); + void onTaskStatus(const QString &status); + void onTaskProgress(qint64 current, qint64 total); - void on_userTextBox_textEdited(const QString &newText); - void on_passTextBox_textEdited(const QString &newText); + void on_userTextBox_textEdited(const QString &newText); + void on_passTextBox_textEdited(const QString &newText); private: - Ui::LoginDialog *ui; - MojangAccountPtr m_account; - std::shared_ptr<Task> m_loginTask; + Ui::LoginDialog *ui; + MojangAccountPtr m_account; + std::shared_ptr<Task> m_loginTask; }; diff --git a/application/dialogs/ModEditDialogCommon.cpp b/application/dialogs/ModEditDialogCommon.cpp deleted file mode 100644 index 4201bdad..00000000 --- a/application/dialogs/ModEditDialogCommon.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "ModEditDialogCommon.h" -#include "CustomMessageBox.h" -#include <QUrl> - -bool lastfirst(QModelIndexList &list, int &first, int &last) -{ - if (list.isEmpty()) - return false; - first = last = list[0].row(); - for (auto item : list) - { - int row = item.row(); - if (row < first) - first = row; - if (row > last) - last = row; - } - return true; -} - -void showWebsiteForMod(QWidget *parentDlg, Mod &m) -{ - QString url = m.homeurl(); - if (url.size()) - { - // catch the cases where the protocol is missing - if (!url.startsWith("http")) - { - url = "http://" + url; - } - DesktopServices::openUrl(url); - } - else - { - CustomMessageBox::selectable( - parentDlg, QObject::tr("How sad!"), - QObject::tr("The mod author didn't provide a website link for this mod."), - QMessageBox::Warning); - } -} diff --git a/application/dialogs/ModEditDialogCommon.h b/application/dialogs/ModEditDialogCommon.h deleted file mode 100644 index fc5e3c2b..00000000 --- a/application/dialogs/ModEditDialogCommon.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include <QModelIndex> -#include <DesktopServices.h> -#include <QWidget> -#include <minecraft/Mod.h> - -bool lastfirst(QModelIndexList &list, int &first, int &last); - -void showWebsiteForMod(QWidget *parentDlg, Mod &m); diff --git a/application/dialogs/NewComponentDialog.cpp b/application/dialogs/NewComponentDialog.cpp index 514aa938..d0d1d24b 100644 --- a/application/dialogs/NewComponentDialog.cpp +++ b/application/dialogs/NewComponentDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,72 +35,72 @@ #include <meta/VersionList.h> NewComponentDialog::NewComponentDialog(const QString & initialName, const QString & initialUid, QWidget *parent) - : QDialog(parent), ui(new Ui::NewComponentDialog) + : QDialog(parent), ui(new Ui::NewComponentDialog) { - ui->setupUi(this); - resize(minimumSizeHint()); + ui->setupUi(this); + resize(minimumSizeHint()); - ui->nameTextBox->setText(initialName); - ui->uidTextBox->setText(initialUid); + ui->nameTextBox->setText(initialName); + ui->uidTextBox->setText(initialUid); - connect(ui->nameTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState); - connect(ui->uidTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState); + connect(ui->nameTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState); + connect(ui->uidTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState); - auto groups = MMC->instances()->getGroups().toSet(); - ui->nameTextBox->setFocus(); + auto groups = MMC->instances()->getGroups().toSet(); + ui->nameTextBox->setFocus(); - originalPlaceholderText = ui->uidTextBox->placeholderText(); - updateDialogState(); + originalPlaceholderText = ui->uidTextBox->placeholderText(); + updateDialogState(); } NewComponentDialog::~NewComponentDialog() { - delete ui; + delete ui; } void NewComponentDialog::updateDialogState() { - auto protoUid = ui->nameTextBox->text().toLower(); - protoUid.remove(QRegularExpression("[^a-z]")); - if(protoUid.isEmpty()) - { - ui->uidTextBox->setPlaceholderText(originalPlaceholderText); - } - else - { - QString suggestedUid = "org.multimc.custom." + protoUid; - ui->uidTextBox->setPlaceholderText(suggestedUid); - } - bool allowOK = !name().isEmpty() && !uid().isEmpty() && !uidBlacklist.contains(uid()); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(allowOK); + auto protoUid = ui->nameTextBox->text().toLower(); + protoUid.remove(QRegularExpression("[^a-z]")); + if(protoUid.isEmpty()) + { + ui->uidTextBox->setPlaceholderText(originalPlaceholderText); + } + else + { + QString suggestedUid = "org.multimc.custom." + protoUid; + ui->uidTextBox->setPlaceholderText(suggestedUid); + } + bool allowOK = !name().isEmpty() && !uid().isEmpty() && !uidBlacklist.contains(uid()); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(allowOK); } QString NewComponentDialog::name() const { - auto result = ui->nameTextBox->text(); - if(result.size()) - { - return result.trimmed(); - } - return QString(); + auto result = ui->nameTextBox->text(); + if(result.size()) + { + return result.trimmed(); + } + return QString(); } QString NewComponentDialog::uid() const { - auto result = ui->uidTextBox->text(); - if(result.size()) - { - return result.trimmed(); - } - result = ui->uidTextBox->placeholderText(); - if(result.size() && result != originalPlaceholderText) - { - return result.trimmed(); - } - return QString(); + auto result = ui->uidTextBox->text(); + if(result.size()) + { + return result.trimmed(); + } + result = ui->uidTextBox->placeholderText(); + if(result.size() && result != originalPlaceholderText) + { + return result.trimmed(); + } + return QString(); } void NewComponentDialog::setBlacklist(QStringList badUids) { - uidBlacklist = badUids; + uidBlacklist = badUids; } diff --git a/application/dialogs/NewComponentDialog.h b/application/dialogs/NewComponentDialog.h index 70caec0f..518f342d 100644 --- a/application/dialogs/NewComponentDialog.h +++ b/application/dialogs/NewComponentDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,22 +27,22 @@ class NewComponentDialog; class NewComponentDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit NewComponentDialog(const QString & initialName = QString(), const QString & initialUid = QString(), QWidget *parent = 0); - virtual ~NewComponentDialog(); - void setBlacklist(QStringList badUids); + explicit NewComponentDialog(const QString & initialName = QString(), const QString & initialUid = QString(), QWidget *parent = 0); + virtual ~NewComponentDialog(); + void setBlacklist(QStringList badUids); - QString name() const; - QString uid() const; + QString name() const; + QString uid() const; private slots: - void updateDialogState(); + void updateDialogState(); private: - Ui::NewComponentDialog *ui; + Ui::NewComponentDialog *ui; - QString originalPlaceholderText; - QStringList uidBlacklist; + QString originalPlaceholderText; + QStringList uidBlacklist; }; diff --git a/application/dialogs/NewComponentDialog.ui b/application/dialogs/NewComponentDialog.ui index b30c2431..03b0d222 100644 --- a/application/dialogs/NewComponentDialog.ui +++ b/application/dialogs/NewComponentDialog.ui @@ -14,7 +14,7 @@ </rect> </property> <property name="windowTitle"> - <string>Copy Instance</string> + <string>Add Empty Component</string> </property> <property name="windowIcon"> <iconset> diff --git a/application/dialogs/NewInstanceDialog.cpp b/application/dialogs/NewInstanceDialog.cpp index b3ce5c6c..3533763d 100644 --- a/application/dialogs/NewInstanceDialog.cpp +++ b/application/dialogs/NewInstanceDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,178 +39,209 @@ #include <pages/modplatform/ImportPage.h> #include <pages/modplatform/TechnicPage.h> -NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent) - : QDialog(parent), ui(new Ui::NewInstanceDialog) -{ - ui->setupUi(this); - - setWindowIcon(MMC->getThemedIcon("new")); - - InstIconKey = "default"; - ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); - - auto groups = MMC->instances()->getGroups().toSet(); - auto groupList = QStringList(groups.toList()); - groupList.sort(Qt::CaseInsensitive); - groupList.removeOne(""); - groupList.push_front(initialGroup); - groupList.push_front(""); - ui->groupBox->addItems(groupList); - int index = groupList.indexOf(initialGroup); - if(index == -1) - { - index = 0; - } - ui->groupBox->setCurrentIndex(index); - ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); - - m_buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok); - m_buttons->button(QDialogButtonBox::Ok)->setDefault(true); - - m_container = new PageContainer(this); - m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); - m_container->layout()->setContentsMargins(0, 0, 0, 0); - ui->verticalLayout->insertWidget(2, m_container); - - m_container->addButtons(m_buttons); - m_buttons->setFocus(); - - if(!url.isEmpty()) - { - m_container->selectPage("import"); - importPage->setUrl(url); - } - - connect(m_buttons->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &NewInstanceDialog::accept); - connect(m_buttons->button(QDialogButtonBox::Help), &QPushButton::clicked, m_container, &PageContainer::help); - - updateDialogState(); - - restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("NewInstanceGeometry").toByteArray())); +NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent) + : QDialog(parent), ui(new Ui::NewInstanceDialog) +{ + ui->setupUi(this); + + setWindowIcon(MMC->getThemedIcon("new")); + + InstIconKey = "default"; + ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); + + auto groups = MMC->instances()->getGroups().toSet(); + auto groupList = QStringList(groups.toList()); + groupList.sort(Qt::CaseInsensitive); + groupList.removeOne(""); + groupList.push_front(initialGroup); + groupList.push_front(""); + ui->groupBox->addItems(groupList); + int index = groupList.indexOf(initialGroup); + if(index == -1) + { + index = 0; + } + ui->groupBox->setCurrentIndex(index); + ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); + + + // NOTE: m_buttons must be initialized before PageContainer, because it indirectly accesses m_buttons through setSuggestedPack! Do not move this below. + m_buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + m_container = new PageContainer(this); + m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); + m_container->layout()->setContentsMargins(0, 0, 0, 0); + ui->verticalLayout->insertWidget(2, m_container); + + m_container->addButtons(m_buttons); + + // Bonk Qt over its stupid head and make sure it understands which button is the default one... + // See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button + auto OkButton = m_buttons->button(QDialogButtonBox::Ok); + OkButton->setDefault(true); + OkButton->setAutoDefault(true); + connect(OkButton, &QPushButton::clicked, this, &NewInstanceDialog::accept); + + auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel); + CancelButton->setDefault(false); + CancelButton->setAutoDefault(false); + connect(CancelButton, &QPushButton::clicked, this, &NewInstanceDialog::reject); + + auto HelpButton = m_buttons->button(QDialogButtonBox::Help); + HelpButton->setDefault(false); + HelpButton->setAutoDefault(false); + connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); + + if(!url.isEmpty()) + { + QUrl actualUrl(url); + if(actualUrl.host() == "www.curseforge.com") { + m_container->selectPage("twitch"); + twitchPage->setUrl(url); + } + else { + m_container->selectPage("import"); + importPage->setUrl(url); + } + } + + updateDialogState(); + + restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("NewInstanceGeometry").toByteArray())); } void NewInstanceDialog::reject() { - MMC->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); - QDialog::reject(); + MMC->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); + QDialog::reject(); } void NewInstanceDialog::accept() { - MMC->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); - importIconNow(); - QDialog::accept(); + MMC->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); + importIconNow(); + QDialog::accept(); } QList<BasePage *> NewInstanceDialog::getPages() { - importPage = new ImportPage(this); - return - { - new VanillaPage(this), - new FTBPage(this), - importPage, - new TwitchPage(this), - new TechnicPage(this) - }; + importPage = new ImportPage(this); + twitchPage = new TwitchPage(this); + return + { + new VanillaPage(this), + importPage, + twitchPage, + new FTBPage(this) + }; } QString NewInstanceDialog::dialogTitle() { - return tr("New Instance"); + return tr("New Instance"); } NewInstanceDialog::~NewInstanceDialog() { - delete ui; + delete ui; } void NewInstanceDialog::setSuggestedPack(const QString& name, InstanceTask* task) { - creationTask.reset(task); - ui->instNameTextBox->setPlaceholderText(name); + creationTask.reset(task); + ui->instNameTextBox->setPlaceholderText(name); + + if(!task) + { + ui->iconButton->setIcon(MMC->icons()->getIcon("default")); + importIcon = false; + } - auto allowOK = task && !instName().isEmpty(); - m_buttons->button(QDialogButtonBox::Ok)->setEnabled(allowOK); + auto allowOK = task && !instName().isEmpty(); + m_buttons->button(QDialogButtonBox::Ok)->setEnabled(allowOK); } void NewInstanceDialog::setSuggestedIconFromFile(const QString &path, const QString &name) { - importIcon = true; - importIconPath = path; - importIconName = name; + importIcon = true; + importIconPath = path; + importIconName = name; - //Hmm, for some reason they can be to small - ui->iconButton->setIcon(QIcon(path)); + //Hmm, for some reason they can be to small + ui->iconButton->setIcon(QIcon(path)); } InstanceTask * NewInstanceDialog::extractTask() { - InstanceTask * extracted = creationTask.get(); - creationTask.release(); - extracted->setName(instName()); - extracted->setGroup(instGroup()); - extracted->setIcon(iconKey()); - return extracted; + InstanceTask * extracted = creationTask.get(); + creationTask.release(); + extracted->setName(instName()); + extracted->setGroup(instGroup()); + extracted->setIcon(iconKey()); + return extracted; } void NewInstanceDialog::updateDialogState() { - auto allowOK = creationTask && !instName().isEmpty(); - m_buttons->button(QDialogButtonBox::Ok)->setEnabled(allowOK); + auto allowOK = creationTask && !instName().isEmpty(); + auto OkButton = m_buttons->button(QDialogButtonBox::Ok); + if(OkButton->isEnabled() != allowOK) + { + OkButton->setEnabled(allowOK); + } } QString NewInstanceDialog::instName() const { - auto result = ui->instNameTextBox->text(); - if(result.size()) - { - return result.trimmed(); - } - result = ui->instNameTextBox->placeholderText(); - if(result.size()) - { - return result.trimmed(); - } - return QString(); + auto result = ui->instNameTextBox->text().trimmed(); + if(result.size()) + { + return result; + } + result = ui->instNameTextBox->placeholderText().trimmed(); + if(result.size()) + { + return result; + } + return QString(); } QString NewInstanceDialog::instGroup() const { - return ui->groupBox->currentText(); + return ui->groupBox->currentText(); } QString NewInstanceDialog::iconKey() const { - return InstIconKey; + return InstIconKey; } void NewInstanceDialog::on_iconButton_clicked() { - importIconNow(); //so the user can switch back - IconPickerDialog dlg(this); - dlg.execWithSelection(InstIconKey); + importIconNow(); //so the user can switch back + IconPickerDialog dlg(this); + dlg.execWithSelection(InstIconKey); - if (dlg.result() == QDialog::Accepted) - { - InstIconKey = dlg.selectedIconKey; - ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); - importIcon = false; - } + if (dlg.result() == QDialog::Accepted) + { + InstIconKey = dlg.selectedIconKey; + ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); + importIcon = false; + } } void NewInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1) { - updateDialogState(); + updateDialogState(); } void NewInstanceDialog::importIconNow() { - if(importIcon) { - MMC->icons()->installIcon(importIconPath, importIconName); - InstIconKey = importIconName; - importIcon = false; - } - MMC->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); + if(importIcon) { + MMC->icons()->installIcon(importIconPath, importIconName); + InstIconKey = importIconName; + importIcon = false; + } + MMC->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); } diff --git a/application/dialogs/NewInstanceDialog.h b/application/dialogs/NewInstanceDialog.h index 1448d225..0b8b2fb8 100644 --- a/application/dialogs/NewInstanceDialog.h +++ b/application/dialogs/NewInstanceDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,49 +29,51 @@ class NewInstanceDialog; class PageContainer; class QDialogButtonBox; class ImportPage; +class TwitchPage; class NewInstanceDialog : public QDialog, public BasePageProvider { - Q_OBJECT + Q_OBJECT public: - explicit NewInstanceDialog(const QString & initialGroup, const QString & url = QString(), QWidget *parent = 0); - ~NewInstanceDialog(); + explicit NewInstanceDialog(const QString & initialGroup, const QString & url = QString(), QWidget *parent = 0); + ~NewInstanceDialog(); - void updateDialogState(); + void updateDialogState(); - void setSuggestedPack(const QString & name = QString(), InstanceTask * task = nullptr); - void setSuggestedIconFromFile(const QString &path, const QString &name); + void setSuggestedPack(const QString & name = QString(), InstanceTask * task = nullptr); + void setSuggestedIconFromFile(const QString &path, const QString &name); - InstanceTask * extractTask(); + InstanceTask * extractTask(); - QString dialogTitle() override; - QList<BasePage *> getPages() override; + QString dialogTitle() override; + QList<BasePage *> getPages() override; - QString instName() const; - QString instGroup() const; - QString iconKey() const; + QString instName() const; + QString instGroup() const; + QString iconKey() const; public slots: - void accept() override; - void reject() override; + void accept() override; + void reject() override; private slots: - void on_iconButton_clicked(); - void on_instNameTextBox_textChanged(const QString &arg1); + void on_iconButton_clicked(); + void on_instNameTextBox_textChanged(const QString &arg1); private: - Ui::NewInstanceDialog *ui = nullptr; - PageContainer * m_container = nullptr; - QDialogButtonBox * m_buttons = nullptr; + Ui::NewInstanceDialog *ui = nullptr; + PageContainer * m_container = nullptr; + QDialogButtonBox * m_buttons = nullptr; - QString InstIconKey; - ImportPage *importPage = nullptr; - std::unique_ptr<InstanceTask> creationTask; + QString InstIconKey; + ImportPage *importPage = nullptr; + TwitchPage *twitchPage = nullptr; + std::unique_ptr<InstanceTask> creationTask; - bool importIcon = false; - QString importIconPath; - QString importIconName; + bool importIcon = false; + QString importIconPath; + QString importIconName; - void importIconNow(); + void importIconNow(); }; diff --git a/application/dialogs/NotificationDialog.cpp b/application/dialogs/NotificationDialog.cpp index 86b03bda..f2a35ae2 100644 --- a/application/dialogs/NotificationDialog.cpp +++ b/application/dialogs/NotificationDialog.cpp @@ -2,85 +2,85 @@ #include "ui_NotificationDialog.h" #include <QTimerEvent> -#include <QStyle> +#include <QStyle> NotificationDialog::NotificationDialog(const NotificationChecker::NotificationEntry &entry, QWidget *parent) : - QDialog(parent, Qt::MSWindowsFixedSizeDialogHint | Qt::WindowTitleHint | Qt::CustomizeWindowHint), - ui(new Ui::NotificationDialog) + QDialog(parent, Qt::MSWindowsFixedSizeDialogHint | Qt::WindowTitleHint | Qt::CustomizeWindowHint), + ui(new Ui::NotificationDialog) { - ui->setupUi(this); + ui->setupUi(this); - QStyle::StandardPixmap icon; - switch (entry.type) - { - case NotificationChecker::NotificationEntry::Critical: - icon = QStyle::SP_MessageBoxCritical; - break; - case NotificationChecker::NotificationEntry::Warning: - icon = QStyle::SP_MessageBoxWarning; - break; - default: - case NotificationChecker::NotificationEntry::Information: - icon = QStyle::SP_MessageBoxInformation; - break; - } - ui->iconLabel->setPixmap(style()->standardPixmap(icon, 0, this)); - ui->messageLabel->setText(entry.message); + QStyle::StandardPixmap icon; + switch (entry.type) + { + case NotificationChecker::NotificationEntry::Critical: + icon = QStyle::SP_MessageBoxCritical; + break; + case NotificationChecker::NotificationEntry::Warning: + icon = QStyle::SP_MessageBoxWarning; + break; + default: + case NotificationChecker::NotificationEntry::Information: + icon = QStyle::SP_MessageBoxInformation; + break; + } + ui->iconLabel->setPixmap(style()->standardPixmap(icon, 0, this)); + ui->messageLabel->setText(entry.message); - m_dontShowAgainText = tr("Don't show again"); - m_closeText = tr("Close"); + m_dontShowAgainText = tr("Don't show again"); + m_closeText = tr("Close"); - ui->dontShowAgainBtn->setText(m_dontShowAgainText + QString(" (%1)").arg(m_dontShowAgainTime)); - ui->closeBtn->setText(m_closeText + QString(" (%1)").arg(m_closeTime)); + ui->dontShowAgainBtn->setText(m_dontShowAgainText + QString(" (%1)").arg(m_dontShowAgainTime)); + ui->closeBtn->setText(m_closeText + QString(" (%1)").arg(m_closeTime)); - startTimer(1000); + startTimer(1000); } NotificationDialog::~NotificationDialog() { - delete ui; + delete ui; } void NotificationDialog::timerEvent(QTimerEvent *event) { - if (m_dontShowAgainTime > 0) - { - m_dontShowAgainTime--; - if (m_dontShowAgainTime == 0) - { - ui->dontShowAgainBtn->setText(m_dontShowAgainText); - ui->dontShowAgainBtn->setEnabled(true); - } - else - { - ui->dontShowAgainBtn->setText(m_dontShowAgainText + QString(" (%1)").arg(m_dontShowAgainTime)); - } - } - if (m_closeTime > 0) - { - m_closeTime--; - if (m_closeTime == 0) - { - ui->closeBtn->setText(m_closeText); - ui->closeBtn->setEnabled(true); - } - else - { - ui->closeBtn->setText(m_closeText + QString(" (%1)").arg(m_closeTime)); - } - } + if (m_dontShowAgainTime > 0) + { + m_dontShowAgainTime--; + if (m_dontShowAgainTime == 0) + { + ui->dontShowAgainBtn->setText(m_dontShowAgainText); + ui->dontShowAgainBtn->setEnabled(true); + } + else + { + ui->dontShowAgainBtn->setText(m_dontShowAgainText + QString(" (%1)").arg(m_dontShowAgainTime)); + } + } + if (m_closeTime > 0) + { + m_closeTime--; + if (m_closeTime == 0) + { + ui->closeBtn->setText(m_closeText); + ui->closeBtn->setEnabled(true); + } + else + { + ui->closeBtn->setText(m_closeText + QString(" (%1)").arg(m_closeTime)); + } + } - if (m_closeTime == 0 && m_dontShowAgainTime == 0) - { - killTimer(event->timerId()); - } + if (m_closeTime == 0 && m_dontShowAgainTime == 0) + { + killTimer(event->timerId()); + } } void NotificationDialog::on_dontShowAgainBtn_clicked() { - done(DontShowAgain); + done(DontShowAgain); } void NotificationDialog::on_closeBtn_clicked() { - done(Normal); + done(Normal); } diff --git a/application/dialogs/NotificationDialog.h b/application/dialogs/NotificationDialog.h index 27b9e853..e1cbb9fa 100644 --- a/application/dialogs/NotificationDialog.h +++ b/application/dialogs/NotificationDialog.h @@ -11,34 +11,34 @@ class NotificationDialog; class NotificationDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit NotificationDialog(const NotificationChecker::NotificationEntry &entry, QWidget *parent = 0); - ~NotificationDialog(); + explicit NotificationDialog(const NotificationChecker::NotificationEntry &entry, QWidget *parent = 0); + ~NotificationDialog(); - enum ExitCode - { - Normal, - DontShowAgain - }; + enum ExitCode + { + Normal, + DontShowAgain + }; protected: - void timerEvent(QTimerEvent *event); + void timerEvent(QTimerEvent *event); private: - Ui::NotificationDialog *ui; + Ui::NotificationDialog *ui; - int m_dontShowAgainTime = 10; - int m_closeTime = 5; + int m_dontShowAgainTime = 10; + int m_closeTime = 5; - QString m_dontShowAgainText; - QString m_closeText; + QString m_dontShowAgainText; + QString m_closeText; private slots: - void on_dontShowAgainBtn_clicked(); - void on_closeBtn_clicked(); + void on_dontShowAgainBtn_clicked(); + void on_closeBtn_clicked(); }; #endif // NOTIFICATIONDIALOG_H diff --git a/application/dialogs/ProfileSelectDialog.cpp b/application/dialogs/ProfileSelectDialog.cpp index f1b335f9..b64de01a 100644 --- a/application/dialogs/ProfileSelectDialog.cpp +++ b/application/dialogs/ProfileSelectDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,91 +26,91 @@ #include <MultiMC.h> ProfileSelectDialog::ProfileSelectDialog(const QString &message, int flags, QWidget *parent) - : QDialog(parent), ui(new Ui::ProfileSelectDialog) + : QDialog(parent), ui(new Ui::ProfileSelectDialog) { - ui->setupUi(this); - - m_accounts = MMC->accounts(); - auto view = ui->listView; - //view->setModel(m_accounts.get()); - //view->hideColumn(MojangAccountList::ActiveColumn); - view->setColumnCount(1); - view->setRootIsDecorated(false); - if(QTreeWidgetItem* header = view->headerItem()) - { - header->setText(0, tr("Name")); - } - else - { - view->setHeaderLabel(tr("Name")); - } - QList <QTreeWidgetItem *> items; - for (int i = 0; i < m_accounts->count(); i++) - { - MojangAccountPtr account = m_accounts->at(i); - for (auto profile : account->profiles()) - { - auto profileLabel = profile.name; - if(account->isInUse()) - { - profileLabel += tr(" (in use)"); - } - auto item = new QTreeWidgetItem(view); - item->setText(0, profileLabel); - item->setIcon(0, SkinUtils::getFaceFromCache(profile.id)); - item->setData(0, MojangAccountList::PointerRole, QVariant::fromValue(account)); - items.append(item); - } - } - view->addTopLevelItems(items); - - // Set the message label. - ui->msgLabel->setVisible(!message.isEmpty()); - ui->msgLabel->setText(message); - - // Flags... - ui->globalDefaultCheck->setVisible(flags & GlobalDefaultCheckbox); - ui->instDefaultCheck->setVisible(flags & InstanceDefaultCheckbox); - qDebug() << flags; - - // Select the first entry in the list. - ui->listView->setCurrentIndex(ui->listView->model()->index(0, 0)); - - connect(ui->listView, SIGNAL(doubleClicked(QModelIndex)), SLOT(on_buttonBox_accepted())); + ui->setupUi(this); + + m_accounts = MMC->accounts(); + auto view = ui->listView; + //view->setModel(m_accounts.get()); + //view->hideColumn(MojangAccountList::ActiveColumn); + view->setColumnCount(1); + view->setRootIsDecorated(false); + if(QTreeWidgetItem* header = view->headerItem()) + { + header->setText(0, tr("Name")); + } + else + { + view->setHeaderLabel(tr("Name")); + } + QList <QTreeWidgetItem *> items; + for (int i = 0; i < m_accounts->count(); i++) + { + MojangAccountPtr account = m_accounts->at(i); + for (auto profile : account->profiles()) + { + auto profileLabel = profile.name; + if(account->isInUse()) + { + profileLabel += tr(" (in use)"); + } + auto item = new QTreeWidgetItem(view); + item->setText(0, profileLabel); + item->setIcon(0, SkinUtils::getFaceFromCache(profile.id)); + item->setData(0, MojangAccountList::PointerRole, QVariant::fromValue(account)); + items.append(item); + } + } + view->addTopLevelItems(items); + + // Set the message label. + ui->msgLabel->setVisible(!message.isEmpty()); + ui->msgLabel->setText(message); + + // Flags... + ui->globalDefaultCheck->setVisible(flags & GlobalDefaultCheckbox); + ui->instDefaultCheck->setVisible(flags & InstanceDefaultCheckbox); + qDebug() << flags; + + // Select the first entry in the list. + ui->listView->setCurrentIndex(ui->listView->model()->index(0, 0)); + + connect(ui->listView, SIGNAL(doubleClicked(QModelIndex)), SLOT(on_buttonBox_accepted())); } ProfileSelectDialog::~ProfileSelectDialog() { - delete ui; + delete ui; } MojangAccountPtr ProfileSelectDialog::selectedAccount() const { - return m_selected; + return m_selected; } bool ProfileSelectDialog::useAsGlobalDefault() const { - return ui->globalDefaultCheck->isChecked(); + return ui->globalDefaultCheck->isChecked(); } bool ProfileSelectDialog::useAsInstDefaullt() const { - return ui->instDefaultCheck->isChecked(); + return ui->instDefaultCheck->isChecked(); } void ProfileSelectDialog::on_buttonBox_accepted() { - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.size() > 0) - { - QModelIndex selected = selection.first(); - m_selected = selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>(); - } - close(); + QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.size() > 0) + { + QModelIndex selected = selection.first(); + m_selected = selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>(); + } + close(); } void ProfileSelectDialog::on_buttonBox_rejected() { - close(); + close(); } diff --git a/application/dialogs/ProfileSelectDialog.h b/application/dialogs/ProfileSelectDialog.h index b1268743..40fa0843 100644 --- a/application/dialogs/ProfileSelectDialog.h +++ b/application/dialogs/ProfileSelectDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,63 +28,63 @@ class ProfileSelectDialog; class ProfileSelectDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - enum Flags - { - NoFlags = 0, - - /*! - * Shows a check box on the dialog that allows the user to specify that the account - * they've selected should be used as the global default for all instances. - */ - GlobalDefaultCheckbox, - - /*! - * Shows a check box on the dialog that allows the user to specify that the account - * they've selected should be used as the default for the instance they are currently launching. - * This is not currently implemented. - */ - InstanceDefaultCheckbox, - }; - - /*! - * Constructs a new account select dialog with the given parent and message. - * The message will be shown at the top of the dialog. It is an empty string by default. - */ - explicit ProfileSelectDialog(const QString& message="", int flags=0, QWidget *parent = 0); - ~ProfileSelectDialog(); - - /*! - * Gets a pointer to the account that the user selected. - * This is null if the user clicked cancel or hasn't clicked OK yet. - */ - MojangAccountPtr selectedAccount() const; - - /*! - * Returns true if the user checked the "use as global default" checkbox. - * If the checkbox wasn't shown, this function returns false. - */ - bool useAsGlobalDefault() const; - - /*! - * Returns true if the user checked the "use as instance default" checkbox. - * If the checkbox wasn't shown, this function returns false. - */ - bool useAsInstDefaullt() const; + enum Flags + { + NoFlags = 0, + + /*! + * Shows a check box on the dialog that allows the user to specify that the account + * they've selected should be used as the global default for all instances. + */ + GlobalDefaultCheckbox, + + /*! + * Shows a check box on the dialog that allows the user to specify that the account + * they've selected should be used as the default for the instance they are currently launching. + * This is not currently implemented. + */ + InstanceDefaultCheckbox, + }; + + /*! + * Constructs a new account select dialog with the given parent and message. + * The message will be shown at the top of the dialog. It is an empty string by default. + */ + explicit ProfileSelectDialog(const QString& message="", int flags=0, QWidget *parent = 0); + ~ProfileSelectDialog(); + + /*! + * Gets a pointer to the account that the user selected. + * This is null if the user clicked cancel or hasn't clicked OK yet. + */ + MojangAccountPtr selectedAccount() const; + + /*! + * Returns true if the user checked the "use as global default" checkbox. + * If the checkbox wasn't shown, this function returns false. + */ + bool useAsGlobalDefault() const; + + /*! + * Returns true if the user checked the "use as instance default" checkbox. + * If the checkbox wasn't shown, this function returns false. + */ + bool useAsInstDefaullt() const; public slots: - void on_buttonBox_accepted(); + void on_buttonBox_accepted(); - void on_buttonBox_rejected(); + void on_buttonBox_rejected(); protected: - std::shared_ptr<MojangAccountList> m_accounts; + std::shared_ptr<MojangAccountList> m_accounts; - //! The account that was selected when the user clicked OK. - MojangAccountPtr m_selected; + //! The account that was selected when the user clicked OK. + MojangAccountPtr m_selected; private: - Ui::ProfileSelectDialog *ui; + Ui::ProfileSelectDialog *ui; }; diff --git a/application/dialogs/ProgressDialog.cpp b/application/dialogs/ProgressDialog.cpp index 056a0ffb..0e186fc2 100644 --- a/application/dialogs/ProgressDialog.cpp +++ b/application/dialogs/ProgressDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,117 +23,117 @@ ProgressDialog::ProgressDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ProgressDialog) { - ui->setupUi(this); - this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); - setSkipButton(false); - changeProgress(0, 100); + ui->setupUi(this); + this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); + setSkipButton(false); + changeProgress(0, 100); } void ProgressDialog::setSkipButton(bool present, QString label) { - ui->skipButton->setAutoDefault(false); - ui->skipButton->setDefault(false); - ui->skipButton->setFocusPolicy(Qt::ClickFocus); - ui->skipButton->setEnabled(present); - ui->skipButton->setVisible(present); - ui->skipButton->setText(label); - updateSize(); + ui->skipButton->setAutoDefault(false); + ui->skipButton->setDefault(false); + ui->skipButton->setFocusPolicy(Qt::ClickFocus); + ui->skipButton->setEnabled(present); + ui->skipButton->setVisible(present); + ui->skipButton->setText(label); + updateSize(); } void ProgressDialog::on_skipButton_clicked(bool checked) { - Q_UNUSED(checked); - task->abort(); + Q_UNUSED(checked); + task->abort(); } ProgressDialog::~ProgressDialog() { - delete ui; + delete ui; } void ProgressDialog::updateSize() { QSize qSize = QSize(480, minimumSizeHint().height()); - resize(qSize); + resize(qSize); setFixedSize(qSize); } int ProgressDialog::execWithTask(Task *task) { - this->task = task; - QDialog::DialogCode result; - - if(!task) - { - qDebug() << "Programmer error: progress dialog created with null task."; - return Accepted; - } - - if(handleImmediateResult(result)) - { - return result; - } - - // Connect signals. - connect(task, SIGNAL(started()), SLOT(onTaskStarted())); - connect(task, SIGNAL(failed(QString)), SLOT(onTaskFailed(QString))); - connect(task, SIGNAL(succeeded()), SLOT(onTaskSucceeded())); - connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString &))); - connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64))); - - // if this didn't connect to an already running task, invoke start - if(!task->isRunning()) - { - task->start(); - } - if(task->isRunning()) - { - changeProgress(task->getProgress(), task->getTotalProgress()); - changeStatus(task->getStatus()); - return QDialog::exec(); - } - else if(handleImmediateResult(result)) - { - return result; - } - else - { - return QDialog::Rejected; - } + this->task = task; + QDialog::DialogCode result; + + if(!task) + { + qDebug() << "Programmer error: progress dialog created with null task."; + return Accepted; + } + + if(handleImmediateResult(result)) + { + return result; + } + + // Connect signals. + connect(task, SIGNAL(started()), SLOT(onTaskStarted())); + connect(task, SIGNAL(failed(QString)), SLOT(onTaskFailed(QString))); + connect(task, SIGNAL(succeeded()), SLOT(onTaskSucceeded())); + connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString &))); + connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64))); + + // if this didn't connect to an already running task, invoke start + if(!task->isRunning()) + { + task->start(); + } + if(task->isRunning()) + { + changeProgress(task->getProgress(), task->getTotalProgress()); + changeStatus(task->getStatus()); + return QDialog::exec(); + } + else if(handleImmediateResult(result)) + { + return result; + } + else + { + return QDialog::Rejected; + } } // TODO: only provide the unique_ptr overloads int ProgressDialog::execWithTask(std::unique_ptr<Task> &&task) { - connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater); - return execWithTask(task.release()); + connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater); + return execWithTask(task.release()); } int ProgressDialog::execWithTask(std::unique_ptr<Task> &task) { - connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater); - return execWithTask(task.release()); + connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater); + return execWithTask(task.release()); } bool ProgressDialog::handleImmediateResult(QDialog::DialogCode &result) { - if(task->isFinished()) - { - if(task->wasSuccessful()) - { - result = QDialog::Accepted; - } - else - { - result = QDialog::Rejected; - } - return true; - } - return false; + if(task->isFinished()) + { + if(task->wasSuccessful()) + { + result = QDialog::Accepted; + } + else + { + result = QDialog::Rejected; + } + return true; + } + return false; } Task *ProgressDialog::getTask() { - return task; + return task; } void ProgressDialog::onTaskStarted() @@ -142,55 +142,55 @@ void ProgressDialog::onTaskStarted() void ProgressDialog::onTaskFailed(QString failure) { - reject(); + reject(); } void ProgressDialog::onTaskSucceeded() { - accept(); + accept(); } void ProgressDialog::changeStatus(const QString &status) { - ui->statusLabel->setText(status); - updateSize(); + ui->statusLabel->setText(status); + updateSize(); } void ProgressDialog::changeProgress(qint64 current, qint64 total) { - ui->taskProgressBar->setMaximum(total); - ui->taskProgressBar->setValue(current); + ui->taskProgressBar->setMaximum(total); + ui->taskProgressBar->setValue(current); } void ProgressDialog::keyPressEvent(QKeyEvent *e) { - if(ui->skipButton->isVisible()) - { - if (e->key() == Qt::Key_Escape) - { - on_skipButton_clicked(true); - return; - } - else if(e->key() == Qt::Key_Tab) - { - ui->skipButton->setFocusPolicy(Qt::StrongFocus); - ui->skipButton->setFocus(); - ui->skipButton->setAutoDefault(true); - ui->skipButton->setDefault(true); - return; - } - } - QDialog::keyPressEvent(e); + if(ui->skipButton->isVisible()) + { + if (e->key() == Qt::Key_Escape) + { + on_skipButton_clicked(true); + return; + } + else if(e->key() == Qt::Key_Tab) + { + ui->skipButton->setFocusPolicy(Qt::StrongFocus); + ui->skipButton->setFocus(); + ui->skipButton->setAutoDefault(true); + ui->skipButton->setDefault(true); + return; + } + } + QDialog::keyPressEvent(e); } void ProgressDialog::closeEvent(QCloseEvent *e) { - if (task && task->isRunning()) - { - e->ignore(); - } - else - { - QDialog::closeEvent(e); - } + if (task && task->isRunning()) + { + e->ignore(); + } + else + { + QDialog::closeEvent(e); + } } diff --git a/application/dialogs/ProgressDialog.h b/application/dialogs/ProgressDialog.h index f27b71e1..a3779e3f 100644 --- a/application/dialogs/ProgressDialog.h +++ b/application/dialogs/ProgressDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,45 +27,45 @@ class ProgressDialog; class ProgressDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit ProgressDialog(QWidget *parent = 0); - ~ProgressDialog(); + explicit ProgressDialog(QWidget *parent = 0); + ~ProgressDialog(); - void updateSize(); + void updateSize(); - int execWithTask(Task *task); - int execWithTask(std::unique_ptr<Task> &&task); - int execWithTask(std::unique_ptr<Task> &task); + int execWithTask(Task *task); + int execWithTask(std::unique_ptr<Task> &&task); + int execWithTask(std::unique_ptr<Task> &task); - void setSkipButton(bool present, QString label = QString()); + void setSkipButton(bool present, QString label = QString()); - Task *getTask(); + Task *getTask(); public slots: - void onTaskStarted(); - void onTaskFailed(QString failure); - void onTaskSucceeded(); + void onTaskStarted(); + void onTaskFailed(QString failure); + void onTaskSucceeded(); - void changeStatus(const QString &status); - void changeProgress(qint64 current, qint64 total); + void changeStatus(const QString &status); + void changeProgress(qint64 current, qint64 total); private slots: - void on_skipButton_clicked(bool checked); + void on_skipButton_clicked(bool checked); protected: - virtual void keyPressEvent(QKeyEvent *e); - virtual void closeEvent(QCloseEvent *e); + virtual void keyPressEvent(QKeyEvent *e); + virtual void closeEvent(QCloseEvent *e); private: - bool handleImmediateResult(QDialog::DialogCode &result); + bool handleImmediateResult(QDialog::DialogCode &result); private: - Ui::ProgressDialog *ui; + Ui::ProgressDialog *ui; - Task *task; + Task *task; }; diff --git a/application/dialogs/SkinUploadDialog.cpp b/application/dialogs/SkinUploadDialog.cpp index 93414c6e..7d2ff829 100644 --- a/application/dialogs/SkinUploadDialog.cpp +++ b/application/dialogs/SkinUploadDialog.cpp @@ -9,106 +9,106 @@ void SkinUploadDialog::on_buttonBox_rejected() { - close(); + close(); } void SkinUploadDialog::on_buttonBox_accepted() { - AuthSessionPtr session = std::make_shared<AuthSession>(); - auto login = m_acct->login(session); - ProgressDialog prog(this); - if (prog.execWithTask((Task*)login.get()) != QDialog::Accepted) - { - //FIXME: recover with password prompt - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to login!"), QMessageBox::Warning)->exec(); - close(); - return; - } - QString fileName; - QString input = ui->skinPathTextBox->text(); - QRegExp urlPrefixMatcher("^([a-z]+)://.+$"); - bool isLocalFile = false; - // it has an URL prefix -> it is an URL - if(urlPrefixMatcher.exactMatch(input)) - { - QUrl fileURL = input; - if(fileURL.isValid()) - { - // local? - if(fileURL.isLocalFile()) - { - isLocalFile = true; - fileName = fileURL.toLocalFile(); - } - else - { - CustomMessageBox::selectable( - this, - tr("Skin Upload"), - tr("Using remote URLs for setting skins is not implemented yet."), - QMessageBox::Warning - )->exec(); - close(); - return; - } - } - else - { - CustomMessageBox::selectable( - this, - tr("Skin Upload"), - tr("You cannot use an invalid URL for uploading skins."), - QMessageBox::Warning - )->exec(); - close(); - return; - } - } - else - { - // just assume it's a path then - isLocalFile = true; - fileName = ui->skinPathTextBox->text(); - } - if (isLocalFile && !QFile::exists(fileName)) - { - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec(); - close(); - return; - } - SkinUpload::Model model = SkinUpload::STEVE; - if (ui->steveBtn->isChecked()) - { - model = SkinUpload::STEVE; - } - else if (ui->alexBtn->isChecked()) - { - model = SkinUpload::ALEX; - } - SkinUploadPtr upload = std::make_shared<SkinUpload>(this, session, FS::read(fileName), model); - if (prog.execWithTask((Task*)upload.get()) != QDialog::Accepted) - { - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec(); - close(); - return; - } - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Success"), QMessageBox::Information)->exec(); - close(); + AuthSessionPtr session = std::make_shared<AuthSession>(); + auto login = m_acct->login(session); + ProgressDialog prog(this); + if (prog.execWithTask((Task*)login.get()) != QDialog::Accepted) + { + //FIXME: recover with password prompt + CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to login!"), QMessageBox::Warning)->exec(); + close(); + return; + } + QString fileName; + QString input = ui->skinPathTextBox->text(); + QRegExp urlPrefixMatcher("^([a-z]+)://.+$"); + bool isLocalFile = false; + // it has an URL prefix -> it is an URL + if(urlPrefixMatcher.exactMatch(input)) + { + QUrl fileURL = input; + if(fileURL.isValid()) + { + // local? + if(fileURL.isLocalFile()) + { + isLocalFile = true; + fileName = fileURL.toLocalFile(); + } + else + { + CustomMessageBox::selectable( + this, + tr("Skin Upload"), + tr("Using remote URLs for setting skins is not implemented yet."), + QMessageBox::Warning + )->exec(); + close(); + return; + } + } + else + { + CustomMessageBox::selectable( + this, + tr("Skin Upload"), + tr("You cannot use an invalid URL for uploading skins."), + QMessageBox::Warning + )->exec(); + close(); + return; + } + } + else + { + // just assume it's a path then + isLocalFile = true; + fileName = ui->skinPathTextBox->text(); + } + if (isLocalFile && !QFile::exists(fileName)) + { + CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec(); + close(); + return; + } + SkinUpload::Model model = SkinUpload::STEVE; + if (ui->steveBtn->isChecked()) + { + model = SkinUpload::STEVE; + } + else if (ui->alexBtn->isChecked()) + { + model = SkinUpload::ALEX; + } + SkinUploadPtr upload = std::make_shared<SkinUpload>(this, session, FS::read(fileName), model); + if (prog.execWithTask((Task*)upload.get()) != QDialog::Accepted) + { + CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec(); + close(); + return; + } + CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Success"), QMessageBox::Information)->exec(); + close(); } void SkinUploadDialog::on_skinBrowseBtn_clicked() { - QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), "*.png"); - if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) - { - return; - } - QString cooked_path = FS::NormalizePath(raw_path); - ui->skinPathTextBox->setText(cooked_path); + QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), "*.png"); + if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) + { + return; + } + QString cooked_path = FS::NormalizePath(raw_path); + ui->skinPathTextBox->setText(cooked_path); } SkinUploadDialog::SkinUploadDialog(MojangAccountPtr acct, QWidget *parent) - :QDialog(parent), m_acct(acct), ui(new Ui::SkinUploadDialog) + :QDialog(parent), m_acct(acct), ui(new Ui::SkinUploadDialog) { - ui->setupUi(this); + ui->setupUi(this); } diff --git a/application/dialogs/SkinUploadDialog.h b/application/dialogs/SkinUploadDialog.h index 514eabc8..deb44eac 100644 --- a/application/dialogs/SkinUploadDialog.h +++ b/application/dialogs/SkinUploadDialog.h @@ -5,25 +5,25 @@ namespace Ui { - class SkinUploadDialog; + class SkinUploadDialog; } class SkinUploadDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit SkinUploadDialog(MojangAccountPtr acct, QWidget *parent = 0); - virtual ~SkinUploadDialog() {}; + explicit SkinUploadDialog(MojangAccountPtr acct, QWidget *parent = 0); + virtual ~SkinUploadDialog() {}; public slots: - void on_buttonBox_accepted(); + void on_buttonBox_accepted(); - void on_buttonBox_rejected(); + void on_buttonBox_rejected(); - void on_skinBrowseBtn_clicked(); + void on_skinBrowseBtn_clicked(); protected: - MojangAccountPtr m_acct; + MojangAccountPtr m_acct; private: - Ui::SkinUploadDialog *ui; + Ui::SkinUploadDialog *ui; }; diff --git a/application/dialogs/UpdateDialog.cpp b/application/dialogs/UpdateDialog.cpp index 30c7173d..242a5b70 100644 --- a/application/dialogs/UpdateDialog.cpp +++ b/application/dialogs/UpdateDialog.cpp @@ -10,20 +10,20 @@ UpdateDialog::UpdateDialog(bool hasUpdate, QWidget *parent) : QDialog(parent), ui(new Ui::UpdateDialog) { - ui->setupUi(this); - auto channel = MMC->settings()->get("UpdateChannel").toString(); - if(hasUpdate) - { - ui->label->setText(tr("A new %1 update is available!").arg(channel)); - } - else - { - ui->label->setText(tr("No %1 updates found. You are running the latest version.").arg(channel)); - ui->btnUpdateNow->setHidden(true); - ui->btnUpdateLater->setText(tr("Close")); - } - loadChangelog(); - restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("UpdateDialogGeometry").toByteArray())); + ui->setupUi(this); + auto channel = MMC->settings()->get("UpdateChannel").toString(); + if(hasUpdate) + { + ui->label->setText(tr("A new %1 update is available!").arg(channel)); + } + else + { + ui->label->setText(tr("No %1 updates found. You are running the latest version.").arg(channel)); + ui->btnUpdateNow->setHidden(true); + ui->btnUpdateLater->setText(tr("Close")); + } + loadChangelog(); + restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("UpdateDialogGeometry").toByteArray())); } UpdateDialog::~UpdateDialog() @@ -32,150 +32,150 @@ UpdateDialog::~UpdateDialog() void UpdateDialog::loadChangelog() { - auto channel = MMC->settings()->get("UpdateChannel").toString(); - dljob.reset(new NetJob("Changelog")); - QString url; - if(channel == "stable") - { - url = QString("https://raw.githubusercontent.com/MultiMC/MultiMC5/%1/changelog.md").arg(channel); - m_changelogType = CHANGELOG_MARKDOWN; - } - else - { - url = QString("https://api.github.com/repos/MultiMC/MultiMC5/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel); - m_changelogType = CHANGELOG_COMMITS; - } - dljob->addNetAction(Net::Download::makeByteArray(QUrl(url), &changelogData)); - connect(dljob.get(), &NetJob::succeeded, this, &UpdateDialog::changelogLoaded); - connect(dljob.get(), &NetJob::failed, this, &UpdateDialog::changelogFailed); - dljob->start(); + auto channel = MMC->settings()->get("UpdateChannel").toString(); + dljob.reset(new NetJob("Changelog")); + QString url; + if(channel == "stable") + { + url = QString("https://raw.githubusercontent.com/MultiMC/MultiMC5/%1/changelog.md").arg(channel); + m_changelogType = CHANGELOG_MARKDOWN; + } + else + { + url = QString("https://api.github.com/repos/MultiMC/MultiMC5/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel); + m_changelogType = CHANGELOG_COMMITS; + } + dljob->addNetAction(Net::Download::makeByteArray(QUrl(url), &changelogData)); + connect(dljob.get(), &NetJob::succeeded, this, &UpdateDialog::changelogLoaded); + connect(dljob.get(), &NetJob::failed, this, &UpdateDialog::changelogFailed); + dljob->start(); } QString reprocessMarkdown(QByteArray markdown) { - HoeDown hoedown; - QString output = hoedown.process(markdown); + HoeDown hoedown; + QString output = hoedown.process(markdown); - // HACK: easier than customizing hoedown - output.replace(QRegExp("GH-([0-9]+)"), "<a href=\"https://github.com/MultiMC/MultiMC5/issues/\\1\">GH-\\1</a>"); - qDebug() << output; - return output; + // HACK: easier than customizing hoedown + output.replace(QRegExp("GH-([0-9]+)"), "<a href=\"https://github.com/MultiMC/MultiMC5/issues/\\1\">GH-\\1</a>"); + qDebug() << output; + return output; } QString reprocessCommits(QByteArray json) { - auto channel = MMC->settings()->get("UpdateChannel").toString(); - try - { - QString result; - auto document = Json::requireDocument(json); - auto rootobject = Json::requireObject(document); - auto status = Json::requireString(rootobject, "status"); - auto diff_url = Json::requireString(rootobject, "html_url"); + auto channel = MMC->settings()->get("UpdateChannel").toString(); + try + { + QString result; + auto document = Json::requireDocument(json); + auto rootobject = Json::requireObject(document); + auto status = Json::requireString(rootobject, "status"); + auto diff_url = Json::requireString(rootobject, "html_url"); - auto print_commits = [&]() - { - result += "<table cellspacing=0 cellpadding=2 style='border-width: 1px; border-style: solid'>"; - auto commitarray = Json::requireArray(rootobject, "commits"); - for(int i = commitarray.size() - 1; i >= 0; i--) - { - const auto & commitval = commitarray[i]; - auto commitobj = Json::requireObject(commitval); - auto parents_info = Json::ensureArray(commitobj, "parents"); - // NOTE: this ignores merge commits, because they have more than one parent - if(parents_info.size() > 1) - { - continue; - } - auto commit_url = Json::requireString(commitobj, "html_url"); - auto commit_info = Json::requireObject(commitobj, "commit"); - auto commit_message = Json::requireString(commit_info, "message"); - auto lines = commit_message.split('\n'); - QRegularExpression regexp("(?<prefix>(GH-(?<issuenr>[0-9]+))|(NOISSUE)|(SCRATCH))? *(?<rest>.*) *"); - auto match = regexp.match(lines.takeFirst(), 0, QRegularExpression::NormalMatch); - auto issuenr = match.captured("issuenr"); - auto prefix = match.captured("prefix"); - auto rest = match.captured("rest"); - result += "<tr><td>"; - if(issuenr.length()) - { - result += QString("<a href=\"https://github.com/MultiMC/MultiMC5/issues/%1\">GH-%2</a>").arg(issuenr, issuenr); - } - else if(prefix.length()) - { - result += QString("<a href=\"%1\">%2</a>").arg(commit_url, prefix); - } - else - { - result += QString("<a href=\"%1\">NOISSUE</a>").arg(commit_url); - } - result += "</td>"; - lines.prepend(rest); - result += "<td><p>" + lines.join("<br />") + "</p></td></tr>"; - } - result += "</table>"; - }; + auto print_commits = [&]() + { + result += "<table cellspacing=0 cellpadding=2 style='border-width: 1px; border-style: solid'>"; + auto commitarray = Json::requireArray(rootobject, "commits"); + for(int i = commitarray.size() - 1; i >= 0; i--) + { + const auto & commitval = commitarray[i]; + auto commitobj = Json::requireObject(commitval); + auto parents_info = Json::ensureArray(commitobj, "parents"); + // NOTE: this ignores merge commits, because they have more than one parent + if(parents_info.size() > 1) + { + continue; + } + auto commit_url = Json::requireString(commitobj, "html_url"); + auto commit_info = Json::requireObject(commitobj, "commit"); + auto commit_message = Json::requireString(commit_info, "message"); + auto lines = commit_message.split('\n'); + QRegularExpression regexp("(?<prefix>(GH-(?<issuenr>[0-9]+))|(NOISSUE)|(SCRATCH))? *(?<rest>.*) *"); + auto match = regexp.match(lines.takeFirst(), 0, QRegularExpression::NormalMatch); + auto issuenr = match.captured("issuenr"); + auto prefix = match.captured("prefix"); + auto rest = match.captured("rest"); + result += "<tr><td>"; + if(issuenr.length()) + { + result += QString("<a href=\"https://github.com/MultiMC/MultiMC5/issues/%1\">GH-%2</a>").arg(issuenr, issuenr); + } + else if(prefix.length()) + { + result += QString("<a href=\"%1\">%2</a>").arg(commit_url, prefix); + } + else + { + result += QString("<a href=\"%1\">NOISSUE</a>").arg(commit_url); + } + result += "</td>"; + lines.prepend(rest); + result += "<td><p>" + lines.join("<br />") + "</p></td></tr>"; + } + result += "</table>"; + }; - if(status == "identical") - { - return QObject::tr("<p>There are no code changes between your current version and latest %1.</p>").arg(channel); - } - else if(status == "ahead") - { - result += QObject::tr("<p>Following commits were added since last update:</p>"); - print_commits(); - } - else if(status == "diverged") - { - auto commit_ahead = Json::requireInteger(rootobject, "ahead_by"); - auto commit_behind = Json::requireInteger(rootobject, "behind_by"); - result += QObject::tr("<p>The update removes %1 commits and adds the following %2:</p>").arg(commit_behind).arg(commit_ahead); - print_commits(); - } - result += QObject::tr("<p>You can <a href=\"%1\">look at the changes on github</a>.</p>").arg(diff_url); - return result; - } - catch (JSONValidationError & e) - { - qWarning() << "Got an unparseable commit log from github:" << e.what(); - qDebug() << json; - } - return QString(); + if(status == "identical") + { + return QObject::tr("<p>There are no code changes between your current version and latest %1.</p>").arg(channel); + } + else if(status == "ahead") + { + result += QObject::tr("<p>Following commits were added since last update:</p>"); + print_commits(); + } + else if(status == "diverged") + { + auto commit_ahead = Json::requireInteger(rootobject, "ahead_by"); + auto commit_behind = Json::requireInteger(rootobject, "behind_by"); + result += QObject::tr("<p>The update removes %1 commits and adds the following %2:</p>").arg(commit_behind).arg(commit_ahead); + print_commits(); + } + result += QObject::tr("<p>You can <a href=\"%1\">look at the changes on github</a>.</p>").arg(diff_url); + return result; + } + catch (const JSONValidationError &e) + { + qWarning() << "Got an unparseable commit log from github:" << e.what(); + qDebug() << json; + } + return QString(); } void UpdateDialog::changelogLoaded() { - QString result; - switch(m_changelogType) - { - case CHANGELOG_COMMITS: - result = reprocessCommits(changelogData); - break; - case CHANGELOG_MARKDOWN: - result = reprocessMarkdown(changelogData); - break; - } - changelogData.clear(); - ui->changelogBrowser->setHtml(result); + QString result; + switch(m_changelogType) + { + case CHANGELOG_COMMITS: + result = reprocessCommits(changelogData); + break; + case CHANGELOG_MARKDOWN: + result = reprocessMarkdown(changelogData); + break; + } + changelogData.clear(); + ui->changelogBrowser->setHtml(result); } void UpdateDialog::changelogFailed(QString reason) { - ui->changelogBrowser->setHtml(tr("<p align=\"center\" <span style=\"font-size:22pt;\">Failed to fetch changelog... Error: %1</span></p>").arg(reason)); + ui->changelogBrowser->setHtml(tr("<p align=\"center\" <span style=\"font-size:22pt;\">Failed to fetch changelog... Error: %1</span></p>").arg(reason)); } void UpdateDialog::on_btnUpdateLater_clicked() { - reject(); + reject(); } void UpdateDialog::on_btnUpdateNow_clicked() { - done(UPDATE_NOW); + done(UPDATE_NOW); } void UpdateDialog::closeEvent(QCloseEvent* evt) { - MMC->settings()->set("UpdateDialogGeometry", saveGeometry().toBase64()); - QDialog::closeEvent(evt); + MMC->settings()->set("UpdateDialogGeometry", saveGeometry().toBase64()); + QDialog::closeEvent(evt); } diff --git a/application/dialogs/UpdateDialog.h b/application/dialogs/UpdateDialog.h index 78960c99..6a871b1c 100644 --- a/application/dialogs/UpdateDialog.h +++ b/application/dialogs/UpdateDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,43 +25,43 @@ class UpdateDialog; enum UpdateAction { - UPDATE_LATER = QDialog::Rejected, - UPDATE_NOW = QDialog::Accepted, + UPDATE_LATER = QDialog::Rejected, + UPDATE_NOW = QDialog::Accepted, }; enum ChangelogType { - CHANGELOG_MARKDOWN, - CHANGELOG_COMMITS + CHANGELOG_MARKDOWN, + CHANGELOG_COMMITS }; class UpdateDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit UpdateDialog(bool hasUpdate = true, QWidget *parent = 0); - ~UpdateDialog(); + explicit UpdateDialog(bool hasUpdate = true, QWidget *parent = 0); + ~UpdateDialog(); public slots: - void on_btnUpdateNow_clicked(); - void on_btnUpdateLater_clicked(); + void on_btnUpdateNow_clicked(); + void on_btnUpdateLater_clicked(); - /// Starts loading the changelog - void loadChangelog(); + /// Starts loading the changelog + void loadChangelog(); - /// Slot for when the chengelog loads successfully. - void changelogLoaded(); + /// Slot for when the chengelog loads successfully. + void changelogLoaded(); - /// Slot for when the chengelog fails to load... - void changelogFailed(QString reason); + /// Slot for when the chengelog fails to load... + void changelogFailed(QString reason); protected: - void closeEvent(QCloseEvent * ) override; + void closeEvent(QCloseEvent * ) override; private: - Ui::UpdateDialog *ui; - QByteArray changelogData; - NetJobPtr dljob; - ChangelogType m_changelogType = CHANGELOG_MARKDOWN; + Ui::UpdateDialog *ui; + QByteArray changelogData; + NetJobPtr dljob; + ChangelogType m_changelogType = CHANGELOG_MARKDOWN; }; diff --git a/application/dialogs/VersionSelectDialog.cpp b/application/dialogs/VersionSelectDialog.cpp index c7009497..0b6ba87e 100644 --- a/application/dialogs/VersionSelectDialog.cpp +++ b/application/dialogs/VersionSelectDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,109 +33,109 @@ #include <widgets/VersionSelectWidget.h> VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent, bool cancelable) - : QDialog(parent) + : QDialog(parent) { - setObjectName(QStringLiteral("VersionSelectDialog")); - resize(400, 347); - m_verticalLayout = new QVBoxLayout(this); - m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + setObjectName(QStringLiteral("VersionSelectDialog")); + resize(400, 347); + m_verticalLayout = new QVBoxLayout(this); + m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - m_versionWidget = new VersionSelectWidget(parent); - m_verticalLayout->addWidget(m_versionWidget); + m_versionWidget = new VersionSelectWidget(parent); + m_verticalLayout->addWidget(m_versionWidget); - m_horizontalLayout = new QHBoxLayout(); - m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + m_horizontalLayout = new QHBoxLayout(); + m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); - m_refreshButton = new QPushButton(this); - m_refreshButton->setObjectName(QStringLiteral("refreshButton")); - m_horizontalLayout->addWidget(m_refreshButton); + m_refreshButton = new QPushButton(this); + m_refreshButton->setObjectName(QStringLiteral("refreshButton")); + m_horizontalLayout->addWidget(m_refreshButton); - m_buttonBox = new QDialogButtonBox(this); - m_buttonBox->setObjectName(QStringLiteral("buttonBox")); - m_buttonBox->setOrientation(Qt::Horizontal); - m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - m_horizontalLayout->addWidget(m_buttonBox); + m_buttonBox = new QDialogButtonBox(this); + m_buttonBox->setObjectName(QStringLiteral("buttonBox")); + m_buttonBox->setOrientation(Qt::Horizontal); + m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + m_horizontalLayout->addWidget(m_buttonBox); - m_verticalLayout->addLayout(m_horizontalLayout); + m_verticalLayout->addLayout(m_horizontalLayout); - retranslate(); + retranslate(); - QObject::connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - QObject::connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + QObject::connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + QObject::connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); - QMetaObject::connectSlotsByName(this); - setWindowModality(Qt::WindowModal); - setWindowTitle(title); + QMetaObject::connectSlotsByName(this); + setWindowModality(Qt::WindowModal); + setWindowTitle(title); - m_vlist = vlist; + m_vlist = vlist; - if (!cancelable) - { - m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); - } + if (!cancelable) + { + m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); + } } void VersionSelectDialog::retranslate() { - // FIXME: overrides custom title given in constructor! - setWindowTitle(tr("Choose Version")); - m_refreshButton->setToolTip(tr("Reloads the version list.")); - m_refreshButton->setText(tr("&Refresh")); + // FIXME: overrides custom title given in constructor! + setWindowTitle(tr("Choose Version")); + m_refreshButton->setToolTip(tr("Reloads the version list.")); + m_refreshButton->setText(tr("&Refresh")); } void VersionSelectDialog::setCurrentVersion(const QString& version) { - m_currentVersion = version; - m_versionWidget->setCurrentVersion(version); + m_currentVersion = version; + m_versionWidget->setCurrentVersion(version); } void VersionSelectDialog::setEmptyString(QString emptyString) { - m_versionWidget->setEmptyString(emptyString); + m_versionWidget->setEmptyString(emptyString); } void VersionSelectDialog::setEmptyErrorString(QString emptyErrorString) { - m_versionWidget->setEmptyErrorString(emptyErrorString); + m_versionWidget->setEmptyErrorString(emptyErrorString); } void VersionSelectDialog::setResizeOn(int column) { - resizeOnColumn = column; + resizeOnColumn = column; } int VersionSelectDialog::exec() { - QDialog::open(); - m_versionWidget->initialize(m_vlist); - if(resizeOnColumn != -1) - { - m_versionWidget->setResizeOn(resizeOnColumn); - } - return QDialog::exec(); + QDialog::open(); + m_versionWidget->initialize(m_vlist); + if(resizeOnColumn != -1) + { + m_versionWidget->setResizeOn(resizeOnColumn); + } + return QDialog::exec(); } void VersionSelectDialog::selectRecommended() { - m_versionWidget->selectRecommended(); + m_versionWidget->selectRecommended(); } BaseVersionPtr VersionSelectDialog::selectedVersion() const { - return m_versionWidget->selectedVersion(); + return m_versionWidget->selectedVersion(); } void VersionSelectDialog::on_refreshButton_clicked() { - m_versionWidget->loadList(); + m_versionWidget->loadList(); } void VersionSelectDialog::setExactFilter(BaseVersionList::ModelRoles role, QString filter) { - m_versionWidget->setExactFilter(role, filter); + m_versionWidget->setExactFilter(role, filter); } void VersionSelectDialog::setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter) { - m_versionWidget->setFuzzyFilter(role, filter); + m_versionWidget->setFuzzyFilter(role, filter); } diff --git a/application/dialogs/VersionSelectDialog.h b/application/dialogs/VersionSelectDialog.h index 127a0ee9..14bc4d76 100644 --- a/application/dialogs/VersionSelectDialog.h +++ b/application/dialogs/VersionSelectDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,43 +36,43 @@ class VersionProxyModel; class VersionSelectDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent = 0, bool cancelable = true); - virtual ~VersionSelectDialog() {}; + explicit VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent = 0, bool cancelable = true); + virtual ~VersionSelectDialog() {}; - int exec() override; + int exec() override; - BaseVersionPtr selectedVersion() const; + BaseVersionPtr selectedVersion() const; - void setCurrentVersion(const QString & version); - void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter); - void setExactFilter(BaseVersionList::ModelRoles role, QString filter); - void setEmptyString(QString emptyString); - void setEmptyErrorString(QString emptyErrorString); - void setResizeOn(int column); + void setCurrentVersion(const QString & version); + void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter); + void setExactFilter(BaseVersionList::ModelRoles role, QString filter); + void setEmptyString(QString emptyString); + void setEmptyErrorString(QString emptyErrorString); + void setResizeOn(int column); private slots: - void on_refreshButton_clicked(); + void on_refreshButton_clicked(); private: - void retranslate(); - void selectRecommended(); + void retranslate(); + void selectRecommended(); private: - QString m_currentVersion; - VersionSelectWidget *m_versionWidget = nullptr; - QVBoxLayout *m_verticalLayout = nullptr; - QHBoxLayout *m_horizontalLayout = nullptr; - QPushButton *m_refreshButton = nullptr; - QDialogButtonBox *m_buttonBox = nullptr; + QString m_currentVersion; + VersionSelectWidget *m_versionWidget = nullptr; + QVBoxLayout *m_verticalLayout = nullptr; + QHBoxLayout *m_horizontalLayout = nullptr; + QPushButton *m_refreshButton = nullptr; + QDialogButtonBox *m_buttonBox = nullptr; - BaseVersionList *m_vlist = nullptr; + BaseVersionList *m_vlist = nullptr; - VersionProxyModel *m_proxyModel = nullptr; + VersionProxyModel *m_proxyModel = nullptr; - int resizeOnColumn = -1; + int resizeOnColumn = -1; - Task * loadTask = nullptr; + Task * loadTask = nullptr; }; diff --git a/application/groupview/AccessibleGroupView.cpp b/application/groupview/AccessibleGroupView.cpp new file mode 100644 index 00000000..9a1bb821 --- /dev/null +++ b/application/groupview/AccessibleGroupView.cpp @@ -0,0 +1,774 @@ +#include "GroupView.h" +#include "AccessibleGroupView.h" +#include "AccessibleGroupView_p.h" + +#include <qvariant.h> +#include <qaccessible.h> +#include <qheaderview.h> + +QAccessibleInterface *groupViewAccessibleFactory(const QString &classname, QObject *object) +{ + QAccessibleInterface *iface = 0; + if (!object || !object->isWidgetType()) + return iface; + + QWidget *widget = static_cast<QWidget*>(object); + + if (classname == QLatin1String("GroupView")) { + iface = new AccessibleGroupView((GroupView *)widget); + } + return iface; +} + + +QAbstractItemView *AccessibleGroupView::view() const +{ + return qobject_cast<QAbstractItemView*>(object()); +} + +int AccessibleGroupView::logicalIndex(const QModelIndex &index) const +{ + if (!view()->model() || !index.isValid()) + return -1; + return index.row() * (index.model()->columnCount()) + index.column(); +} + +AccessibleGroupView::AccessibleGroupView(QWidget *w) + : QAccessibleObject(w) +{ + Q_ASSERT(view()); +} + +bool AccessibleGroupView::isValid() const +{ + return view(); +} + +AccessibleGroupView::~AccessibleGroupView() +{ + for (QAccessible::Id id : childToId) { + QAccessible::deleteAccessibleInterface(id); + } +} + +QAccessibleInterface *AccessibleGroupView::cellAt(int row, int column) const +{ + if (!view()->model()) { + return 0; + } + + QModelIndex index = view()->model()->index(row, column, view()->rootIndex()); + if (Q_UNLIKELY(!index.isValid())) { + qWarning() << "AccessibleGroupView::cellAt: invalid index: " << index << " for " << view(); + return 0; + } + + return child(logicalIndex(index)); +} + +QAccessibleInterface *AccessibleGroupView::caption() const +{ + return 0; +} + +QString AccessibleGroupView::columnDescription(int column) const +{ + if (!view()->model()) + return QString(); + + return view()->model()->headerData(column, Qt::Horizontal).toString(); +} + +int AccessibleGroupView::columnCount() const +{ + if (!view()->model()) + return 0; + return 1; +} + +int AccessibleGroupView::rowCount() const +{ + if (!view()->model()) + return 0; + return view()->model()->rowCount(); +} + +int AccessibleGroupView::selectedCellCount() const +{ + if (!view()->selectionModel()) + return 0; + return view()->selectionModel()->selectedIndexes().count(); +} + +int AccessibleGroupView::selectedColumnCount() const +{ + if (!view()->selectionModel()) + return 0; + return view()->selectionModel()->selectedColumns().count(); +} + +int AccessibleGroupView::selectedRowCount() const +{ + if (!view()->selectionModel()) + return 0; + return view()->selectionModel()->selectedRows().count(); +} + +QString AccessibleGroupView::rowDescription(int row) const +{ + if (!view()->model()) + return QString(); + return view()->model()->headerData(row, Qt::Vertical).toString(); +} + +QList<QAccessibleInterface *> AccessibleGroupView::selectedCells() const +{ + QList<QAccessibleInterface*> cells; + if (!view()->selectionModel()) + return cells; + const QModelIndexList selectedIndexes = view()->selectionModel()->selectedIndexes(); + cells.reserve(selectedIndexes.size()); + for (const QModelIndex &index : selectedIndexes) + cells.append(child(logicalIndex(index))); + return cells; +} + +QList<int> AccessibleGroupView::selectedColumns() const +{ + if (!view()->selectionModel()) { + return QList<int>(); + } + + const QModelIndexList selectedColumns = view()->selectionModel()->selectedColumns(); + + QList<int> columns; + columns.reserve(selectedColumns.size()); + for (const QModelIndex &index : selectedColumns) { + columns.append(index.column()); + } + + return columns; +} + +QList<int> AccessibleGroupView::selectedRows() const +{ + if (!view()->selectionModel()) { + return QList<int>(); + } + + QList<int> rows; + + const QModelIndexList selectedRows = view()->selectionModel()->selectedRows(); + + rows.reserve(selectedRows.size()); + for (const QModelIndex &index : selectedRows) { + rows.append(index.row()); + } + + return rows; +} + +QAccessibleInterface *AccessibleGroupView::summary() const +{ + return 0; +} + +bool AccessibleGroupView::isColumnSelected(int column) const +{ + if (!view()->selectionModel()) { + return false; + } + + return view()->selectionModel()->isColumnSelected(column, QModelIndex()); +} + +bool AccessibleGroupView::isRowSelected(int row) const +{ + if (!view()->selectionModel()) { + return false; + } + + return view()->selectionModel()->isRowSelected(row, QModelIndex()); +} + +bool AccessibleGroupView::selectRow(int row) +{ + if (!view()->model() || !view()->selectionModel()) { + return false; + } + QModelIndex index = view()->model()->index(row, 0, view()->rootIndex()); + + if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectColumns) { + return false; + } + + switch (view()->selectionMode()) { + case QAbstractItemView::NoSelection: { + return false; + } + case QAbstractItemView::SingleSelection: { + if (view()->selectionBehavior() != QAbstractItemView::SelectRows && columnCount() > 1 ) + return false; + view()->clearSelection(); + break; + } + case QAbstractItemView::ContiguousSelection: { + if ((!row || !view()->selectionModel()->isRowSelected(row - 1, view()->rootIndex())) && !view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex())) { + view()->clearSelection(); + } + break; + } + default: { + break; + } + } + + view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); + return true; +} + +bool AccessibleGroupView::selectColumn(int column) +{ + if (!view()->model() || !view()->selectionModel()) { + return false; + } + QModelIndex index = view()->model()->index(0, column, view()->rootIndex()); + + if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectRows) { + return false; + } + + switch (view()->selectionMode()) { + case QAbstractItemView::NoSelection: { + return false; + } + case QAbstractItemView::SingleSelection: { + if (view()->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1) { + return false; + } + // fallthrough intentional + } + case QAbstractItemView::ContiguousSelection: { + if ((!column || !view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) && !view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) { + view()->clearSelection(); + } + break; + } + default: { + break; + } + } + + view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Columns); + return true; +} + +bool AccessibleGroupView::unselectRow(int row) +{ + if (!view()->model() || !view()->selectionModel()) { + return false; + } + + QModelIndex index = view()->model()->index(row, 0, view()->rootIndex()); + if (!index.isValid()) { + return false; + } + + QItemSelection selection(index, index); + auto selectionModel = view()->selectionModel(); + + switch (view()->selectionMode()) { + case QAbstractItemView::SingleSelection: + // no unselect + if (selectedRowCount() == 1) { + return false; + } + break; + case QAbstractItemView::ContiguousSelection: { + // no unselect + if (selectedRowCount() == 1) { + return false; + } + + + if ((!row || selectionModel->isRowSelected(row - 1, view()->rootIndex())) && selectionModel->isRowSelected(row + 1, view()->rootIndex())) { + //If there are rows selected both up the current row and down the current rown, + //the ones which are down the current row will be deselected + selection = QItemSelection(index, view()->model()->index(rowCount() - 1, 0, view()->rootIndex())); + } + } + default: { + break; + } + } + + selectionModel->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Rows); + return true; +} + +bool AccessibleGroupView::unselectColumn(int column) +{ + auto model = view()->model(); + if (!model || !view()->selectionModel()) { + return false; + } + + QModelIndex index = model->index(0, column, view()->rootIndex()); + if (!index.isValid()) { + return false; + } + + QItemSelection selection(index, index); + + switch (view()->selectionMode()) { + case QAbstractItemView::SingleSelection: { + //In SingleSelection and ContiguousSelection once an item + //is selected, there's no way for the user to unselect all items + if (selectedColumnCount() == 1) { + return false; + } + break; + } + case QAbstractItemView::ContiguousSelection: + if (selectedColumnCount() == 1) { + return false; + } + + if ((!column || view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) + && view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) { + //If there are columns selected both at the left of the current row and at the right + //of the current row, the ones which are at the right will be deselected + selection = QItemSelection(index, model->index(0, columnCount() - 1, view()->rootIndex())); + } + default: + break; + } + + view()->selectionModel()->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Columns); + return true; +} + +QAccessible::Role AccessibleGroupView::role() const +{ + return QAccessible::List; +} + +QAccessible::State AccessibleGroupView::state() const +{ + return QAccessible::State(); +} + +QAccessibleInterface *AccessibleGroupView::childAt(int x, int y) const +{ + QPoint viewportOffset = view()->viewport()->mapTo(view(), QPoint(0,0)); + QPoint indexPosition = view()->mapFromGlobal(QPoint(x, y) - viewportOffset); + // FIXME: if indexPosition < 0 in one coordinate, return header + + QModelIndex index = view()->indexAt(indexPosition); + if (index.isValid()) { + return child(logicalIndex(index)); + } + return 0; +} + +int AccessibleGroupView::childCount() const +{ + if (!view()->model()) { + return 0; + } + return (view()->model()->rowCount()) * (view()->model()->columnCount()); +} + +int AccessibleGroupView::indexOfChild(const QAccessibleInterface *iface) const +{ + if (!view()->model()) + return -1; + QAccessibleInterface *parent = iface->parent(); + if (parent->object() != view()) + return -1; + + Q_ASSERT(iface->role() != QAccessible::TreeItem); // should be handled by tree class + if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) { + const AccessibleGroupViewItem* cell = static_cast<const AccessibleGroupViewItem*>(iface); + return logicalIndex(cell->m_index); + } else if (iface->role() == QAccessible::Pane) { + return 0; // corner button + } else { + qWarning() << "AccessibleGroupView::indexOfChild has a child with unknown role..." << iface->role() << iface->text(QAccessible::Name); + } + // FIXME: we are in denial of our children. this should stop. + return -1; +} + +QString AccessibleGroupView::text(QAccessible::Text t) const +{ + if (t == QAccessible::Description) + return view()->accessibleDescription(); + return view()->accessibleName(); +} + +QRect AccessibleGroupView::rect() const +{ + if (!view()->isVisible()) + return QRect(); + QPoint pos = view()->mapToGlobal(QPoint(0, 0)); + return QRect(pos.x(), pos.y(), view()->width(), view()->height()); +} + +QAccessibleInterface *AccessibleGroupView::parent() const +{ + if (view() && view()->parent()) { + if (qstrcmp("QComboBoxPrivateContainer", view()->parent()->metaObject()->className()) == 0) { + return QAccessible::queryAccessibleInterface(view()->parent()->parent()); + } + return QAccessible::queryAccessibleInterface(view()->parent()); + } + return 0; +} + +QAccessibleInterface *AccessibleGroupView::child(int logicalIndex) const +{ + if (!view()->model()) + return 0; + + auto id = childToId.constFind(logicalIndex); + if (id != childToId.constEnd()) + return QAccessible::accessibleInterface(id.value()); + + int columns = view()->model()->columnCount(); + + int row = logicalIndex / columns; + int column = logicalIndex % columns; + + QAccessibleInterface *iface = 0; + + QModelIndex index = view()->model()->index(row, column, view()->rootIndex()); + if (Q_UNLIKELY(!index.isValid())) { + qWarning("AccessibleGroupView::child: Invalid index at: %d %d", row, column); + return 0; + } + iface = new AccessibleGroupViewItem(view(), index); + + QAccessible::registerAccessibleInterface(iface); + childToId.insert(logicalIndex, QAccessible::uniqueId(iface)); + return iface; +} + +void *AccessibleGroupView::interface_cast(QAccessible::InterfaceType t) +{ + if (t == QAccessible::TableInterface) + return static_cast<QAccessibleTableInterface*>(this); + return 0; +} + +void AccessibleGroupView::modelChange(QAccessibleTableModelChangeEvent *event) +{ + // if there is no cache yet, we don't update anything + if (childToId.isEmpty()) + return; + + switch (event->modelChangeType()) { + case QAccessibleTableModelChangeEvent::ModelReset: + for (QAccessible::Id id : childToId) + QAccessible::deleteAccessibleInterface(id); + childToId.clear(); + break; + + // rows are inserted: move every row after that + case QAccessibleTableModelChangeEvent::RowsInserted: + case QAccessibleTableModelChangeEvent::ColumnsInserted: { + + ChildCache newCache; + ChildCache::ConstIterator iter = childToId.constBegin(); + + while (iter != childToId.constEnd()) { + QAccessible::Id id = iter.value(); + QAccessibleInterface *iface = QAccessible::accessibleInterface(id); + Q_ASSERT(iface); + if (indexOfChild(iface) >= 0) { + newCache.insert(indexOfChild(iface), id); + } else { + // ### This should really not happen, + // but it might if the view has a root index set. + // This needs to be fixed. + QAccessible::deleteAccessibleInterface(id); + } + ++iter; + } + childToId = newCache; + break; + } + + case QAccessibleTableModelChangeEvent::ColumnsRemoved: + case QAccessibleTableModelChangeEvent::RowsRemoved: { + ChildCache newCache; + ChildCache::ConstIterator iter = childToId.constBegin(); + while (iter != childToId.constEnd()) { + QAccessible::Id id = iter.value(); + QAccessibleInterface *iface = QAccessible::accessibleInterface(id); + Q_ASSERT(iface); + if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) { + Q_ASSERT(iface->tableCellInterface()); + AccessibleGroupViewItem *cell = static_cast<AccessibleGroupViewItem*>(iface->tableCellInterface()); + // Since it is a QPersistentModelIndex, we only need to check if it is valid + if (cell->m_index.isValid()) + newCache.insert(indexOfChild(cell), id); + else + QAccessible::deleteAccessibleInterface(id); + } + ++iter; + } + childToId = newCache; + break; + } + + case QAccessibleTableModelChangeEvent::DataChanged: + // nothing to do in this case + break; + } +} + +// TABLE CELL + +AccessibleGroupViewItem::AccessibleGroupViewItem(QAbstractItemView *view_, const QModelIndex &index_) + : view(view_), m_index(index_) +{ + if (Q_UNLIKELY(!index_.isValid())) + qWarning() << "AccessibleGroupViewItem::AccessibleGroupViewItem with invalid index: " << index_; +} + +void *AccessibleGroupViewItem::interface_cast(QAccessible::InterfaceType t) +{ + if (t == QAccessible::TableCellInterface) + return static_cast<QAccessibleTableCellInterface*>(this); + if (t == QAccessible::ActionInterface) + return static_cast<QAccessibleActionInterface*>(this); + return 0; +} + +int AccessibleGroupViewItem::columnExtent() const { return 1; } +int AccessibleGroupViewItem::rowExtent() const { return 1; } + +QList<QAccessibleInterface*> AccessibleGroupViewItem::rowHeaderCells() const +{ + return {}; +} + +QList<QAccessibleInterface*> AccessibleGroupViewItem::columnHeaderCells() const +{ + return {}; +} + +int AccessibleGroupViewItem::columnIndex() const +{ + if (!isValid()) { + return -1; + } + + return m_index.column(); +} + +int AccessibleGroupViewItem::rowIndex() const +{ + if (!isValid()) { + return -1; + } + + return m_index.row(); +} + +bool AccessibleGroupViewItem::isSelected() const +{ + if (!isValid()) { + return false; + } + + return view->selectionModel()->isSelected(m_index); +} + +QStringList AccessibleGroupViewItem::actionNames() const +{ + QStringList names; + names << toggleAction(); + return names; +} + +void AccessibleGroupViewItem::doAction(const QString& actionName) +{ + if (actionName == toggleAction()) { + if (isSelected()) { + unselectCell(); + } + else { + selectCell(); + } + } +} + +QStringList AccessibleGroupViewItem::keyBindingsForAction(const QString &) const +{ + return QStringList(); +} + + +void AccessibleGroupViewItem::selectCell() +{ + if (!isValid()) { + return; + } + QAbstractItemView::SelectionMode selectionMode = view->selectionMode(); + if (selectionMode == QAbstractItemView::NoSelection) { + return; + } + + Q_ASSERT(table()); + QAccessibleTableInterface *cellTable = table()->tableInterface(); + + switch (view->selectionBehavior()) { + case QAbstractItemView::SelectItems: + break; + case QAbstractItemView::SelectColumns: + if (cellTable) + cellTable->selectColumn(m_index.column()); + return; + case QAbstractItemView::SelectRows: + if (cellTable) + cellTable->selectRow(m_index.row()); + return; + } + + if (selectionMode == QAbstractItemView::SingleSelection) { + view->clearSelection(); + } + + view->selectionModel()->select(m_index, QItemSelectionModel::Select); +} + +void AccessibleGroupViewItem::unselectCell() +{ + if (!isValid()) + return; + QAbstractItemView::SelectionMode selectionMode = view->selectionMode(); + if (selectionMode == QAbstractItemView::NoSelection) + return; + + QAccessibleTableInterface *cellTable = table()->tableInterface(); + + switch (view->selectionBehavior()) { + case QAbstractItemView::SelectItems: + break; + case QAbstractItemView::SelectColumns: + if (cellTable) + cellTable->unselectColumn(m_index.column()); + return; + case QAbstractItemView::SelectRows: + if (cellTable) + cellTable->unselectRow(m_index.row()); + return; + } + + //If the mode is not MultiSelection or ExtendedSelection and only + //one cell is selected it cannot be unselected by the user + if ((selectionMode != QAbstractItemView::MultiSelection) && (selectionMode != QAbstractItemView::ExtendedSelection) && (view->selectionModel()->selectedIndexes().count() <= 1)) + return; + + view->selectionModel()->select(m_index, QItemSelectionModel::Deselect); +} + +QAccessibleInterface *AccessibleGroupViewItem::table() const +{ + return QAccessible::queryAccessibleInterface(view); +} + +QAccessible::Role AccessibleGroupViewItem::role() const +{ + return QAccessible::ListItem; +} + +QAccessible::State AccessibleGroupViewItem::state() const +{ + QAccessible::State st; + if (!isValid()) + return st; + + QRect globalRect = view->rect(); + globalRect.translate(view->mapToGlobal(QPoint(0,0))); + if (!globalRect.intersects(rect())) + st.invisible = true; + + if (view->selectionModel()->isSelected(m_index)) + st.selected = true; + if (view->selectionModel()->currentIndex() == m_index) + st.focused = true; + if (m_index.model()->data(m_index, Qt::CheckStateRole).toInt() == Qt::Checked) + st.checked = true; + + Qt::ItemFlags flags = m_index.flags(); + if (flags & Qt::ItemIsSelectable) { + st.selectable = true; + st.focusable = true; + if (view->selectionMode() == QAbstractItemView::MultiSelection) + st.multiSelectable = true; + if (view->selectionMode() == QAbstractItemView::ExtendedSelection) + st.extSelectable = true; + } + return st; +} + + +QRect AccessibleGroupViewItem::rect() const +{ + QRect r; + if (!isValid()) + return r; + r = view->visualRect(m_index); + + if (!r.isNull()) { + r.translate(view->viewport()->mapTo(view, QPoint(0,0))); + r.translate(view->mapToGlobal(QPoint(0, 0))); + } + return r; +} + +QString AccessibleGroupViewItem::text(QAccessible::Text t) const +{ + QString value; + if (!isValid()) + return value; + QAbstractItemModel *model = view->model(); + switch (t) { + case QAccessible::Name: + value = model->data(m_index, Qt::AccessibleTextRole).toString(); + if (value.isEmpty()) + value = model->data(m_index, Qt::DisplayRole).toString(); + break; + case QAccessible::Description: + value = model->data(m_index, Qt::AccessibleDescriptionRole).toString(); + break; + default: + break; + } + return value; +} + +void AccessibleGroupViewItem::setText(QAccessible::Text /*t*/, const QString &text) +{ + if (!isValid() || !(m_index.flags() & Qt::ItemIsEditable)) + return; + view->model()->setData(m_index, text); +} + +bool AccessibleGroupViewItem::isValid() const +{ + return view && view->model() && m_index.isValid(); +} + +QAccessibleInterface *AccessibleGroupViewItem::parent() const +{ + return QAccessible::queryAccessibleInterface(view); +} + +QAccessibleInterface *AccessibleGroupViewItem::child(int) const +{ + return 0; +} diff --git a/application/groupview/AccessibleGroupView.h b/application/groupview/AccessibleGroupView.h new file mode 100644 index 00000000..9bfd1745 --- /dev/null +++ b/application/groupview/AccessibleGroupView.h @@ -0,0 +1,6 @@ +#pragma once + +#include <QString> +class QAccessibleInterface; + +QAccessibleInterface *groupViewAccessibleFactory(const QString &classname, QObject *object); diff --git a/application/groupview/AccessibleGroupView_p.h b/application/groupview/AccessibleGroupView_p.h new file mode 100644 index 00000000..cdec1c0a --- /dev/null +++ b/application/groupview/AccessibleGroupView_p.h @@ -0,0 +1,116 @@ +#pragma once + +#include "GroupView.h" +#include "QtCore/qpointer.h" +#include <QtGui/qaccessible.h> +#include <QAccessibleWidget> +#include <QAbstractItemView> +// #include <QHeaderView> + +class QAccessibleTableCell; +class QAccessibleTableHeaderCell; + +class AccessibleGroupView :public QAccessibleTableInterface, public QAccessibleObject +{ +public: + explicit AccessibleGroupView(QWidget *w); + bool isValid() const override; + + QAccessible::Role role() const override; + QAccessible::State state() const override; + QString text(QAccessible::Text t) const override; + QRect rect() const override; + + QAccessibleInterface *childAt(int x, int y) const override; + int childCount() const override; + int indexOfChild(const QAccessibleInterface *) const override; + + QAccessibleInterface *parent() const override; + QAccessibleInterface *child(int index) const override; + + void *interface_cast(QAccessible::InterfaceType t) override; + + // table interface + QAccessibleInterface *cellAt(int row, int column) const override; + QAccessibleInterface *caption() const override; + QAccessibleInterface *summary() const override; + QString columnDescription(int column) const override; + QString rowDescription(int row) const override; + int columnCount() const override; + int rowCount() const override; + + // selection + int selectedCellCount() const override; + int selectedColumnCount() const override; + int selectedRowCount() const override; + QList<QAccessibleInterface*> selectedCells() const override; + QList<int> selectedColumns() const override; + QList<int> selectedRows() const override; + bool isColumnSelected(int column) const override; + bool isRowSelected(int row) const override; + bool selectRow(int row) override; + bool selectColumn(int column) override; + bool unselectRow(int row) override; + bool unselectColumn(int column) override; + + QAbstractItemView *view() const; + + void modelChange(QAccessibleTableModelChangeEvent *event) override; + +protected: + // maybe vector + typedef QHash<int, QAccessible::Id> ChildCache; + mutable ChildCache childToId; + + virtual ~AccessibleGroupView(); + +private: + inline int logicalIndex(const QModelIndex &index) const; +}; + +class AccessibleGroupViewItem: public QAccessibleInterface, public QAccessibleTableCellInterface, public QAccessibleActionInterface +{ +public: + AccessibleGroupViewItem(QAbstractItemView *view, const QModelIndex &m_index); + + void *interface_cast(QAccessible::InterfaceType t) override; + QObject *object() const override { return nullptr; } + QAccessible::Role role() const override; + QAccessible::State state() const override; + QRect rect() const override; + bool isValid() const override; + + QAccessibleInterface *childAt(int, int) const override { return nullptr; } + int childCount() const override { return 0; } + int indexOfChild(const QAccessibleInterface *) const override { return -1; } + + QString text(QAccessible::Text t) const override; + void setText(QAccessible::Text t, const QString &text) override; + + QAccessibleInterface *parent() const override; + QAccessibleInterface *child(int) const override; + + // cell interface + int columnExtent() const override; + QList<QAccessibleInterface*> columnHeaderCells() const override; + int columnIndex() const override; + int rowExtent() const override; + QList<QAccessibleInterface*> rowHeaderCells() const override; + int rowIndex() const override; + bool isSelected() const override; + QAccessibleInterface* table() const override; + + //action interface + QStringList actionNames() const override; + void doAction(const QString &actionName) override; + QStringList keyBindingsForAction(const QString &actionName) const override; + +private: + QPointer<QAbstractItemView > view; + QPersistentModelIndex m_index; + + void selectCell(); + void unselectCell(); + + friend class AccessibleGroupView; +}; diff --git a/application/groupview/GroupView.cpp b/application/groupview/GroupView.cpp index 0d6aa49e..bc7ef6c0 100644 --- a/application/groupview/GroupView.cpp +++ b/application/groupview/GroupView.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,943 +25,984 @@ #include <QMimeData> #include <QCache> #include <QScrollBar> +#include <QAccessible> #include "VisualGroup.h" #include <QDebug> template <typename T> bool listsIntersect(const QList<T> &l1, const QList<T> t2) { - for (auto &item : l1) - { - if (t2.contains(item)) - { - return true; - } - } - return false; + for (auto &item : l1) + { + if (t2.contains(item)) + { + return true; + } + } + return false; } GroupView::GroupView(QWidget *parent) - : QAbstractItemView(parent) + : QAbstractItemView(parent) { - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - setAcceptDrops(true); - setAutoScroll(true); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setAcceptDrops(true); + setAutoScroll(true); } GroupView::~GroupView() { - qDeleteAll(m_groups); - m_groups.clear(); + qDeleteAll(m_groups); + m_groups.clear(); } void GroupView::setModel(QAbstractItemModel *model) { - QAbstractItemView::setModel(model); - connect(model, &QAbstractItemModel::modelReset, this, &GroupView::modelReset); - connect(model, &QAbstractItemModel::rowsRemoved, this, &GroupView::rowsRemoved); + QAbstractItemView::setModel(model); + connect(model, &QAbstractItemModel::modelReset, this, &GroupView::modelReset); + connect(model, &QAbstractItemModel::rowsRemoved, this, &GroupView::rowsRemoved); } void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - const QVector<int> &roles) + const QVector<int> &roles) { - scheduleDelayedItemsLayout(); + scheduleDelayedItemsLayout(); } void GroupView::rowsInserted(const QModelIndex &parent, int start, int end) { - scheduleDelayedItemsLayout(); + scheduleDelayedItemsLayout(); } void GroupView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { - scheduleDelayedItemsLayout(); + scheduleDelayedItemsLayout(); } void GroupView::modelReset() { - scheduleDelayedItemsLayout(); + scheduleDelayedItemsLayout(); } void GroupView::rowsRemoved() { - scheduleDelayedItemsLayout(); + scheduleDelayedItemsLayout(); } +void GroupView::currentChanged(const QModelIndex& current, const QModelIndex& previous) +{ + QAbstractItemView::currentChanged(current, previous); + // TODO: for accessibility support, implement+register a factory, steal QAccessibleTable from Qt and return an instance of it for GroupView. + if (QAccessible::isActive() && current.isValid()) { + QAccessibleEvent event(this, QAccessible::Focus); + event.setChild(current.row()); + QAccessible::updateAccessibility(&event); + } +} + + class LocaleString : public QString { public: - LocaleString(const char *s) : QString(s) - { - } - LocaleString(const QString &s) : QString(s) - { - } + LocaleString(const char *s) : QString(s) + { + } + LocaleString(const QString &s) : QString(s) + { + } }; inline bool operator<(const LocaleString &lhs, const LocaleString &rhs) { - return (QString::localeAwareCompare(lhs, rhs) < 0); + return (QString::localeAwareCompare(lhs, rhs) < 0); } void GroupView::updateScrollbar() { - int previousScroll = verticalScrollBar()->value(); - if (m_groups.isEmpty()) - { - verticalScrollBar()->setRange(0, 0); - } - else - { - int totalHeight = 0; - // top margin - totalHeight += m_categoryMargin; - int itemScroll = 0; - for (auto category : m_groups) - { - category->m_verticalPosition = totalHeight; - totalHeight += category->totalHeight() + m_categoryMargin; - if(!itemScroll && category->totalHeight() != 0) - { - itemScroll = category->contentHeight() / category->numRows(); - } - } - // do not divide by zero - if(itemScroll == 0) - itemScroll = 64; - - totalHeight += m_bottomMargin; - verticalScrollBar()->setSingleStep ( itemScroll ); - const int rowsPerPage = qMax ( viewport()->height() / itemScroll, 1 ); - verticalScrollBar()->setPageStep ( rowsPerPage * itemScroll ); - - verticalScrollBar()->setRange(0, totalHeight - height()); - } - - verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); + int previousScroll = verticalScrollBar()->value(); + if (m_groups.isEmpty()) + { + verticalScrollBar()->setRange(0, 0); + } + else + { + int totalHeight = 0; + // top margin + totalHeight += m_categoryMargin; + int itemScroll = 0; + for (auto category : m_groups) + { + category->m_verticalPosition = totalHeight; + totalHeight += category->totalHeight() + m_categoryMargin; + if(!itemScroll && category->totalHeight() != 0) + { + itemScroll = category->contentHeight() / category->numRows(); + } + } + // do not divide by zero + if(itemScroll == 0) + itemScroll = 64; + + totalHeight += m_bottomMargin; + verticalScrollBar()->setSingleStep ( itemScroll ); + const int rowsPerPage = qMax ( viewport()->height() / itemScroll, 1 ); + verticalScrollBar()->setPageStep ( rowsPerPage * itemScroll ); + + verticalScrollBar()->setRange(0, totalHeight - height()); + } + + verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); } void GroupView::updateGeometries() { - geometryCache.clear(); - - QMap<LocaleString, VisualGroup *> cats; - - for (int i = 0; i < model()->rowCount(); ++i) - { - const QString groupName = model()->index(i, 0).data(GroupViewRoles::GroupRole).toString(); - if (!cats.contains(groupName)) - { - VisualGroup *old = this->category(groupName); - if (old) - { - auto cat = new VisualGroup(old); - cats.insert(groupName, cat); - cat->update(); - } - else - { - auto cat = new VisualGroup(groupName, this); - cats.insert(groupName, cat); - cat->update(); - } - } - } - - qDeleteAll(m_groups); - m_groups = cats.values(); - updateScrollbar(); - viewport()->update(); + geometryCache.clear(); + + QMap<LocaleString, VisualGroup *> cats; + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QString groupName = model()->index(i, 0).data(GroupViewRoles::GroupRole).toString(); + if (!cats.contains(groupName)) + { + VisualGroup *old = this->category(groupName); + if (old) + { + auto cat = new VisualGroup(old); + cats.insert(groupName, cat); + cat->update(); + } + else + { + auto cat = new VisualGroup(groupName, this); + cats.insert(groupName, cat); + cat->update(); + } + } + } + + qDeleteAll(m_groups); + m_groups = cats.values(); + updateScrollbar(); + viewport()->update(); } bool GroupView::isIndexHidden(const QModelIndex &index) const { - VisualGroup *cat = category(index); - if (cat) - { - return cat->collapsed; - } - else - { - return false; - } + VisualGroup *cat = category(index); + if (cat) + { + return cat->collapsed; + } + else + { + return false; + } } VisualGroup *GroupView::category(const QModelIndex &index) const { - return category(index.data(GroupViewRoles::GroupRole).toString()); + return category(index.data(GroupViewRoles::GroupRole).toString()); } VisualGroup *GroupView::category(const QString &cat) const { - for (auto group : m_groups) - { - if (group->text == cat) - { - return group; - } - } - return nullptr; + for (auto group : m_groups) + { + if (group->text == cat) + { + return group; + } + } + return nullptr; } VisualGroup *GroupView::categoryAt(const QPoint &pos, VisualGroup::HitResults & result) const { - for (auto group : m_groups) - { - result = group->hitScan(pos); - if(result != VisualGroup::NoHit) - { - return group; - } - } - result = VisualGroup::NoHit; - return nullptr; + for (auto group : m_groups) + { + result = group->hitScan(pos); + if(result != VisualGroup::NoHit) + { + return group; + } + } + result = VisualGroup::NoHit; + return nullptr; } QString GroupView::groupNameAt(const QPoint &point) { - VisualGroup::HitResults hitresult; - auto group = categoryAt(point + offset(), hitresult); - if(group && (hitresult & (VisualGroup::HeaderHit | VisualGroup::BodyHit))) - { - return group->text; - } - return QString(); + executeDelayedItemsLayout(); + + VisualGroup::HitResults hitresult; + auto group = categoryAt(point + offset(), hitresult); + if(group && (hitresult & (VisualGroup::HeaderHit | VisualGroup::BodyHit))) + { + return group->text; + } + return QString(); } int GroupView::calculateItemsPerRow() const { - return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing)); + return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing)); } int GroupView::contentWidth() const { - return width() - m_leftMargin - m_rightMargin; + return width() - m_leftMargin - m_rightMargin; } int GroupView::itemWidth() const { - return m_itemWidth; + return m_itemWidth; } void GroupView::mousePressEvent(QMouseEvent *event) { - // endCategoryEditor(); - - QPoint visualPos = event->pos(); - QPoint geometryPos = event->pos() + offset(); - - QPersistentModelIndex index = indexAt(visualPos); - - m_pressedIndex = index; - m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); - m_pressedPosition = geometryPos; - - VisualGroup::HitResults hitresult; - m_pressedCategory = categoryAt(geometryPos, hitresult); - if (m_pressedCategory && hitresult & VisualGroup::CheckboxHit) - { - setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState); - event->accept(); - return; - } - - if (index.isValid() && (index.flags() & Qt::ItemIsEnabled)) - { - if(index != currentIndex()) - { - // FIXME: better! - m_currentCursorColumn = -1; - } - // we disable scrollTo for mouse press so the item doesn't change position - // when the user is interacting with it (ie. clicking on it) - bool autoScroll = hasAutoScroll(); - setAutoScroll(false); - selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); - - setAutoScroll(autoScroll); - QRect rect(visualPos, visualPos); - setSelection(rect, QItemSelectionModel::ClearAndSelect); - - // signal handlers may change the model - emit pressed(index); - } - else - { - // Forces a finalize() even if mouse is pressed, but not on a item - selectionModel()->select(QModelIndex(), QItemSelectionModel::Select); - } + executeDelayedItemsLayout(); + + QPoint visualPos = event->pos(); + QPoint geometryPos = event->pos() + offset(); + + QPersistentModelIndex index = indexAt(visualPos); + + m_pressedIndex = index; + m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); + m_pressedPosition = geometryPos; + + VisualGroup::HitResults hitresult; + m_pressedCategory = categoryAt(geometryPos, hitresult); + if (m_pressedCategory && hitresult & VisualGroup::CheckboxHit) + { + setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState); + event->accept(); + return; + } + + if (index.isValid() && (index.flags() & Qt::ItemIsEnabled)) + { + if(index != currentIndex()) + { + // FIXME: better! + m_currentCursorColumn = -1; + } + // we disable scrollTo for mouse press so the item doesn't change position + // when the user is interacting with it (ie. clicking on it) + bool autoScroll = hasAutoScroll(); + setAutoScroll(false); + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + + setAutoScroll(autoScroll); + QRect rect(visualPos, visualPos); + setSelection(rect, QItemSelectionModel::ClearAndSelect); + + // signal handlers may change the model + emit pressed(index); + } + else + { + // Forces a finalize() even if mouse is pressed, but not on a item + selectionModel()->select(QModelIndex(), QItemSelectionModel::Select); + } } void GroupView::mouseMoveEvent(QMouseEvent *event) { - QPoint topLeft; - QPoint visualPos = event->pos(); - QPoint geometryPos = event->pos() + offset(); - - if (state() == ExpandingState || state() == CollapsingState) - { - return; - } - - if (state() == DraggingState) - { - topLeft = m_pressedPosition - offset(); - if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) - { - m_pressedIndex = QModelIndex(); - startDrag(model()->supportedDragActions()); - setState(NoState); - stopAutoScroll(); - } - return; - } - - if (selectionMode() != SingleSelection) - { - topLeft = m_pressedPosition - offset(); - } - else - { - topLeft = geometryPos; - } - - if (m_pressedIndex.isValid() && (state() != DragSelectingState) && - (event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty()) - { - setState(DraggingState); - return; - } - - if ((event->buttons() & Qt::LeftButton) && selectionModel()) - { - setState(DragSelectingState); - - setSelection(QRect(visualPos, visualPos), QItemSelectionModel::ClearAndSelect); - QModelIndex index = indexAt(visualPos); - - // set at the end because it might scroll the view - if (index.isValid() && (index != selectionModel()->currentIndex()) && - (index.flags() & Qt::ItemIsEnabled)) - { - selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); - } - } + executeDelayedItemsLayout(); + + QPoint topLeft; + QPoint visualPos = event->pos(); + QPoint geometryPos = event->pos() + offset(); + + if (state() == ExpandingState || state() == CollapsingState) + { + return; + } + + if (state() == DraggingState) + { + topLeft = m_pressedPosition - offset(); + if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) + { + m_pressedIndex = QModelIndex(); + startDrag(model()->supportedDragActions()); + setState(NoState); + stopAutoScroll(); + } + return; + } + + if (selectionMode() != SingleSelection) + { + topLeft = m_pressedPosition - offset(); + } + else + { + topLeft = geometryPos; + } + + if (m_pressedIndex.isValid() && (state() != DragSelectingState) && + (event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty()) + { + setState(DraggingState); + return; + } + + if ((event->buttons() & Qt::LeftButton) && selectionModel()) + { + setState(DragSelectingState); + + setSelection(QRect(visualPos, visualPos), QItemSelectionModel::ClearAndSelect); + QModelIndex index = indexAt(visualPos); + + // set at the end because it might scroll the view + if (index.isValid() && (index != selectionModel()->currentIndex()) && + (index.flags() & Qt::ItemIsEnabled)) + { + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + } + } } void GroupView::mouseReleaseEvent(QMouseEvent *event) { - QPoint visualPos = event->pos(); - QPoint geometryPos = event->pos() + offset(); - QPersistentModelIndex index = indexAt(visualPos); - - VisualGroup::HitResults hitresult; - - bool click = (index == m_pressedIndex && index.isValid()) || - (m_pressedCategory && m_pressedCategory == categoryAt(geometryPos, hitresult)); - - if (click && m_pressedCategory) - { - if (state() == ExpandingState) - { - m_pressedCategory->collapsed = false; - updateGeometries(); - viewport()->update(); - event->accept(); - return; - } - else if (state() == CollapsingState) - { - m_pressedCategory->collapsed = true; - updateGeometries(); - viewport()->update(); - event->accept(); - return; - } - } - - m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate; - - setState(NoState); - - if (click) - { - if (event->button() == Qt::LeftButton) - { - emit clicked(index); - } - QStyleOptionViewItem option = viewOptions(); - if (m_pressedAlreadySelected) - { - option.state |= QStyle::State_Selected; - } - if ((model()->flags(index) & Qt::ItemIsEnabled) && - style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) - { - emit activated(index); - } - } + executeDelayedItemsLayout(); + + QPoint visualPos = event->pos(); + QPoint geometryPos = event->pos() + offset(); + QPersistentModelIndex index = indexAt(visualPos); + + VisualGroup::HitResults hitresult; + + bool click = (index == m_pressedIndex && index.isValid()) || + (m_pressedCategory && m_pressedCategory == categoryAt(geometryPos, hitresult)); + + if (click && m_pressedCategory) + { + if (state() == ExpandingState) + { + m_pressedCategory->collapsed = false; + updateGeometries(); + viewport()->update(); + event->accept(); + return; + } + else if (state() == CollapsingState) + { + m_pressedCategory->collapsed = true; + updateGeometries(); + viewport()->update(); + event->accept(); + return; + } + } + + m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate; + + setState(NoState); + + if (click) + { + if (event->button() == Qt::LeftButton) + { + emit clicked(index); + } + QStyleOptionViewItem option = viewOptions(); + if (m_pressedAlreadySelected) + { + option.state |= QStyle::State_Selected; + } + if ((model()->flags(index) & Qt::ItemIsEnabled) && + style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) + { + emit activated(index); + } + } } void GroupView::mouseDoubleClickEvent(QMouseEvent *event) { - QModelIndex index = indexAt(event->pos()); - if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index)) - { - QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(), - event->screenPos(), event->button(), event->buttons(), - event->modifiers()); - mousePressEvent(&me); - return; - } - // signal handlers may change the model - QPersistentModelIndex persistent = index; - emit doubleClicked(persistent); + executeDelayedItemsLayout(); + + QModelIndex index = indexAt(event->pos()); + if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index)) + { + QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(), + event->screenPos(), event->button(), event->buttons(), + event->modifiers()); + mousePressEvent(&me); + return; + } + // signal handlers may change the model + QPersistentModelIndex persistent = index; + emit doubleClicked(persistent); + + QStyleOptionViewItem option = viewOptions(); + if ((model()->flags(index) & Qt::ItemIsEnabled) && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) + { + emit activated(index); + } } void GroupView::paintEvent(QPaintEvent *event) { - executeDelayedItemsLayout(); - - QPainter painter(this->viewport()); - - QStyleOptionViewItem option(viewOptions()); - option.widget = this; - - int wpWidth = viewport()->width(); - option.rect.setWidth(wpWidth); - for (int i = 0; i < m_groups.size(); ++i) - { - VisualGroup *category = m_groups.at(i); - int y = category->verticalPosition(); - y -= verticalOffset(); - QRect backup = option.rect; - int height = category->totalHeight(); - option.rect.setTop(y); - option.rect.setHeight(height); - option.rect.setLeft(m_leftMargin); - option.rect.setRight(wpWidth - m_rightMargin); - category->drawHeader(&painter, option); - y += category->totalHeight() + m_categoryMargin; - option.rect = backup; - } - - for (int i = 0; i < model()->rowCount(); ++i) - { - const QModelIndex index = model()->index(i, 0); - if (isIndexHidden(index)) - { - continue; - } - Qt::ItemFlags flags = index.flags(); - option.rect = visualRect(index); - option.features |= QStyleOptionViewItem::WrapText; - if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) - { - option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected - : QStyle::State_None; - } - else - { - option.state &= ~QStyle::State_Selected; - } - option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None; - if (!(flags & Qt::ItemIsEnabled)) - { - option.state &= ~QStyle::State_Enabled; - } - itemDelegate()->paint(&painter, option, index); - } - - /* - * Drop indicators for manual reordering... - */ + executeDelayedItemsLayout(); + + QPainter painter(this->viewport()); + + QStyleOptionViewItem option(viewOptions()); + option.widget = this; + + int wpWidth = viewport()->width(); + option.rect.setWidth(wpWidth); + for (int i = 0; i < m_groups.size(); ++i) + { + VisualGroup *category = m_groups.at(i); + int y = category->verticalPosition(); + y -= verticalOffset(); + QRect backup = option.rect; + int height = category->totalHeight(); + option.rect.setTop(y); + option.rect.setHeight(height); + option.rect.setLeft(m_leftMargin); + option.rect.setRight(wpWidth - m_rightMargin); + category->drawHeader(&painter, option); + y += category->totalHeight() + m_categoryMargin; + option.rect = backup; + } + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QModelIndex index = model()->index(i, 0); + if (isIndexHidden(index)) + { + continue; + } + Qt::ItemFlags flags = index.flags(); + option.rect = visualRect(index); + option.features |= QStyleOptionViewItem::WrapText; + if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) + { + option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected + : QStyle::State_None; + } + else + { + option.state &= ~QStyle::State_Selected; + } + option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None; + if (!(flags & Qt::ItemIsEnabled)) + { + option.state &= ~QStyle::State_Enabled; + } + itemDelegate()->paint(&painter, option, index); + } + + /* + * Drop indicators for manual reordering... + */ #if 0 - if (!m_lastDragPosition.isNull()) - { - QPair<Group *, int> pair = rowDropPos(m_lastDragPosition); - Group *category = pair.first; - int row = pair.second; - if (category) - { - int internalRow = row - category->firstItemIndex; - QLine line; - if (internalRow >= category->numItems()) - { - QRect toTheRightOfRect = visualRect(category->lastItem()); - line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight()); - } - else - { - QRect toTheLeftOfRect = visualRect(model()->index(row, 0)); - line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft()); - } - painter.save(); - painter.setPen(QPen(Qt::black, 3)); - painter.drawLine(line); - painter.restore(); - } - } + if (!m_lastDragPosition.isNull()) + { + QPair<Group *, int> pair = rowDropPos(m_lastDragPosition); + Group *category = pair.first; + int row = pair.second; + if (category) + { + int internalRow = row - category->firstItemIndex; + QLine line; + if (internalRow >= category->numItems()) + { + QRect toTheRightOfRect = visualRect(category->lastItem()); + line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight()); + } + else + { + QRect toTheLeftOfRect = visualRect(model()->index(row, 0)); + line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft()); + } + painter.save(); + painter.setPen(QPen(Qt::black, 3)); + painter.drawLine(line); + painter.restore(); + } + } #endif } void GroupView::resizeEvent(QResizeEvent *event) { - int newItemsPerRow = calculateItemsPerRow(); - if(newItemsPerRow != m_currentItemsPerRow) - { - m_currentCursorColumn = -1; - m_currentItemsPerRow = newItemsPerRow; - updateGeometries(); - } - else - { - updateScrollbar(); - } + int newItemsPerRow = calculateItemsPerRow(); + if(newItemsPerRow != m_currentItemsPerRow) + { + m_currentCursorColumn = -1; + m_currentItemsPerRow = newItemsPerRow; + updateGeometries(); + } + else + { + updateScrollbar(); + } } void GroupView::dragEnterEvent(QDragEnterEvent *event) { - if (!isDragEventAccepted(event)) - { - return; - } - m_lastDragPosition = event->pos() + offset(); - viewport()->update(); - event->accept(); + executeDelayedItemsLayout(); + + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos() + offset(); + viewport()->update(); + event->accept(); } void GroupView::dragMoveEvent(QDragMoveEvent *event) { - if (!isDragEventAccepted(event)) - { - return; - } - m_lastDragPosition = event->pos() + offset(); - viewport()->update(); - event->accept(); + executeDelayedItemsLayout(); + + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos() + offset(); + viewport()->update(); + event->accept(); } void GroupView::dragLeaveEvent(QDragLeaveEvent *event) { - m_lastDragPosition = QPoint(); - viewport()->update(); + executeDelayedItemsLayout(); + + m_lastDragPosition = QPoint(); + viewport()->update(); } void GroupView::dropEvent(QDropEvent *event) { - m_lastDragPosition = QPoint(); - - stopAutoScroll(); - setState(NoState); - - if (event->source() == this) - { - if(event->possibleActions() & Qt::MoveAction) - { - QPair<VisualGroup *, int> dropPos = rowDropPos(event->pos() + offset()); - const VisualGroup *category = dropPos.first; - const int row = dropPos.second; - - if (row == -1) - { - viewport()->update(); - return; - } - - const QString categoryText = category->text; - if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) - { - model()->setData(model()->index(row, 0), categoryText, - GroupViewRoles::GroupRole); - event->setDropAction(Qt::MoveAction); - event->accept(); - } - updateGeometries(); - viewport()->update(); - } - } - auto mimedata = event->mimeData(); - - // check if the action is supported - if (!mimedata) - { - return; - } - - // files dropped from outside? - if (mimedata->hasUrls()) - { - auto urls = mimedata->urls(); - event->accept(); - emit droppedURLs(urls); - } + executeDelayedItemsLayout(); + + m_lastDragPosition = QPoint(); + + stopAutoScroll(); + setState(NoState); + + if (event->source() == this) + { + if(event->possibleActions() & Qt::MoveAction) + { + QPair<VisualGroup *, int> dropPos = rowDropPos(event->pos() + offset()); + const VisualGroup *category = dropPos.first; + const int row = dropPos.second; + + if (row == -1) + { + viewport()->update(); + return; + } + + const QString categoryText = category->text; + if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) + { + model()->setData(model()->index(row, 0), categoryText, + GroupViewRoles::GroupRole); + event->setDropAction(Qt::MoveAction); + event->accept(); + } + updateGeometries(); + viewport()->update(); + } + } + auto mimedata = event->mimeData(); + + // check if the action is supported + if (!mimedata) + { + return; + } + + // files dropped from outside? + if (mimedata->hasUrls()) + { + auto urls = mimedata->urls(); + event->accept(); + emit droppedURLs(urls); + } } void GroupView::startDrag(Qt::DropActions supportedActions) { - QModelIndexList indexes = selectionModel()->selectedIndexes(); - if(indexes.count() == 0) - return; - - QMimeData *data = model()->mimeData(indexes); - if (!data) - { - return; - } - QRect rect; - QPixmap pixmap = renderToPixmap(indexes, &rect); - //rect.translate(offset()); - // rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); - QDrag *drag = new QDrag(this); - drag->setPixmap(pixmap); - drag->setMimeData(data); - Qt::DropAction defaultDropAction = Qt::IgnoreAction; - if (this->defaultDropAction() != Qt::IgnoreAction && - (supportedActions & this->defaultDropAction())) - { - defaultDropAction = this->defaultDropAction(); - } - if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) - { - const QItemSelection selection = selectionModel()->selection(); - - for (auto it = selection.constBegin(); it != selection.constEnd(); ++it) - { - QModelIndex parent = (*it).parent(); - if ((*it).left() != 0) - { - continue; - } - if ((*it).right() != (model()->columnCount(parent) - 1)) - { - continue; - } - int count = (*it).bottom() - (*it).top() + 1; - model()->removeRows((*it).top(), count, parent); - } - } + executeDelayedItemsLayout(); + + QModelIndexList indexes = selectionModel()->selectedIndexes(); + if(indexes.count() == 0) + return; + + QMimeData *data = model()->mimeData(indexes); + if (!data) + { + return; + } + QRect rect; + QPixmap pixmap = renderToPixmap(indexes, &rect); + //rect.translate(offset()); + // rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); + QDrag *drag = new QDrag(this); + drag->setPixmap(pixmap); + drag->setMimeData(data); + Qt::DropAction defaultDropAction = Qt::IgnoreAction; + if (this->defaultDropAction() != Qt::IgnoreAction && + (supportedActions & this->defaultDropAction())) + { + defaultDropAction = this->defaultDropAction(); + } + if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) + { + const QItemSelection selection = selectionModel()->selection(); + + for (auto it = selection.constBegin(); it != selection.constEnd(); ++it) + { + QModelIndex parent = (*it).parent(); + if ((*it).left() != 0) + { + continue; + } + if ((*it).right() != (model()->columnCount(parent) - 1)) + { + continue; + } + int count = (*it).bottom() - (*it).top() + 1; + model()->removeRows((*it).top(), count, parent); + } + } } QRect GroupView::visualRect(const QModelIndex &index) const { - return geometryRect(index).translated(-offset()); + const_cast<GroupView*>(this)->executeDelayedItemsLayout(); + + return geometryRect(index).translated(-offset()); } QRect GroupView::geometryRect(const QModelIndex &index) const { - if (!index.isValid() || isIndexHidden(index) || index.column() > 0) - { - return QRect(); - } + const_cast<GroupView*>(this)->executeDelayedItemsLayout(); + + if (!index.isValid() || isIndexHidden(index) || index.column() > 0) + { + return QRect(); + } - int row = index.row(); - if(geometryCache.contains(row)) - { - return *geometryCache[row]; - } + int row = index.row(); + if(geometryCache.contains(row)) + { + return *geometryCache[row]; + } - const VisualGroup *cat = category(index); - QPair<int, int> pos = cat->positionOf(index); - int x = pos.first; - // int y = pos.second; + const VisualGroup *cat = category(index); + QPair<int, int> pos = cat->positionOf(index); + int x = pos.first; + // int y = pos.second; - QRect out; - out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + cat->rowTopOf(index)); - out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); - out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); - geometryCache.insert(row, new QRect(out)); - return out; + QRect out; + out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + cat->rowTopOf(index)); + out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); + out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); + geometryCache.insert(row, new QRect(out)); + return out; } QModelIndex GroupView::indexAt(const QPoint &point) const { - const_cast<GroupView*>(this)->executeDelayedItemsLayout(); + const_cast<GroupView*>(this)->executeDelayedItemsLayout(); - for (int i = 0; i < model()->rowCount(); ++i) - { - QModelIndex index = model()->index(i, 0); - if (visualRect(index).contains(point)) - { - return index; - } - } - return QModelIndex(); + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).contains(point)) + { + return index; + } + } + return QModelIndex(); } -void GroupView::setSelection(const QRect &rect, - const QItemSelectionModel::SelectionFlags commands) +void GroupView::setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) { - for (int i = 0; i < model()->rowCount(); ++i) - { - QModelIndex index = model()->index(i, 0); - QRect itemRect = visualRect(index); - if (itemRect.intersects(rect)) - { - selectionModel()->select(index, commands); - update(itemRect.translated(-offset())); - } - } + executeDelayedItemsLayout(); + + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + QRect itemRect = visualRect(index); + if (itemRect.intersects(rect)) + { + selectionModel()->select(index, commands); + update(itemRect.translated(-offset())); + } + } } QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) const { - Q_ASSERT(r); - auto paintPairs = draggablePaintPairs(indices, r); - if (paintPairs.isEmpty()) - { - return QPixmap(); - } - QPixmap pixmap(r->size()); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - QStyleOptionViewItem option = viewOptions(); - option.state |= QStyle::State_Selected; - for (int j = 0; j < paintPairs.count(); ++j) - { - option.rect = paintPairs.at(j).first.translated(-r->topLeft()); - const QModelIndex ¤t = paintPairs.at(j).second; - itemDelegate()->paint(&painter, option, current); - } - return pixmap; -} - -QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices, - QRect *r) const -{ - Q_ASSERT(r); - QRect &rect = *r; - QList<QPair<QRect, QModelIndex>> ret; - for (int i = 0; i < indices.count(); ++i) - { - const QModelIndex &index = indices.at(i); - const QRect current = geometryRect(index); - ret += qMakePair(current, index); - rect |= current; - } - return ret; + Q_ASSERT(r); + auto paintPairs = draggablePaintPairs(indices, r); + if (paintPairs.isEmpty()) + { + return QPixmap(); + } + QPixmap pixmap(r->size()); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + QStyleOptionViewItem option = viewOptions(); + option.state |= QStyle::State_Selected; + for (int j = 0; j < paintPairs.count(); ++j) + { + option.rect = paintPairs.at(j).first.translated(-r->topLeft()); + const QModelIndex ¤t = paintPairs.at(j).second; + itemDelegate()->paint(&painter, option, current); + } + return pixmap; +} + +QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const +{ + Q_ASSERT(r); + QRect &rect = *r; + QList<QPair<QRect, QModelIndex>> ret; + for (int i = 0; i < indices.count(); ++i) + { + const QModelIndex &index = indices.at(i); + const QRect current = geometryRect(index); + ret += qMakePair(current, index); + rect |= current; + } + return ret; } bool GroupView::isDragEventAccepted(QDropEvent *event) { - return true; + return true; } QPair<VisualGroup *, int> GroupView::rowDropPos(const QPoint &pos) { - return qMakePair<VisualGroup*, int>(nullptr, -1); + return qMakePair<VisualGroup*, int>(nullptr, -1); } QPoint GroupView::offset() const { - return QPoint(horizontalOffset(), verticalOffset()); + return QPoint(horizontalOffset(), verticalOffset()); } QRegion GroupView::visualRegionForSelection(const QItemSelection &selection) const { - QRegion region; - for (auto &range : selection) - { - int start_row = range.top(); - int end_row = range.bottom(); - for (int row = start_row; row <= end_row; ++row) - { - int start_column = range.left(); - int end_column = range.right(); - for (int column = start_column; column <= end_column; ++column) - { - QModelIndex index = model()->index(row, column, rootIndex()); - region += visualRect(index); // OK - } - } - } - return region; + QRegion region; + for (auto &range : selection) + { + int start_row = range.top(); + int end_row = range.bottom(); + for (int row = start_row; row <= end_row; ++row) + { + int start_column = range.left(); + int end_column = range.right(); + for (int column = start_column; column <= end_column; ++column) + { + QModelIndex index = model()->index(row, column, rootIndex()); + region += visualRect(index); // OK + } + } + } + return region; } QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction, - Qt::KeyboardModifiers modifiers) -{ - auto current = currentIndex(); - if(!current.isValid()) - { - return current; - } - auto cat = category(current); - int group_index = m_groups.indexOf(cat); - if(group_index < 0) - return current; - - auto real_group = m_groups[group_index]; - int beginning_row = 0; - for(auto group: m_groups) - { - if(group == real_group) - break; - beginning_row += group->numRows(); - } - - QPair<int, int> pos = cat->positionOf(current); - int column = pos.first; - int row = pos.second; - if(m_currentCursorColumn < 0) - { - m_currentCursorColumn = column; - } - switch(cursorAction) - { - case MoveUp: - { - if(row == 0) - { - int prevgroupindex = group_index-1; - while(prevgroupindex >= 0) - { - auto prevgroup = m_groups[prevgroupindex]; - if(prevgroup->collapsed) - { - prevgroupindex--; - continue; - } - int newRow = prevgroup->numRows() - 1; - int newRowSize = prevgroup->rows[newRow].size(); - int newColumn = m_currentCursorColumn; - if (m_currentCursorColumn >= newRowSize) - { - newColumn = newRowSize - 1; - } - return prevgroup->rows[newRow][newColumn]; - } - } - else - { - int newRow = row - 1; - int newRowSize = cat->rows[newRow].size(); - int newColumn = m_currentCursorColumn; - if (m_currentCursorColumn >= newRowSize) - { - newColumn = newRowSize - 1; - } - return cat->rows[newRow][newColumn]; - } - return current; - } - case MoveDown: - { - if(row == cat->rows.size() - 1) - { - int nextgroupindex = group_index+1; - while (nextgroupindex < m_groups.size()) - { - auto nextgroup = m_groups[nextgroupindex]; - if(nextgroup->collapsed) - { - nextgroupindex++; - continue; - } - int newRowSize = nextgroup->rows[0].size(); - int newColumn = m_currentCursorColumn; - if (m_currentCursorColumn >= newRowSize) - { - newColumn = newRowSize - 1; - } - return nextgroup->rows[0][newColumn]; - } - } - else - { - int newRow = row + 1; - int newRowSize = cat->rows[newRow].size(); - int newColumn = m_currentCursorColumn; - if (m_currentCursorColumn >= newRowSize) - { - newColumn = newRowSize - 1; - } - return cat->rows[newRow][newColumn]; - } - return current; - } - case MoveLeft: - { - if(column > 0) - { - m_currentCursorColumn = column - 1; - return cat->rows[row][column - 1]; - } - // TODO: moving to previous line - return current; - } - case MoveRight: - { - if(column < cat->rows[row].size() - 1) - { - m_currentCursorColumn = column + 1; - return cat->rows[row][column + 1]; - } - // TODO: moving to next line - return current; - } - case MoveHome: - { - m_currentCursorColumn = 0; - return cat->rows[row][0]; - } - case MoveEnd: - { - auto last = cat->rows[row].size() - 1; - m_currentCursorColumn = last; - return cat->rows[row][last]; - } - default: - break; - } - return current; + Qt::KeyboardModifiers modifiers) +{ + auto current = currentIndex(); + if(!current.isValid()) + { + return current; + } + auto cat = category(current); + int group_index = m_groups.indexOf(cat); + if(group_index < 0) + return current; + + auto real_group = m_groups[group_index]; + int beginning_row = 0; + for(auto group: m_groups) + { + if(group == real_group) + break; + beginning_row += group->numRows(); + } + + QPair<int, int> pos = cat->positionOf(current); + int column = pos.first; + int row = pos.second; + if(m_currentCursorColumn < 0) + { + m_currentCursorColumn = column; + } + switch(cursorAction) + { + case MoveUp: + { + if(row == 0) + { + int prevgroupindex = group_index-1; + while(prevgroupindex >= 0) + { + auto prevgroup = m_groups[prevgroupindex]; + if(prevgroup->collapsed) + { + prevgroupindex--; + continue; + } + int newRow = prevgroup->numRows() - 1; + int newRowSize = prevgroup->rows[newRow].size(); + int newColumn = m_currentCursorColumn; + if (m_currentCursorColumn >= newRowSize) + { + newColumn = newRowSize - 1; + } + return prevgroup->rows[newRow][newColumn]; + } + } + else + { + int newRow = row - 1; + int newRowSize = cat->rows[newRow].size(); + int newColumn = m_currentCursorColumn; + if (m_currentCursorColumn >= newRowSize) + { + newColumn = newRowSize - 1; + } + return cat->rows[newRow][newColumn]; + } + return current; + } + case MoveDown: + { + if(row == cat->rows.size() - 1) + { + int nextgroupindex = group_index+1; + while (nextgroupindex < m_groups.size()) + { + auto nextgroup = m_groups[nextgroupindex]; + if(nextgroup->collapsed) + { + nextgroupindex++; + continue; + } + int newRowSize = nextgroup->rows[0].size(); + int newColumn = m_currentCursorColumn; + if (m_currentCursorColumn >= newRowSize) + { + newColumn = newRowSize - 1; + } + return nextgroup->rows[0][newColumn]; + } + } + else + { + int newRow = row + 1; + int newRowSize = cat->rows[newRow].size(); + int newColumn = m_currentCursorColumn; + if (m_currentCursorColumn >= newRowSize) + { + newColumn = newRowSize - 1; + } + return cat->rows[newRow][newColumn]; + } + return current; + } + case MoveLeft: + { + if(column > 0) + { + m_currentCursorColumn = column - 1; + return cat->rows[row][column - 1]; + } + // TODO: moving to previous line + return current; + } + case MoveRight: + { + if(column < cat->rows[row].size() - 1) + { + m_currentCursorColumn = column + 1; + return cat->rows[row][column + 1]; + } + // TODO: moving to next line + return current; + } + case MoveHome: + { + m_currentCursorColumn = 0; + return cat->rows[row][0]; + } + case MoveEnd: + { + auto last = cat->rows[row].size() - 1; + m_currentCursorColumn = last; + return cat->rows[row][last]; + } + default: + break; + } + return current; } int GroupView::horizontalOffset() const { - return horizontalScrollBar()->value(); + return horizontalScrollBar()->value(); } int GroupView::verticalOffset() const { - return verticalScrollBar()->value(); + return verticalScrollBar()->value(); } void GroupView::scrollContentsBy(int dx, int dy) { - scrollDirtyRegion(dx, dy); - viewport()->scroll(dx, dy); + scrollDirtyRegion(dx, dy); + viewport()->scroll(dx, dy); } void GroupView::scrollTo(const QModelIndex &index, ScrollHint hint) { - if (!index.isValid()) - return; + if (!index.isValid()) + return; - const QRect rect = visualRect(index); - if (hint == EnsureVisible && viewport()->rect().contains(rect)) - { - viewport()->update(rect); - return; - } + const QRect rect = visualRect(index); + if (hint == EnsureVisible && viewport()->rect().contains(rect)) + { + viewport()->update(rect); + return; + } - verticalScrollBar()->setValue(verticalScrollToValue(index, rect, hint)); + verticalScrollBar()->setValue(verticalScrollToValue(index, rect, hint)); } int GroupView::verticalScrollToValue(const QModelIndex &index, const QRect &rect, QListView::ScrollHint hint) const { - const QRect area = viewport()->rect(); - const bool above = (hint == QListView::EnsureVisible && rect.top() < area.top()); - const bool below = (hint == QListView::EnsureVisible && rect.bottom() > area.bottom()); - - int verticalValue = verticalScrollBar()->value(); - QRect adjusted = rect.adjusted(-spacing(), -spacing(), spacing(), spacing()); - if (hint == QListView::PositionAtTop || above) - verticalValue += adjusted.top(); - else if (hint == QListView::PositionAtBottom || below) - verticalValue += qMin(adjusted.top(), adjusted.bottom() - area.height() + 1); - else if (hint == QListView::PositionAtCenter) - verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2); - return verticalValue; + const QRect area = viewport()->rect(); + const bool above = (hint == QListView::EnsureVisible && rect.top() < area.top()); + const bool below = (hint == QListView::EnsureVisible && rect.bottom() > area.bottom()); + + int verticalValue = verticalScrollBar()->value(); + QRect adjusted = rect.adjusted(-spacing(), -spacing(), spacing(), spacing()); + if (hint == QListView::PositionAtTop || above) + verticalValue += adjusted.top(); + else if (hint == QListView::PositionAtBottom || below) + verticalValue += qMin(adjusted.top(), adjusted.bottom() - area.height() + 1); + else if (hint == QListView::PositionAtCenter) + verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2); + return verticalValue; } diff --git a/application/groupview/GroupView.h b/application/groupview/GroupView.h index 07c65bb5..db29a0d4 100644 --- a/application/groupview/GroupView.h +++ b/application/groupview/GroupView.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,126 +23,127 @@ struct GroupViewRoles { - enum - { - GroupRole = Qt::UserRole, - ProgressValueRole, - ProgressMaximumRole - }; + enum + { + GroupRole = Qt::UserRole, + ProgressValueRole, + ProgressMaximumRole + }; }; class GroupView : public QAbstractItemView { - Q_OBJECT + Q_OBJECT public: - GroupView(QWidget *parent = 0); - ~GroupView(); + GroupView(QWidget *parent = 0); + ~GroupView(); - void setModel(QAbstractItemModel *model) override; + void setModel(QAbstractItemModel *model) override; - /// return geometry rectangle occupied by the specified model item - QRect geometryRect(const QModelIndex &index) const; - /// return visual rectangle occupied by the specified model item - virtual QRect visualRect(const QModelIndex &index) const override; - /// get the model index at the specified visual point - virtual QModelIndex indexAt(const QPoint &point) const override; - QString groupNameAt(const QPoint &point); - void setSelection(const QRect &rect, - const QItemSelectionModel::SelectionFlags commands) override; + /// return geometry rectangle occupied by the specified model item + QRect geometryRect(const QModelIndex &index) const; + /// return visual rectangle occupied by the specified model item + virtual QRect visualRect(const QModelIndex &index) const override; + /// get the model index at the specified visual point + virtual QModelIndex indexAt(const QPoint &point) const override; + QString groupNameAt(const QPoint &point); + void setSelection(const QRect &rect, + const QItemSelectionModel::SelectionFlags commands) override; - virtual int horizontalOffset() const override; - virtual int verticalOffset() const override; - virtual void scrollContentsBy(int dx, int dy) override; - virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override; + virtual int horizontalOffset() const override; + virtual int verticalOffset() const override; + virtual void scrollContentsBy(int dx, int dy) override; + virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override; - virtual QModelIndex moveCursor(CursorAction cursorAction, - Qt::KeyboardModifiers modifiers) override; + virtual QModelIndex moveCursor(CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) override; - virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; + virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; - int spacing() const - { - return m_spacing; - }; + int spacing() const + { + return m_spacing; + }; public slots: - virtual void updateGeometries() override; + virtual void updateGeometries() override; protected slots: - virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - const QVector<int> &roles) override; - virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; - virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override; - void modelReset(); - void rowsRemoved(); + virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector<int> &roles) override; + virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; + virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override; + void modelReset(); + void rowsRemoved(); + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; signals: - void droppedURLs(QList<QUrl> urls); + void droppedURLs(QList<QUrl> urls); protected: - virtual bool isIndexHidden(const QModelIndex &index) const override; - void mousePressEvent(QMouseEvent *event) override; - void mouseMoveEvent(QMouseEvent *event) override; - void mouseReleaseEvent(QMouseEvent *event) override; - void mouseDoubleClickEvent(QMouseEvent *event) override; - void paintEvent(QPaintEvent *event) override; - void resizeEvent(QResizeEvent *event) override; + virtual bool isIndexHidden(const QModelIndex &index) const override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; - void dragEnterEvent(QDragEnterEvent *event) override; - void dragMoveEvent(QDragMoveEvent *event) override; - void dragLeaveEvent(QDragLeaveEvent *event) override; - void dropEvent(QDropEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; - void startDrag(Qt::DropActions supportedActions) override; + void startDrag(Qt::DropActions supportedActions) override; - void updateScrollbar(); + void updateScrollbar(); private: - friend struct VisualGroup; - QList<VisualGroup *> m_groups; - - // geometry - int m_leftMargin = 5; - int m_rightMargin = 5; - int m_bottomMargin = 5; - int m_categoryMargin = 5; - int m_spacing = 5; - int m_itemWidth = 100; - int m_currentItemsPerRow = -1; - int m_currentCursorColumn= -1; - mutable QCache<int, QRect> geometryCache; - - // point where the currently active mouse action started in geometry coordinates - QPoint m_pressedPosition; - QPersistentModelIndex m_pressedIndex; - bool m_pressedAlreadySelected; - VisualGroup *m_pressedCategory; - QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; - QPoint m_lastDragPosition; - - VisualGroup *category(const QModelIndex &index) const; - VisualGroup *category(const QString &cat) const; - VisualGroup *categoryAt(const QPoint &pos, VisualGroup::HitResults & result) const; - - int itemsPerRow() const - { - return m_currentItemsPerRow; - }; - int contentWidth() const; + friend struct VisualGroup; + QList<VisualGroup *> m_groups; + + // geometry + int m_leftMargin = 5; + int m_rightMargin = 5; + int m_bottomMargin = 5; + int m_categoryMargin = 5; + int m_spacing = 5; + int m_itemWidth = 100; + int m_currentItemsPerRow = -1; + int m_currentCursorColumn= -1; + mutable QCache<int, QRect> geometryCache; + + // point where the currently active mouse action started in geometry coordinates + QPoint m_pressedPosition; + QPersistentModelIndex m_pressedIndex; + bool m_pressedAlreadySelected; + VisualGroup *m_pressedCategory; + QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; + QPoint m_lastDragPosition; + + VisualGroup *category(const QModelIndex &index) const; + VisualGroup *category(const QString &cat) const; + VisualGroup *categoryAt(const QPoint &pos, VisualGroup::HitResults & result) const; + + int itemsPerRow() const + { + return m_currentItemsPerRow; + }; + int contentWidth() const; private: /* methods */ - int itemWidth() const; - int calculateItemsPerRow() const; - int verticalScrollToValue(const QModelIndex &index, const QRect &rect, - QListView::ScrollHint hint) const; - QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; - QList<QPair<QRect, QModelIndex>> draggablePaintPairs(const QModelIndexList &indices, - QRect *r) const; + int itemWidth() const; + int calculateItemsPerRow() const; + int verticalScrollToValue(const QModelIndex &index, const QRect &rect, + QListView::ScrollHint hint) const; + QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; + QList<QPair<QRect, QModelIndex>> draggablePaintPairs(const QModelIndexList &indices, + QRect *r) const; - bool isDragEventAccepted(QDropEvent *event); + bool isDragEventAccepted(QDropEvent *event); - QPair<VisualGroup *, int> rowDropPos(const QPoint &pos); + QPair<VisualGroup *, int> rowDropPos(const QPoint &pos); - QPoint offset() const; + QPoint offset() const; }; diff --git a/application/groupview/GroupedProxyModel.cpp b/application/groupview/GroupedProxyModel.cpp index c13f2411..d5fbd93c 100644 --- a/application/groupview/GroupedProxyModel.cpp +++ b/application/groupview/GroupedProxyModel.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,25 +24,25 @@ GroupedProxyModel::GroupedProxyModel(QObject *parent) : QSortFilterProxyModel(pa bool GroupedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - const QString leftCategory = left.data(GroupViewRoles::GroupRole).toString(); - const QString rightCategory = right.data(GroupViewRoles::GroupRole).toString(); - if (leftCategory == rightCategory) - { - return subSortLessThan(left, right); - } - else - { - // FIXME: real group sorting happens in GroupView::updateGeometries(), see LocaleString - auto result = leftCategory.localeAwareCompare(rightCategory); - if(result == 0) - { - return subSortLessThan(left, right); - } - return result < 0; - } + const QString leftCategory = left.data(GroupViewRoles::GroupRole).toString(); + const QString rightCategory = right.data(GroupViewRoles::GroupRole).toString(); + if (leftCategory == rightCategory) + { + return subSortLessThan(left, right); + } + else + { + // FIXME: real group sorting happens in GroupView::updateGeometries(), see LocaleString + auto result = leftCategory.localeAwareCompare(rightCategory); + if(result == 0) + { + return subSortLessThan(left, right); + } + return result < 0; + } } bool GroupedProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const { - return left.row() < right.row(); + return left.row() < right.row(); } diff --git a/application/groupview/GroupedProxyModel.h b/application/groupview/GroupedProxyModel.h index babeb308..810657e7 100644 --- a/application/groupview/GroupedProxyModel.h +++ b/application/groupview/GroupedProxyModel.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,12 @@ class GroupedProxyModel : public QSortFilterProxyModel { - Q_OBJECT + Q_OBJECT public: - GroupedProxyModel(QObject *parent = 0); + GroupedProxyModel(QObject *parent = 0); protected: - virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; - virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const; + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const; }; diff --git a/application/groupview/InstanceDelegate.cpp b/application/groupview/InstanceDelegate.cpp index 0855c04a..39830d1a 100644 --- a/application/groupview/InstanceDelegate.cpp +++ b/application/groupview/InstanceDelegate.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,33 +19,35 @@ #include <QTextLayout> #include <QApplication> #include <QtMath> +#include <QDebug> #include "GroupView.h" #include "BaseInstance.h" #include "InstanceList.h" #include <xdgicon.h> +#include <QTextEdit> // Origin: Qt static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, - qreal &widthUsed) + qreal &widthUsed) { - height = 0; - widthUsed = 0; - textLayout.beginLayout(); - QString str = textLayout.text(); - while (true) - { - QTextLine line = textLayout.createLine(); - if (!line.isValid()) - break; - if (line.textLength() == 0) - break; - line.setLineWidth(lineWidth); - line.setPosition(QPointF(0, height)); - height += line.height(); - widthUsed = qMax(widthUsed, line.naturalTextWidth()); - } - textLayout.endLayout(); + height = 0; + widthUsed = 0; + textLayout.beginLayout(); + QString str = textLayout.text(); + while (true) + { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + if (line.textLength() == 0) + break; + line.setLineWidth(lineWidth); + line.setPosition(QPointF(0, height)); + height += line.height(); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout.endLayout(); } ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent) @@ -53,291 +55,374 @@ ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent } void drawSelectionRect(QPainter *painter, const QStyleOptionViewItem &option, - const QRect &rect) + const QRect &rect) { - if ((option.state & QStyle::State_Selected)) - painter->fillRect(rect, option.palette.brush(QPalette::Highlight)); - else - { - QColor backgroundColor = option.palette.color(QPalette::Background); - backgroundColor.setAlpha(160); - painter->fillRect(rect, QBrush(backgroundColor)); - } + if ((option.state & QStyle::State_Selected)) + painter->fillRect(rect, option.palette.brush(QPalette::Highlight)); + else + { + QColor backgroundColor = option.palette.color(QPalette::Background); + backgroundColor.setAlpha(160); + painter->fillRect(rect, QBrush(backgroundColor)); + } } void drawFocusRect(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) { - if (!(option.state & QStyle::State_HasFocus)) - return; - QStyleOptionFocusRect opt; - opt.direction = option.direction; - opt.fontMetrics = option.fontMetrics; - opt.palette = option.palette; - opt.rect = rect; - // opt.state = option.state | QStyle::State_KeyboardFocusChange | - // QStyle::State_Item; - auto col = option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base; - opt.backgroundColor = option.palette.color(col); - // Apparently some widget styles expect this hint to not be set - painter->setRenderHint(QPainter::Antialiasing, false); - - QStyle *style = option.widget ? option.widget->style() : QApplication::style(); - - style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget); - - painter->setRenderHint(QPainter::Antialiasing); + if (!(option.state & QStyle::State_HasFocus)) + return; + QStyleOptionFocusRect opt; + opt.direction = option.direction; + opt.fontMetrics = option.fontMetrics; + opt.palette = option.palette; + opt.rect = rect; + // opt.state = option.state | QStyle::State_KeyboardFocusChange | + // QStyle::State_Item; + auto col = option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base; + opt.backgroundColor = option.palette.color(col); + // Apparently some widget styles expect this hint to not be set + painter->setRenderHint(QPainter::Antialiasing, false); + + QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + + style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget); + + painter->setRenderHint(QPainter::Antialiasing); } // TODO this can be made a lot prettier void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItem &option, - const int value, const int maximum) + const int value, const int maximum) { - if (maximum == 0 || value == maximum) - { - return; - } + if (maximum == 0 || value == maximum) + { + return; + } - painter->save(); + painter->save(); - qreal percent = (qreal)value / (qreal)maximum; - QColor color = option.palette.color(QPalette::Dark); - color.setAlphaF(0.70f); - painter->setBrush(color); - painter->setPen(QPen(QBrush(), 0)); - painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16); + qreal percent = (qreal)value / (qreal)maximum; + QColor color = option.palette.color(QPalette::Dark); + color.setAlphaF(0.70f); + painter->setBrush(color); + painter->setPen(QPen(QBrush(), 0)); + painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16); - painter->restore(); + painter->restore(); } void drawBadges(QPainter *painter, const QStyleOptionViewItem &option, BaseInstance *instance, QIcon::Mode mode, QIcon::State state) { - QList<QString> pixmaps; - if (instance->isRunning()) - { - pixmaps.append("status-running"); - } - else if (instance->hasCrashed() || instance->hasVersionBroken()) - { - pixmaps.append("status-bad"); - } - if (instance->hasUpdateAvailable()) - { - pixmaps.append("checkupdate"); - } - - static const int itemSide = 24; - static const int spacing = 1; - const int itemsPerRow = qMax(1, qFloor(double(option.rect.width() + spacing) / double(itemSide + spacing))); - const int rows = qCeil((double)pixmaps.size() / (double)itemsPerRow); - QListIterator<QString> it(pixmaps); - painter->translate(option.rect.topLeft()); - for (int y = 0; y < rows; ++y) - { - for (int x = 0; x < itemsPerRow; ++x) - { - if (!it.hasNext()) - { - return; - } - // FIXME: inject this. - auto icon = XdgIcon::fromTheme(it.next()); - // opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); - const QPixmap pixmap; - // itemSide - QRect badgeRect( - option.rect.width() - x * itemSide + qMax(x - 1, 0) * spacing - itemSide, - y * itemSide + qMax(y - 1, 0) * spacing, - itemSide, - itemSide - ); - icon.paint(painter, badgeRect, Qt::AlignCenter, mode, state); - } - } - painter->translate(-option.rect.topLeft()); + QList<QString> pixmaps; + if (instance->isRunning()) + { + pixmaps.append("status-running"); + } + else if (instance->hasCrashed() || instance->hasVersionBroken()) + { + pixmaps.append("status-bad"); + } + if (instance->hasUpdateAvailable()) + { + pixmaps.append("checkupdate"); + } + + static const int itemSide = 24; + static const int spacing = 1; + const int itemsPerRow = qMax(1, qFloor(double(option.rect.width() + spacing) / double(itemSide + spacing))); + const int rows = qCeil((double)pixmaps.size() / (double)itemsPerRow); + QListIterator<QString> it(pixmaps); + painter->translate(option.rect.topLeft()); + for (int y = 0; y < rows; ++y) + { + for (int x = 0; x < itemsPerRow; ++x) + { + if (!it.hasNext()) + { + return; + } + // FIXME: inject this. + auto icon = XdgIcon::fromTheme(it.next()); + // opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); + const QPixmap pixmap; + // itemSide + QRect badgeRect( + option.rect.width() - x * itemSide + qMax(x - 1, 0) * spacing - itemSide, + y * itemSide + qMax(y - 1, 0) * spacing, + itemSide, + itemSide + ); + icon.paint(painter, badgeRect, Qt::AlignCenter, mode, state); + } + } + painter->translate(-option.rect.topLeft()); } static QSize viewItemTextSize(const QStyleOptionViewItem *option) { - QStyle *style = option->widget ? option->widget->style() : QApplication::style(); - QTextOption textOption; - textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - QTextLayout textLayout; - textLayout.setTextOption(textOption); - textLayout.setFont(option->font); - textLayout.setText(option->text); - const int textMargin = - style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1; - QRect bounds(0, 0, 100 - 2 * textMargin, 600); - qreal height = 0, widthUsed = 0; - viewItemTextLayout(textLayout, bounds.width(), height, widthUsed); - const QSize size(qCeil(widthUsed), qCeil(height)); - return QSize(size.width() + 2 * textMargin, size.height()); + QStyle *style = option->widget ? option->widget->style() : QApplication::style(); + QTextOption textOption; + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(option->font); + textLayout.setText(option->text); + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1; + QRect bounds(0, 0, 100 - 2 * textMargin, 600); + qreal height = 0, widthUsed = 0; + viewItemTextLayout(textLayout, bounds.width(), height, widthUsed); + const QSize size(qCeil(widthUsed), qCeil(height)); + return QSize(size.width() + 2 * textMargin, size.height()); } void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const + const QModelIndex &index) const { - QStyleOptionViewItem opt = option; - initStyleOption(&opt, index); - painter->save(); - painter->setClipRect(opt.rect); - - opt.features |= QStyleOptionViewItem::WrapText; - opt.text = index.data().toString(); - opt.textElideMode = Qt::ElideRight; - opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; - - QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); - - // const int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); - const int iconSize = 48; - QRect iconbox = opt.rect; - const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1; - QRect textRect = opt.rect; - QRect textHighlightRect = textRect; - // clip the decoration on top, remove width padding - textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0); - - textHighlightRect.adjust(0, iconSize + 5, 0, 0); - - // draw background - { - // FIXME: unused - // QSize textSize = viewItemTextSize ( &opt ); - QPalette::ColorGroup cg; - QStyleOptionViewItem opt2(opt); - - if ((opt.widget && opt.widget->isEnabled()) || (opt.state & QStyle::State_Enabled)) - { - if (!(opt.state & QStyle::State_Active)) - cg = QPalette::Inactive; - else - cg = QPalette::Normal; - } - else - { - cg = QPalette::Disabled; - } - opt2.palette.setCurrentColorGroup(cg); - - // fill in background, if any - - if (opt.backgroundBrush.style() != Qt::NoBrush) - { - QPointF oldBO = painter->brushOrigin(); - painter->setBrushOrigin(opt.rect.topLeft()); - painter->fillRect(opt.rect, opt.backgroundBrush); - painter->setBrushOrigin(oldBO); - } - - drawSelectionRect(painter, opt2, textHighlightRect); - - /* - if (opt.showDecorationSelected) - { - drawSelectionRect(painter, opt2, opt.rect); - drawFocusRect(painter, opt2, opt.rect); - // painter->fillRect ( opt.rect, opt.palette.brush ( cg, QPalette::Highlight ) ); - } - else - { - - // if ( opt.state & QStyle::State_Selected ) - { - // QRect textRect = subElementRect ( QStyle::SE_ItemViewItemText, opt, - // opt.widget ); - // painter->fillRect ( textHighlightRect, opt.palette.brush ( cg, - // QPalette::Highlight ) ); - drawSelectionRect(painter, opt2, textHighlightRect); - drawFocusRect(painter, opt2, textHighlightRect); - } - } - */ - } - - // icon mode and state, also used for badges - QIcon::Mode mode = QIcon::Normal; - if (!(opt.state & QStyle::State_Enabled)) - mode = QIcon::Disabled; - else if (opt.state & QStyle::State_Selected) - mode = QIcon::Selected; - QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; - - // draw the icon - { - iconbox.setHeight(iconSize); - opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); - } - // set the text colors - QPalette::ColorGroup cg = - opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; - if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) - cg = QPalette::Inactive; - if (opt.state & QStyle::State_Selected) - { - painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); - } - else - { - painter->setPen(opt.palette.color(cg, QPalette::Text)); - } - - // draw the text - QTextOption textOption; - textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - textOption.setTextDirection(opt.direction); - textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment)); - QTextLayout textLayout; - textLayout.setTextOption(textOption); - textLayout.setFont(opt.font); - textLayout.setText(opt.text); - - qreal width, height; - viewItemTextLayout(textLayout, textRect.width(), height, width); - - const int lineCount = textLayout.lineCount(); - - const QRect layoutRect = QStyle::alignedRect( - opt.direction, opt.displayAlignment, QSize(textRect.width(), int(height)), textRect); - const QPointF position = layoutRect.topLeft(); - for (int i = 0; i < lineCount; ++i) - { - const QTextLine line = textLayout.lineAt(i); - line.draw(painter, position); - } - - // FIXME: this really has no business of being here. Make generic. - auto instance = (BaseInstance*)index.data(InstanceList::InstancePointerRole) - .value<void *>(); - if (instance) - { - drawBadges(painter, opt, instance, mode, state); - } - - drawProgressOverlay(painter, opt, index.data(GroupViewRoles::ProgressValueRole).toInt(), - index.data(GroupViewRoles::ProgressMaximumRole).toInt()); - - painter->restore(); + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + painter->save(); + painter->setClipRect(opt.rect); + + opt.features |= QStyleOptionViewItem::WrapText; + opt.text = index.data().toString(); + opt.textElideMode = Qt::ElideRight; + opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; + + QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); + + // const int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); + const int iconSize = 48; + QRect iconbox = opt.rect; + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1; + QRect textRect = opt.rect; + QRect textHighlightRect = textRect; + // clip the decoration on top, remove width padding + textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0); + + textHighlightRect.adjust(0, iconSize + 5, 0, 0); + + // draw background + { + // FIXME: unused + // QSize textSize = viewItemTextSize ( &opt ); + drawSelectionRect(painter, opt, textHighlightRect); + /* + QPalette::ColorGroup cg; + QStyleOptionViewItem opt2(opt); + + if ((opt.widget && opt.widget->isEnabled()) || (opt.state & QStyle::State_Enabled)) + { + if (!(opt.state & QStyle::State_Active)) + cg = QPalette::Inactive; + else + cg = QPalette::Normal; + } + else + { + cg = QPalette::Disabled; + } + */ + /* + opt2.palette.setCurrentColorGroup(cg); + + // fill in background, if any + + + if (opt.backgroundBrush.style() != Qt::NoBrush) + { + QPointF oldBO = painter->brushOrigin(); + painter->setBrushOrigin(opt.rect.topLeft()); + painter->fillRect(opt.rect, opt.backgroundBrush); + painter->setBrushOrigin(oldBO); + } + + drawSelectionRect(painter, opt2, textHighlightRect); + */ + + /* + if (opt.showDecorationSelected) + { + drawSelectionRect(painter, opt2, opt.rect); + drawFocusRect(painter, opt2, opt.rect); + // painter->fillRect ( opt.rect, opt.palette.brush ( cg, QPalette::Highlight ) ); + } + else + { + + // if ( opt.state & QStyle::State_Selected ) + { + // QRect textRect = subElementRect ( QStyle::SE_ItemViewItemText, opt, + // opt.widget ); + // painter->fillRect ( textHighlightRect, opt.palette.brush ( cg, + // QPalette::Highlight ) ); + drawSelectionRect(painter, opt2, textHighlightRect); + drawFocusRect(painter, opt2, textHighlightRect); + } + } + */ + } + + // icon mode and state, also used for badges + QIcon::Mode mode = QIcon::Normal; + if (!(opt.state & QStyle::State_Enabled)) + mode = QIcon::Disabled; + else if (opt.state & QStyle::State_Selected) + mode = QIcon::Selected; + QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; + + // draw the icon + { + iconbox.setHeight(iconSize); + opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); + } + // set the text colors + QPalette::ColorGroup cg = + opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) + cg = QPalette::Inactive; + if (opt.state & QStyle::State_Selected) + { + painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); + } + else + { + painter->setPen(opt.palette.color(cg, QPalette::Text)); + } + + // draw the text + QTextOption textOption; + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + textOption.setTextDirection(opt.direction); + textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment)); + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(opt.font); + textLayout.setText(opt.text); + + qreal width, height; + viewItemTextLayout(textLayout, textRect.width(), height, width); + + const int lineCount = textLayout.lineCount(); + + const QRect layoutRect = QStyle::alignedRect( + opt.direction, opt.displayAlignment, QSize(textRect.width(), int(height)), textRect); + const QPointF position = layoutRect.topLeft(); + for (int i = 0; i < lineCount; ++i) + { + const QTextLine line = textLayout.lineAt(i); + line.draw(painter, position); + } + + // FIXME: this really has no business of being here. Make generic. + auto instance = (BaseInstance*)index.data(InstanceList::InstancePointerRole) + .value<void *>(); + if (instance) + { + drawBadges(painter, opt, instance, mode, state); + } + + drawProgressOverlay(painter, opt, index.data(GroupViewRoles::ProgressValueRole).toInt(), + index.data(GroupViewRoles::ProgressMaximumRole).toInt()); + + painter->restore(); } QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option, - const QModelIndex &index) const + const QModelIndex &index) const { - QStyleOptionViewItem opt = option; - initStyleOption(&opt, index); - opt.features |= QStyleOptionViewItem::WrapText; - opt.text = index.data().toString(); - opt.textElideMode = Qt::ElideRight; - opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; - - QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); - const int textMargin = - style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1; - int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables - QSize szz = viewItemTextSize(&opt); - height += szz.height(); - // FIXME: maybe the icon items could scale and keep proportions? - QSize sz(100, height); - return sz; + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + opt.features |= QStyleOptionViewItem::WrapText; + opt.text = index.data().toString(); + opt.textElideMode = Qt::ElideRight; + opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; + + QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1; + int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables + QSize szz = viewItemTextSize(&opt); + height += szz.height(); + // FIXME: maybe the icon items could scale and keep proportions? + QSize sz(100, height); + return sz; } +class NoReturnTextEdit: public QTextEdit +{ + Q_OBJECT +public: + explicit NoReturnTextEdit(QWidget *parent) : QTextEdit(parent) + { + setTextInteractionFlags(Qt::TextEditorInteraction); + setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + } + bool event(QEvent * event) override + { + auto eventType = event->type(); + if(eventType == QEvent::KeyPress || eventType == QEvent::KeyRelease) + { + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); + auto key = keyEvent->key(); + if (key == Qt::Key_Return || key == Qt::Key_Enter) + { + emit editingDone(); + return true; + } + if(key == Qt::Key_Tab) + { + return true; + } + } + return QTextEdit::event(event); + } +signals: + void editingDone(); +}; + +void ListViewDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + const int iconSize = 48; + QRect textRect = option.rect; + // QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + textRect.adjust(0, iconSize + 5, 0, 0); + editor->setGeometry(textRect); +} + +void ListViewDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + auto text = index.data(Qt::EditRole).toString(); + QTextEdit * realeditor = qobject_cast<NoReturnTextEdit *>(editor); + realeditor->setAlignment(Qt::AlignHCenter | Qt::AlignTop); + realeditor->append(text); + realeditor->selectAll(); + realeditor->document()->clearUndoRedoStacks(); +} + +void ListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const +{ + QTextEdit * realeditor = qobject_cast<NoReturnTextEdit *>(editor); + QString text = realeditor->toPlainText(); + text.replace(QChar('\n'), QChar(' ')); + text = text.trimmed(); + if(text.size() != 0) + { + model->setData(index, text); + } +} + +QWidget * ListViewDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + auto editor = new NoReturnTextEdit(parent); + connect(editor, &NoReturnTextEdit::editingDone, this, &ListViewDelegate::editingDone); + return editor; +} + +void ListViewDelegate::editingDone() +{ + NoReturnTextEdit *editor = qobject_cast<NoReturnTextEdit *>(sender()); + emit commitData(editor); + emit closeEditor(editor); +} + +#include "InstanceDelegate.moc" diff --git a/application/groupview/InstanceDelegate.h b/application/groupview/InstanceDelegate.h index c0148570..be49f943 100644 --- a/application/groupview/InstanceDelegate.h +++ b/application/groupview/InstanceDelegate.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,20 @@ class ListViewDelegate : public QStyledItemDelegate { + Q_OBJECT + public: - explicit ListViewDelegate(QObject *parent = 0); + explicit ListViewDelegate(QObject *parent = 0); + virtual ~ListViewDelegate() {} + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index) const override; + QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override; + + void setEditorData(QWidget * editor, const QModelIndex & index) const override; + void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; -protected: - void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const; - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; +private slots: + void editingDone(); }; diff --git a/application/groupview/VisualGroup.cpp b/application/groupview/VisualGroup.cpp index 940c7a8b..f3e6751d 100644 --- a/application/groupview/VisualGroup.cpp +++ b/application/groupview/VisualGroup.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,290 +28,290 @@ VisualGroup::VisualGroup(const QString &text, GroupView *view) : view(view), tex } VisualGroup::VisualGroup(const VisualGroup *other) - : view(other->view), text(other->text), collapsed(other->collapsed) + : view(other->view), text(other->text), collapsed(other->collapsed) { } void VisualGroup::update() { - auto temp_items = items(); - auto itemsPerRow = view->itemsPerRow(); - - int numRows = qMax(1, qCeil((qreal)temp_items.size() / (qreal)itemsPerRow)); - rows = QVector<VisualRow>(numRows); - - int maxRowHeight = 0; - int positionInRow = 0; - int currentRow = 0; - int offsetFromTop = 0; - for (auto item: temp_items) - { - if(positionInRow == itemsPerRow) - { - rows[currentRow].height = maxRowHeight; - rows[currentRow].top = offsetFromTop; - currentRow ++; - offsetFromTop += maxRowHeight + 5; - positionInRow = 0; - maxRowHeight = 0; - } - auto itemHeight = view->itemDelegate()->sizeHint(view->viewOptions(), item).height(); - if(itemHeight > maxRowHeight) - { - maxRowHeight = itemHeight; - } - rows[currentRow].items.append(item); - positionInRow++; - } - rows[currentRow].height = maxRowHeight; - rows[currentRow].top = offsetFromTop; + auto temp_items = items(); + auto itemsPerRow = view->itemsPerRow(); + + int numRows = qMax(1, qCeil((qreal)temp_items.size() / (qreal)itemsPerRow)); + rows = QVector<VisualRow>(numRows); + + int maxRowHeight = 0; + int positionInRow = 0; + int currentRow = 0; + int offsetFromTop = 0; + for (auto item: temp_items) + { + if(positionInRow == itemsPerRow) + { + rows[currentRow].height = maxRowHeight; + rows[currentRow].top = offsetFromTop; + currentRow ++; + offsetFromTop += maxRowHeight + 5; + positionInRow = 0; + maxRowHeight = 0; + } + auto itemHeight = view->itemDelegate()->sizeHint(view->viewOptions(), item).height(); + if(itemHeight > maxRowHeight) + { + maxRowHeight = itemHeight; + } + rows[currentRow].items.append(item); + positionInRow++; + } + rows[currentRow].height = maxRowHeight; + rows[currentRow].top = offsetFromTop; } QPair<int, int> VisualGroup::positionOf(const QModelIndex &index) const { - int y = 0; - for (auto & row: rows) - { - for(auto x = 0; x < row.items.size(); x++) - { - if(row.items[x] == index) - { - return qMakePair(x,y); - } - } - y++; - } - qWarning() << "Item" << index.row() << index.data(Qt::DisplayRole).toString() << "not found in visual group" << text; - return qMakePair(0, 0); + int y = 0; + for (auto & row: rows) + { + for(auto x = 0; x < row.items.size(); x++) + { + if(row.items[x] == index) + { + return qMakePair(x,y); + } + } + y++; + } + qWarning() << "Item" << index.row() << index.data(Qt::DisplayRole).toString() << "not found in visual group" << text; + return qMakePair(0, 0); } int VisualGroup::rowTopOf(const QModelIndex &index) const { - auto position = positionOf(index); - return rows[position.second].top; + auto position = positionOf(index); + return rows[position.second].top; } int VisualGroup::rowHeightOf(const QModelIndex &index) const { - auto position = positionOf(index); - return rows[position.second].height; + auto position = positionOf(index); + return rows[position.second].height; } VisualGroup::HitResults VisualGroup::hitScan(const QPoint &pos) const { - VisualGroup::HitResults results = VisualGroup::NoHit; - int y_start = verticalPosition(); - int body_start = y_start + headerHeight(); - int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5? - int y = pos.y(); - // int x = pos.x(); - if (y < y_start) - { - results = VisualGroup::NoHit; - } - else if (y < body_start) - { - results = VisualGroup::HeaderHit; - int collapseSize = headerHeight() - 4; - - // the icon - QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize); - if (iconRect.contains(pos)) - { - results |= VisualGroup::CheckboxHit; - } - } - else if (y < body_end) - { - results |= VisualGroup::BodyHit; - } - return results; + VisualGroup::HitResults results = VisualGroup::NoHit; + int y_start = verticalPosition(); + int body_start = y_start + headerHeight(); + int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5? + int y = pos.y(); + // int x = pos.x(); + if (y < y_start) + { + results = VisualGroup::NoHit; + } + else if (y < body_start) + { + results = VisualGroup::HeaderHit; + int collapseSize = headerHeight() - 4; + + // the icon + QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize); + if (iconRect.contains(pos)) + { + results |= VisualGroup::CheckboxHit; + } + } + else if (y < body_end) + { + results |= VisualGroup::BodyHit; + } + return results; } void VisualGroup::drawHeader(QPainter *painter, const QStyleOptionViewItem &option) { - painter->setRenderHint(QPainter::Antialiasing); - - const QRect optRect = option.rect; - QFont font(QApplication::font()); - font.setBold(true); - const QFontMetrics fontMetrics = QFontMetrics(font); - - QColor outlineColor = option.palette.text().color(); - outlineColor.setAlphaF(0.35); - - //BEGIN: top left corner - { - painter->save(); - painter->setPen(outlineColor); - const QPointF topLeft(optRect.topLeft()); - QRectF arc(topLeft, QSizeF(4, 4)); - arc.translate(0.5, 0.5); - painter->drawArc(arc, 1440, 1440); - painter->restore(); - } - //END: top left corner - - //BEGIN: left vertical line - { - QPoint start(optRect.topLeft()); - start.ry() += 3; - QPoint verticalGradBottom(optRect.topLeft()); - verticalGradBottom.ry() += fontMetrics.height() + 5; - QLinearGradient gradient(start, verticalGradBottom); - gradient.setColorAt(0, outlineColor); - gradient.setColorAt(1, Qt::transparent); - painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); - } - //END: left vertical line - - //BEGIN: horizontal line - { - QPoint start(optRect.topLeft()); - start.rx() += 3; - QPoint horizontalGradTop(optRect.topLeft()); - horizontalGradTop.rx() += optRect.width() - 6; - painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); - } - //END: horizontal line - - //BEGIN: top right corner - { - painter->save(); - painter->setPen(outlineColor); - QPointF topRight(optRect.topRight()); - topRight.rx() -= 4; - QRectF arc(topRight, QSizeF(4, 4)); - arc.translate(0.5, 0.5); - painter->drawArc(arc, 0, 1440); - painter->restore(); - } - //END: top right corner - - //BEGIN: right vertical line - { - QPoint start(optRect.topRight()); - start.ry() += 3; - QPoint verticalGradBottom(optRect.topRight()); - verticalGradBottom.ry() += fontMetrics.height() + 5; - QLinearGradient gradient(start, verticalGradBottom); - gradient.setColorAt(0, outlineColor); - gradient.setColorAt(1, Qt::transparent); - painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); - } - //END: right vertical line - - //BEGIN: checkboxy thing - { - painter->save(); - painter->setRenderHint(QPainter::Antialiasing, false); - painter->setFont(font); - QColor penColor(option.palette.text().color()); - penColor.setAlphaF(0.6); - painter->setPen(penColor); - QRect iconSubRect(option.rect); - iconSubRect.setTop(iconSubRect.top() + 7); - iconSubRect.setLeft(iconSubRect.left() + 7); - - int sizing = fontMetrics.height(); - int even = ( (sizing - 1) % 2 ); - - iconSubRect.setHeight(sizing - even); - iconSubRect.setWidth(sizing - even); - painter->drawRect(iconSubRect); - - - /* - if(collapsed) - painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "+"); - else - painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "-"); - */ - painter->setBrush(option.palette.text()); - painter->fillRect(iconSubRect.x(), iconSubRect.y() + iconSubRect.height() / 2, - iconSubRect.width(), 2, penColor); - if (collapsed) - { - painter->fillRect(iconSubRect.x() + iconSubRect.width() / 2, iconSubRect.y(), 2, - iconSubRect.height(), penColor); - } - - painter->restore(); - } - //END: checkboxy thing - - //BEGIN: text - { - QRect textRect(option.rect); - textRect.setTop(textRect.top() + 7); - textRect.setLeft(textRect.left() + 7 + fontMetrics.height() + 7); - textRect.setHeight(fontMetrics.height()); - textRect.setRight(textRect.right() - 7); - - painter->save(); - painter->setFont(font); - QColor penColor(option.palette.text().color()); - penColor.setAlphaF(0.6); - painter->setPen(penColor); - painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); - painter->restore(); - } - //END: text + painter->setRenderHint(QPainter::Antialiasing); + + const QRect optRect = option.rect; + QFont font(QApplication::font()); + font.setBold(true); + const QFontMetrics fontMetrics = QFontMetrics(font); + + QColor outlineColor = option.palette.text().color(); + outlineColor.setAlphaF(0.35); + + //BEGIN: top left corner + { + painter->save(); + painter->setPen(outlineColor); + const QPointF topLeft(optRect.topLeft()); + QRectF arc(topLeft, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 1440, 1440); + painter->restore(); + } + //END: top left corner + + //BEGIN: left vertical line + { + QPoint start(optRect.topLeft()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topLeft()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); + } + //END: left vertical line + + //BEGIN: horizontal line + { + QPoint start(optRect.topLeft()); + start.rx() += 3; + QPoint horizontalGradTop(optRect.topLeft()); + horizontalGradTop.rx() += optRect.width() - 6; + painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); + } + //END: horizontal line + + //BEGIN: top right corner + { + painter->save(); + painter->setPen(outlineColor); + QPointF topRight(optRect.topRight()); + topRight.rx() -= 4; + QRectF arc(topRight, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 0, 1440); + painter->restore(); + } + //END: top right corner + + //BEGIN: right vertical line + { + QPoint start(optRect.topRight()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topRight()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); + } + //END: right vertical line + + //BEGIN: checkboxy thing + { + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setFont(font); + QColor penColor(option.palette.text().color()); + penColor.setAlphaF(0.6); + painter->setPen(penColor); + QRect iconSubRect(option.rect); + iconSubRect.setTop(iconSubRect.top() + 7); + iconSubRect.setLeft(iconSubRect.left() + 7); + + int sizing = fontMetrics.height(); + int even = ( (sizing - 1) % 2 ); + + iconSubRect.setHeight(sizing - even); + iconSubRect.setWidth(sizing - even); + painter->drawRect(iconSubRect); + + + /* + if(collapsed) + painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "+"); + else + painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "-"); + */ + painter->setBrush(option.palette.text()); + painter->fillRect(iconSubRect.x(), iconSubRect.y() + iconSubRect.height() / 2, + iconSubRect.width(), 2, penColor); + if (collapsed) + { + painter->fillRect(iconSubRect.x() + iconSubRect.width() / 2, iconSubRect.y(), 2, + iconSubRect.height(), penColor); + } + + painter->restore(); + } + //END: checkboxy thing + + //BEGIN: text + { + QRect textRect(option.rect); + textRect.setTop(textRect.top() + 7); + textRect.setLeft(textRect.left() + 7 + fontMetrics.height() + 7); + textRect.setHeight(fontMetrics.height()); + textRect.setRight(textRect.right() - 7); + + painter->save(); + painter->setFont(font); + QColor penColor(option.palette.text().color()); + penColor.setAlphaF(0.6); + painter->setPen(penColor); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); + painter->restore(); + } + //END: text } int VisualGroup::totalHeight() const { - return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'? + return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'? } int VisualGroup::headerHeight() const { - QFont font(QApplication::font()); + QFont font(QApplication::font()); font.setBold(true); QFontMetrics fontMetrics(font); const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */ + 11 /* top and bottom separation */; return height; - /* - int raw = view->viewport()->fontMetrics().height() + 4; - // add english. maybe. depends on font height. - if (raw % 2 == 0) - raw++; - return std::min(raw, 25); - */ + /* + int raw = view->viewport()->fontMetrics().height() + 4; + // add english. maybe. depends on font height. + if (raw % 2 == 0) + raw++; + return std::min(raw, 25); + */ } int VisualGroup::contentHeight() const { - if (collapsed) - { - return 0; - } - auto last = rows[numRows() - 1]; - return last.top + last.height; + if (collapsed) + { + return 0; + } + auto last = rows[numRows() - 1]; + return last.top + last.height; } int VisualGroup::numRows() const { - return rows.size(); + return rows.size(); } int VisualGroup::verticalPosition() const { - return m_verticalPosition; + return m_verticalPosition; } QList<QModelIndex> VisualGroup::items() const { - QList<QModelIndex> indices; - for (int i = 0; i < view->model()->rowCount(); ++i) - { - const QModelIndex index = view->model()->index(i, 0); - if (index.data(GroupViewRoles::GroupRole).toString() == text) - { - indices.append(index); - } - } - return indices; + QList<QModelIndex> indices; + for (int i = 0; i < view->model()->rowCount(); ++i) + { + const QModelIndex index = view->model()->index(i, 0); + if (index.data(GroupViewRoles::GroupRole).toString() == text) + { + indices.append(index); + } + } + return indices; } diff --git a/application/groupview/VisualGroup.h b/application/groupview/VisualGroup.h index 2caac49f..356a6da2 100644 --- a/application/groupview/VisualGroup.h +++ b/application/groupview/VisualGroup.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,81 +26,81 @@ class QModelIndex; struct VisualRow { - QList<QModelIndex> items; - int height = 0; - int top = 0; - inline int size() const - { - return items.size(); - } - inline QModelIndex &operator[](int i) - { - return items[i]; - } + QList<QModelIndex> items; + int height = 0; + int top = 0; + inline int size() const + { + return items.size(); + } + inline QModelIndex &operator[](int i) + { + return items[i]; + } }; struct VisualGroup { /* constructors */ - VisualGroup(const QString &text, GroupView *view); - VisualGroup(const VisualGroup *other); + VisualGroup(const QString &text, GroupView *view); + VisualGroup(const VisualGroup *other); /* data */ - GroupView *view = nullptr; - QString text; - bool collapsed = false; - QVector<VisualRow> rows; - int firstItemIndex = 0; - int m_verticalPosition = 0; + GroupView *view = nullptr; + QString text; + bool collapsed = false; + QVector<VisualRow> rows; + int firstItemIndex = 0; + int m_verticalPosition = 0; /* logic */ - /// update the internal list of items and flow them into the rows. - void update(); + /// update the internal list of items and flow them into the rows. + void update(); - /// draw the header at y-position. - void drawHeader(QPainter *painter, const QStyleOptionViewItem &option); + /// draw the header at y-position. + void drawHeader(QPainter *painter, const QStyleOptionViewItem &option); - /// height of the group, in total. includes a small bit of padding. - int totalHeight() const; + /// height of the group, in total. includes a small bit of padding. + int totalHeight() const; - /// height of the group header, in pixels - int headerHeight() const; + /// height of the group header, in pixels + int headerHeight() const; - /// height of the group content, in pixels - int contentHeight() const; + /// height of the group content, in pixels + int contentHeight() const; - /// the number of visual rows this group has - int numRows() const; + /// the number of visual rows this group has + int numRows() const; - /// actually calculate the above value - int calculateNumRows() const; + /// actually calculate the above value + int calculateNumRows() const; - /// the height at which this group starts, in pixels - int verticalPosition() const; + /// the height at which this group starts, in pixels + int verticalPosition() const; - /// relative geometry - top of the row of the given item - int rowTopOf(const QModelIndex &index) const; + /// relative geometry - top of the row of the given item + int rowTopOf(const QModelIndex &index) const; - /// height of the row of the given item - int rowHeightOf(const QModelIndex &index) const; + /// height of the row of the given item + int rowHeightOf(const QModelIndex &index) const; - /// x/y position of the given item inside the group (in items!) - QPair<int, int> positionOf(const QModelIndex &index) const; + /// x/y position of the given item inside the group (in items!) + QPair<int, int> positionOf(const QModelIndex &index) const; - enum HitResult - { - NoHit = 0x0, - TextHit = 0x1, - CheckboxHit = 0x2, - HeaderHit = 0x4, - BodyHit = 0x8 - }; - Q_DECLARE_FLAGS(HitResults, HitResult) + enum HitResult + { + NoHit = 0x0, + TextHit = 0x1, + CheckboxHit = 0x2, + HeaderHit = 0x4, + BodyHit = 0x8 + }; + Q_DECLARE_FLAGS(HitResults, HitResult) - /// shoot! BANG! what did we hit? - HitResults hitScan (const QPoint &pos) const; + /// shoot! BANG! what did we hit? + HitResults hitScan (const QPoint &pos) const; - QList<QModelIndex> items() const; + QList<QModelIndex> items() const; }; Q_DECLARE_OPERATORS_FOR_FLAGS(VisualGroup::HitResults) diff --git a/application/install_prereqs.cmake.in b/application/install_prereqs.cmake.in index 2906a4ec..e4408d16 100644 --- a/application/install_prereqs.cmake.in +++ b/application/install_prereqs.cmake.in @@ -1,20 +1,20 @@ set(CMAKE_MODULE_PATH "@CMAKE_MODULE_PATH@") -file(GLOB_RECURSE QTPLUGINS "${CMAKE_INSTALL_PREFIX}/@PLUGIN_DEST_DIR@/*@CMAKE_SHARED_LIBRARY_SUFFIX@") +file(GLOB_RECURSE QTPLUGINS "${CMAKE_INSTALL_PREFIX}/@PLUGIN_DEST_DIR@/*@CMAKE_SHARED_LIBRARY_SUFFIX@") function(gp_resolved_file_type_override resolved_file type_var) - if(resolved_file MATCHES "^/(usr/)?lib/libQt") - set(${type_var} other PARENT_SCOPE) - elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libxcb-") - set(${type_var} other PARENT_SCOPE) - elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libicu") - set(${type_var} other PARENT_SCOPE) - elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libpng") - set(${type_var} other PARENT_SCOPE) - elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libproxy") - set(${type_var} other PARENT_SCOPE) - elseif((resolved_file MATCHES "^/(usr/)?lib(.+)?/libstdc\\+\\+") AND (UNIX AND NOT APPLE)) - set(${type_var} other PARENT_SCOPE) - endif() + if(resolved_file MATCHES "^/(usr/)?lib/libQt") + set(${type_var} other PARENT_SCOPE) + elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libxcb-") + set(${type_var} other PARENT_SCOPE) + elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libicu") + set(${type_var} other PARENT_SCOPE) + elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libpng") + set(${type_var} other PARENT_SCOPE) + elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libproxy") + set(${type_var} other PARENT_SCOPE) + elseif((resolved_file MATCHES "^/(usr/)?lib(.+)?/libstdc\\+\\+") AND (UNIX AND NOT APPLE)) + set(${type_var} other PARENT_SCOPE) + endif() endfunction() set(gp_tool "@CMAKE_GP_TOOL@") diff --git a/application/main.cpp b/application/main.cpp index 2f90f2b3..c871bf1b 100644 --- a/application/main.cpp +++ b/application/main.cpp @@ -16,46 +16,47 @@ int main(int argc, char *argv[]) { #ifdef BREAK_INFINITE_LOOP - while(true) - { - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - } + while(true) + { + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } #endif #ifdef BREAK_EXCEPTION - throw 42; + throw 42; #endif #ifdef BREAK_RETURN - return 42; + return 42; #endif #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif - // initialize Qt - MultiMC app(argc, argv); + // initialize Qt + MultiMC app(argc, argv); - switch (app.status()) - { - case MultiMC::StartingUp: - case MultiMC::Initialized: - { - Q_INIT_RESOURCE(multimc); - Q_INIT_RESOURCE(backgrounds); - Q_INIT_RESOURCE(assets); + switch (app.status()) + { + case MultiMC::StartingUp: + case MultiMC::Initialized: + { + Q_INIT_RESOURCE(multimc); + Q_INIT_RESOURCE(backgrounds); + Q_INIT_RESOURCE(assets); - Q_INIT_RESOURCE(pe_dark); - Q_INIT_RESOURCE(pe_light); - Q_INIT_RESOURCE(pe_blue); - Q_INIT_RESOURCE(pe_colored); - Q_INIT_RESOURCE(OSX); - Q_INIT_RESOURCE(iOS); - Q_INIT_RESOURCE(flat); - return app.exec(); - } - case MultiMC::Failed: - return 1; - case MultiMC::Succeeded: - return 0; - } + Q_INIT_RESOURCE(pe_dark); + Q_INIT_RESOURCE(pe_light); + Q_INIT_RESOURCE(pe_blue); + Q_INIT_RESOURCE(pe_colored); + Q_INIT_RESOURCE(OSX); + Q_INIT_RESOURCE(iOS); + Q_INIT_RESOURCE(flat); + return app.exec(); + } + case MultiMC::Failed: + return 1; + case MultiMC::Succeeded: + return 0; + } } diff --git a/application/package/linux/MultiMC b/application/package/linux/MultiMC index 2d4be9f0..da6373bc 100755 --- a/application/package/linux/MultiMC +++ b/application/package/linux/MultiMC @@ -2,10 +2,10 @@ # Basic start script for running MultiMC with the libs packaged with it. function printerror { - printf "$1" - if which zenity >/dev/null; then zenity --error --text="$1" &>/dev/null; - elif which kdialog >/dev/null; then kdialog --error "$1" &>/dev/null; - fi + printf "$1" + if which zenity >/dev/null; then zenity --error --text="$1" &>/dev/null; + elif which kdialog >/dev/null; then kdialog --error "$1" &>/dev/null; + fi } if [[ $EUID -eq 0 ]]; then @@ -28,66 +28,66 @@ export QT_FONTPATH="${MMC_DIR}/fonts" # Detect missing dependencies... DEPS_LIST=`ldd "${MMC_DIR}"/plugins/*/*.so 2>/dev/null | grep "not found" | sort -u | awk -vORS=", " '{ print $1 }'` if [ "x$DEPS_LIST" = "x" ]; then - # We have all our dependencies. Run MultiMC. - echo "No missing dependencies found." + # We have all our dependencies. Run MultiMC. + echo "No missing dependencies found." - # Just to be sure... - chmod +x "${MMC_DIR}/bin/MultiMC" + # Just to be sure... + chmod +x "${MMC_DIR}/bin/MultiMC" - # Run MultiMC - "${MMC_DIR}/bin/MultiMC" -d "${MMC_DIR}" "$@" + # Run MultiMC + "${MMC_DIR}/bin/MultiMC" -d "${MMC_DIR}" "$@" - # Run MultiMC in valgrind - # valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${MMC_DIR}/bin/MultiMC" -d "${MMC_DIR}" "$@" + # Run MultiMC in valgrind + # valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${MMC_DIR}/bin/MultiMC" -d "${MMC_DIR}" "$@" - # Run MultiMC with callgrind, delay instrumentation - # valgrind --log-file="valgrind.log" --tool=callgrind --instr-atstart=no "${MMC_DIR}/bin/MultiMC" -d "${MMC_DIR}" "$@" - # use callgrind_control -i on/off to profile actions + # Run MultiMC with callgrind, delay instrumentation + # valgrind --log-file="valgrind.log" --tool=callgrind --instr-atstart=no "${MMC_DIR}/bin/MultiMC" -d "${MMC_DIR}" "$@" + # use callgrind_control -i on/off to profile actions - # Exit with MultiMC's exit code. - exit $? + # Exit with MultiMC's exit code. + exit $? else - # apt - if which apt-file &>/dev/null; then - LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` - COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do apt-file -l search $LIBRARY; done` - COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` - INSTALL_CMD="sudo apt-get install $COMMAND_LIBS" - # pacman - elif which pkgfile &>/dev/null; then - LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` - COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do pkgfile $LIBRARY; done` - COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` - INSTALL_CMD="sudo pacman -S $COMMAND_LIBS" - # dnf - elif which dnf &>/dev/null; then - LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` - COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do dnf whatprovides -q $LIBRARY; done` - COMMAND_LIBS=`echo "$COMMAND_LIBS" | grep -v 'Repo' | sort -u | awk -vORS=" " '{ print $1 }'` - INSTALL_CMD="sudo dnf install $COMMAND_LIBS" - # yum - elif which yum &>/dev/null; then - LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` - COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do yum whatprovides $LIBRARY; done` - COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` - INSTALL_CMD="sudo yum install $COMMAND_LIBS" - # zypper - elif which zypper &>/dev/null; then - LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` - COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do zypper wp $LIBRARY; done` - COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` - INSTALL_CMD="sudo zypper install $COMMAND_LIBS" - # emerge - elif which pfl &>/dev/null; then - LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` - COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do pfl $LIBRARY; done` - COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` - INSTALL_CMD="sudo emerge $COMMAND_LIBS" - fi + # apt + if which apt-file &>/dev/null; then + LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` + COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do apt-file -l search $LIBRARY; done` + COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` + INSTALL_CMD="sudo apt-get install $COMMAND_LIBS" + # pacman + elif which pkgfile &>/dev/null; then + LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` + COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do pkgfile $LIBRARY; done` + COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` + INSTALL_CMD="sudo pacman -S $COMMAND_LIBS" + # dnf + elif which dnf &>/dev/null; then + LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` + COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do dnf whatprovides -q $LIBRARY; done` + COMMAND_LIBS=`echo "$COMMAND_LIBS" | grep -v 'Repo' | sort -u | awk -vORS=" " '{ print $1 }'` + INSTALL_CMD="sudo dnf install $COMMAND_LIBS" + # yum + elif which yum &>/dev/null; then + LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` + COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do yum whatprovides $LIBRARY; done` + COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` + INSTALL_CMD="sudo yum install $COMMAND_LIBS" + # zypper + elif which zypper &>/dev/null; then + LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` + COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do zypper wp $LIBRARY; done` + COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` + INSTALL_CMD="sudo zypper install $COMMAND_LIBS" + # emerge + elif which pfl &>/dev/null; then + LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` + COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do pfl $LIBRARY; done` + COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` + INSTALL_CMD="sudo emerge $COMMAND_LIBS" + fi - MESSAGE="Error: MultiMC is missing the following libraries that it needs to work correctly:\n\t${DEPS_LIST}\nPlease install them from your distribution's package manager." - MESSAGE="$MESSAGE\n\nHint (please apply common sense): $INSTALL_CMD\n" + MESSAGE="Error: MultiMC is missing the following libraries that it needs to work correctly:\n\t${DEPS_LIST}\nPlease install them from your distribution's package manager." + MESSAGE="$MESSAGE\n\nHint (please apply common sense): $INSTALL_CMD\n" - printerror "$MESSAGE" - exit 1 + printerror "$MESSAGE" + exit 1 fi diff --git a/application/package/linux/multimc.desktop b/application/package/linux/multimc.desktop index 514b330f..770f24f1 100755 --- a/application/package/linux/multimc.desktop +++ b/application/package/linux/multimc.desktop @@ -8,3 +8,4 @@ Terminal=false Exec=multimc Icon=multimc Categories=Game +Keywords=game;minecraft; diff --git a/application/package/ubuntu/multimc/DEBIAN/control b/application/package/ubuntu/multimc/DEBIAN/control new file mode 100644 index 00000000..34c223a5 --- /dev/null +++ b/application/package/ubuntu/multimc/DEBIAN/control @@ -0,0 +1,12 @@ +Package: multimc +Version: 1.3-1 +Architecture: all +Maintainer: Petr Mrázek <peterix@gmail.com> +Section: games +Priority: optional +Installed-Size: 75 +Depends: zenity, desktop-file-utils, qt5-default +Recommends: openjdk-8-jre +Homepage: http://multimc.org +Description: A local install wrapper for MultiMC + diff --git a/application/package/ubuntu/multimc/DEBIAN/postrm b/application/package/ubuntu/multimc/DEBIAN/postrm new file mode 100755 index 00000000..f9bbc8a7 --- /dev/null +++ b/application/package/ubuntu/multimc/DEBIAN/postrm @@ -0,0 +1,3 @@ +#!/bin/sh +set -e +update-desktop-database diff --git a/application/package/ubuntu/multimc/opt/multimc/icon.svg b/application/package/ubuntu/multimc/opt/multimc/icon.svg new file mode 100644 index 00000000..8bb0e289 --- /dev/null +++ b/application/package/ubuntu/multimc/opt/multimc/icon.svg @@ -0,0 +1,353 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="68.26667" + height="68.26667" + id="svg4427" + version="1.1" + inkscape:version="0.92.1 r" + sodipodi:docname="multimc-smooth-biginfinity.svg" + inkscape:export-filename="/home/peterix/playground/MultiMC-icons/multimc-smooth-biginfinity.png" + inkscape:export-xdpi="180" + inkscape:export-ydpi="180"> + <defs + id="defs4429"> + <linearGradient + inkscape:collect="always" + id="linearGradient4809"> + <stop + style="stop-color:#98c867;stop-opacity:1" + offset="0" + id="stop4805" /> + <stop + style="stop-color:#5c9a33;stop-opacity:1" + offset="1" + id="stop4807" /> + </linearGradient> + <linearGradient + id="linearGradient5668" + inkscape:collect="always"> + <stop + id="stop5670" + offset="0" + style="stop-color:#75b54b;stop-opacity:1;" /> + <stop + id="stop5672" + offset="1" + style="stop-color:#75b54b;stop-opacity:0.6" /> + </linearGradient> + <linearGradient + id="linearGradient5084" + inkscape:collect="always"> + <stop + id="stop5086" + offset="0" + style="stop-color:#000000;stop-opacity:0.8" /> + <stop + id="stop5088" + offset="1" + style="stop-color:#000000;stop-opacity:0.35" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5668" + id="linearGradient5072" + x1="6.7342591" + y1="28.510933" + x2="50.506943" + y2="61.773685" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-0.01532073,-0.00938002)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5084" + id="linearGradient5082" + x1="14.312115" + y1="9.7948904" + x2="44.097023" + y2="82.973114" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5668" + id="linearGradient3281" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-0.01532073,-0.00938002)" + x1="6.7342591" + y1="28.510933" + x2="50.506943" + y2="61.773685" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5668" + id="linearGradient3283" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-0.01532073,-0.00938002)" + x1="6.7342591" + y1="28.510933" + x2="50.506943" + y2="61.773685" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5668" + id="linearGradient3286" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.2671525,0,0,0.89790119,-0.01941371,-0.00842234)" + x1="6.7342591" + y1="28.510933" + x2="50.506943" + y2="61.773685" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5084" + id="linearGradient3288" + gradientUnits="userSpaceOnUse" + x1="14.312115" + y1="9.7948904" + x2="44.097023" + y2="82.973114" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5084" + id="linearGradient3290" + gradientUnits="userSpaceOnUse" + x1="14.312115" + y1="9.7948904" + x2="44.097023" + y2="82.973114" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5084" + id="linearGradient3293" + gradientUnits="userSpaceOnUse" + x1="14.312115" + y1="9.7948904" + x2="44.097023" + y2="82.973114" + gradientTransform="scale(1.2671525,0.89790119)" /> + <linearGradient + inkscape:collect="always" + id="linearGradient5580"> + <stop + style="stop-color:#000000;stop-opacity:0.0627451" + offset="0" + id="stop5576" /> + <stop + style="stop-color:#322217;stop-opacity:0.58823532" + offset="1" + id="stop5578" /> + </linearGradient> + <linearGradient + id="linearGradient3999" + inkscape:collect="always"> + <stop + id="stop3995" + offset="0" + style="stop-color:#a3704b;stop-opacity:1" /> + <stop + id="stop3997" + offset="1" + style="stop-color:#6a4a33;stop-opacity:1" /> + </linearGradient> + <linearGradient + id="linearGradient2727" + inkscape:collect="always"> + <stop + id="stop2723" + offset="0" + style="stop-color:#966c4a;stop-opacity:1" /> + <stop + id="stop2725" + offset="1" + style="stop-color:#593d29;stop-opacity:1" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2727" + id="linearGradient2050" + gradientUnits="userSpaceOnUse" + x1="36.546478" + y1="33.80484" + x2="86.415741" + y2="97.065842" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3999" + id="radialGradient2052" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-9.105292e-4,-0.00104444)" + cx="34.133331" + cy="34.133335" + fx="34.133331" + fy="34.133335" + r="29.866665" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5580" + id="linearGradient2140" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-0.0010513,-9.083059e-4)" + x1="29.866674" + y1="29.867579" + x2="38.400005" + y2="38.400913" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5084" + id="linearGradient4790" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.2671525,0,0,0.89790119,-0.82864077,-1.0012743)" + x1="14.312115" + y1="9.7948904" + x2="44.097023" + y2="82.973114" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4809" + id="radialGradient4803" + cx="-42.66758" + cy="-34.134373" + fx="-42.66758" + fy="-34.134373" + r="34.132812" + gradientTransform="matrix(1.7500268,0.1250019,-0.01781176,0.24936465,95.393964,18.110151)" + gradientUnits="userSpaceOnUse" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="3.6203867" + inkscape:cx="52.171166" + inkscape:cy="11.292073" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:document-units="px" + inkscape:grid-bbox="true" + inkscape:window-width="1368" + inkscape:window-height="905" + inkscape:window-x="2452" + inkscape:window-y="723" + inkscape:window-maximized="0" + inkscape:snap-bbox="true" + inkscape:bbox-paths="false" + inkscape:snap-bbox-edge-midpoints="false" + inkscape:bbox-nodes="true" + inkscape:snap-bbox-midpoints="false" + inkscape:snap-smooth-nodes="true" + inkscape:snap-midpoints="false" + inkscape:snap-intersection-paths="true" + inkscape:object-paths="true" + inkscape:snap-object-midpoints="true" + inkscape:snap-text-baseline="true" + inkscape:snap-center="true"> + <inkscape:grid + type="xygrid" + id="grid4446" + empspacing="16" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + spacingx="4.2666667" + spacingy="4.2666667" + originx="0" + originy="0" /> + </sodipodi:namedview> + <metadata + id="metadata4432"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:label="Layer 1" + inkscape:groupmode="layer"> + <g + id="g2048" + transform="translate(9.113e-4,0.00104183)"> + <rect + rx="8.5333338" + ry="8.5333338" + style="fill:url(#linearGradient2050);fill-opacity:1;stroke:none;stroke-width:17.06666756" + id="rect2026" + width="68.26667" + height="68.26667" + x="-1.3322676e-15" + y="3.0270508e-06" /> + <rect + rx="4.2666626" + y="4.2656283" + x="4.2657552" + height="59.733334" + width="59.73333" + id="rect2028" + style="fill:url(#radialGradient2052);fill-opacity:1;stroke:none;stroke-width:14.93333435" + ry="4.2666669" /> + <path + inkscape:connector-curvature="0" + id="path4811" + d="m 4.2669272,4.2645856 -9.11e-4,8.5333334 h 4.267577 v 4.267579 h 8.5332038 v 4.265625 h 4.265625 V 8.5322946 H 25.6 v 8.5332034 h 4.265625 v -4.267579 h 4.267578 v 8.533204 h 4.265625 v -4.265625 h 4.267578 v 4.265625 h 4.267579 v -4.265625 h 4.265624 v -4.267579 h 4.267579 v 4.267579 h 8.533203 l -1.3e-4,-12.8009124 z" + style="opacity:0.6;fill:#593d29;fill-opacity:1;stroke:none;stroke-width:17.06666756" + sodipodi:nodetypes="ccccccccccccccccccccccccccc" /> + <path + style="fill:url(#radialGradient4803);fill-opacity:1;stroke:none;stroke-width:17.06666756" + d="m 8.5329442,-0.0018207 c -4.7274675,0 -8.5332035,3.805736 -8.5332035,8.533203 v 4.2675787 h 4.265625 V 8.5313823 c 0,-0.521698 0.105433,-1.01339 0.27539,-1.47461 -0.169616,0.460814 -0.27539,0.953462 -0.27539,1.47461 h 4.2675785 v 4.2675787 h 4.2656248 4.267578 v 4.265625 h 4.265625 V 12.798961 8.5313823 4.2657573 h 4.267578 v 4.265625 4.2675787 h 4.265625 V 8.5313823 h 4.267578 v 4.2675787 4.265625 h 4.265625 v -4.265625 h 4.267578 v 4.265625 h 4.267579 v -4.265625 h 4.265624 V 8.5313823 h 4.267579 v 4.2675787 h 4.265625 4.267578 V 8.5313823 h 4.265625 c 0,-4.727467 -3.805737,-8.533203 -8.533203,-8.533203 z m -3.019531,5.513671 c -0.318089,0.317888 -0.570428,0.695824 -0.7753915,1.101563 0.2048795,-0.405231 0.4576385,-0.784012 0.7753915,-1.101563 z" + id="path4794" + inkscape:connector-curvature="0" /> + <path + style="opacity:1;fill:url(#linearGradient2140);fill-opacity:1;stroke:none;stroke-width:17.06666756" + d="m 8.5322887,-9.083059e-4 c -4.72747,0 -8.5332,3.8057359059 -8.5332,8.5332029059 V 59.731515 c 0,4.727467 3.80573,8.535156 8.5332,8.535156 H 59.731502 c 4.72747,0 8.5332,-3.807689 8.5332,-8.535156 V 8.5322946 c 0,-4.727467 -3.80573,-8.5332029059 -8.5332,-8.5332029059 z m 0,4.2675779059 H 59.731502 c 2.36373,0 4.26758,1.901892 4.26758,4.265625 V 59.731515 c 0,2.363733 -1.90385,4.267578 -4.26758,4.267578 H 8.5322887 c -2.36373,0 -4.26758,-1.903845 -4.26758,-4.267578 V 8.5322946 c 0,-2.363733 1.90385,-4.265625 4.26758,-4.265625 z" + id="path2046" + inkscape:connector-curvature="0" /> + </g> + <g + id="g1092"> + <path + inkscape:connector-curvature="0" + id="path4786" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient4790);fill-opacity:1;stroke:none;stroke-width:1.06666672;opacity:0.5" + d="m 38.886673,44.940882 c -0.974277,-0.801673 -2.231353,-2.137814 -3.771231,-4.008427 -2.105641,2.672298 -4.085536,4.598569 -5.939688,5.778816 -2.325625,1.425227 -5.295467,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124578 -10.5594458,-3.37376 C 6.7526311,43.114834 5.275567,39.986037 5.2755773,36.088937 5.275567,32.347763 6.7526311,29.207831 9.7067742,26.669132 12.346618,24.419991 15.897857,23.295407 20.360501,23.295373 c 2.294138,3.4e-5 4.289747,0.334069 5.986829,1.002107 1.979863,0.73491 3.645488,1.737016 4.996881,3.00632 1.257039,1.135751 2.514115,2.471891 3.771231,4.008428 2.105563,-2.672257 4.085457,-4.598527 5.939689,-5.778816 2.325544,-1.425186 5.295385,-2.137794 8.909533,-2.137828 4.242577,3.4e-5 7.762388,1.124618 10.559447,3.37376 2.954063,2.360546 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477134,6.881147 -4.431197,9.419806 -2.639925,2.24918 -6.191163,3.373767 -10.653727,3.373758 -2.294219,9e-6 -4.289826,-0.334026 -5.98683,-1.002106 -1.697101,-0.601255 -3.362726,-1.603361 -4.996881,-3.006321 M 19.747676,44.473233 c 5.185412,1.1e-5 9.333763,-2.672271 12.445062,-8.016856 -3.991253,-5.834464 -8.139602,-8.751705 -12.445062,-8.751733 -3.142715,2.8e-5 -5.515446,0.801713 -7.118198,2.405057 -1.728498,1.71474 -2.592737,3.707818 -2.592722,5.979236 -1.5e-5,2.494152 0.864224,4.509499 2.592722,6.046046 1.759887,1.558846 4.132618,2.338261 7.118198,2.33825 M 50.483209,27.77145 c -4.682663,2.9e-5 -8.831013,2.672312 -12.445062,8.016856 3.959745,5.834503 8.108095,8.751746 12.445062,8.751733 3.142633,1.3e-5 5.515364,-0.801671 7.118198,-2.405056 1.728416,-1.714701 2.592656,-3.707778 2.592722,-5.979238 -6.6e-5,-2.49411 -0.864306,-4.509456 -2.592722,-6.046044 -1.759968,-1.558805 -4.132699,-2.338222 -7.118198,-2.338251" /> + <path + d="m 39.715314,45.942156 c -0.974277,-0.801673 -2.231353,-2.137814 -3.771231,-4.008427 -2.105641,2.672298 -4.085536,4.598569 -5.939688,5.778816 -2.325625,1.425227 -5.295467,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124578 -10.559446,-3.37376 -2.9541431,-2.360505 -4.4312072,-5.489302 -4.4311969,-9.386402 -1.03e-5,-3.741174 1.4770538,-6.881106 4.4311969,-9.419805 2.639844,-2.249141 6.191083,-3.373725 10.653727,-3.373759 2.294138,3.4e-5 4.289747,0.334069 5.986829,1.002107 1.979863,0.73491 3.645488,1.737016 4.996881,3.00632 1.257039,1.135751 2.514115,2.471891 3.771231,4.008428 2.105563,-2.672257 4.085457,-4.598527 5.939689,-5.778816 2.325544,-1.425186 5.295385,-2.137794 8.909533,-2.137828 4.242577,3.4e-5 7.762388,1.124618 10.559447,3.37376 2.954063,2.360546 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477134,6.881147 -4.431197,9.419806 -2.639925,2.24918 -6.191163,3.373767 -10.653727,3.373758 -2.294219,9e-6 -4.289826,-0.334026 -5.98683,-1.002106 -1.697101,-0.601255 -3.362726,-1.603361 -4.996881,-3.006321 M 20.576317,45.474507 c 5.185412,1.1e-5 9.333763,-2.672271 12.445062,-8.016856 -3.991253,-5.834464 -8.139602,-8.751705 -12.445062,-8.751733 -3.142715,2.8e-5 -5.515446,0.801713 -7.118198,2.405057 -1.728498,1.71474 -2.592737,3.707818 -2.592722,5.979236 -1.5e-5,2.494152 0.864224,4.509499 2.592722,6.046046 1.759887,1.558846 4.132618,2.338261 7.118198,2.33825 M 51.31185,28.772724 c -4.682663,2.9e-5 -8.831013,2.672312 -12.445062,8.016856 3.959745,5.834503 8.108095,8.751746 12.445062,8.751733 3.142633,1.3e-5 5.515364,-0.801671 7.118198,-2.405056 1.728416,-1.714701 2.592656,-3.707778 2.592722,-5.979238 -6.6e-5,-2.49411 -0.864306,-4.509456 -2.592722,-6.046044 C 56.67008,29.55217 54.297349,28.772753 51.31185,28.772724" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3293);fill-opacity:1;stroke:none;stroke-width:1.06666672;opacity:0.5" + id="path3279" + inkscape:connector-curvature="0" /> + <path + d="m 37.904564,42.951873 c -0.974278,-0.801672 -2.231352,-2.137814 -3.771231,-4.008428 -2.105642,2.672298 -4.085537,4.598568 -5.939688,5.778817 -2.325625,1.425227 -5.295466,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124577 -10.5594464,-3.37376 -2.9541428,-2.360505 -4.4312068,-5.489302 -4.4311963,-9.386401 -1.05e-5,-3.741175 1.4770535,-6.881107 4.4311963,-9.419805 2.6398444,-2.249142 6.1910824,-3.373727 10.6537284,-3.37376 2.294137,3.3e-5 4.289745,0.334068 5.986829,1.002107 1.979863,0.734909 3.645487,1.737016 4.99688,3.00632 1.257039,1.13575 2.514116,2.471891 3.771231,4.008428 2.105562,-2.672257 4.085456,-4.598528 5.939689,-5.778817 2.325544,-1.425185 5.295387,-2.137795 8.909534,-2.137828 4.242576,3.3e-5 7.762387,1.12462 10.559446,3.373761 2.954062,2.360545 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477135,6.881148 -4.431197,9.419805 -2.639924,2.249182 -6.191164,3.373767 -10.653728,3.37376 -2.294217,7e-6 -4.289826,-0.334028 -5.986828,-1.002107 -1.697101,-0.601254 -3.362727,-1.603361 -4.996882,-3.006321 m -19.138997,-0.46765 c 5.185412,1.3e-5 9.333762,-2.67227 12.445062,-8.016856 -3.991252,-5.834462 -8.139602,-8.751704 -12.445062,-8.751733 -3.142714,2.9e-5 -5.515444,0.801714 -7.118198,2.405056 -1.7284972,1.714743 -2.5927368,3.707819 -2.5927216,5.979239 -1.52e-5,2.49415 0.8642244,4.509496 2.5927216,6.046045 1.759888,1.558845 4.132618,2.338262 7.118198,2.338249 M 49.5011,25.782442 c -4.682663,2.8e-5 -8.831014,2.672311 -12.445063,8.016855 3.959745,5.834504 8.108096,8.751745 12.445063,8.751733 3.142634,1.2e-5 5.515365,-0.801673 7.118198,-2.405056 1.728417,-1.7147 2.592657,-3.707778 2.592721,-5.979238 -6.4e-5,-2.49411 -0.864304,-4.509456 -2.592721,-6.046046 C 54.85933,26.561886 52.486599,25.78247 49.5011,25.782442" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3286);fill-opacity:1;stroke:none;stroke-width:1.06666672" + id="path3272" + inkscape:connector-curvature="0" /> + <path + sodipodi:nodetypes="ccscsccccccccccccccccccccscscccccsccscccccc" + inkscape:connector-curvature="0" + id="text5100" + d="m 19.4,21.166667 c -4.462644,3.3e-5 -8.026822,1.150858 -10.6666667,3.4 -2.9541428,2.538698 -4.4333436,5.658825 -4.4333333,9.4 -1.03e-5,3.897098 1.4791905,7.039495 4.4333333,9.4 -1.622701,-2.044271 -2.433341,-4.51168 -2.4333333,-7.4 -1.03e-5,-3.741175 1.4791905,-6.861302 4.433333,-9.4 2.639845,-2.249142 6.204023,-3.399967 10.666667,-3.4 2.294138,3.3e-5 4.302916,0.365295 6,1.033333 1.979862,0.73491 3.615274,1.730695 4.966667,3 0.06836,0.06177 0.131637,0.137049 0.2,0.2 -0.731813,-0.797005 -1.468213,-1.538822 -2.2,-2.2 -1.351393,-1.269305 -2.986805,-2.26509 -4.966667,-3 -1.697084,-0.668038 -3.705862,-1.0333 -6,-1.033333 z m 29.6,0.1 c -3.614148,3.3e-5 -6.574457,0.74148 -8.9,2.166666 -1.818222,1.157367 -3.923451,3.291388 -5.983333,5.883334 0.618278,0.658774 1.248369,1.377605 1.866666,2.133333 2.105562,-2.672257 4.262434,-4.836378 6.116667,-6.016667 2.325543,-1.425186 5.285852,-2.166633 8.9,-2.166666 4.242576,3.3e-5 7.769607,1.150858 10.566667,3.4 -0.570388,-0.722129 -1.227721,-1.382884 -2,-2 C 56.769607,22.417525 53.242576,21.2667 49,21.266667 Z m 8.866667,8.1 c 0.9092,1.305235 1.366619,2.857751 1.366666,4.666666 -6.5e-5,2.271461 -0.871584,4.285301 -2.6,6 -1.602834,1.603384 -3.957366,2.400012 -7.1,2.4 -2.653707,8e-6 -5.320858,-1.032242 -7.833333,-3.216666 3.136636,3.509305 6.469807,5.216676 9.833333,5.216666 3.142634,1.2e-5 5.497166,-0.796616 7.1,-2.4 1.728416,-1.714699 2.599935,-3.728539 2.6,-6 -6.5e-5,-2.49411 -0.871584,-4.496744 -2.6,-6.033333 -0.24943,-0.220921 -0.49262,-0.443723 -0.766666,-0.633333 z m -26.633334,4.966666 c -3.1113,5.344585 -7.247921,8.033345 -12.433333,8.033334 -2.58055,1e-5 -4.543473,-0.352086 -6.208333,-1.516667 0.348871,0.50642 0.590094,0.752276 1.075,1.183333 1.759888,1.558846 4.147753,2.333345 7.133333,2.333334 5.185412,1.1e-5 9.322033,-2.688749 12.433333,-8.033334 z m 4.933334,6.5 c -0.04103,0.05207 -0.09239,0.08182 -0.133334,0.133334 0.687326,0.744419 1.306949,1.359747 1.833334,1.8 -0.529404,-0.580895 -1.078447,-1.178283 -1.7,-1.933334 z" + style="font-style:normal;font-weight:normal;font-size:76.18933868px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:0.3;fill:#ccff00;fill-opacity:1;stroke:none;stroke-width:1.06666672" /> + <path + sodipodi:nodetypes="ccsccscccccccccccccccccccscscccccsccsccccccc" + id="text5058-0" + d="m 19.730474,21.54714 c -4.462645,3.3e-5 -8.026823,1.150859 -10.6666669,3.4 -2.9541429,2.538699 -4.433344,5.658826 -4.4333333,9.4 -1.07e-5,3.897099 1.4791904,7.039495 4.4333333,9.4 0.042837,0.03444 0.090155,0.06608 0.1333334,0.1 -2.2392086,-2.228193 -3.3666752,-5.040417 -3.3666667,-8.433333 -1.07e-5,-3.741174 1.4791904,-6.861301 4.4333332,-9.4 2.639844,-2.249141 6.204022,-3.399967 10.666667,-3.4 2.294137,3.3e-5 4.302916,0.365295 6,1.033333 1.870874,0.694455 3.42364,1.628367 4.733333,2.8 -0.314265,-0.308986 -0.652406,-0.582729 -0.966667,-0.866666 -1.351393,-1.269305 -2.986804,-2.265091 -4.966666,-3 -1.697084,-0.668039 -3.705863,-1.033301 -6,-1.033334 z m 29.6,0.1 c -3.614149,3.3e-5 -6.574457,0.741481 -8.9,2.166667 -1.813279,1.154221 -3.963039,3.235656 -6.016667,5.816667 0.355649,0.402628 0.711011,0.798625 1.066667,1.233333 2.105561,-2.672257 4.295767,-4.803044 6.15,-5.983333 2.325543,-1.425187 5.285851,-2.166634 8.9,-2.166667 4.22442,3.3e-5 7.742084,1.136734 10.533333,3.366667 -0.36096,-0.367566 -0.745726,-0.696967 -1.166667,-1.033334 -2.797059,-2.249141 -6.32409,-3.399967 -10.566666,-3.4 z m 8.233333,7.333334 c 1.323326,1.449243 1.999942,3.250987 2,5.433333 -6.5e-5,2.27146 -0.871584,4.2853 -2.6,6 -1.602834,1.603383 -3.957366,2.400012 -7.1,2.4 -2.406328,6e-6 -4.776468,-0.90386 -7.066667,-2.7 2.669147,2.483838 5.436929,3.766674 8.266667,3.766667 3.142634,1.1e-5 5.497166,-0.796617 7.1,-2.4 1.728416,-1.7147 2.599935,-3.72854 2.6,-6 -6.5e-5,-2.49411 -0.871584,-4.496745 -2.6,-6.033334 -0.185641,-0.164422 -0.400724,-0.319587 -0.6,-0.466666 z m -26,5.733333 c -3.1113,5.344584 -7.247921,8.033345 -12.433333,8.033333 -2.612382,1.1e-5 -4.759372,-0.60651 -6.433334,-1.8 0.166027,0.176488 0.313947,0.367942 0.5,0.533334 1.759888,1.558845 4.147754,2.333345 7.133334,2.333333 5.185412,1.2e-5 9.322033,-2.688749 12.433333,-8.033333 z m 4.133333,5.566667 c -0.04657,0.05909 -0.08689,0.108298 -0.133333,0.166666 1.038571,1.18897 1.9748,2.169945 2.7,2.766667 0.06249,0.05364 0.137426,0.08086 0.2,0.133333 -0.792178,-0.781249 -1.706288,-1.778539 -2.766667,-3.066666 z" + style="font-style:normal;font-weight:normal;font-size:76.18933868px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:0.6;fill:#ccff00;fill-opacity:1;stroke:none;stroke-width:1.06666672" + inkscape:connector-curvature="0" /> + </g> + </g> +</svg> diff --git a/application/package/ubuntu/multimc/opt/multimc/run.sh b/application/package/ubuntu/multimc/opt/multimc/run.sh new file mode 100755 index 00000000..c493a513 --- /dev/null +++ b/application/package/ubuntu/multimc/opt/multimc/run.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +INSTDIR="${XDG_DATA_HOME-$HOME/.local/share}/multimc" + +if [ `getconf LONG_BIT` = "64" ] +then + PACKAGE="mmc-stable-lin64.tar.gz" +else + PACKAGE="mmc-stable-lin32.tar.gz" +fi + +deploy() { + mkdir -p $INSTDIR + cd ${INSTDIR} + + wget --progress=dot:force "https://files.multimc.org/downloads/${PACKAGE}" 2>&1 | sed -u 's/.* \([0-9]\+%\)\ \+\([0-9.]\+.\) \(.*\)/\1\n# Downloading at \2\/s, ETA \3/' | zenity --progress --auto-close --auto-kill --title="Downloading MultiMC..." + + tar -xzf ${PACKAGE} --transform='s,MultiMC/,,' + rm ${PACKAGE} + chmod +x MultiMC +} + +runmmc() { + cd ${INSTDIR} + ./MultiMC "$@" +} + +if [[ ! -f ${INSTDIR}/MultiMC ]]; then + deploy + runmmc "$@" +else + runmmc "$@" +fi diff --git a/application/package/ubuntu/multimc/usr/share/applications/multimc.desktop b/application/package/ubuntu/multimc/usr/share/applications/multimc.desktop new file mode 100755 index 00000000..e0456f89 --- /dev/null +++ b/application/package/ubuntu/multimc/usr/share/applications/multimc.desktop @@ -0,0 +1,16 @@ +[Desktop Entry] +Categories=Game; +Exec=/opt/multimc/run.sh +Icon=/opt/multimc/icon.svg +Keywords=game;Minecraft; +MimeType= +Name=MultiMC 5 +Path= +StartupNotify=true +Terminal=false +TerminalOptions= +Type=Application +X-DBUS-ServiceName= +X-DBUS-StartupType= +X-KDE-SubstituteUID=false +X-KDE-Username= diff --git a/application/package/ubuntu/readme.md b/application/package/ubuntu/readme.md new file mode 100644 index 00000000..5b0d6b27 --- /dev/null +++ b/application/package/ubuntu/readme.md @@ -0,0 +1,12 @@ +# What is this? +A simple ubuntu package for MultiMC that wraps the contains a script that downloads and installs real MultiMC on ubuntu based systems. + +It contains a `.desktop` file, an icon, and a simple script that does the heavy lifting. + +# How to build this? +You need dpkg utils. Rename the `multimc` folder to `multimc_1.3-1` and then run: +``` +fakeroot dpkg-deb --build multimc_1.3-1 +``` + +Replace the version with whatever is appropriate. diff --git a/application/pagedialog/PageDialog.cpp b/application/pagedialog/PageDialog.cpp index 1b5284d2..a1a78d78 100644 --- a/application/pagedialog/PageDialog.cpp +++ b/application/pagedialog/PageDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,35 +26,36 @@ #include "widgets/PageContainer.h" PageDialog::PageDialog(BasePageProvider *pageProvider, QString defaultId, QWidget *parent) - : QDialog(parent) + : QDialog(parent) { - setWindowTitle(pageProvider->dialogTitle()); - m_container = new PageContainer(pageProvider, defaultId, this); + setWindowTitle(pageProvider->dialogTitle()); + m_container = new PageContainer(pageProvider, defaultId, this); - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addWidget(m_container); - mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0, 0, 0, 0); - setLayout(mainLayout); + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(m_container); + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0, 0, 0, 0); + setLayout(mainLayout); - QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Close); - buttons->button(QDialogButtonBox::Close)->setDefault(true); - m_container->addButtons(buttons); + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Close); + buttons->button(QDialogButtonBox::Close)->setDefault(true); + buttons->setContentsMargins(6, 0, 6, 0); + m_container->addButtons(buttons); - connect(buttons->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SLOT(close())); - connect(buttons->button(QDialogButtonBox::Help), SIGNAL(clicked()), m_container, SLOT(help())); + connect(buttons->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SLOT(close())); + connect(buttons->button(QDialogButtonBox::Help), SIGNAL(clicked()), m_container, SLOT(help())); - restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("PagedGeometry").toByteArray())); + restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("PagedGeometry").toByteArray())); } void PageDialog::closeEvent(QCloseEvent *event) { - qDebug() << "Paged dialog close requested"; - if (m_container->prepareToClose()) - { - qDebug() << "Paged dialog close approved"; - MMC->settings()->set("PagedGeometry", saveGeometry().toBase64()); - qDebug() << "Paged dialog geometry saved"; - QDialog::closeEvent(event); - } + qDebug() << "Paged dialog close requested"; + if (m_container->prepareToClose()) + { + qDebug() << "Paged dialog close approved"; + MMC->settings()->set("PagedGeometry", saveGeometry().toBase64()); + qDebug() << "Paged dialog geometry saved"; + QDialog::closeEvent(event); + } } diff --git a/application/pagedialog/PageDialog.h b/application/pagedialog/PageDialog.h index a287f9c9..b92d90b5 100644 --- a/application/pagedialog/PageDialog.h +++ b/application/pagedialog/PageDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,15 +21,15 @@ class PageContainer; class PageDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit PageDialog(BasePageProvider *pageProvider, QString defaultId = QString(), QWidget *parent = 0); - virtual ~PageDialog() {} + explicit PageDialog(BasePageProvider *pageProvider, QString defaultId = QString(), QWidget *parent = 0); + virtual ~PageDialog() {} private slots: - virtual void closeEvent(QCloseEvent *event); + virtual void closeEvent(QCloseEvent *event); private: - PageContainer * m_container; + PageContainer * m_container; }; diff --git a/application/pages/BasePage.h b/application/pages/BasePage.h index d4547770..2ad56d71 100644 --- a/application/pages/BasePage.h +++ b/application/pages/BasePage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,35 +24,35 @@ class BasePage { public: - virtual ~BasePage() {} - virtual QString id() const = 0; - virtual QString displayName() const = 0; - virtual QIcon icon() const = 0; - virtual bool apply() { return true; } - virtual bool shouldDisplay() const { return true; } - virtual QString helpPage() const { return QString(); } - void opened() - { - isOpened = true; - openedImpl(); - } - void closed() - { - isOpened = false; - closedImpl(); - } - virtual void openedImpl() {} - virtual void closedImpl() {} - virtual void setParentContainer(BasePageContainer * container) - { - m_container = container; - }; + virtual ~BasePage() {} + virtual QString id() const = 0; + virtual QString displayName() const = 0; + virtual QIcon icon() const = 0; + virtual bool apply() { return true; } + virtual bool shouldDisplay() const { return true; } + virtual QString helpPage() const { return QString(); } + void opened() + { + isOpened = true; + openedImpl(); + } + void closed() + { + isOpened = false; + closedImpl(); + } + virtual void openedImpl() {} + virtual void closedImpl() {} + virtual void setParentContainer(BasePageContainer * container) + { + m_container = container; + }; public: - int stackIndex = -1; - int listIndex = -1; + int stackIndex = -1; + int listIndex = -1; protected: - BasePageContainer * m_container = nullptr; - bool isOpened = false; + BasePageContainer * m_container = nullptr; + bool isOpened = false; }; typedef std::shared_ptr<BasePage> BasePagePtr; diff --git a/application/pages/BasePageContainer.h b/application/pages/BasePageContainer.h index ff7315c2..f8c7adeb 100644 --- a/application/pages/BasePageContainer.h +++ b/application/pages/BasePageContainer.h @@ -3,8 +3,8 @@ class BasePageContainer { public: - virtual ~BasePageContainer(){}; - virtual bool selectPage(QString pageId) = 0; - virtual void refreshContainer() = 0; - virtual bool requestClose() = 0; + virtual ~BasePageContainer(){}; + virtual bool selectPage(QString pageId) = 0; + virtual void refreshContainer() = 0; + virtual bool requestClose() = 0; }; diff --git a/application/pages/BasePageProvider.h b/application/pages/BasePageProvider.h index 1cc7458a..ad29f163 100644 --- a/application/pages/BasePageProvider.h +++ b/application/pages/BasePageProvider.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,46 +22,47 @@ class BasePageProvider { public: - virtual QList<BasePage *> getPages() = 0; - virtual QString dialogTitle() = 0; + virtual QList<BasePage *> getPages() = 0; + virtual QString dialogTitle() = 0; }; class GenericPageProvider : public BasePageProvider { - typedef std::function<BasePage *()> PageCreator; + typedef std::function<BasePage *()> PageCreator; public: - explicit GenericPageProvider(const QString &dialogTitle) - : m_dialogTitle(dialogTitle) - { - } + explicit GenericPageProvider(const QString &dialogTitle) + : m_dialogTitle(dialogTitle) + { + } + virtual ~GenericPageProvider() {} - QList<BasePage *> getPages() override - { - QList<BasePage *> pages; - for (PageCreator creator : m_creators) - { - pages.append(creator()); - } - return pages; - } - QString dialogTitle() override { return m_dialogTitle; } + QList<BasePage *> getPages() override + { + QList<BasePage *> pages; + for (PageCreator creator : m_creators) + { + pages.append(creator()); + } + return pages; + } + QString dialogTitle() override { return m_dialogTitle; } - void setDialogTitle(const QString &title) - { - m_dialogTitle = title; - } - void addPageCreator(PageCreator page) - { - m_creators.append(page); - } + void setDialogTitle(const QString &title) + { + m_dialogTitle = title; + } + void addPageCreator(PageCreator page) + { + m_creators.append(page); + } - template<typename PageClass> - void addPage() - { - addPageCreator([](){return new PageClass();}); - } + template<typename PageClass> + void addPage() + { + addPageCreator([](){return new PageClass();}); + } private: - QList<PageCreator> m_creators; - QString m_dialogTitle; + QList<PageCreator> m_creators; + QString m_dialogTitle; }; diff --git a/application/pages/global/AccountListPage.cpp b/application/pages/global/AccountListPage.cpp index 63943174..c14134f3 100644 --- a/application/pages/global/AccountListPage.cpp +++ b/application/pages/global/AccountListPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ #include "ui_AccountListPage.h" #include <QItemSelectionModel> +#include <QMenu> #include <QDebug> @@ -34,120 +35,159 @@ #include "MultiMC.h" AccountListPage::AccountListPage(QWidget *parent) - : QWidget(parent), ui(new Ui::AccountListPage) + : QMainWindow(parent), ui(new Ui::AccountListPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); + ui->setupUi(this); + ui->listView->setEmptyString(tr( + "Welcome!\n" + "If you're new here, you can click the \"Add\" button to add your Mojang or Minecraft account." + )); + ui->listView->setEmptyMode(VersionListView::String); + ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); - m_accounts = MMC->accounts(); + m_accounts = MMC->accounts(); - ui->listView->setModel(m_accounts.get()); - ui->listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + ui->listView->setModel(m_accounts.get()); + ui->listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + ui->listView->setSelectionMode(QAbstractItemView::SingleSelection); - // Expand the account column - ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch); + // Expand the account column + ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch); - QItemSelectionModel *selectionModel = ui->listView->selectionModel(); + QItemSelectionModel *selectionModel = ui->listView->selectionModel(); - connect(selectionModel, &QItemSelectionModel::selectionChanged, - [this](const QItemSelection &sel, const QItemSelection &dsel) - { updateButtonStates(); }); + connect(selectionModel, &QItemSelectionModel::selectionChanged, [this](const QItemSelection &sel, const QItemSelection &dsel) { + updateButtonStates(); + }); + connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu); - connect(m_accounts.get(), SIGNAL(listChanged()), SLOT(listChanged())); - connect(m_accounts.get(), SIGNAL(activeAccountChanged()), SLOT(listChanged())); + connect(m_accounts.get(), SIGNAL(listChanged()), SLOT(listChanged())); + connect(m_accounts.get(), SIGNAL(activeAccountChanged()), SLOT(listChanged())); - updateButtonStates(); + updateButtonStates(); } AccountListPage::~AccountListPage() { - delete ui; + delete ui; } +void AccountListPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->listView->mapToGlobal(pos)); + delete menu; +} + +void AccountListPage::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } + QMainWindow::changeEvent(event); +} + +QMenu * AccountListPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction(ui->toolBar->toggleViewAction() ); + return filteredMenu; +} + + void AccountListPage::listChanged() { - updateButtonStates(); + updateButtonStates(); } -void AccountListPage::on_addAccountBtn_clicked() +void AccountListPage::on_actionAdd_triggered() { - addAccount(tr("Please enter your Mojang or Minecraft account username and password to add " - "your account.")); + addAccount(tr("Please enter your Mojang or Minecraft account username and password to add " + "your account.")); } -void AccountListPage::on_rmAccountBtn_clicked() +void AccountListPage::on_actionRemove_triggered() { - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.size() > 0) - { - QModelIndex selected = selection.first(); - m_accounts->removeAccount(selected); - } + QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.size() > 0) + { + QModelIndex selected = selection.first(); + m_accounts->removeAccount(selected); + } } -void AccountListPage::on_setDefaultBtn_clicked() +void AccountListPage::on_actionSetDefault_triggered() { - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.size() > 0) - { - QModelIndex selected = selection.first(); - MojangAccountPtr account = - selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>(); - m_accounts->setActiveAccount(account->username()); - } + QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.size() > 0) + { + QModelIndex selected = selection.first(); + MojangAccountPtr account = + selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>(); + m_accounts->setActiveAccount(account->username()); + } } -void AccountListPage::on_noDefaultBtn_clicked() +void AccountListPage::on_actionNoDefault_triggered() { - m_accounts->setActiveAccount(""); + m_accounts->setActiveAccount(""); } void AccountListPage::updateButtonStates() { - // If there is no selection, disable buttons that require something selected. - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - - ui->rmAccountBtn->setEnabled(selection.size() > 0); - ui->setDefaultBtn->setEnabled(selection.size() > 0); - ui->uploadSkinBtn->setEnabled(selection.size() > 0); + // If there is no selection, disable buttons that require something selected. + QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); + + ui->actionRemove->setEnabled(selection.size() > 0); + ui->actionSetDefault->setEnabled(selection.size() > 0); + ui->actionUploadSkin->setEnabled(selection.size() > 0); + + if(m_accounts->activeAccount().get() == nullptr) { + ui->actionNoDefault->setEnabled(false); + ui->actionNoDefault->setChecked(true); + } + else { + ui->actionNoDefault->setEnabled(true); + ui->actionNoDefault->setChecked(false); + } - ui->noDefaultBtn->setDown(m_accounts->activeAccount().get() == nullptr); } void AccountListPage::addAccount(const QString &errMsg) { - // TODO: The login dialog isn't quite done yet - MojangAccountPtr account = LoginDialog::newAccount(this, errMsg); - - if (account != nullptr) - { - m_accounts->addAccount(account); - if (m_accounts->count() == 1) - m_accounts->setActiveAccount(account->username()); - - // Grab associated player skins - auto job = new NetJob("Player skins: " + account->username()); - - for (AccountProfile profile : account->profiles()) - { - auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png"); - auto action = Net::Download::makeCached(QUrl("https://" + URLConstants::SKINS_BASE + profile.id + ".png"), meta); - job->addNetAction(action); - meta->setStale(true); - } - - job->start(); - } + // TODO: The login dialog isn't quite done yet + MojangAccountPtr account = LoginDialog::newAccount(this, errMsg); + + if (account != nullptr) + { + m_accounts->addAccount(account); + if (m_accounts->count() == 1) + m_accounts->setActiveAccount(account->username()); + + // Grab associated player skins + auto job = new NetJob("Player skins: " + account->username()); + + for (AccountProfile profile : account->profiles()) + { + auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png"); + auto action = Net::Download::makeCached(QUrl(URLConstants::SKINS_BASE + profile.id + ".png"), meta); + job->addNetAction(action); + meta->setStale(true); + } + + job->start(); + } } -void AccountListPage::on_uploadSkinBtn_clicked() +void AccountListPage::on_actionUploadSkin_triggered() { - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.size() > 0) - { - QModelIndex selected = selection.first(); - MojangAccountPtr account = selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>(); - SkinUploadDialog dialog(account, this); - dialog.exec(); - } + QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.size() > 0) + { + QModelIndex selected = selection.first(); + MojangAccountPtr account = selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>(); + SkinUploadDialog dialog(account, this); + dialog.exec(); + } } diff --git a/application/pages/global/AccountListPage.h b/application/pages/global/AccountListPage.h index fa5561fe..4c8bc00b 100644 --- a/application/pages/global/AccountListPage.h +++ b/application/pages/global/AccountListPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ #pragma once -#include <QDialog> +#include <QMainWindow> #include <memory> #include "pages/BasePage.h" @@ -30,59 +30,64 @@ class AccountListPage; class AuthenticateTask; -class AccountListPage : public QWidget, public BasePage +class AccountListPage : public QMainWindow, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit AccountListPage(QWidget *parent = 0); - ~AccountListPage(); - - QString displayName() const override - { - return tr("Accounts"); - } - QIcon icon() const override - { - auto icon = MMC->getThemedIcon("accounts"); - if(icon.isNull()) - { - icon = MMC->getThemedIcon("noaccount"); - } - return icon; - } - QString id() const override - { - return "accounts"; - } - QString helpPage() const override - { - return "Getting-Started#adding-an-account"; - } + explicit AccountListPage(QWidget *parent = 0); + ~AccountListPage(); + + QString displayName() const override + { + return tr("Accounts"); + } + QIcon icon() const override + { + auto icon = MMC->getThemedIcon("accounts"); + if(icon.isNull()) + { + icon = MMC->getThemedIcon("noaccount"); + } + return icon; + } + QString id() const override + { + return "accounts"; + } + QString helpPage() const override + { + return "Getting-Started#adding-an-account"; + } + +private: + void changeEvent(QEvent * event) override; + QMenu * createPopupMenu() override; public slots: - void on_addAccountBtn_clicked(); + void on_actionAdd_triggered(); - void on_rmAccountBtn_clicked(); + void on_actionRemove_triggered(); - void on_setDefaultBtn_clicked(); + void on_actionSetDefault_triggered(); - void on_noDefaultBtn_clicked(); + void on_actionNoDefault_triggered(); - void on_uploadSkinBtn_clicked(); + void on_actionUploadSkin_triggered(); - void listChanged(); + void listChanged(); - //! Updates the states of the dialog's buttons. - void updateButtonStates(); + //! Updates the states of the dialog's buttons. + void updateButtonStates(); protected: - std::shared_ptr<MojangAccountList> m_accounts; + std::shared_ptr<MojangAccountList> m_accounts; protected slots: - void addAccount(const QString& errMsg=""); + void ShowContextMenu(const QPoint &pos); + void addAccount(const QString& errMsg=""); private: - Ui::AccountListPage *ui; + Ui::AccountListPage *ui; }; diff --git a/application/pages/global/AccountListPage.ui b/application/pages/global/AccountListPage.ui index f4e87680..433c1f7f 100644 --- a/application/pages/global/AccountListPage.ui +++ b/application/pages/global/AccountListPage.ui @@ -1,122 +1,94 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>AccountListPage</class> - <widget class="QWidget" name="AccountListPage"> + <widget class="QMainWindow" name="AccountListPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>694</width> - <height>609</height> + <width>800</width> + <height>600</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <property name="leftMargin"> - <number>0</number> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <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="VersionListView" name="listView"/> + </item> + </layout> + </widget> + <widget class="WideBar" name="toolBar"> + <property name="windowTitle"> + <string>toolBar</string> + </property> + <attribute name="toolBarArea"> + <enum>RightToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionAdd"/> + <addaction name="actionRemove"/> + <addaction name="actionSetDefault"/> + <addaction name="actionNoDefault"/> + <addaction name="actionUploadSkin"/> + </widget> + <action name="actionAdd"> + <property name="text"> + <string>Add</string> + </property> + </action> + <action name="actionRemove"> + <property name="text"> + <string>Remove</string> + </property> + </action> + <action name="actionSetDefault"> + <property name="text"> + <string>Set Default</string> </property> - <property name="topMargin"> - <number>0</number> + </action> + <action name="actionNoDefault"> + <property name="checkable"> + <bool>true</bool> </property> - <property name="rightMargin"> - <number>0</number> + <property name="text"> + <string>No Default</string> </property> - <property name="bottomMargin"> - <number>0</number> + </action> + <action name="actionUploadSkin"> + <property name="text"> + <string>Upload Skin</string> </property> - <item> - <widget class="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string notr="true">Tab 1</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QLabel" name="welcomeLabel"> - <property name="text"> - <string><html><head/><body><p>Welcome! If you're new here, you can click the &quot;Add&quot; button to add your Mojang or Minecraft account.</p></body></html></string> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QTreeView" name="listView"/> - </item> - <item> - <layout class="QVBoxLayout" name="manageAcctsBtnBox"> - <item> - <widget class="QPushButton" name="addAccountBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmAccountBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <spacer name="buttonSpacer"> - <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="QPushButton" name="setDefaultBtn"> - <property name="toolTip"> - <string><html><head/><body><p>Set the currently selected account as the active account. The active account is the account that is used to log in (unless it is overridden in an instance-specific setting).</p></body></html></string> - </property> - <property name="text"> - <string>&Set Default</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="noDefaultBtn"> - <property name="toolTip"> - <string>Set no default account. This will cause MultiMC to prompt you to select an account every time you launch an instance that doesn't have its own default set.</string> - </property> - <property name="text"> - <string>&No Default</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="uploadSkinBtn"> - <property name="toolTip"> - <string>Opens a dialog to select and upload a skin image to the selected account.</string> - </property> - <property name="text"> - <string>&Upload Skin</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </item> - </layout> - </widget> - </widget> - </item> - </layout> + </action> </widget> + <customwidgets> + <customwidget> + <class>VersionListView</class> + <extends>QTreeView</extends> + <header>widgets/VersionListView.h</header> + </customwidget> + <customwidget> + <class>WideBar</class> + <extends>QToolBar</extends> + <header>widgets/WideBar.h</header> + </customwidget> + </customwidgets> <resources/> <connections/> </ui> diff --git a/application/pages/global/CustomCommandsPage.cpp b/application/pages/global/CustomCommandsPage.cpp index 1352b6be..3b182319 100644 --- a/application/pages/global/CustomCommandsPage.cpp +++ b/application/pages/global/CustomCommandsPage.cpp @@ -6,17 +6,18 @@ CustomCommandsPage::CustomCommandsPage(QWidget* parent): QWidget(parent) { - auto verticalLayout = new QVBoxLayout(this); - verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - verticalLayout->setContentsMargins(0, 0, 0, 0); + auto verticalLayout = new QVBoxLayout(this); + verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + verticalLayout->setContentsMargins(0, 0, 0, 0); - auto tabWidget = new QTabWidget(this); - tabWidget->setObjectName(QStringLiteral("tabWidget")); - commands = new CustomCommands(this); - tabWidget->addTab(commands, "Foo"); - tabWidget->tabBar()->hide(); - verticalLayout->addWidget(tabWidget); - loadSettings(); + auto tabWidget = new QTabWidget(this); + tabWidget->setObjectName(QStringLiteral("tabWidget")); + commands = new CustomCommands(this); + commands->setContentsMargins(6, 6, 6, 6); + tabWidget->addTab(commands, "Foo"); + tabWidget->tabBar()->hide(); + verticalLayout->addWidget(tabWidget); + loadSettings(); } CustomCommandsPage::~CustomCommandsPage() @@ -25,26 +26,26 @@ CustomCommandsPage::~CustomCommandsPage() bool CustomCommandsPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void CustomCommandsPage::applySettings() { - auto s = MMC->settings(); - s->set("PreLaunchCommand", commands->prelaunchCommand()); - s->set("WrapperCommand", commands->wrapperCommand()); - s->set("PostExitCommand", commands->postexitCommand()); + auto s = MMC->settings(); + s->set("PreLaunchCommand", commands->prelaunchCommand()); + s->set("WrapperCommand", commands->wrapperCommand()); + s->set("PostExitCommand", commands->postexitCommand()); } void CustomCommandsPage::loadSettings() { - auto s = MMC->settings(); - commands->initialize( - false, - true, - s->get("PreLaunchCommand").toString(), - s->get("WrapperCommand").toString(), - s->get("PostExitCommand").toString() - ); + auto s = MMC->settings(); + commands->initialize( + false, + true, + s->get("PreLaunchCommand").toString(), + s->get("WrapperCommand").toString(), + s->get("PostExitCommand").toString() + ); } diff --git a/application/pages/global/CustomCommandsPage.h b/application/pages/global/CustomCommandsPage.h index 52256ed3..a9047e63 100644 --- a/application/pages/global/CustomCommandsPage.h +++ b/application/pages/global/CustomCommandsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2018-2018 MultiMC Contributors +/* Copyright 2018-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,32 +24,32 @@ class CustomCommandsPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit CustomCommandsPage(QWidget *parent = 0); - ~CustomCommandsPage(); + explicit CustomCommandsPage(QWidget *parent = 0); + ~CustomCommandsPage(); - QString displayName() const override - { - return tr("Custom Commands"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("custom-commands"); - } - QString id() const override - { - return "custom-commands"; - } - QString helpPage() const override - { - return "Custom-commands"; - } - bool apply() override; + QString displayName() const override + { + return tr("Custom Commands"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("custom-commands"); + } + QString id() const override + { + return "custom-commands"; + } + QString helpPage() const override + { + return "Custom-commands"; + } + bool apply() override; private: - void applySettings(); - void loadSettings(); - CustomCommands * commands; + void applySettings(); + void loadSettings(); + CustomCommands * commands; }; diff --git a/application/pages/global/ExternalToolsPage.cpp b/application/pages/global/ExternalToolsPage.cpp index 20887b54..7de6f858 100644 --- a/application/pages/global/ExternalToolsPage.cpp +++ b/application/pages/global/ExternalToolsPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ #include <QMessageBox> #include <QFileDialog> #include <QStandardPaths> +#include <QTabBar> #include "settings/SettingsObject.h" #include "tools/BaseProfiler.h" @@ -28,206 +29,206 @@ #include <QTabBar> ExternalToolsPage::ExternalToolsPage(QWidget *parent) : - QWidget(parent), - ui(new Ui::ExternalToolsPage) + QWidget(parent), + ui(new Ui::ExternalToolsPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); - #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) - ui->jsonEditorTextBox->setClearButtonEnabled(true); - #endif + #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) + ui->jsonEditorTextBox->setClearButtonEnabled(true); + #endif - ui->mceditLink->setOpenExternalLinks(true); - ui->jvisualvmLink->setOpenExternalLinks(true); - ui->jprofilerLink->setOpenExternalLinks(true); - loadSettings(); + ui->mceditLink->setOpenExternalLinks(true); + ui->jvisualvmLink->setOpenExternalLinks(true); + ui->jprofilerLink->setOpenExternalLinks(true); + loadSettings(); } ExternalToolsPage::~ExternalToolsPage() { - delete ui; + delete ui; } void ExternalToolsPage::loadSettings() { - auto s = MMC->settings(); - ui->jprofilerPathEdit->setText(s->get("JProfilerPath").toString()); - ui->jvisualvmPathEdit->setText(s->get("JVisualVMPath").toString()); - ui->mceditPathEdit->setText(s->get("MCEditPath").toString()); + auto s = MMC->settings(); + ui->jprofilerPathEdit->setText(s->get("JProfilerPath").toString()); + ui->jvisualvmPathEdit->setText(s->get("JVisualVMPath").toString()); + ui->mceditPathEdit->setText(s->get("MCEditPath").toString()); - // Editors - ui->jsonEditorTextBox->setText(s->get("JsonEditor").toString()); + // Editors + ui->jsonEditorTextBox->setText(s->get("JsonEditor").toString()); } void ExternalToolsPage::applySettings() { - auto s = MMC->settings(); - - s->set("JProfilerPath", ui->jprofilerPathEdit->text()); - s->set("JVisualVMPath", ui->jvisualvmPathEdit->text()); - s->set("MCEditPath", ui->mceditPathEdit->text()); - - // Editors - QString jsonEditor = ui->jsonEditorTextBox->text(); - if (!jsonEditor.isEmpty() && - (!QFileInfo(jsonEditor).exists() || !QFileInfo(jsonEditor).isExecutable())) - { - QString found = QStandardPaths::findExecutable(jsonEditor); - if (!found.isEmpty()) - { - jsonEditor = found; - } - } - s->set("JsonEditor", jsonEditor); + auto s = MMC->settings(); + + s->set("JProfilerPath", ui->jprofilerPathEdit->text()); + s->set("JVisualVMPath", ui->jvisualvmPathEdit->text()); + s->set("MCEditPath", ui->mceditPathEdit->text()); + + // Editors + QString jsonEditor = ui->jsonEditorTextBox->text(); + if (!jsonEditor.isEmpty() && + (!QFileInfo(jsonEditor).exists() || !QFileInfo(jsonEditor).isExecutable())) + { + QString found = QStandardPaths::findExecutable(jsonEditor); + if (!found.isEmpty()) + { + jsonEditor = found; + } + } + s->set("JsonEditor", jsonEditor); } void ExternalToolsPage::on_jprofilerPathBtn_clicked() { - QString raw_dir = ui->jprofilerPathEdit->text(); - QString error; - do - { - raw_dir = QFileDialog::getExistingDirectory(this, tr("JProfiler Folder"), raw_dir); - if (raw_dir.isEmpty()) - { - break; - } - QString cooked_dir = FS::NormalizePath(raw_dir); - if (!MMC->profilers()["jprofiler"]->check(cooked_dir, &error)) - { - QMessageBox::critical(this, tr("Error"), tr("Error while checking JProfiler install:\n%1").arg(error)); - continue; - } - else - { - ui->jprofilerPathEdit->setText(cooked_dir); - break; - } - } while (1); + QString raw_dir = ui->jprofilerPathEdit->text(); + QString error; + do + { + raw_dir = QFileDialog::getExistingDirectory(this, tr("JProfiler Folder"), raw_dir); + if (raw_dir.isEmpty()) + { + break; + } + QString cooked_dir = FS::NormalizePath(raw_dir); + if (!MMC->profilers()["jprofiler"]->check(cooked_dir, &error)) + { + QMessageBox::critical(this, tr("Error"), tr("Error while checking JProfiler install:\n%1").arg(error)); + continue; + } + else + { + ui->jprofilerPathEdit->setText(cooked_dir); + break; + } + } while (1); } void ExternalToolsPage::on_jprofilerCheckBtn_clicked() { - QString error; - if (!MMC->profilers()["jprofiler"]->check(ui->jprofilerPathEdit->text(), &error)) - { - QMessageBox::critical(this, tr("Error"), tr("Error while checking JProfiler install:\n%1").arg(error)); - } - else - { - QMessageBox::information(this, tr("OK"), tr("JProfiler setup seems to be OK")); - } + QString error; + if (!MMC->profilers()["jprofiler"]->check(ui->jprofilerPathEdit->text(), &error)) + { + QMessageBox::critical(this, tr("Error"), tr("Error while checking JProfiler install:\n%1").arg(error)); + } + else + { + QMessageBox::information(this, tr("OK"), tr("JProfiler setup seems to be OK")); + } } void ExternalToolsPage::on_jvisualvmPathBtn_clicked() { - QString raw_dir = ui->jvisualvmPathEdit->text(); - QString error; - do - { - raw_dir = QFileDialog::getOpenFileName(this, tr("JVisualVM Executable"), raw_dir); - if (raw_dir.isEmpty()) - { - break; - } - QString cooked_dir = FS::NormalizePath(raw_dir); - if (!MMC->profilers()["jvisualvm"]->check(cooked_dir, &error)) - { - QMessageBox::critical(this, tr("Error"), tr("Error while checking JVisualVM install:\n%1").arg(error)); - continue; - } - else - { - ui->jvisualvmPathEdit->setText(cooked_dir); - break; - } - } while (1); + QString raw_dir = ui->jvisualvmPathEdit->text(); + QString error; + do + { + raw_dir = QFileDialog::getOpenFileName(this, tr("JVisualVM Executable"), raw_dir); + if (raw_dir.isEmpty()) + { + break; + } + QString cooked_dir = FS::NormalizePath(raw_dir); + if (!MMC->profilers()["jvisualvm"]->check(cooked_dir, &error)) + { + QMessageBox::critical(this, tr("Error"), tr("Error while checking JVisualVM install:\n%1").arg(error)); + continue; + } + else + { + ui->jvisualvmPathEdit->setText(cooked_dir); + break; + } + } while (1); } void ExternalToolsPage::on_jvisualvmCheckBtn_clicked() { - QString error; - if (!MMC->profilers()["jvisualvm"]->check(ui->jvisualvmPathEdit->text(), &error)) - { - QMessageBox::critical(this, tr("Error"), tr("Error while checking JVisualVM install:\n%1").arg(error)); - } - else - { - QMessageBox::information(this, tr("OK"), tr("JVisualVM setup seems to be OK")); - } + QString error; + if (!MMC->profilers()["jvisualvm"]->check(ui->jvisualvmPathEdit->text(), &error)) + { + QMessageBox::critical(this, tr("Error"), tr("Error while checking JVisualVM install:\n%1").arg(error)); + } + else + { + QMessageBox::information(this, tr("OK"), tr("JVisualVM setup seems to be OK")); + } } void ExternalToolsPage::on_mceditPathBtn_clicked() { - QString raw_dir = ui->mceditPathEdit->text(); - QString error; - do - { + QString raw_dir = ui->mceditPathEdit->text(); + QString error; + do + { #ifdef Q_OS_OSX - raw_dir = QFileDialog::getOpenFileName(this, tr("MCEdit Application"), raw_dir); + raw_dir = QFileDialog::getOpenFileName(this, tr("MCEdit Application"), raw_dir); #else - raw_dir = QFileDialog::getExistingDirectory(this, tr("MCEdit Folder"), raw_dir); + raw_dir = QFileDialog::getExistingDirectory(this, tr("MCEdit Folder"), raw_dir); #endif - if (raw_dir.isEmpty()) - { - break; - } - QString cooked_dir = FS::NormalizePath(raw_dir); - if (!MMC->mcedit()->check(cooked_dir, error)) - { - QMessageBox::critical(this, tr("Error"), tr("Error while checking MCEdit install:\n%1").arg(error)); - continue; - } - else - { - ui->mceditPathEdit->setText(cooked_dir); - break; - } - } while (1); + if (raw_dir.isEmpty()) + { + break; + } + QString cooked_dir = FS::NormalizePath(raw_dir); + if (!MMC->mcedit()->check(cooked_dir, error)) + { + QMessageBox::critical(this, tr("Error"), tr("Error while checking MCEdit install:\n%1").arg(error)); + continue; + } + else + { + ui->mceditPathEdit->setText(cooked_dir); + break; + } + } while (1); } void ExternalToolsPage::on_mceditCheckBtn_clicked() { - QString error; - if (!MMC->mcedit()->check(ui->mceditPathEdit->text(), error)) - { - QMessageBox::critical(this, tr("Error"), tr("Error while checking MCEdit install:\n%1").arg(error)); - } - else - { - QMessageBox::information(this, tr("OK"), tr("MCEdit setup seems to be OK")); - } + QString error; + if (!MMC->mcedit()->check(ui->mceditPathEdit->text(), error)) + { + QMessageBox::critical(this, tr("Error"), tr("Error while checking MCEdit install:\n%1").arg(error)); + } + else + { + QMessageBox::information(this, tr("OK"), tr("MCEdit setup seems to be OK")); + } } void ExternalToolsPage::on_jsonEditorBrowseBtn_clicked() { - QString raw_file = QFileDialog::getOpenFileName( - this, tr("JSON Editor"), - ui->jsonEditorTextBox->text().isEmpty() + QString raw_file = QFileDialog::getOpenFileName( + this, tr("JSON Editor"), + ui->jsonEditorTextBox->text().isEmpty() #if defined(Q_OS_LINUX) - ? QString("/usr/bin") + ? QString("/usr/bin") #else - ? QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).first() + ? QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).first() #endif - : ui->jsonEditorTextBox->text()); - - if (raw_file.isEmpty()) - { - return; - } - QString cooked_file = FS::NormalizePath(raw_file); - - // it has to exist and be an executable - if (QFileInfo(cooked_file).exists() && QFileInfo(cooked_file).isExecutable()) - { - ui->jsonEditorTextBox->setText(cooked_file); - } - else - { - QMessageBox::warning(this, tr("Invalid"), - tr("The file chosen does not seem to be an executable")); - } + : ui->jsonEditorTextBox->text()); + + if (raw_file.isEmpty()) + { + return; + } + QString cooked_file = FS::NormalizePath(raw_file); + + // it has to exist and be an executable + if (QFileInfo(cooked_file).exists() && QFileInfo(cooked_file).isExecutable()) + { + ui->jsonEditorTextBox->setText(cooked_file); + } + else + { + QMessageBox::warning(this, tr("Invalid"), + tr("The file chosen does not seem to be an executable")); + } } bool ExternalToolsPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } diff --git a/application/pages/global/ExternalToolsPage.h b/application/pages/global/ExternalToolsPage.h index de46d8a6..06e64273 100644 --- a/application/pages/global/ExternalToolsPage.h +++ b/application/pages/global/ExternalToolsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,49 +26,49 @@ class ExternalToolsPage; class ExternalToolsPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit ExternalToolsPage(QWidget *parent = 0); - ~ExternalToolsPage(); + explicit ExternalToolsPage(QWidget *parent = 0); + ~ExternalToolsPage(); - QString displayName() const override - { - return tr("External Tools"); - } - QIcon icon() const override - { - auto icon = MMC->getThemedIcon("externaltools"); - if(icon.isNull()) - { - icon = MMC->getThemedIcon("loadermods"); - } - return icon; - } - QString id() const override - { - return "external-tools"; - } - QString helpPage() const override - { - return "Tools"; - } - virtual bool apply() override; + QString displayName() const override + { + return tr("External Tools"); + } + QIcon icon() const override + { + auto icon = MMC->getThemedIcon("externaltools"); + if(icon.isNull()) + { + icon = MMC->getThemedIcon("loadermods"); + } + return icon; + } + QString id() const override + { + return "external-tools"; + } + QString helpPage() const override + { + return "Tools"; + } + virtual bool apply() override; private: - void loadSettings(); - void applySettings(); + void loadSettings(); + void applySettings(); private: - Ui::ExternalToolsPage *ui; + Ui::ExternalToolsPage *ui; private slots: - void on_jprofilerPathBtn_clicked(); - void on_jprofilerCheckBtn_clicked(); - void on_jvisualvmPathBtn_clicked(); - void on_jvisualvmCheckBtn_clicked(); - void on_mceditPathBtn_clicked(); - void on_mceditCheckBtn_clicked(); - void on_jsonEditorBrowseBtn_clicked(); + void on_jprofilerPathBtn_clicked(); + void on_jprofilerCheckBtn_clicked(); + void on_jvisualvmPathBtn_clicked(); + void on_jvisualvmCheckBtn_clicked(); + void on_mceditPathBtn_clicked(); + void on_mceditCheckBtn_clicked(); + void on_jsonEditorBrowseBtn_clicked(); }; diff --git a/application/pages/global/ExternalToolsPage.ui b/application/pages/global/ExternalToolsPage.ui index 5f19898b..e79e9388 100644 --- a/application/pages/global/ExternalToolsPage.ui +++ b/application/pages/global/ExternalToolsPage.ui @@ -63,7 +63,7 @@ <item> <widget class="QLabel" name="jprofilerLink"> <property name="text"> - <string notr="true"><html><head/><body><p><a href="http://www.ej-technologies.com/products/jprofiler/overview.html">http://www.ej-technologies.com/products/jprofiler/overview.html</a></p></body></html></string> + <string notr="true"><html><head/><body><p><a href="https://www.ej-technologies.com/products/jprofiler/overview.html">https://www.ej-technologies.com/products/jprofiler/overview.html</a></p></body></html></string> </property> </widget> </item> @@ -137,7 +137,7 @@ <item> <widget class="QLabel" name="mceditLink"> <property name="text"> - <string notr="true"><html><head/><body><p><a href="http://www.mcedit.net/">http://www.mcedit.net/</a></p></body></html></string> + <string notr="true"><html><head/><body><p><a href="https://www.mcedit.net/">https://www.mcedit.net/</a></p></body></html></string> </property> </widget> </item> diff --git a/application/pages/global/JavaPage.cpp b/application/pages/global/JavaPage.cpp index ce733778..211dc929 100644 --- a/application/pages/global/JavaPage.cpp +++ b/application/pages/global/JavaPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ #include <QFileDialog> #include <QMessageBox> #include <QDir> +#include <QTabBar> #include "dialogs/VersionSelectDialog.h" @@ -34,120 +35,120 @@ JavaPage::JavaPage(QWidget *parent) : QWidget(parent), ui(new Ui::JavaPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); - auto sysMB = Sys::getSystemRam() / Sys::megabyte; - ui->maxMemSpinBox->setMaximum(sysMB); - loadSettings(); + auto sysMB = Sys::getSystemRam() / Sys::megabyte; + ui->maxMemSpinBox->setMaximum(sysMB); + loadSettings(); } JavaPage::~JavaPage() { - delete ui; + delete ui; } bool JavaPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void JavaPage::applySettings() { - auto s = MMC->settings(); - - // Memory - int min = ui->minMemSpinBox->value(); - int max = ui->maxMemSpinBox->value(); - if(min < max) - { - s->set("MinMemAlloc", min); - s->set("MaxMemAlloc", max); - } - else - { - s->set("MinMemAlloc", max); - s->set("MaxMemAlloc", min); - } - s->set("PermGen", ui->permGenSpinBox->value()); - - // Java Settings - s->set("JavaPath", ui->javaPathTextBox->text()); - s->set("JvmArgs", ui->jvmArgsTextBox->text()); - JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget()); + auto s = MMC->settings(); + + // Memory + int min = ui->minMemSpinBox->value(); + int max = ui->maxMemSpinBox->value(); + if(min < max) + { + s->set("MinMemAlloc", min); + s->set("MaxMemAlloc", max); + } + else + { + s->set("MinMemAlloc", max); + s->set("MaxMemAlloc", min); + } + s->set("PermGen", ui->permGenSpinBox->value()); + + // Java Settings + s->set("JavaPath", ui->javaPathTextBox->text()); + s->set("JvmArgs", ui->jvmArgsTextBox->text()); + JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget()); } void JavaPage::loadSettings() { - auto s = MMC->settings(); - // Memory - int min = s->get("MinMemAlloc").toInt(); - int max = s->get("MaxMemAlloc").toInt(); - if(min < max) - { - ui->minMemSpinBox->setValue(min); - ui->maxMemSpinBox->setValue(max); - } - else - { - ui->minMemSpinBox->setValue(max); - ui->maxMemSpinBox->setValue(min); - } - ui->permGenSpinBox->setValue(s->get("PermGen").toInt()); - - // Java Settings - ui->javaPathTextBox->setText(s->get("JavaPath").toString()); - ui->jvmArgsTextBox->setText(s->get("JvmArgs").toString()); + auto s = MMC->settings(); + // Memory + int min = s->get("MinMemAlloc").toInt(); + int max = s->get("MaxMemAlloc").toInt(); + if(min < max) + { + ui->minMemSpinBox->setValue(min); + ui->maxMemSpinBox->setValue(max); + } + else + { + ui->minMemSpinBox->setValue(max); + ui->maxMemSpinBox->setValue(min); + } + ui->permGenSpinBox->setValue(s->get("PermGen").toInt()); + + // Java Settings + ui->javaPathTextBox->setText(s->get("JavaPath").toString()); + ui->jvmArgsTextBox->setText(s->get("JvmArgs").toString()); } void JavaPage::on_javaDetectBtn_clicked() { - JavaInstallPtr java; + JavaInstallPtr java; - VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true); - vselect.setResizeOn(2); - vselect.exec(); + VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true); + vselect.setResizeOn(2); + vselect.exec(); - if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) - { - java = std::dynamic_pointer_cast<JavaInstall>(vselect.selectedVersion()); - ui->javaPathTextBox->setText(java->path); - } + if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) + { + java = std::dynamic_pointer_cast<JavaInstall>(vselect.selectedVersion()); + ui->javaPathTextBox->setText(java->path); + } } void JavaPage::on_javaBrowseBtn_clicked() { - QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable")); - - // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if(raw_path.isEmpty()) - { - return; - } - - QString cooked_path = FS::NormalizePath(raw_path); - QFileInfo javaInfo(cooked_path);; - if(!javaInfo.exists() || !javaInfo.isExecutable()) - { - return; - } - ui->javaPathTextBox->setText(cooked_path); + QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable")); + + // do not allow current dir - it's dirty. Do not allow dirs that don't exist + if(raw_path.isEmpty()) + { + return; + } + + QString cooked_path = FS::NormalizePath(raw_path); + QFileInfo javaInfo(cooked_path);; + if(!javaInfo.exists() || !javaInfo.isExecutable()) + { + return; + } + ui->javaPathTextBox->setText(cooked_path); } void JavaPage::on_javaTestBtn_clicked() { - if(checker) - { - return; - } - checker.reset(new JavaCommon::TestCheck( - this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->text(), - ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value())); - connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished())); - checker->run(); + if(checker) + { + return; + } + checker.reset(new JavaCommon::TestCheck( + this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->text(), + ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value())); + connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished())); + checker->run(); } void JavaPage::checkerFinished() { - checker.reset(); + checker.reset(); } diff --git a/application/pages/global/JavaPage.h b/application/pages/global/JavaPage.h index e3a9f37f..3efebb78 100644 --- a/application/pages/global/JavaPage.h +++ b/application/pages/global/JavaPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,42 +31,42 @@ class JavaPage; class JavaPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit JavaPage(QWidget *parent = 0); - ~JavaPage(); + explicit JavaPage(QWidget *parent = 0); + ~JavaPage(); - QString displayName() const override - { - return tr("Java"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("java"); - } - QString id() const override - { - return "java-settings"; - } - QString helpPage() const override - { - return "Java-settings"; - } - bool apply() override; + QString displayName() const override + { + return tr("Java"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("java"); + } + QString id() const override + { + return "java-settings"; + } + QString helpPage() const override + { + return "Java-settings"; + } + bool apply() override; private: - void applySettings(); - void loadSettings(); + void applySettings(); + void loadSettings(); private slots: - void on_javaDetectBtn_clicked(); - void on_javaTestBtn_clicked(); - void on_javaBrowseBtn_clicked(); - void checkerFinished(); + void on_javaDetectBtn_clicked(); + void on_javaTestBtn_clicked(); + void on_javaBrowseBtn_clicked(); + void checkerFinished(); private: - Ui::JavaPage *ui; - unique_qobject_ptr<JavaCommon::TestCheck> checker; + Ui::JavaPage *ui; + unique_qobject_ptr<JavaCommon::TestCheck> checker; }; diff --git a/application/pages/global/LanguagePage.cpp b/application/pages/global/LanguagePage.cpp new file mode 100644 index 00000000..ae3168cc --- /dev/null +++ b/application/pages/global/LanguagePage.cpp @@ -0,0 +1,51 @@ +#include "LanguagePage.h" + +#include "widgets/LanguageSelectionWidget.h" +#include <QVBoxLayout> + +LanguagePage::LanguagePage(QWidget* parent) : + QWidget(parent) +{ + setObjectName(QStringLiteral("languagePage")); + auto layout = new QVBoxLayout(this); + mainWidget = new LanguageSelectionWidget(this); + layout->setContentsMargins(0,0,0,0); + layout->addWidget(mainWidget); + retranslate(); +} + +LanguagePage::~LanguagePage() +{ +} + +bool LanguagePage::apply() +{ + applySettings(); + return true; +} + +void LanguagePage::applySettings() +{ + auto settings = MMC->settings(); + QString key = mainWidget->getSelectedLanguageKey(); + settings->set("Language", key); +} + +void LanguagePage::loadSettings() +{ + // NIL +} + +void LanguagePage::retranslate() +{ + mainWidget->retranslate(); +} + +void LanguagePage::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) + { + retranslate(); + } + QWidget::changeEvent(event); +} diff --git a/application/pages/global/LanguagePage.h b/application/pages/global/LanguagePage.h new file mode 100644 index 00000000..c4df2ea9 --- /dev/null +++ b/application/pages/global/LanguagePage.h @@ -0,0 +1,60 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <memory> +#include "pages/BasePage.h" +#include <MultiMC.h> +#include <QWidget> + +class LanguageSelectionWidget; + +class LanguagePage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit LanguagePage(QWidget *parent = 0); + virtual ~LanguagePage(); + + QString displayName() const override + { + return tr("Language"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("language"); + } + QString id() const override + { + return "language-settings"; + } + QString helpPage() const override + { + return "Language-settings"; + } + bool apply() override; + + void changeEvent(QEvent * ) override; + +private: + void applySettings(); + void loadSettings(); + void retranslate(); + +private: + LanguageSelectionWidget *mainWidget; +}; diff --git a/application/pages/global/MinecraftPage.cpp b/application/pages/global/MinecraftPage.cpp index 4efc2fa0..5e0d9625 100644 --- a/application/pages/global/MinecraftPage.cpp +++ b/application/pages/global/MinecraftPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ #include <QMessageBox> #include <QDir> +#include <QTabBar> #include "settings/SettingsObject.h" #include "MultiMC.h" @@ -25,52 +26,52 @@ MinecraftPage::MinecraftPage(QWidget *parent) : QWidget(parent), ui(new Ui::MinecraftPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - loadSettings(); - updateCheckboxStuff(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + loadSettings(); + updateCheckboxStuff(); } MinecraftPage::~MinecraftPage() { - delete ui; + delete ui; } bool MinecraftPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void MinecraftPage::updateCheckboxStuff() { - ui->windowWidthSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked()); - ui->windowHeightSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked()); + ui->windowWidthSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked()); + ui->windowHeightSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked()); } void MinecraftPage::on_maximizedCheckBox_clicked(bool checked) { - Q_UNUSED(checked); - updateCheckboxStuff(); + Q_UNUSED(checked); + updateCheckboxStuff(); } void MinecraftPage::applySettings() { - auto s = MMC->settings(); + auto s = MMC->settings(); - // Window Size - s->set("LaunchMaximized", ui->maximizedCheckBox->isChecked()); - s->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); - s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); + // Window Size + s->set("LaunchMaximized", ui->maximizedCheckBox->isChecked()); + s->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); + s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); } void MinecraftPage::loadSettings() { - auto s = MMC->settings(); + auto s = MMC->settings(); - // Window Size - ui->maximizedCheckBox->setChecked(s->get("LaunchMaximized").toBool()); - ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt()); - ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt()); + // Window Size + ui->maximizedCheckBox->setChecked(s->get("LaunchMaximized").toBool()); + ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt()); + ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt()); } diff --git a/application/pages/global/MinecraftPage.h b/application/pages/global/MinecraftPage.h index d1abd6fe..39265fbe 100644 --- a/application/pages/global/MinecraftPage.h +++ b/application/pages/global/MinecraftPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,40 +31,40 @@ class MinecraftPage; class MinecraftPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit MinecraftPage(QWidget *parent = 0); - ~MinecraftPage(); + explicit MinecraftPage(QWidget *parent = 0); + ~MinecraftPage(); - QString displayName() const override - { - return tr("Minecraft"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("minecraft"); - } - QString id() const override - { - return "minecraft-settings"; - } - QString helpPage() const override - { - return "Minecraft-settings"; - } - bool apply() override; + QString displayName() const override + { + return tr("Minecraft"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("minecraft"); + } + QString id() const override + { + return "minecraft-settings"; + } + QString helpPage() const override + { + return "Minecraft-settings"; + } + bool apply() override; private: - void updateCheckboxStuff(); - void applySettings(); - void loadSettings(); + void updateCheckboxStuff(); + void applySettings(); + void loadSettings(); private slots: - void on_maximizedCheckBox_clicked(bool checked); + void on_maximizedCheckBox_clicked(bool checked); private: - Ui::MinecraftPage *ui; + Ui::MinecraftPage *ui; }; diff --git a/application/pages/global/MultiMCPage.cpp b/application/pages/global/MultiMCPage.cpp index 21e8123c..d10d4caa 100644 --- a/application/pages/global/MultiMCPage.cpp +++ b/application/pages/global/MultiMCPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,208 +32,190 @@ // FIXME: possibly move elsewhere enum InstSortMode { - // Sort alphabetically by name. - Sort_Name, - // Sort by which instance was launched most recently. - Sort_LastLaunch + // Sort alphabetically by name. + Sort_Name, + // Sort by which instance was launched most recently. + Sort_LastLaunch }; MultiMCPage::MultiMCPage(QWidget *parent) : QWidget(parent), ui(new Ui::MultiMCPage) { - ui->setupUi(this); - auto origForeground = ui->fontPreview->palette().color(ui->fontPreview->foregroundRole()); - auto origBackground = ui->fontPreview->palette().color(ui->fontPreview->backgroundRole()); - m_colors.reset(new LogColorCache(origForeground, origBackground)); - - ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name); - ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch); - - defaultFormat = new QTextCharFormat(ui->fontPreview->currentCharFormat()); - - m_languageModel = MMC->translations(); - loadSettings(); - - if(BuildConfig.UPDATER_ENABLED) - { - QObject::connect(MMC->updateChecker().get(), &UpdateChecker::channelListLoaded, this, - &MultiMCPage::refreshUpdateChannelList); - - if (MMC->updateChecker()->hasChannels()) - { - refreshUpdateChannelList(); - } - else - { - MMC->updateChecker()->updateChanList(false); - } - } - else - { - ui->updateSettingsBox->setHidden(true); - } -/* // Analytics - if(BuildConfig.ANALYTICS_ID.isEmpty()) - { - ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->analyticsTab)); - } -*/ connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview())); - connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview())); - connect(ui->languageBox, SIGNAL(currentIndexChanged(int)), SLOT(languageIndexChanged(int))); + ui->setupUi(this); + auto origForeground = ui->fontPreview->palette().color(ui->fontPreview->foregroundRole()); + auto origBackground = ui->fontPreview->palette().color(ui->fontPreview->backgroundRole()); + m_colors.reset(new LogColorCache(origForeground, origBackground)); + + ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name); + ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch); + + defaultFormat = new QTextCharFormat(ui->fontPreview->currentCharFormat()); + + m_languageModel = MMC->translations(); + loadSettings(); + + if(BuildConfig.UPDATER_ENABLED) + { + QObject::connect(MMC->updateChecker().get(), &UpdateChecker::channelListLoaded, this, + &MultiMCPage::refreshUpdateChannelList); + + if (MMC->updateChecker()->hasChannels()) + { + refreshUpdateChannelList(); + } + else + { + MMC->updateChecker()->updateChanList(false); + } + } + else + { + ui->updateSettingsBox->setHidden(true); + } + connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview())); + connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview())); } MultiMCPage::~MultiMCPage() { - delete ui; + delete ui; } bool MultiMCPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void MultiMCPage::on_instDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Folder"), ui->instDirTextBox->text()); - - // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) - { - QString cooked_dir = FS::NormalizePath(raw_dir); - if (FS::checkProblemticPathJava(QDir(cooked_dir))) - { - QMessageBox warning; - warning.setText(tr("You're trying to specify an instance folder which\'s path " - "contains at least one \'!\'. " - "Java is known to cause problems if that is the case, your " - "instances (probably) won't start!")); - warning.setInformativeText( - tr("Do you really want to use this path? " - "Selecting \"No\" will close this and not alter your instance path.")); - warning.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - int result = warning.exec(); - if (result == QMessageBox::Yes) - { - ui->instDirTextBox->setText(cooked_dir); - } - } - else - { - ui->instDirTextBox->setText(cooked_dir); - } - } + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Folder"), ui->instDirTextBox->text()); + + // do not allow current dir - it's dirty. Do not allow dirs that don't exist + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) + { + QString cooked_dir = FS::NormalizePath(raw_dir); + if (FS::checkProblemticPathJava(QDir(cooked_dir))) + { + QMessageBox warning; + warning.setText(tr("You're trying to specify an instance folder which\'s path " + "contains at least one \'!\'. " + "Java is known to cause problems if that is the case, your " + "instances (probably) won't start!")); + warning.setInformativeText( + tr("Do you really want to use this path? " + "Selecting \"No\" will close this and not alter your instance path.")); + warning.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + int result = warning.exec(); + if (result == QMessageBox::Yes) + { + ui->instDirTextBox->setText(cooked_dir); + } + } + else + { + ui->instDirTextBox->setText(cooked_dir); + } + } } void MultiMCPage::on_iconsDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Icons Folder"), ui->iconsDirTextBox->text()); - - // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) - { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->iconsDirTextBox->setText(cooked_dir); - } + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Icons Folder"), ui->iconsDirTextBox->text()); + + // do not allow current dir - it's dirty. Do not allow dirs that don't exist + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) + { + QString cooked_dir = FS::NormalizePath(raw_dir); + ui->iconsDirTextBox->setText(cooked_dir); + } } void MultiMCPage::on_modsDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text()); - - // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) - { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->modsDirTextBox->setText(cooked_dir); - } -} - -void MultiMCPage::languageIndexChanged(int index) -{ - auto languageCode = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); - if(languageCode.isEmpty()) - { - qWarning() << "Unknown language at index" << index; - return; - } - auto translations = MMC->translations(); - translations->selectLanguage(languageCode); - translations->updateLanguage(languageCode); + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text()); + + // do not allow current dir - it's dirty. Do not allow dirs that don't exist + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) + { + QString cooked_dir = FS::NormalizePath(raw_dir); + ui->modsDirTextBox->setText(cooked_dir); + } } void MultiMCPage::refreshUpdateChannelList() { - // Stop listening for selection changes. It's going to change a lot while we update it and - // we don't need to update the - // description label constantly. - QObject::disconnect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, - SLOT(updateChannelSelectionChanged(int))); - - QList<UpdateChecker::ChannelListEntry> channelList = MMC->updateChecker()->getChannelList(); - ui->updateChannelComboBox->clear(); - int selection = -1; - for (int i = 0; i < channelList.count(); i++) - { - UpdateChecker::ChannelListEntry entry = channelList.at(i); - - // When it comes to selection, we'll rely on the indexes of a channel entry being the - // same in the - // combo box as it is in the update checker's channel list. - // This probably isn't very safe, but the channel list doesn't change often enough (or - // at all) for - // this to be a big deal. Hope it doesn't break... - ui->updateChannelComboBox->addItem(entry.name); - - // If the update channel we just added was the selected one, set the current index in - // the combo box to it. - if (entry.id == m_currentUpdateChannel) - { - qDebug() << "Selected index" << i << "channel id" << m_currentUpdateChannel; - selection = i; - } - } - - ui->updateChannelComboBox->setCurrentIndex(selection); - - // Start listening for selection changes again and update the description label. - QObject::connect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, - SLOT(updateChannelSelectionChanged(int))); - refreshUpdateChannelDesc(); - - // Now that we've updated the channel list, we can enable the combo box. - // It starts off disabled so that if the channel list hasn't been loaded, it will be - // disabled. - ui->updateChannelComboBox->setEnabled(true); + // Stop listening for selection changes. It's going to change a lot while we update it and + // we don't need to update the + // description label constantly. + QObject::disconnect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, + SLOT(updateChannelSelectionChanged(int))); + + QList<UpdateChecker::ChannelListEntry> channelList = MMC->updateChecker()->getChannelList(); + ui->updateChannelComboBox->clear(); + int selection = -1; + for (int i = 0; i < channelList.count(); i++) + { + UpdateChecker::ChannelListEntry entry = channelList.at(i); + + // When it comes to selection, we'll rely on the indexes of a channel entry being the + // same in the + // combo box as it is in the update checker's channel list. + // This probably isn't very safe, but the channel list doesn't change often enough (or + // at all) for + // this to be a big deal. Hope it doesn't break... + ui->updateChannelComboBox->addItem(entry.name); + + // If the update channel we just added was the selected one, set the current index in + // the combo box to it. + if (entry.id == m_currentUpdateChannel) + { + qDebug() << "Selected index" << i << "channel id" << m_currentUpdateChannel; + selection = i; + } + } + + ui->updateChannelComboBox->setCurrentIndex(selection); + + // Start listening for selection changes again and update the description label. + QObject::connect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, + SLOT(updateChannelSelectionChanged(int))); + refreshUpdateChannelDesc(); + + // Now that we've updated the channel list, we can enable the combo box. + // It starts off disabled so that if the channel list hasn't been loaded, it will be + // disabled. + ui->updateChannelComboBox->setEnabled(true); } void MultiMCPage::updateChannelSelectionChanged(int index) { - refreshUpdateChannelDesc(); + refreshUpdateChannelDesc(); } void MultiMCPage::refreshUpdateChannelDesc() { - // Get the channel list. - QList<UpdateChecker::ChannelListEntry> channelList = MMC->updateChecker()->getChannelList(); - int selectedIndex = ui->updateChannelComboBox->currentIndex(); - if (selectedIndex < 0) - { - return; - } - if (selectedIndex < channelList.count()) - { - // Find the channel list entry with the given index. - UpdateChecker::ChannelListEntry selected = channelList.at(selectedIndex); - - // Set the description text. - ui->updateChannelDescLabel->setText(selected.description); - - // Set the currently selected channel ID. - m_currentUpdateChannel = selected.id; - } + // Get the channel list. + QList<UpdateChecker::ChannelListEntry> channelList = MMC->updateChecker()->getChannelList(); + int selectedIndex = ui->updateChannelComboBox->currentIndex(); + if (selectedIndex < 0) + { + return; + } + if (selectedIndex < channelList.count()) + { + // Find the channel list entry with the given index. + UpdateChecker::ChannelListEntry selected = channelList.at(selectedIndex); + + // Set the description text. + ui->updateChannelDescLabel->setText(selected.description); + + // Set the currently selected channel ID. + m_currentUpdateChannel = selected.id; + } } void MultiMCPage::applySettings() { +<<<<<<< HEAD auto s = MMC->settings(); // Language @@ -434,39 +416,232 @@ void MultiMCPage::loadSettings() { ui->analyticsCheck->setChecked(s->get("Analytics").toBool()); } */ +======= + auto s = MMC->settings(); + + if (ui->resetNotificationsBtn->isChecked()) + { + s->set("ShownNotifications", QString()); + } + + // Updates + s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked()); + s->set("UpdateChannel", m_currentUpdateChannel); + auto original = s->get("IconTheme").toString(); + //FIXME: make generic + switch (ui->themeComboBox->currentIndex()) + { + case 1: + s->set("IconTheme", "pe_dark"); + break; + case 2: + s->set("IconTheme", "pe_light"); + break; + case 3: + s->set("IconTheme", "pe_blue"); + break; + case 4: + s->set("IconTheme", "pe_colored"); + break; + case 5: + s->set("IconTheme", "OSX"); + break; + case 6: + s->set("IconTheme", "iOS"); + break; + case 7: + s->set("IconTheme", "flat"); + break; + case 8: + s->set("IconTheme", "custom"); + break; + case 0: + default: + s->set("IconTheme", "multimc"); + break; + } + + if(original != s->get("IconTheme")) + { + MMC->setIconTheme(s->get("IconTheme").toString()); + } + + auto originalAppTheme = s->get("ApplicationTheme").toString(); + auto newAppTheme = ui->themeComboBoxColors->currentData().toString(); + if(originalAppTheme != newAppTheme) + { + s->set("ApplicationTheme", newAppTheme); + MMC->setApplicationTheme(newAppTheme, false); + } + + // Console settings + s->set("ShowConsole", ui->showConsoleCheck->isChecked()); + s->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); + s->set("ShowConsoleOnError", ui->showConsoleErrorCheck->isChecked()); + QString consoleFontFamily = ui->consoleFont->currentFont().family(); + s->set("ConsoleFont", consoleFontFamily); + s->set("ConsoleFontSize", ui->fontSizeBox->value()); + s->set("ConsoleMaxLines", ui->lineLimitSpinBox->value()); + s->set("ConsoleOverflowStop", ui->checkStopLogging->checkState() != Qt::Unchecked); + + // Folders + // TODO: Offer to move instances to new instance folder. + s->set("InstanceDir", ui->instDirTextBox->text()); + s->set("CentralModsDir", ui->modsDirTextBox->text()); + s->set("IconsDir", ui->iconsDirTextBox->text()); + + auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId(); + switch (sortMode) + { + case Sort_LastLaunch: + s->set("InstSortMode", "LastLaunch"); + break; + case Sort_Name: + default: + s->set("InstSortMode", "Name"); + break; + } + + // Analytics + if(!BuildConfig.ANALYTICS_ID.isEmpty()) + { + s->set("Analytics", ui->analyticsCheck->isChecked()); + } +} +void MultiMCPage::loadSettings() +{ + auto s = MMC->settings(); + // Updates + ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool()); + m_currentUpdateChannel = s->get("UpdateChannel").toString(); + //FIXME: make generic + auto theme = s->get("IconTheme").toString(); + if (theme == "pe_dark") + { + ui->themeComboBox->setCurrentIndex(1); + } + else if (theme == "pe_light") + { + ui->themeComboBox->setCurrentIndex(2); + } + else if (theme == "pe_blue") + { + ui->themeComboBox->setCurrentIndex(3); + } + else if (theme == "pe_colored") + { + ui->themeComboBox->setCurrentIndex(4); + } + else if (theme == "OSX") + { + ui->themeComboBox->setCurrentIndex(5); + } + else if (theme == "iOS") + { + ui->themeComboBox->setCurrentIndex(6); + } + else if (theme == "flat") + { + ui->themeComboBox->setCurrentIndex(7); + } + else if (theme == "custom") + { + ui->themeComboBox->setCurrentIndex(8); + } + else + { + ui->themeComboBox->setCurrentIndex(0); + } + + { + auto currentTheme = s->get("ApplicationTheme").toString(); + auto themes = MMC->getValidApplicationThemes(); + int idx = 0; + for(auto &theme: themes) + { + ui->themeComboBoxColors->addItem(theme->name(), theme->id()); + if(currentTheme == theme->id()) + { + ui->themeComboBoxColors->setCurrentIndex(idx); + } + idx++; + } + } + + // Console settings + ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool()); + ui->autoCloseConsoleCheck->setChecked(s->get("AutoCloseConsole").toBool()); + ui->showConsoleErrorCheck->setChecked(s->get("ShowConsoleOnError").toBool()); + QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); + QFont consoleFont(fontFamily); + ui->consoleFont->setCurrentFont(consoleFont); + + bool conversionOk = true; + int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); + if(!conversionOk) + { + fontSize = 11; + } + ui->fontSizeBox->setValue(fontSize); + refreshFontPreview(); + ui->lineLimitSpinBox->setValue(s->get("ConsoleMaxLines").toInt()); + ui->checkStopLogging->setChecked(s->get("ConsoleOverflowStop").toBool()); + + // Folders + ui->instDirTextBox->setText(s->get("InstanceDir").toString()); + ui->modsDirTextBox->setText(s->get("CentralModsDir").toString()); + ui->iconsDirTextBox->setText(s->get("IconsDir").toString()); + + QString sortMode = s->get("InstSortMode").toString(); + + if (sortMode == "LastLaunch") + { + ui->sortLastLaunchedBtn->setChecked(true); + } + else + { + ui->sortByNameBtn->setChecked(true); + } + + // Analytics + if(!BuildConfig.ANALYTICS_ID.isEmpty()) + { + ui->analyticsCheck->setChecked(s->get("Analytics").toBool()); + } +>>>>>>> origin/stable } void MultiMCPage::refreshFontPreview() { - int fontSize = ui->fontSizeBox->value(); - QString fontFamily = ui->consoleFont->currentFont().family(); - ui->fontPreview->clear(); - defaultFormat->setFont(QFont(fontFamily, fontSize)); - { - QTextCharFormat format(*defaultFormat); - format.setForeground(m_colors->getFront(MessageLevel::Error)); - // append a paragraph/line - auto workCursor = ui->fontPreview->textCursor(); - workCursor.movePosition(QTextCursor::End); - workCursor.insertText(tr("[Something/ERROR] A spooky error!"), format); - workCursor.insertBlock(); - } - { - QTextCharFormat format(*defaultFormat); - format.setForeground(m_colors->getFront(MessageLevel::Message)); - // append a paragraph/line - auto workCursor = ui->fontPreview->textCursor(); - workCursor.movePosition(QTextCursor::End); - workCursor.insertText(tr("[Test/INFO] A harmless message..."), format); - workCursor.insertBlock(); - } - { - QTextCharFormat format(*defaultFormat); - format.setForeground(m_colors->getFront(MessageLevel::Warning)); - // append a paragraph/line - auto workCursor = ui->fontPreview->textCursor(); - workCursor.movePosition(QTextCursor::End); - workCursor.insertText(tr("[Something/WARN] A not so spooky warning."), format); - workCursor.insertBlock(); - } + int fontSize = ui->fontSizeBox->value(); + QString fontFamily = ui->consoleFont->currentFont().family(); + ui->fontPreview->clear(); + defaultFormat->setFont(QFont(fontFamily, fontSize)); + { + QTextCharFormat format(*defaultFormat); + format.setForeground(m_colors->getFront(MessageLevel::Error)); + // append a paragraph/line + auto workCursor = ui->fontPreview->textCursor(); + workCursor.movePosition(QTextCursor::End); + workCursor.insertText(tr("[Something/ERROR] A spooky error!"), format); + workCursor.insertBlock(); + } + { + QTextCharFormat format(*defaultFormat); + format.setForeground(m_colors->getFront(MessageLevel::Message)); + // append a paragraph/line + auto workCursor = ui->fontPreview->textCursor(); + workCursor.movePosition(QTextCursor::End); + workCursor.insertText(tr("[Test/INFO] A harmless message..."), format); + workCursor.insertBlock(); + } + { + QTextCharFormat format(*defaultFormat); + format.setForeground(m_colors->getFront(MessageLevel::Warning)); + // append a paragraph/line + auto workCursor = ui->fontPreview->textCursor(); + workCursor.movePosition(QTextCursor::End); + workCursor.insertText(tr("[Something/WARN] A not so spooky warning."), format); + workCursor.insertBlock(); + } } diff --git a/application/pages/global/MultiMCPage.h b/application/pages/global/MultiMCPage.h index d5194c0e..27a801be 100644 --- a/application/pages/global/MultiMCPage.h +++ b/application/pages/global/MultiMCPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,71 +34,69 @@ class MultiMCPage; class MultiMCPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit MultiMCPage(QWidget *parent = 0); - ~MultiMCPage(); - - QString displayName() const override - { - return "MultiMC"; - } - QIcon icon() const override - { - return MMC->getThemedIcon("multimc"); - } - QString id() const override - { - return "multimc-settings"; - } - QString helpPage() const override - { - return "MultiMC-settings"; - } - bool apply() override; + explicit MultiMCPage(QWidget *parent = 0); + ~MultiMCPage(); + + QString displayName() const override + { + return "MultiMC"; + } + QIcon icon() const override + { + return MMC->getThemedIcon("multimc"); + } + QString id() const override + { + return "multimc-settings"; + } + QString helpPage() const override + { + return "MultiMC-settings"; + } + bool apply() override; private: - void applySettings(); - void loadSettings(); + void applySettings(); + void loadSettings(); private slots: - void on_instDirBrowseBtn_clicked(); - void on_modsDirBrowseBtn_clicked(); - void on_iconsDirBrowseBtn_clicked(); + void on_instDirBrowseBtn_clicked(); + void on_modsDirBrowseBtn_clicked(); + void on_iconsDirBrowseBtn_clicked(); - void languageIndexChanged(int index); + /*! + * Updates the list of update channels in the combo box. + */ + void refreshUpdateChannelList(); - /*! - * Updates the list of update channels in the combo box. - */ - void refreshUpdateChannelList(); + /*! + * Updates the channel description label. + */ + void refreshUpdateChannelDesc(); - /*! - * Updates the channel description label. - */ - void refreshUpdateChannelDesc(); + /*! + * Updates the font preview + */ + void refreshFontPreview(); - /*! - * Updates the font preview - */ - void refreshFontPreview(); - - void updateChannelSelectionChanged(int index); + void updateChannelSelectionChanged(int index); private: - Ui::MultiMCPage *ui; + Ui::MultiMCPage *ui; - /*! - * Stores the currently selected update channel. - */ - QString m_currentUpdateChannel; + /*! + * Stores the currently selected update channel. + */ + QString m_currentUpdateChannel; - // default format for the font preview... - QTextCharFormat *defaultFormat; + // default format for the font preview... + QTextCharFormat *defaultFormat; - std::unique_ptr<LogColorCache> m_colors; + std::unique_ptr<LogColorCache> m_colors; - std::shared_ptr<TranslationsModel> m_languageModel; + std::shared_ptr<TranslationsModel> m_languageModel; }; diff --git a/application/pages/global/MultiMCPage.ui b/application/pages/global/MultiMCPage.ui index 124401c3..ea034919 100644 --- a/application/pages/global/MultiMCPage.ui +++ b/application/pages/global/MultiMCPage.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>467</width> + <width>514</width> <height>629</height> </rect> </property> @@ -229,18 +229,6 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>Language:</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QComboBox" name="languageBox"/> - </item> - </layout> - </widget> - </item> - <item> <widget class="QGroupBox" name="themeBox"> <property name="title"> <string>Theme</string> @@ -570,7 +558,6 @@ <tabstop>resetNotificationsBtn</tabstop> <tabstop>sortLastLaunchedBtn</tabstop> <tabstop>sortByNameBtn</tabstop> - <tabstop>languageBox</tabstop> <tabstop>themeComboBox</tabstop> <tabstop>themeComboBoxColors</tabstop> <tabstop>showConsoleCheck</tabstop> diff --git a/application/pages/global/PackagesPage.cpp b/application/pages/global/PackagesPage.cpp index 7cf1e3f5..1bbbed7a 100644 --- a/application/pages/global/PackagesPage.cpp +++ b/application/pages/global/PackagesPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,192 +33,192 @@ using namespace Meta; static QString formatRequires(const VersionPtr &version) { - QStringList lines; - auto & reqs = version->requires(); - auto iter = reqs.begin(); - while (iter != reqs.end()) - { - auto &uid = iter->uid; - auto &version = iter->equalsVersion; - const QString readable = ENV.metadataIndex()->hasUid(uid) ? ENV.metadataIndex()->get(uid)->humanReadable() : uid; - if(!version.isEmpty()) - { - lines.append(QString("%1 (%2)").arg(readable, version)); - } - else - { - lines.append(QString("%1").arg(readable)); - } - iter++; - } - return lines.join('\n'); + QStringList lines; + auto & reqs = version->requires(); + auto iter = reqs.begin(); + while (iter != reqs.end()) + { + auto &uid = iter->uid; + auto &version = iter->equalsVersion; + const QString readable = ENV.metadataIndex()->hasUid(uid) ? ENV.metadataIndex()->get(uid)->humanReadable() : uid; + if(!version.isEmpty()) + { + lines.append(QString("%1 (%2)").arg(readable, version)); + } + else + { + lines.append(QString("%1").arg(readable)); + } + iter++; + } + return lines.join('\n'); } PackagesPage::PackagesPage(QWidget *parent) : - QWidget(parent), - ui(new Ui::PackagesPage) + QWidget(parent), + ui(new Ui::PackagesPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - - m_fileProxy = new QSortFilterProxyModel(this); - m_fileProxy->setSortRole(Qt::DisplayRole); - m_fileProxy->setSortCaseSensitivity(Qt::CaseInsensitive); - m_fileProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_fileProxy->setFilterRole(Qt::DisplayRole); - m_fileProxy->setFilterKeyColumn(0); - m_fileProxy->sort(0); - m_fileProxy->setSourceModel(ENV.metadataIndex().get()); - ui->indexView->setModel(m_fileProxy); - - m_filterProxy = new QSortFilterProxyModel(this); - m_filterProxy->setSortRole(VersionList::SortRole); - m_filterProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_filterProxy->setFilterRole(Qt::DisplayRole); - m_filterProxy->setFilterKeyColumn(0); - m_filterProxy->sort(0, Qt::DescendingOrder); - ui->versionsView->setModel(m_filterProxy); - - m_versionProxy = new VersionProxyModel(this); - m_filterProxy->setSourceModel(m_versionProxy); - - connect(ui->indexView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateCurrentVersionList); - connect(ui->versionsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateVersion); - connect(m_filterProxy, &QSortFilterProxyModel::dataChanged, this, &PackagesPage::versionListDataChanged); - - updateCurrentVersionList(QModelIndex()); - updateVersion(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + + m_fileProxy = new QSortFilterProxyModel(this); + m_fileProxy->setSortRole(Qt::DisplayRole); + m_fileProxy->setSortCaseSensitivity(Qt::CaseInsensitive); + m_fileProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_fileProxy->setFilterRole(Qt::DisplayRole); + m_fileProxy->setFilterKeyColumn(0); + m_fileProxy->sort(0); + m_fileProxy->setSourceModel(ENV.metadataIndex().get()); + ui->indexView->setModel(m_fileProxy); + + m_filterProxy = new QSortFilterProxyModel(this); + m_filterProxy->setSortRole(VersionList::SortRole); + m_filterProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_filterProxy->setFilterRole(Qt::DisplayRole); + m_filterProxy->setFilterKeyColumn(0); + m_filterProxy->sort(0, Qt::DescendingOrder); + ui->versionsView->setModel(m_filterProxy); + + m_versionProxy = new VersionProxyModel(this); + m_filterProxy->setSourceModel(m_versionProxy); + + connect(ui->indexView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateCurrentVersionList); + connect(ui->versionsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateVersion); + connect(m_filterProxy, &QSortFilterProxyModel::dataChanged, this, &PackagesPage::versionListDataChanged); + + updateCurrentVersionList(QModelIndex()); + updateVersion(); } PackagesPage::~PackagesPage() { - delete ui; + delete ui; } QIcon PackagesPage::icon() const { - return MMC->getThemedIcon("packages"); + return MMC->getThemedIcon("packages"); } void PackagesPage::on_refreshIndexBtn_clicked() { - ENV.metadataIndex()->load(Net::Mode::Online); + ENV.metadataIndex()->load(Net::Mode::Online); } void PackagesPage::on_refreshFileBtn_clicked() { - VersionListPtr list = ui->indexView->currentIndex().data(Index::ListPtrRole).value<VersionListPtr>(); - if (!list) - { - return; - } - list->load(Net::Mode::Online); + VersionListPtr list = ui->indexView->currentIndex().data(Index::ListPtrRole).value<VersionListPtr>(); + if (!list) + { + return; + } + list->load(Net::Mode::Online); } void PackagesPage::on_refreshVersionBtn_clicked() { - VersionPtr version = ui->versionsView->currentIndex().data(VersionList::VersionPtrRole).value<VersionPtr>(); - if (!version) - { - return; - } - version->load(Net::Mode::Online); + VersionPtr version = ui->versionsView->currentIndex().data(VersionList::VersionPtrRole).value<VersionPtr>(); + if (!version) + { + return; + } + version->load(Net::Mode::Online); } void PackagesPage::on_fileSearchEdit_textChanged(const QString &search) { - if (search.isEmpty()) - { - m_fileProxy->setFilterFixedString(QString()); - } - else - { - QStringList parts = search.split(' '); - std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape); - m_fileProxy->setFilterRegExp(".*" + parts.join(".*") + ".*"); - } + if (search.isEmpty()) + { + m_fileProxy->setFilterFixedString(QString()); + } + else + { + QStringList parts = search.split(' '); + std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape); + m_fileProxy->setFilterRegExp(".*" + parts.join(".*") + ".*"); + } } void PackagesPage::on_versionSearchEdit_textChanged(const QString &search) { - if (search.isEmpty()) - { - m_filterProxy->setFilterFixedString(QString()); - } - else - { - QStringList parts = search.split(' '); - std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape); - m_filterProxy->setFilterRegExp(".*" + parts.join(".*") + ".*"); - } + if (search.isEmpty()) + { + m_filterProxy->setFilterFixedString(QString()); + } + else + { + QStringList parts = search.split(' '); + std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape); + m_filterProxy->setFilterRegExp(".*" + parts.join(".*") + ".*"); + } } void PackagesPage::updateCurrentVersionList(const QModelIndex &index) { - if (index.isValid()) - { - VersionListPtr list = index.data(Index::ListPtrRole).value<VersionListPtr>(); - ui->versionsBox->setEnabled(true); - ui->refreshFileBtn->setEnabled(true); - ui->fileUidLabel->setEnabled(true); - ui->fileUid->setText(list->uid()); - ui->fileNameLabel->setEnabled(true); - ui->fileName->setText(list->name()); - m_versionProxy->setSourceModel(list.get()); - ui->refreshFileBtn->setText(tr("Refresh %1").arg(list->humanReadable())); - list->load(Net::Mode::Offline); - } - else - { - ui->versionsBox->setEnabled(false); - ui->refreshFileBtn->setEnabled(false); - ui->fileUidLabel->setEnabled(false); - ui->fileUid->clear(); - ui->fileNameLabel->setEnabled(false); - ui->fileName->clear(); - m_versionProxy->setSourceModel(nullptr); - ui->refreshFileBtn->setText(tr("Refresh")); - } + if (index.isValid()) + { + VersionListPtr list = index.data(Index::ListPtrRole).value<VersionListPtr>(); + ui->versionsBox->setEnabled(true); + ui->refreshFileBtn->setEnabled(true); + ui->fileUidLabel->setEnabled(true); + ui->fileUid->setText(list->uid()); + ui->fileNameLabel->setEnabled(true); + ui->fileName->setText(list->name()); + m_versionProxy->setSourceModel(list.get()); + ui->refreshFileBtn->setText(tr("Refresh %1").arg(list->humanReadable())); + list->load(Net::Mode::Offline); + } + else + { + ui->versionsBox->setEnabled(false); + ui->refreshFileBtn->setEnabled(false); + ui->fileUidLabel->setEnabled(false); + ui->fileUid->clear(); + ui->fileNameLabel->setEnabled(false); + ui->fileName->clear(); + m_versionProxy->setSourceModel(nullptr); + ui->refreshFileBtn->setText(tr("Refresh")); + } } void PackagesPage::versionListDataChanged(const QModelIndex &tl, const QModelIndex &br) { - if (QItemSelection(tl, br).contains(ui->versionsView->currentIndex())) - { - updateVersion(); - } + if (QItemSelection(tl, br).contains(ui->versionsView->currentIndex())) + { + updateVersion(); + } } void PackagesPage::updateVersion() { - VersionPtr version = std::dynamic_pointer_cast<Version>( - ui->versionsView->currentIndex().data(VersionList::VersionPointerRole).value<BaseVersionPtr>()); - if (version) - { - ui->refreshVersionBtn->setEnabled(true); - ui->versionVersionLabel->setEnabled(true); - ui->versionVersion->setText(version->version()); - ui->versionTimeLabel->setEnabled(true); - ui->versionTime->setText(version->time().toString("yyyy-MM-dd HH:mm")); - ui->versionTypeLabel->setEnabled(true); - ui->versionType->setText(version->type()); - ui->versionRequiresLabel->setEnabled(true); - ui->versionRequires->setText(formatRequires(version)); - ui->refreshVersionBtn->setText(tr("Refresh %1").arg(version->version())); - } - else - { - ui->refreshVersionBtn->setEnabled(false); - ui->versionVersionLabel->setEnabled(false); - ui->versionVersion->clear(); - ui->versionTimeLabel->setEnabled(false); - ui->versionTime->clear(); - ui->versionTypeLabel->setEnabled(false); - ui->versionType->clear(); - ui->versionRequiresLabel->setEnabled(false); - ui->versionRequires->clear(); - ui->refreshVersionBtn->setText(tr("Refresh")); - } + VersionPtr version = std::dynamic_pointer_cast<Version>( + ui->versionsView->currentIndex().data(VersionList::VersionPointerRole).value<BaseVersionPtr>()); + if (version) + { + ui->refreshVersionBtn->setEnabled(true); + ui->versionVersionLabel->setEnabled(true); + ui->versionVersion->setText(version->version()); + ui->versionTimeLabel->setEnabled(true); + ui->versionTime->setText(version->time().toString("yyyy-MM-dd HH:mm")); + ui->versionTypeLabel->setEnabled(true); + ui->versionType->setText(version->type()); + ui->versionRequiresLabel->setEnabled(true); + ui->versionRequires->setText(formatRequires(version)); + ui->refreshVersionBtn->setText(tr("Refresh %1").arg(version->version())); + } + else + { + ui->refreshVersionBtn->setEnabled(false); + ui->versionVersionLabel->setEnabled(false); + ui->versionVersion->clear(); + ui->versionTimeLabel->setEnabled(false); + ui->versionTime->clear(); + ui->versionTypeLabel->setEnabled(false); + ui->versionType->clear(); + ui->versionRequiresLabel->setEnabled(false); + ui->versionRequires->clear(); + ui->refreshVersionBtn->setText(tr("Refresh")); + } } void PackagesPage::openedImpl() { - ENV.metadataIndex()->load(Net::Mode::Offline); + ENV.metadataIndex()->load(Net::Mode::Offline); } diff --git a/application/pages/global/PackagesPage.h b/application/pages/global/PackagesPage.h index ad155d9e..872961a5 100644 --- a/application/pages/global/PackagesPage.h +++ b/application/pages/global/PackagesPage.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,30 +28,30 @@ class VersionProxyModel; class PackagesPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit PackagesPage(QWidget *parent = 0); - ~PackagesPage(); + explicit PackagesPage(QWidget *parent = 0); + ~PackagesPage(); - QString id() const override { return "packages-global"; } - QString displayName() const override { return tr("Packages"); } - QIcon icon() const override; - void openedImpl() override; + QString id() const override { return "packages-global"; } + QString displayName() const override { return tr("Packages"); } + QIcon icon() const override; + void openedImpl() override; private slots: - void on_refreshIndexBtn_clicked(); - void on_refreshFileBtn_clicked(); - void on_refreshVersionBtn_clicked(); - void on_fileSearchEdit_textChanged(const QString &search); - void on_versionSearchEdit_textChanged(const QString &search); - void updateCurrentVersionList(const QModelIndex &index); - void versionListDataChanged(const QModelIndex &tl, const QModelIndex &br); + void on_refreshIndexBtn_clicked(); + void on_refreshFileBtn_clicked(); + void on_refreshVersionBtn_clicked(); + void on_fileSearchEdit_textChanged(const QString &search); + void on_versionSearchEdit_textChanged(const QString &search); + void updateCurrentVersionList(const QModelIndex &index); + void versionListDataChanged(const QModelIndex &tl, const QModelIndex &br); private: - Ui::PackagesPage *ui; - QSortFilterProxyModel *m_fileProxy; - QSortFilterProxyModel *m_filterProxy; - VersionProxyModel *m_versionProxy; + Ui::PackagesPage *ui; + QSortFilterProxyModel *m_fileProxy; + QSortFilterProxyModel *m_filterProxy; + VersionProxyModel *m_versionProxy; - void updateVersion(); + void updateVersion(); }; diff --git a/application/pages/global/PasteEEPage.cpp b/application/pages/global/PasteEEPage.cpp index 83dd6411..5355d63a 100644 --- a/application/pages/global/PasteEEPage.cpp +++ b/application/pages/global/PasteEEPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ #include <QMessageBox> #include <QFileDialog> #include <QStandardPaths> +#include <QTabBar> #include "settings/SettingsObject.h" #include "tools/BaseProfiler.h" @@ -26,56 +27,56 @@ #include <QTabBar> PasteEEPage::PasteEEPage(QWidget *parent) : - QWidget(parent), - ui(new Ui::PasteEEPage) + QWidget(parent), + ui(new Ui::PasteEEPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide();\ - connect(ui->customAPIkeyEdit, &QLineEdit::textEdited, this, &PasteEEPage::textEdited); - loadSettings(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide();\ + connect(ui->customAPIkeyEdit, &QLineEdit::textEdited, this, &PasteEEPage::textEdited); + loadSettings(); } PasteEEPage::~PasteEEPage() { - delete ui; + delete ui; } void PasteEEPage::loadSettings() { - auto s = MMC->settings(); - QString keyToUse = s->get("PasteEEAPIKey").toString(); - if(keyToUse == "multimc") - { - ui->multimcButton->setChecked(true); - } - else - { - ui->customButton->setChecked(true); - ui->customAPIkeyEdit->setText(keyToUse); - } + auto s = MMC->settings(); + QString keyToUse = s->get("PasteEEAPIKey").toString(); + if(keyToUse == "multimc") + { + ui->multimcButton->setChecked(true); + } + else + { + ui->customButton->setChecked(true); + ui->customAPIkeyEdit->setText(keyToUse); + } } void PasteEEPage::applySettings() { - auto s = MMC->settings(); + auto s = MMC->settings(); - QString pasteKeyToUse; - if (ui->customButton->isChecked()) - pasteKeyToUse = ui->customAPIkeyEdit->text(); - else - { - pasteKeyToUse = "multimc"; - } - s->set("PasteEEAPIKey", pasteKeyToUse); + QString pasteKeyToUse; + if (ui->customButton->isChecked()) + pasteKeyToUse = ui->customAPIkeyEdit->text(); + else + { + pasteKeyToUse = "multimc"; + } + s->set("PasteEEAPIKey", pasteKeyToUse); } bool PasteEEPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void PasteEEPage::textEdited(const QString& text) { - ui->customButton->setChecked(true); + ui->customButton->setChecked(true); } diff --git a/application/pages/global/PasteEEPage.h b/application/pages/global/PasteEEPage.h index 1b152577..78cf41f4 100644 --- a/application/pages/global/PasteEEPage.h +++ b/application/pages/global/PasteEEPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,37 +26,37 @@ class PasteEEPage; class PasteEEPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit PasteEEPage(QWidget *parent = 0); - ~PasteEEPage(); - - QString displayName() const override - { - return tr("Log Upload"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("log"); - } - QString id() const override - { - return "log-upload"; - } - QString helpPage() const override - { - return "Log-Upload"; - } - virtual bool apply() override; + explicit PasteEEPage(QWidget *parent = 0); + ~PasteEEPage(); + + QString displayName() const override + { + return tr("Log Upload"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("log"); + } + QString id() const override + { + return "log-upload"; + } + QString helpPage() const override + { + return "Log-Upload"; + } + virtual bool apply() override; private: - void loadSettings(); - void applySettings(); + void loadSettings(); + void applySettings(); private slots: - void textEdited(const QString &text); + void textEdited(const QString &text); private: - Ui::PasteEEPage *ui; + Ui::PasteEEPage *ui; }; diff --git a/application/pages/global/ProxyPage.cpp b/application/pages/global/ProxyPage.cpp index 7dbcb860..ee56a54e 100644 --- a/application/pages/global/ProxyPage.cpp +++ b/application/pages/global/ProxyPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,81 +16,86 @@ #include "ProxyPage.h" #include "ui_ProxyPage.h" +#include <QTabBar> + #include "settings/SettingsObject.h" #include "MultiMC.h" -#include <QTabBar> +#include "Env.h" ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - loadSettings(); - updateCheckboxStuff(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + loadSettings(); + updateCheckboxStuff(); - connect(ui->proxyGroup, SIGNAL(buttonClicked(int)), SLOT(proxyChanged(int))); + connect(ui->proxyGroup, SIGNAL(buttonClicked(int)), SLOT(proxyChanged(int))); } ProxyPage::~ProxyPage() { - delete ui; + delete ui; } bool ProxyPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void ProxyPage::updateCheckboxStuff() { - ui->proxyAddrBox->setEnabled(!ui->proxyNoneBtn->isChecked() && - !ui->proxyDefaultBtn->isChecked()); - ui->proxyAuthBox->setEnabled(!ui->proxyNoneBtn->isChecked() && - !ui->proxyDefaultBtn->isChecked()); + ui->proxyAddrBox->setEnabled(!ui->proxyNoneBtn->isChecked() && + !ui->proxyDefaultBtn->isChecked()); + ui->proxyAuthBox->setEnabled(!ui->proxyNoneBtn->isChecked() && + !ui->proxyDefaultBtn->isChecked()); } void ProxyPage::proxyChanged(int) { - updateCheckboxStuff(); + updateCheckboxStuff(); } void ProxyPage::applySettings() { - auto s = MMC->settings(); + auto s = MMC->settings(); + + // Proxy + QString proxyType = "None"; + if (ui->proxyDefaultBtn->isChecked()) + proxyType = "Default"; + else if (ui->proxyNoneBtn->isChecked()) + proxyType = "None"; + else if (ui->proxySOCKS5Btn->isChecked()) + proxyType = "SOCKS5"; + else if (ui->proxyHTTPBtn->isChecked()) + proxyType = "HTTP"; - // Proxy - QString proxyType = "None"; - if (ui->proxyDefaultBtn->isChecked()) - proxyType = "Default"; - else if (ui->proxyNoneBtn->isChecked()) - proxyType = "None"; - else if (ui->proxySOCKS5Btn->isChecked()) - proxyType = "SOCKS5"; - else if (ui->proxyHTTPBtn->isChecked()) - proxyType = "HTTP"; + s->set("ProxyType", proxyType); + s->set("ProxyAddr", ui->proxyAddrEdit->text()); + s->set("ProxyPort", ui->proxyPortEdit->value()); + s->set("ProxyUser", ui->proxyUserEdit->text()); + s->set("ProxyPass", ui->proxyPassEdit->text()); - s->set("ProxyType", proxyType); - s->set("ProxyAddr", ui->proxyAddrEdit->text()); - s->set("ProxyPort", ui->proxyPortEdit->value()); - s->set("ProxyUser", ui->proxyUserEdit->text()); - s->set("ProxyPass", ui->proxyPassEdit->text()); + ENV.updateProxySettings(proxyType, ui->proxyAddrEdit->text(), ui->proxyPortEdit->value(), + ui->proxyUserEdit->text(), ui->proxyPassEdit->text()); } void ProxyPage::loadSettings() { - auto s = MMC->settings(); - // Proxy - QString proxyType = s->get("ProxyType").toString(); - if (proxyType == "Default") - ui->proxyDefaultBtn->setChecked(true); - else if (proxyType == "None") - ui->proxyNoneBtn->setChecked(true); - else if (proxyType == "SOCKS5") - ui->proxySOCKS5Btn->setChecked(true); - else if (proxyType == "HTTP") - ui->proxyHTTPBtn->setChecked(true); + auto s = MMC->settings(); + // Proxy + QString proxyType = s->get("ProxyType").toString(); + if (proxyType == "Default") + ui->proxyDefaultBtn->setChecked(true); + else if (proxyType == "None") + ui->proxyNoneBtn->setChecked(true); + else if (proxyType == "SOCKS5") + ui->proxySOCKS5Btn->setChecked(true); + else if (proxyType == "HTTP") + ui->proxyHTTPBtn->setChecked(true); - ui->proxyAddrEdit->setText(s->get("ProxyAddr").toString()); - ui->proxyPortEdit->setValue(s->get("ProxyPort").value<qint16>()); - ui->proxyUserEdit->setText(s->get("ProxyUser").toString()); - ui->proxyPassEdit->setText(s->get("ProxyPass").toString()); + ui->proxyAddrEdit->setText(s->get("ProxyAddr").toString()); + ui->proxyPortEdit->setValue(s->get("ProxyPort").value<qint16>()); + ui->proxyUserEdit->setText(s->get("ProxyUser").toString()); + ui->proxyPassEdit->setText(s->get("ProxyPass").toString()); } diff --git a/application/pages/global/ProxyPage.h b/application/pages/global/ProxyPage.h index 565c2857..d87bc120 100644 --- a/application/pages/global/ProxyPage.h +++ b/application/pages/global/ProxyPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,39 +28,39 @@ class ProxyPage; class ProxyPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit ProxyPage(QWidget *parent = 0); - ~ProxyPage(); + explicit ProxyPage(QWidget *parent = 0); + ~ProxyPage(); - QString displayName() const override - { - return tr("Proxy"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("proxy"); - } - QString id() const override - { - return "proxy-settings"; - } - QString helpPage() const override - { - return "Proxy-settings"; - } - bool apply() override; + QString displayName() const override + { + return tr("Proxy"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("proxy"); + } + QString id() const override + { + return "proxy-settings"; + } + QString helpPage() const override + { + return "Proxy-settings"; + } + bool apply() override; private: - void updateCheckboxStuff(); - void applySettings(); - void loadSettings(); + void updateCheckboxStuff(); + void applySettings(); + void loadSettings(); private slots: - void proxyChanged(int); + void proxyChanged(int); private: - Ui::ProxyPage *ui; + Ui::ProxyPage *ui; }; diff --git a/application/pages/instance/GameOptionsPage.cpp b/application/pages/instance/GameOptionsPage.cpp new file mode 100644 index 00000000..5555fc79 --- /dev/null +++ b/application/pages/instance/GameOptionsPage.cpp @@ -0,0 +1,40 @@ +#include "GameOptionsPage.h" +#include "ui_GameOptionsPage.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/gameoptions/GameOptions.h" + +GameOptionsPage::GameOptionsPage(MinecraftInstance * inst, QWidget* parent) + : QWidget(parent), ui(new Ui::GameOptionsPage) +{ + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + m_model = inst->gameOptionsModel(); + ui->optionsView->setModel(m_model.get()); + auto head = ui->optionsView->header(); + if(head->count()) + { + head->setSectionResizeMode(0, QHeaderView::ResizeToContents); + for(int i = 1; i < head->count(); i++) + { + head->setSectionResizeMode(i, QHeaderView::Stretch); + } + } +} + +GameOptionsPage::~GameOptionsPage() +{ + // m_model->save(); +} + +void GameOptionsPage::openedImpl() +{ + // m_model->observe(); +} + +void GameOptionsPage::closedImpl() +{ + // m_model->unobserve(); +} + +#include "GameOptionsPage.moc" + diff --git a/application/pages/instance/GameOptionsPage.h b/application/pages/instance/GameOptionsPage.h new file mode 100644 index 00000000..ae47747f --- /dev/null +++ b/application/pages/instance/GameOptionsPage.h @@ -0,0 +1,63 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QWidget> +#include <QString> + +#include "pages/BasePage.h" +#include <MultiMC.h> + +namespace Ui +{ +class GameOptionsPage; +} + +class GameOptions; +class MinecraftInstance; + +class GameOptionsPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit GameOptionsPage(MinecraftInstance *inst, QWidget *parent = 0); + virtual ~GameOptionsPage(); + + void openedImpl() override; + void closedImpl() override; + + virtual QString displayName() const override + { + return tr("Game Options"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("settings"); + } + virtual QString id() const override + { + return "gameoptions"; + } + virtual QString helpPage() const override + { + return "Game-Options-management"; + } + +private: // data + Ui::GameOptionsPage *ui = nullptr; + std::shared_ptr<GameOptions> m_model; +}; diff --git a/application/pages/instance/GameOptionsPage.ui b/application/pages/instance/GameOptionsPage.ui new file mode 100644 index 00000000..f0a5ce0e --- /dev/null +++ b/application/pages/instance/GameOptionsPage.ui @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>GameOptionsPage</class> + <widget class="QWidget" name="GameOptionsPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>706</width> + <height>575</height> + </rect> + </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="0"> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tab"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <attribute name="title"> + <string notr="true">Tab 1</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0" colspan="2"> + <widget class="QTreeView" name="optionsView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="acceptDrops"> + <bool>true</bool> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="iconSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <attribute name="headerStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>tabWidget</tabstop> + <tabstop>optionsView</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/application/pages/instance/InstanceSettingsPage.cpp b/application/pages/instance/InstanceSettingsPage.cpp index 71e90a32..b7b0a863 100644 --- a/application/pages/instance/InstanceSettingsPage.cpp +++ b/application/pages/instance/InstanceSettingsPage.cpp @@ -15,237 +15,271 @@ #include <widgets/CustomCommands.h> InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) - : QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst) + : QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst) { - m_settings = inst->settings(); - ui->setupUi(this); - auto sysMB = Sys::getSystemRam() / Sys::megabyte; - ui->maxMemSpinBox->setMaximum(sysMB); - loadSettings(); + m_settings = inst->settings(); + ui->setupUi(this); + auto sysMB = Sys::getSystemRam() / Sys::megabyte; + ui->maxMemSpinBox->setMaximum(sysMB); + connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked); + connect(MMC, &MultiMC::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings); + connect(MMC, &MultiMC::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings); + loadSettings(); } bool InstanceSettingsPage::shouldDisplay() const { - return !m_instance->isRunning(); + return !m_instance->isRunning(); } InstanceSettingsPage::~InstanceSettingsPage() { - delete ui; + delete ui; +} + +void InstanceSettingsPage::globalSettingsButtonClicked(bool) +{ + switch(ui->settingsTabs->currentIndex()) { + case 0: + MMC->ShowGlobalSettings(this, "java-settings"); + return; + case 1: + MMC->ShowGlobalSettings(this, "minecraft-settings"); + return; + case 2: + MMC->ShowGlobalSettings(this, "custom-commands"); + return; + } } bool InstanceSettingsPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void InstanceSettingsPage::applySettings() { - SettingsObject::Lock lock(m_settings); - - // Console - bool console = ui->consoleSettingsBox->isChecked(); - m_settings->set("OverrideConsole", console); - if (console) - { - m_settings->set("ShowConsole", ui->showConsoleCheck->isChecked()); - m_settings->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); - m_settings->set("ShowConsoleOnError", ui->showConsoleErrorCheck->isChecked()); - } - else - { - m_settings->reset("ShowConsole"); - m_settings->reset("AutoCloseConsole"); - m_settings->reset("ShowConsoleOnError"); - } - - // Window Size - bool window = ui->windowSizeGroupBox->isChecked(); - m_settings->set("OverrideWindow", window); - if (window) - { - m_settings->set("LaunchMaximized", ui->maximizedCheckBox->isChecked()); - m_settings->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); - m_settings->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); - } - else - { - m_settings->reset("LaunchMaximized"); - m_settings->reset("MinecraftWinWidth"); - m_settings->reset("MinecraftWinHeight"); - } - - // Memory - bool memory = ui->memoryGroupBox->isChecked(); - m_settings->set("OverrideMemory", memory); - if (memory) - { - int min = ui->minMemSpinBox->value(); - int max = ui->maxMemSpinBox->value(); - if(min < max) - { - m_settings->set("MinMemAlloc", min); - m_settings->set("MaxMemAlloc", max); - } - else - { - m_settings->set("MinMemAlloc", max); - m_settings->set("MaxMemAlloc", min); - } - m_settings->set("PermGen", ui->permGenSpinBox->value()); - } - else - { - m_settings->reset("MinMemAlloc"); - m_settings->reset("MaxMemAlloc"); - m_settings->reset("PermGen"); - } - - // Java Install Settings - bool javaInstall = ui->javaSettingsGroupBox->isChecked(); - m_settings->set("OverrideJavaLocation", javaInstall); - if (javaInstall) - { - m_settings->set("JavaPath", ui->javaPathTextBox->text()); - } - else - { - m_settings->reset("JavaPath"); - } - - // Java arguments - bool javaArgs = ui->javaArgumentsGroupBox->isChecked(); - m_settings->set("OverrideJavaArgs", javaArgs); - if(javaArgs) - { - m_settings->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " ")); - JavaCommon::checkJVMArgs(m_settings->get("JvmArgs").toString(), this->parentWidget()); - } - else - { - m_settings->reset("JvmArgs"); - } - - // old generic 'override both' is removed. - m_settings->reset("OverrideJava"); - - // Custom Commands - bool custcmd = ui->customCommands->checked(); - m_settings->set("OverrideCommands", custcmd); - if (custcmd) - { - m_settings->set("PreLaunchCommand", ui->customCommands->prelaunchCommand()); - m_settings->set("WrapperCommand", ui->customCommands->wrapperCommand()); - m_settings->set("PostExitCommand", ui->customCommands->postexitCommand()); - } - else - { - m_settings->reset("PreLaunchCommand"); - m_settings->reset("WrapperCommand"); - m_settings->reset("PostExitCommand"); - } + SettingsObject::Lock lock(m_settings); + + // Console + bool console = ui->consoleSettingsBox->isChecked(); + m_settings->set("OverrideConsole", console); + if (console) + { + m_settings->set("ShowConsole", ui->showConsoleCheck->isChecked()); + m_settings->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); + m_settings->set("ShowConsoleOnError", ui->showConsoleErrorCheck->isChecked()); + } + else + { + m_settings->reset("ShowConsole"); + m_settings->reset("AutoCloseConsole"); + m_settings->reset("ShowConsoleOnError"); + } + + // Window Size + bool window = ui->windowSizeGroupBox->isChecked(); + m_settings->set("OverrideWindow", window); + if (window) + { + m_settings->set("LaunchMaximized", ui->maximizedCheckBox->isChecked()); + m_settings->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); + m_settings->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); + } + else + { + m_settings->reset("LaunchMaximized"); + m_settings->reset("MinecraftWinWidth"); + m_settings->reset("MinecraftWinHeight"); + } + + // Memory + bool memory = ui->memoryGroupBox->isChecked(); + m_settings->set("OverrideMemory", memory); + if (memory) + { + int min = ui->minMemSpinBox->value(); + int max = ui->maxMemSpinBox->value(); + if(min < max) + { + m_settings->set("MinMemAlloc", min); + m_settings->set("MaxMemAlloc", max); + } + else + { + m_settings->set("MinMemAlloc", max); + m_settings->set("MaxMemAlloc", min); + } + m_settings->set("PermGen", ui->permGenSpinBox->value()); + } + else + { + m_settings->reset("MinMemAlloc"); + m_settings->reset("MaxMemAlloc"); + m_settings->reset("PermGen"); + } + + // Java Install Settings + bool javaInstall = ui->javaSettingsGroupBox->isChecked(); + m_settings->set("OverrideJavaLocation", javaInstall); + if (javaInstall) + { + m_settings->set("JavaPath", ui->javaPathTextBox->text()); + } + else + { + m_settings->reset("JavaPath"); + } + + // Java arguments + bool javaArgs = ui->javaArgumentsGroupBox->isChecked(); + m_settings->set("OverrideJavaArgs", javaArgs); + if(javaArgs) + { + m_settings->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " ")); + JavaCommon::checkJVMArgs(m_settings->get("JvmArgs").toString(), this->parentWidget()); + } + else + { + m_settings->reset("JvmArgs"); + } + + // old generic 'override both' is removed. + m_settings->reset("OverrideJava"); + + // Custom Commands + bool custcmd = ui->customCommands->checked(); + m_settings->set("OverrideCommands", custcmd); + if (custcmd) + { + m_settings->set("PreLaunchCommand", ui->customCommands->prelaunchCommand()); + m_settings->set("WrapperCommand", ui->customCommands->wrapperCommand()); + m_settings->set("PostExitCommand", ui->customCommands->postexitCommand()); + } + else + { + m_settings->reset("PreLaunchCommand"); + m_settings->reset("WrapperCommand"); + m_settings->reset("PostExitCommand"); + } } void InstanceSettingsPage::loadSettings() { - // Console - ui->consoleSettingsBox->setChecked(m_settings->get("OverrideConsole").toBool()); - ui->showConsoleCheck->setChecked(m_settings->get("ShowConsole").toBool()); - ui->autoCloseConsoleCheck->setChecked(m_settings->get("AutoCloseConsole").toBool()); - ui->showConsoleErrorCheck->setChecked(m_settings->get("ShowConsoleOnError").toBool()); - - // Window Size - ui->windowSizeGroupBox->setChecked(m_settings->get("OverrideWindow").toBool()); - ui->maximizedCheckBox->setChecked(m_settings->get("LaunchMaximized").toBool()); - ui->windowWidthSpinBox->setValue(m_settings->get("MinecraftWinWidth").toInt()); - ui->windowHeightSpinBox->setValue(m_settings->get("MinecraftWinHeight").toInt()); - - // Memory - ui->memoryGroupBox->setChecked(m_settings->get("OverrideMemory").toBool()); - int min = m_settings->get("MinMemAlloc").toInt(); - int max = m_settings->get("MaxMemAlloc").toInt(); - if(min < max) - { - ui->minMemSpinBox->setValue(min); - ui->maxMemSpinBox->setValue(max); - } - else - { - ui->minMemSpinBox->setValue(max); - ui->maxMemSpinBox->setValue(min); - } - ui->permGenSpinBox->setValue(m_settings->get("PermGen").toInt()); - - // Java Settings - bool overrideJava = m_settings->get("OverrideJava").toBool(); - bool overrideLocation = m_settings->get("OverrideJavaLocation").toBool() || overrideJava; - bool overrideArgs = m_settings->get("OverrideJavaArgs").toBool() || overrideJava; - - ui->javaSettingsGroupBox->setChecked(overrideLocation); - ui->javaPathTextBox->setText(m_settings->get("JavaPath").toString()); - - ui->javaArgumentsGroupBox->setChecked(overrideArgs); - ui->jvmArgsTextBox->setPlainText(m_settings->get("JvmArgs").toString()); - - // Custom commands - ui->customCommands->initialize( - true, - m_settings->get("OverrideCommands").toBool(), - m_settings->get("PreLaunchCommand").toString(), - m_settings->get("WrapperCommand").toString(), - m_settings->get("PostExitCommand").toString() - ); + // Console + ui->consoleSettingsBox->setChecked(m_settings->get("OverrideConsole").toBool()); + ui->showConsoleCheck->setChecked(m_settings->get("ShowConsole").toBool()); + ui->autoCloseConsoleCheck->setChecked(m_settings->get("AutoCloseConsole").toBool()); + ui->showConsoleErrorCheck->setChecked(m_settings->get("ShowConsoleOnError").toBool()); + + // Window Size + ui->windowSizeGroupBox->setChecked(m_settings->get("OverrideWindow").toBool()); + ui->maximizedCheckBox->setChecked(m_settings->get("LaunchMaximized").toBool()); + ui->windowWidthSpinBox->setValue(m_settings->get("MinecraftWinWidth").toInt()); + ui->windowHeightSpinBox->setValue(m_settings->get("MinecraftWinHeight").toInt()); + + // Memory + ui->memoryGroupBox->setChecked(m_settings->get("OverrideMemory").toBool()); + int min = m_settings->get("MinMemAlloc").toInt(); + int max = m_settings->get("MaxMemAlloc").toInt(); + if(min < max) + { + ui->minMemSpinBox->setValue(min); + ui->maxMemSpinBox->setValue(max); + } + else + { + ui->minMemSpinBox->setValue(max); + ui->maxMemSpinBox->setValue(min); + } + ui->permGenSpinBox->setValue(m_settings->get("PermGen").toInt()); + bool permGenVisible = m_settings->get("PermGenVisible").toBool(); + ui->permGenSpinBox->setVisible(permGenVisible); + ui->labelPermGen->setVisible(permGenVisible); + ui->labelPermgenNote->setVisible(permGenVisible); + + + // Java Settings + bool overrideJava = m_settings->get("OverrideJava").toBool(); + bool overrideLocation = m_settings->get("OverrideJavaLocation").toBool() || overrideJava; + bool overrideArgs = m_settings->get("OverrideJavaArgs").toBool() || overrideJava; + + ui->javaSettingsGroupBox->setChecked(overrideLocation); + ui->javaPathTextBox->setText(m_settings->get("JavaPath").toString()); + + ui->javaArgumentsGroupBox->setChecked(overrideArgs); + ui->jvmArgsTextBox->setPlainText(m_settings->get("JvmArgs").toString()); + + // Custom commands + ui->customCommands->initialize( + true, + m_settings->get("OverrideCommands").toBool(), + m_settings->get("PreLaunchCommand").toString(), + m_settings->get("WrapperCommand").toString(), + m_settings->get("PostExitCommand").toString() + ); } void InstanceSettingsPage::on_javaDetectBtn_clicked() { - JavaInstallPtr java; + JavaInstallPtr java; - VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true); - vselect.setResizeOn(2); - vselect.exec(); + VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true); + vselect.setResizeOn(2); + vselect.exec(); - if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) - { - java = std::dynamic_pointer_cast<JavaInstall>(vselect.selectedVersion()); - ui->javaPathTextBox->setText(java->path); - } + if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) + { + java = std::dynamic_pointer_cast<JavaInstall>(vselect.selectedVersion()); + ui->javaPathTextBox->setText(java->path); + bool visible = java->id.requiresPermGen() && m_settings->get("OverrideMemory").toBool(); + ui->permGenSpinBox->setVisible(visible); + ui->labelPermGen->setVisible(visible); + ui->labelPermgenNote->setVisible(visible); + m_settings->set("PermGenVisible", visible); + } } void InstanceSettingsPage::on_javaBrowseBtn_clicked() { - QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable")); - - // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if(raw_path.isEmpty()) - { - return; - } - QString cooked_path = FS::NormalizePath(raw_path); - - QFileInfo javaInfo(cooked_path);; - if(!javaInfo.exists() || !javaInfo.isExecutable()) - { - return; - } - ui->javaPathTextBox->setText(cooked_path); + QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable")); + + // do not allow current dir - it's dirty. Do not allow dirs that don't exist + if(raw_path.isEmpty()) + { + return; + } + QString cooked_path = FS::NormalizePath(raw_path); + + QFileInfo javaInfo(cooked_path); + if(!javaInfo.exists() || !javaInfo.isExecutable()) + { + return; + } + ui->javaPathTextBox->setText(cooked_path); + + // custom Java could be anything... enable perm gen option + ui->permGenSpinBox->setVisible(true); + ui->labelPermGen->setVisible(true); + ui->labelPermgenNote->setVisible(true); + m_settings->set("PermGenVisible", true); } void InstanceSettingsPage::on_javaTestBtn_clicked() { - if(checker) - { - return; - } - checker.reset(new JavaCommon::TestCheck( - this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->toPlainText().replace("\n", " "), - ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value())); - connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished())); - checker->run(); + if(checker) + { + return; + } + checker.reset(new JavaCommon::TestCheck( + this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->toPlainText().replace("\n", " "), + ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value())); + connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished())); + checker->run(); } void InstanceSettingsPage::checkerFinished() { - checker.reset(); + checker.reset(); } diff --git a/application/pages/instance/InstanceSettingsPage.h b/application/pages/instance/InstanceSettingsPage.h index c5d7d3b6..c3c78fd5 100644 --- a/application/pages/instance/InstanceSettingsPage.h +++ b/application/pages/instance/InstanceSettingsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,43 +32,45 @@ class InstanceSettingsPage; class InstanceSettingsPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit InstanceSettingsPage(BaseInstance *inst, QWidget *parent = 0); - virtual ~InstanceSettingsPage(); - virtual QString displayName() const override - { - return tr("Settings"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("instance-settings"); - } - virtual QString id() const override - { - return "settings"; - } - virtual bool apply() override; - virtual QString helpPage() const override - { - return "Instance-settings"; - } - virtual bool shouldDisplay() const override; + explicit InstanceSettingsPage(BaseInstance *inst, QWidget *parent = 0); + virtual ~InstanceSettingsPage(); + virtual QString displayName() const override + { + return tr("Settings"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("instance-settings"); + } + virtual QString id() const override + { + return "settings"; + } + virtual bool apply() override; + virtual QString helpPage() const override + { + return "Instance-settings"; + } + virtual bool shouldDisplay() const override; private slots: - void on_javaDetectBtn_clicked(); - void on_javaTestBtn_clicked(); - void on_javaBrowseBtn_clicked(); + void on_javaDetectBtn_clicked(); + void on_javaTestBtn_clicked(); + void on_javaBrowseBtn_clicked(); - void applySettings(); - void loadSettings(); + void applySettings(); + void loadSettings(); - void checkerFinished(); + void checkerFinished(); + + void globalSettingsButtonClicked(bool checked); private: - Ui::InstanceSettingsPage *ui; - BaseInstance *m_instance; - SettingsObjectPtr m_settings; - unique_qobject_ptr<JavaCommon::TestCheck> checker; + Ui::InstanceSettingsPage *ui; + BaseInstance *m_instance; + SettingsObjectPtr m_settings; + unique_qobject_ptr<JavaCommon::TestCheck> checker; }; diff --git a/application/pages/instance/InstanceSettingsPage.ui b/application/pages/instance/InstanceSettingsPage.ui index 0c180df3..d6de53ee 100644 --- a/application/pages/instance/InstanceSettingsPage.ui +++ b/application/pages/instance/InstanceSettingsPage.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>553</width> - <height>522</height> + <width>738</width> + <height>804</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout"> @@ -24,6 +24,16 @@ <number>0</number> </property> <item> + <widget class="QCommandLinkButton" name="openGlobalJavaSettingsButton"> + <property name="text"> + <string>Open Global Settings</string> + </property> + <property name="description"> + <string>The settings here are overrides for global settings.</string> + </property> + </widget> + </item> + <item> <widget class="QTabWidget" name="settingsTabs"> <property name="tabShape"> <enum>QTabWidget::Rounded</enum> @@ -367,6 +377,7 @@ </customwidget> </customwidgets> <tabstops> + <tabstop>openGlobalJavaSettingsButton</tabstop> <tabstop>settingsTabs</tabstop> <tabstop>javaSettingsGroupBox</tabstop> <tabstop>javaPathTextBox</tabstop> diff --git a/application/pages/instance/LegacyUpgradePage.cpp b/application/pages/instance/LegacyUpgradePage.cpp index f808ab88..af800b03 100644 --- a/application/pages/instance/LegacyUpgradePage.cpp +++ b/application/pages/instance/LegacyUpgradePage.cpp @@ -1,50 +1,50 @@ #include "LegacyUpgradePage.h" #include "ui_LegacyUpgradePage.h" +#include "InstanceList.h" #include "minecraft/legacy/LegacyInstance.h" #include "minecraft/legacy/LegacyUpgradeTask.h" #include "MultiMC.h" -#include "FolderInstanceProvider.h" #include "dialogs/CustomMessageBox.h" #include "dialogs/ProgressDialog.h" LegacyUpgradePage::LegacyUpgradePage(InstancePtr inst, QWidget *parent) - : QWidget(parent), ui(new Ui::LegacyUpgradePage), m_inst(inst) + : QWidget(parent), ui(new Ui::LegacyUpgradePage), m_inst(inst) { - ui->setupUi(this); + ui->setupUi(this); } LegacyUpgradePage::~LegacyUpgradePage() { - delete ui; + delete ui; } void LegacyUpgradePage::runModalTask(Task *task) { - connect(task, &Task::failed, [this](QString reason) - { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Warning)->show(); - }); - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - if(loadDialog.execWithTask(task) == QDialog::Accepted) - { - m_container->requestClose(); - } + connect(task, &Task::failed, [this](QString reason) + { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Warning)->show(); + }); + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + if(loadDialog.execWithTask(task) == QDialog::Accepted) + { + m_container->requestClose(); + } } void LegacyUpgradePage::on_upgradeButton_clicked() { - QString newName = tr("%1 (Migrated)").arg(m_inst->name()); - auto upgradeTask = new LegacyUpgradeTask(m_inst); - upgradeTask->setName(newName); - upgradeTask->setGroup(m_inst->group()); - upgradeTask->setIcon(m_inst->iconKey()); - std::unique_ptr<Task> task(MMC->folderProvider()->wrapInstanceTask(upgradeTask)); - runModalTask(task.get()); + QString newName = tr("%1 (Migrated)").arg(m_inst->name()); + auto upgradeTask = new LegacyUpgradeTask(m_inst); + upgradeTask->setName(newName); + upgradeTask->setGroup(MMC->instances()->getInstanceGroup(m_inst->id())); + upgradeTask->setIcon(m_inst->iconKey()); + unique_qobject_ptr<Task> task(MMC->instances()->wrapInstanceTask(upgradeTask)); + runModalTask(task.get()); } bool LegacyUpgradePage::shouldDisplay() const { - return !m_inst->isRunning(); + return !m_inst->isRunning(); } diff --git a/application/pages/instance/LegacyUpgradePage.h b/application/pages/instance/LegacyUpgradePage.h index 3e1abe93..4f86a721 100644 --- a/application/pages/instance/LegacyUpgradePage.h +++ b/application/pages/instance/LegacyUpgradePage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,36 +29,36 @@ class LegacyUpgradePage; class LegacyUpgradePage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit LegacyUpgradePage(InstancePtr inst, QWidget *parent = 0); - virtual ~LegacyUpgradePage(); - virtual QString displayName() const override - { - return tr("Upgrade"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("checkupdate"); - } - virtual QString id() const override - { - return "upgrade"; - } - virtual QString helpPage() const override - { - return "Legacy-upgrade"; - } - virtual bool shouldDisplay() const override; + explicit LegacyUpgradePage(InstancePtr inst, QWidget *parent = 0); + virtual ~LegacyUpgradePage(); + virtual QString displayName() const override + { + return tr("Upgrade"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("checkupdate"); + } + virtual QString id() const override + { + return "upgrade"; + } + virtual QString helpPage() const override + { + return "Legacy-upgrade"; + } + virtual bool shouldDisplay() const override; private slots: - void on_upgradeButton_clicked(); + void on_upgradeButton_clicked(); private: - void runModalTask(Task *task); + void runModalTask(Task *task); private: - Ui::LegacyUpgradePage *ui; - InstancePtr m_inst; + Ui::LegacyUpgradePage *ui; + InstancePtr m_inst; }; diff --git a/application/pages/instance/LogPage.cpp b/application/pages/instance/LogPage.cpp index 0fa1ee67..94ada424 100644 --- a/application/pages/instance/LogPage.cpp +++ b/application/pages/instance/LogPage.cpp @@ -15,298 +15,298 @@ class LogFormatProxyModel : public QIdentityProxyModel { public: - LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) - { - } - QVariant data(const QModelIndex &index, int role) const override - { - switch(role) - { - case Qt::FontRole: - return m_font; - case Qt::TextColorRole: - { - MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); - return m_colors->getFront(level); - } - case Qt::BackgroundRole: - { - MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); - return m_colors->getBack(level); - } - default: - return QIdentityProxyModel::data(index, role); - } - } - - void setFont(QFont font) - { - m_font = font; - } - - void setColors(LogColorCache* colors) - { - m_colors.reset(colors); - } - - QModelIndex find(const QModelIndex &start, const QString &value, bool reverse) const - { - QModelIndex parentIndex = parent(start); - auto compare = [&](int r) -> QModelIndex - { - QModelIndex idx = index(r, start.column(), parentIndex); - if (!idx.isValid() || idx == start) - { - return QModelIndex(); - } - QVariant v = data(idx, Qt::DisplayRole); - QString t = v.toString(); - if (t.contains(value, Qt::CaseInsensitive)) - return idx; - return QModelIndex(); - }; - if(reverse) - { - int from = start.row(); - int to = 0; - - for (int i = 0; i < 2; ++i) - { - for (int r = from; (r >= to); --r) - { - auto idx = compare(r); - if(idx.isValid()) - return idx; - } - // prepare for the next iteration - from = rowCount() - 1; - to = start.row(); - } - } - else - { - int from = start.row(); - int to = rowCount(parentIndex); - - for (int i = 0; i < 2; ++i) - { - for (int r = from; (r < to); ++r) - { - auto idx = compare(r); - if(idx.isValid()) - return idx; - } - // prepare for the next iteration - from = 0; - to = start.row(); - } - } - return QModelIndex(); - } + LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) + { + } + QVariant data(const QModelIndex &index, int role) const override + { + switch(role) + { + case Qt::FontRole: + return m_font; + case Qt::TextColorRole: + { + MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); + return m_colors->getFront(level); + } + case Qt::BackgroundRole: + { + MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); + return m_colors->getBack(level); + } + default: + return QIdentityProxyModel::data(index, role); + } + } + + void setFont(QFont font) + { + m_font = font; + } + + void setColors(LogColorCache* colors) + { + m_colors.reset(colors); + } + + QModelIndex find(const QModelIndex &start, const QString &value, bool reverse) const + { + QModelIndex parentIndex = parent(start); + auto compare = [&](int r) -> QModelIndex + { + QModelIndex idx = index(r, start.column(), parentIndex); + if (!idx.isValid() || idx == start) + { + return QModelIndex(); + } + QVariant v = data(idx, Qt::DisplayRole); + QString t = v.toString(); + if (t.contains(value, Qt::CaseInsensitive)) + return idx; + return QModelIndex(); + }; + if(reverse) + { + int from = start.row(); + int to = 0; + + for (int i = 0; i < 2; ++i) + { + for (int r = from; (r >= to); --r) + { + auto idx = compare(r); + if(idx.isValid()) + return idx; + } + // prepare for the next iteration + from = rowCount() - 1; + to = start.row(); + } + } + else + { + int from = start.row(); + int to = rowCount(parentIndex); + + for (int i = 0; i < 2; ++i) + { + for (int r = from; (r < to); ++r) + { + auto idx = compare(r); + if(idx.isValid()) + return idx; + } + // prepare for the next iteration + from = 0; + to = start.row(); + } + } + return QModelIndex(); + } private: - QFont m_font; - std::unique_ptr<LogColorCache> m_colors; + QFont m_font; + std::unique_ptr<LogColorCache> m_colors; }; LogPage::LogPage(InstancePtr instance, QWidget *parent) - : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) + : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - - m_proxy = new LogFormatProxyModel(this); - // set up text colors in the log proxy and adapt them to the current theme foreground and background - { - auto origForeground = ui->text->palette().color(ui->text->foregroundRole()); - auto origBackground = ui->text->palette().color(ui->text->backgroundRole()); - m_proxy->setColors(new LogColorCache(origForeground, origBackground)); - } - - // set up fonts in the log proxy - { - QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); - bool conversionOk = false; - int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); - if(!conversionOk) - { - fontSize = 11; - } - m_proxy->setFont(QFont(fontFamily, fontSize)); - } - - ui->text->setModel(m_proxy); - - // set up instance and launch process recognition - { - auto launchTask = m_instance->getLaunchTask(); - if(launchTask) - { - setInstanceLaunchTaskChanged(launchTask, true); - } - connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &LogPage::onInstanceLaunchTaskChanged); - } - - auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); - connect(findShortcut, SIGNAL(activated()), SLOT(findActivated())); - auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this); - connect(findNextShortcut, SIGNAL(activated()), SLOT(findNextActivated())); - connect(ui->searchBar, SIGNAL(returnPressed()), SLOT(on_findButton_clicked())); - auto findPreviousShortcut = new QShortcut(QKeySequence(QKeySequence::FindPrevious), this); - connect(findPreviousShortcut, SIGNAL(activated()), SLOT(findPreviousActivated())); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + + m_proxy = new LogFormatProxyModel(this); + // set up text colors in the log proxy and adapt them to the current theme foreground and background + { + auto origForeground = ui->text->palette().color(ui->text->foregroundRole()); + auto origBackground = ui->text->palette().color(ui->text->backgroundRole()); + m_proxy->setColors(new LogColorCache(origForeground, origBackground)); + } + + // set up fonts in the log proxy + { + QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); + bool conversionOk = false; + int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); + if(!conversionOk) + { + fontSize = 11; + } + m_proxy->setFont(QFont(fontFamily, fontSize)); + } + + ui->text->setModel(m_proxy); + + // set up instance and launch process recognition + { + auto launchTask = m_instance->getLaunchTask(); + if(launchTask) + { + setInstanceLaunchTaskChanged(launchTask, true); + } + connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &LogPage::onInstanceLaunchTaskChanged); + } + + auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); + connect(findShortcut, SIGNAL(activated()), SLOT(findActivated())); + auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this); + connect(findNextShortcut, SIGNAL(activated()), SLOT(findNextActivated())); + connect(ui->searchBar, SIGNAL(returnPressed()), SLOT(on_findButton_clicked())); + auto findPreviousShortcut = new QShortcut(QKeySequence(QKeySequence::FindPrevious), this); + connect(findPreviousShortcut, SIGNAL(activated()), SLOT(findPreviousActivated())); } LogPage::~LogPage() { - delete ui; + delete ui; } void LogPage::modelStateToUI() { - if(m_model->wrapLines()) - { - ui->text->setWordWrap(true); - ui->wrapCheckbox->setCheckState(Qt::Checked); - } - else - { - ui->text->setWordWrap(false); - ui->wrapCheckbox->setCheckState(Qt::Unchecked); - } - if(m_model->suspended()) - { - ui->trackLogCheckbox->setCheckState(Qt::Unchecked); - } - else - { - ui->trackLogCheckbox->setCheckState(Qt::Checked); - } + if(m_model->wrapLines()) + { + ui->text->setWordWrap(true); + ui->wrapCheckbox->setCheckState(Qt::Checked); + } + else + { + ui->text->setWordWrap(false); + ui->wrapCheckbox->setCheckState(Qt::Unchecked); + } + if(m_model->suspended()) + { + ui->trackLogCheckbox->setCheckState(Qt::Unchecked); + } + else + { + ui->trackLogCheckbox->setCheckState(Qt::Checked); + } } void LogPage::UIToModelState() { - if(!m_model) - { - return; - } - m_model->setLineWrap(ui->wrapCheckbox->checkState() == Qt::Checked); - m_model->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked); + if(!m_model) + { + return; + } + m_model->setLineWrap(ui->wrapCheckbox->checkState() == Qt::Checked); + m_model->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked); } -void LogPage::setInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc, bool initial) +void LogPage::setInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc, bool initial) { - m_process = proc; - if(m_process) - { - m_model = proc->getLogModel(); - m_proxy->setSourceModel(m_model.get()); - if(initial) - { - modelStateToUI(); - } - else - { - UIToModelState(); - } - } - else - { - m_proxy->setSourceModel(nullptr); - m_model.reset(); - } + m_process = proc; + if(m_process) + { + m_model = proc->getLogModel(); + m_proxy->setSourceModel(m_model.get()); + if(initial) + { + modelStateToUI(); + } + else + { + UIToModelState(); + } + } + else + { + m_proxy->setSourceModel(nullptr); + m_model.reset(); + } } -void LogPage::onInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc) +void LogPage::onInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc) { - setInstanceLaunchTaskChanged(proc, false); + setInstanceLaunchTaskChanged(proc, false); } bool LogPage::apply() { - return true; + return true; } bool LogPage::shouldDisplay() const { - return m_instance->isRunning() || m_proxy->rowCount() > 0; + return m_instance->isRunning() || m_proxy->rowCount() > 0; } void LogPage::on_btnPaste_clicked() { - if(!m_model) - return; - - //FIXME: turn this into a proper task and move the upload logic out of GuiUtil! - m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log upload triggered at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); - auto url = GuiUtil::uploadPaste(m_model->toPlainText(), this); - if(!url.isEmpty()) - { - m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log uploaded to: %1").arg(url)); - } - else - { - m_model->append(MessageLevel::Error, tr("MultiMC: Log upload failed!")); - } + if(!m_model) + return; + + //FIXME: turn this into a proper task and move the upload logic out of GuiUtil! + m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log upload triggered at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); + auto url = GuiUtil::uploadPaste(m_model->toPlainText(), this); + if(!url.isEmpty()) + { + m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log uploaded to: %1").arg(url)); + } + else + { + m_model->append(MessageLevel::Error, tr("MultiMC: Log upload failed!")); + } } void LogPage::on_btnCopy_clicked() { - if(!m_model) - return; - m_model->append(MessageLevel::MultiMC, QString("Clipboard copy at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); - GuiUtil::setClipboardText(m_model->toPlainText()); + if(!m_model) + return; + m_model->append(MessageLevel::MultiMC, QString("Clipboard copy at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); + GuiUtil::setClipboardText(m_model->toPlainText()); } void LogPage::on_btnClear_clicked() { - if(!m_model) - return; - m_model->clear(); - m_container->refreshContainer(); + if(!m_model) + return; + m_model->clear(); + m_container->refreshContainer(); } void LogPage::on_btnBottom_clicked() { - ui->text->scrollToBottom(); + ui->text->scrollToBottom(); } void LogPage::on_trackLogCheckbox_clicked(bool checked) { - if(!m_model) - return; - m_model->suspend(!checked); + if(!m_model) + return; + m_model->suspend(!checked); } void LogPage::on_wrapCheckbox_clicked(bool checked) { - ui->text->setWordWrap(checked); - if(!m_model) - return; - m_model->setLineWrap(checked); + ui->text->setWordWrap(checked); + if(!m_model) + return; + m_model->setLineWrap(checked); } void LogPage::on_findButton_clicked() { - auto modifiers = QApplication::keyboardModifiers(); - bool reverse = modifiers & Qt::ShiftModifier; - ui->text->findNext(ui->searchBar->text(), reverse); + auto modifiers = QApplication::keyboardModifiers(); + bool reverse = modifiers & Qt::ShiftModifier; + ui->text->findNext(ui->searchBar->text(), reverse); } void LogPage::findNextActivated() { - ui->text->findNext(ui->searchBar->text(), false); + ui->text->findNext(ui->searchBar->text(), false); } void LogPage::findPreviousActivated() { - ui->text->findNext(ui->searchBar->text(), true); + ui->text->findNext(ui->searchBar->text(), true); } void LogPage::findActivated() { - // focus the search bar if it doesn't have focus - if (!ui->searchBar->hasFocus()) - { - ui->searchBar->setFocus(); - ui->searchBar->selectAll(); - } + // focus the search bar if it doesn't have focus + if (!ui->searchBar->hasFocus()) + { + ui->searchBar->setFocus(); + ui->searchBar->selectAll(); + } } diff --git a/application/pages/instance/LogPage.h b/application/pages/instance/LogPage.h index 2229418d..9c3b56a9 100644 --- a/application/pages/instance/LogPage.h +++ b/application/pages/instance/LogPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,56 +31,56 @@ class LogFormatProxyModel; class LogPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit LogPage(InstancePtr instance, QWidget *parent = 0); - virtual ~LogPage(); - virtual QString displayName() const override - { - return tr("Minecraft Log"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("log"); - } - virtual QString id() const override - { - return "console"; - } - virtual bool apply() override; - virtual QString helpPage() const override - { - return "Minecraft-Logs"; - } - virtual bool shouldDisplay() const override; + explicit LogPage(InstancePtr instance, QWidget *parent = 0); + virtual ~LogPage(); + virtual QString displayName() const override + { + return tr("Minecraft Log"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("log"); + } + virtual QString id() const override + { + return "console"; + } + virtual bool apply() override; + virtual QString helpPage() const override + { + return "Minecraft-Logs"; + } + virtual bool shouldDisplay() const override; private slots: - void on_btnPaste_clicked(); - void on_btnCopy_clicked(); - void on_btnClear_clicked(); - void on_btnBottom_clicked(); + void on_btnPaste_clicked(); + void on_btnCopy_clicked(); + void on_btnClear_clicked(); + void on_btnBottom_clicked(); - void on_trackLogCheckbox_clicked(bool checked); - void on_wrapCheckbox_clicked(bool checked); + void on_trackLogCheckbox_clicked(bool checked); + void on_wrapCheckbox_clicked(bool checked); - void on_findButton_clicked(); - void findActivated(); - void findNextActivated(); - void findPreviousActivated(); + void on_findButton_clicked(); + void findActivated(); + void findNextActivated(); + void findPreviousActivated(); - void onInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc); + void onInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc); private: - void modelStateToUI(); - void UIToModelState(); - void setInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc, bool initial); + void modelStateToUI(); + void UIToModelState(); + void setInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc, bool initial); private: - Ui::LogPage *ui; - InstancePtr m_instance; - std::shared_ptr<LaunchTask> m_process; + Ui::LogPage *ui; + InstancePtr m_instance; + shared_qobject_ptr<LaunchTask> m_process; - LogFormatProxyModel * m_proxy; - shared_qobject_ptr <LogModel> m_model; + LogFormatProxyModel * m_proxy; + shared_qobject_ptr <LogModel> m_model; }; diff --git a/application/pages/instance/ModFolderPage.cpp b/application/pages/instance/ModFolderPage.cpp index 90155df3..d449f8bf 100644 --- a/application/pages/instance/ModFolderPage.cpp +++ b/application/pages/instance/ModFolderPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,191 +20,324 @@ #include <QEvent> #include <QKeyEvent> #include <QAbstractItemModel> +#include <QMenu> #include "MultiMC.h" #include "dialogs/CustomMessageBox.h" -#include "dialogs/ModEditDialogCommon.h" #include <GuiUtil.h> -#include "minecraft/ModList.h" -#include "minecraft/Mod.h" +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/Mod.h" #include "minecraft/VersionFilterData.h" #include "minecraft/ComponentList.h" #include <DesktopServices.h> -ModFolderPage::ModFolderPage(BaseInstance *inst, std::shared_ptr<ModList> mods, QString id, - QString iconName, QString displayName, QString helpPage, - QWidget *parent) - : QWidget(parent), ui(new Ui::ModFolderPage) -{ - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - m_inst = inst; - m_mods = mods; - m_id = id; - m_displayName = displayName; - m_iconName = iconName; - m_helpName = helpPage; - m_fileSelectionFilter = "%1 (*.zip *.jar)"; - m_filterModel = new QSortFilterProxyModel(this); - m_filterModel->setDynamicSortFilter(true); - m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); - m_filterModel->setSourceModel(m_mods.get()); - m_filterModel->setFilterKeyColumn(-1); - ui->modTreeView->setModel(m_filterModel); - ui->modTreeView->installEventFilter(this); - ui->modTreeView->sortByColumn(1, Qt::AscendingOrder); - auto smodel = ui->modTreeView->selectionModel(); - connect(smodel, &QItemSelectionModel::currentChanged, this, &ModFolderPage::modCurrent); - connect(ui->filterEdit, &QLineEdit::textChanged, this, &ModFolderPage::on_filterTextChanged ); +#include <QSortFilterProxyModel> +#include "Version.h" + +namespace { + // FIXME: wasteful + void RemoveThePrefix(QString & string) { + QRegularExpression regex(QStringLiteral("^(([Tt][Hh][eE])|([Tt][eE][Hh])) +")); + string.remove(regex); + string = string.trimmed(); + } +} + +class ModSortProxy : public QSortFilterProxyModel +{ +public: + explicit ModSortProxy(QObject *parent = 0) : QSortFilterProxyModel(parent) + { + } + +protected: + bool lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const override + { + ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel()); + if( + !model || + !source_left.isValid() || + !source_right.isValid() || + source_left.column() != source_right.column() + ) { + return QSortFilterProxyModel::lessThan(source_left, source_right); + } + + // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and proceed. + + auto column = (ModFolderModel::Columns) source_left.column(); + bool invert = false; + switch(column) { + // GH-2550 - sort by enabled/disabled + case ModFolderModel::ActiveColumn: { + auto dataL = source_left.data(Qt::CheckStateRole).toBool(); + auto dataR = source_right.data(Qt::CheckStateRole).toBool(); + if(dataL != dataR) { + return dataL > dataR; + } + // fallthrough + invert = sortOrder() == Qt::DescendingOrder; + } + // GH-2722 - sort mod names in a way that discards "The" prefixes + case ModFolderModel::NameColumn: { + auto dataL = model->data(model->index(source_left.row(), ModFolderModel::NameColumn)).toString(); + RemoveThePrefix(dataL); + auto dataR = model->data(model->index(source_right.row(), ModFolderModel::NameColumn)).toString(); + RemoveThePrefix(dataR); + + auto less = dataL.compare(dataR, sortCaseSensitivity()); + if(less != 0) { + return invert ? (less > 0) : (less < 0); + } + // fallthrough + invert = sortOrder() == Qt::DescendingOrder; + } + // GH-2762 - sort versions by parsing them as versions + case ModFolderModel::VersionColumn: { + auto dataL = Version(model->data(model->index(source_left.row(), ModFolderModel::VersionColumn)).toString()); + auto dataR = Version(model->data(model->index(source_right.row(), ModFolderModel::VersionColumn)).toString()); + return invert ? (dataL > dataR) : (dataL < dataR); + } + default: { + return QSortFilterProxyModel::lessThan(source_left, source_right); + } + } + } +}; + +ModFolderPage::ModFolderPage( + BaseInstance *inst, + std::shared_ptr<ModFolderModel> mods, + QString id, + QString iconName, + QString displayName, + QString helpPage, + QWidget *parent +) : + QMainWindow(parent), + ui(new Ui::ModFolderPage) +{ + ui->setupUi(this); + ui->actionsToolbar->insertSpacer(ui->actionView_configs); + + m_inst = inst; + on_RunningState_changed(m_inst && m_inst->isRunning()); + m_mods = mods; + m_id = id; + m_displayName = displayName; + m_iconName = iconName; + m_helpName = helpPage; + m_fileSelectionFilter = "%1 (*.zip *.jar)"; + m_filterModel = new ModSortProxy(this); + m_filterModel->setDynamicSortFilter(true); + m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); + m_filterModel->setSourceModel(m_mods.get()); + m_filterModel->setFilterKeyColumn(-1); + ui->modTreeView->setModel(m_filterModel); + ui->modTreeView->installEventFilter(this); + ui->modTreeView->sortByColumn(1, Qt::AscendingOrder); + ui->modTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->modTreeView, &ModListView::customContextMenuRequested, this, &ModFolderPage::ShowContextMenu); + connect(ui->modTreeView, &ModListView::activated, this, &ModFolderPage::modItemActivated); + + auto smodel = ui->modTreeView->selectionModel(); + connect(smodel, &QItemSelectionModel::currentChanged, this, &ModFolderPage::modCurrent); + connect(ui->filterEdit, &QLineEdit::textChanged, this, &ModFolderPage::on_filterTextChanged ); + connect(m_inst, &BaseInstance::runningStatusChanged, this, &ModFolderPage::on_RunningState_changed); +} + +void ModFolderPage::modItemActivated(const QModelIndex&) +{ + if(!m_controlsEnabled) { + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); + m_mods->setModStatus(selection.indexes(), ModFolderModel::Toggle); +} + +QMenu * ModFolderPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction(ui->actionsToolbar->toggleViewAction() ); + return filteredMenu; +} + +void ModFolderPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->actionsToolbar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->modTreeView->mapToGlobal(pos)); + delete menu; } void ModFolderPage::openedImpl() { - m_mods->startWatching(); + m_mods->startWatching(); } void ModFolderPage::closedImpl() { - m_mods->stopWatching(); + m_mods->stopWatching(); } void ModFolderPage::on_filterTextChanged(const QString& newContents) { - m_viewFilter = newContents; - m_filterModel->setFilterFixedString(m_viewFilter); + m_viewFilter = newContents; + m_filterModel->setFilterFixedString(m_viewFilter); } -CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModList> mods, - QString id, QString iconName, QString displayName, - QString helpPage, QWidget *parent) - : ModFolderPage(inst, mods, id, iconName, displayName, helpPage, parent) +CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods, + QString id, QString iconName, QString displayName, + QString helpPage, QWidget *parent) + : ModFolderPage(inst, mods, id, iconName, displayName, helpPage, parent) { } ModFolderPage::~ModFolderPage() { - m_mods->stopWatching(); - delete ui; + m_mods->stopWatching(); + delete ui; +} + +void ModFolderPage::on_RunningState_changed(bool running) +{ + if(m_controlsEnabled == !running) { + return; + } + m_controlsEnabled = !running; + ui->actionAdd->setEnabled(m_controlsEnabled); + ui->actionDisable->setEnabled(m_controlsEnabled); + ui->actionEnable->setEnabled(m_controlsEnabled); + ui->actionRemove->setEnabled(m_controlsEnabled); } bool ModFolderPage::shouldDisplay() const { - if (m_inst) - return !m_inst->isRunning(); - return true; + return true; } bool CoreModFolderPage::shouldDisplay() const { - if (ModFolderPage::shouldDisplay()) - { - auto inst = dynamic_cast<MinecraftInstance *>(m_inst); - if (!inst) - return true; - auto version = inst->getComponentList(); - if (!version) - return true; - if(!version->getComponent("net.minecraftforge")) - { - return false; - } - if(!version->getComponent("net.minecraft")) - { - return false; - } - if(version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate) - { - return true; - } - } - return false; + if (ModFolderPage::shouldDisplay()) + { + auto inst = dynamic_cast<MinecraftInstance *>(m_inst); + if (!inst) + return true; + auto version = inst->getComponentList(); + if (!version) + return true; + if(!version->getComponent("net.minecraftforge")) + { + return false; + } + if(!version->getComponent("net.minecraft")) + { + return false; + } + if(version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate) + { + return true; + } + } + return false; } bool ModFolderPage::modListFilter(QKeyEvent *keyEvent) { - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmModBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addModBtn_clicked(); - return true; - default: - break; - } - return QWidget::eventFilter(ui->modTreeView, keyEvent); + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_actionRemove_triggered(); + return true; + case Qt::Key_Plus: + on_actionAdd_triggered(); + return true; + default: + break; + } + return QWidget::eventFilter(ui->modTreeView, keyEvent); } bool ModFolderPage::eventFilter(QObject *obj, QEvent *ev) { - if (ev->type() != QEvent::KeyPress) - { - return QWidget::eventFilter(obj, ev); - } - QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); - if (obj == ui->modTreeView) - return modListFilter(keyEvent); - return QWidget::eventFilter(obj, ev); + if (ev->type() != QEvent::KeyPress) + { + return QWidget::eventFilter(obj, ev); + } + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); + if (obj == ui->modTreeView) + return modListFilter(keyEvent); + return QWidget::eventFilter(obj, ev); } -void ModFolderPage::on_addModBtn_clicked() +void ModFolderPage::on_actionAdd_triggered() { - auto list = GuiUtil::BrowseForFiles( - m_helpName, - tr("Select %1", - "Select whatever type of files the page contains. Example: 'Loader Mods'") - .arg(m_displayName), - m_fileSelectionFilter.arg(m_displayName), MMC->settings()->get("CentralModsDir").toString(), - this->parentWidget()); - if (!list.empty()) - { - for (auto filename : list) - { - m_mods->installMod(filename); - } - } + if(!m_controlsEnabled) { + return; + } + auto list = GuiUtil::BrowseForFiles( + m_helpName, + tr("Select %1", + "Select whatever type of files the page contains. Example: 'Loader Mods'") + .arg(m_displayName), + m_fileSelectionFilter.arg(m_displayName), MMC->settings()->get("CentralModsDir").toString(), + this->parentWidget()); + if (!list.empty()) + { + for (auto filename : list) + { + m_mods->installMod(filename); + } + } } -void ModFolderPage::on_enableModBtn_clicked() +void ModFolderPage::on_actionEnable_triggered() { - auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->enableMods(selection.indexes(), true); + if(!m_controlsEnabled) { + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); + m_mods->setModStatus(selection.indexes(), ModFolderModel::Enable); } -void ModFolderPage::on_disableModBtn_clicked() +void ModFolderPage::on_actionDisable_triggered() { - auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->enableMods(selection.indexes(), false); + if(!m_controlsEnabled) { + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); + m_mods->setModStatus(selection.indexes(), ModFolderModel::Disable); } -void ModFolderPage::on_rmModBtn_clicked() +void ModFolderPage::on_actionRemove_triggered() { - auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->deleteMods(selection.indexes()); + if(!m_controlsEnabled) { + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); + m_mods->deleteMods(selection.indexes()); } -void ModFolderPage::on_configFolderBtn_clicked() +void ModFolderPage::on_actionView_configs_triggered() { - DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true); + DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true); } -void ModFolderPage::on_viewModBtn_clicked() +void ModFolderPage::on_actionView_Folder_triggered() { - DesktopServices::openDirectory(m_mods->dir().absolutePath(), true); + DesktopServices::openDirectory(m_mods->dir().absolutePath(), true); } void ModFolderPage::modCurrent(const QModelIndex ¤t, const QModelIndex &previous) { - if (!current.isValid()) - { - ui->frame->clear(); - return; - } - auto sourceCurrent = m_filterModel->mapToSource(current); - int row = sourceCurrent.row(); - Mod &m = m_mods->operator[](row); - ui->frame->updateWithMod(m); + if (!current.isValid()) + { + ui->frame->clear(); + return; + } + auto sourceCurrent = m_filterModel->mapToSource(current); + int row = sourceCurrent.row(); + Mod &m = m_mods->operator[](row); + ui->frame->updateWithMod(m); } diff --git a/application/pages/instance/ModFolderPage.h b/application/pages/instance/ModFolderPage.h index 15e728cb..d49d25c3 100644 --- a/application/pages/instance/ModFolderPage.h +++ b/application/pages/instance/ModFolderPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,94 +15,105 @@ #pragma once -#include <QWidget> +#include <QMainWindow> #include "minecraft/MinecraftInstance.h" #include "pages/BasePage.h" #include <MultiMC.h> -class ModList; +class ModFolderModel; namespace Ui { class ModFolderPage; } -class ModFolderPage : public QWidget, public BasePage +class ModFolderPage : public QMainWindow, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit ModFolderPage(BaseInstance *inst, std::shared_ptr<ModList> mods, QString id, - QString iconName, QString displayName, QString helpPage = "", - QWidget *parent = 0); - virtual ~ModFolderPage(); + explicit ModFolderPage( + BaseInstance *inst, + std::shared_ptr<ModFolderModel> mods, + QString id, + QString iconName, + QString displayName, + QString helpPage = "", + QWidget *parent = 0 + ); + virtual ~ModFolderPage(); - void setFilter(const QString & filter) - { - m_fileSelectionFilter = filter; - } + void setFilter(const QString & filter) + { + m_fileSelectionFilter = filter; + } - virtual QString displayName() const override - { - return m_displayName; - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon(m_iconName); - } - virtual QString id() const override - { - return m_id; - } - virtual QString helpPage() const override - { - return m_helpName; - } - virtual bool shouldDisplay() const override; + virtual QString displayName() const override + { + return m_displayName; + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon(m_iconName); + } + virtual QString id() const override + { + return m_id; + } + virtual QString helpPage() const override + { + return m_helpName; + } + virtual bool shouldDisplay() const override; - virtual void openedImpl() override; - virtual void closedImpl() override; + virtual void openedImpl() override; + virtual void closedImpl() override; protected: - bool eventFilter(QObject *obj, QEvent *ev) override; - bool modListFilter(QKeyEvent *ev); + bool eventFilter(QObject *obj, QEvent *ev) override; + bool modListFilter(QKeyEvent *ev); + QMenu * createPopupMenu() override; protected: - BaseInstance *m_inst; + BaseInstance *m_inst = nullptr; protected: - Ui::ModFolderPage *ui; - std::shared_ptr<ModList> m_mods; - QSortFilterProxyModel *m_filterModel; - QString m_iconName; - QString m_id; - QString m_displayName; - QString m_helpName; - QString m_fileSelectionFilter; - QString m_viewFilter; + Ui::ModFolderPage *ui = nullptr; + std::shared_ptr<ModFolderModel> m_mods; + QSortFilterProxyModel *m_filterModel = nullptr; + QString m_iconName; + QString m_id; + QString m_displayName; + QString m_helpName; + QString m_fileSelectionFilter; + QString m_viewFilter; + bool m_controlsEnabled = true; public slots: - void modCurrent(const QModelIndex ¤t, const QModelIndex &previous); + void modCurrent(const QModelIndex ¤t, const QModelIndex &previous); private slots: - void on_filterTextChanged(const QString & newContents); - void on_addModBtn_clicked(); - void on_rmModBtn_clicked(); - void on_viewModBtn_clicked(); - void on_enableModBtn_clicked(); - void on_disableModBtn_clicked(); - void on_configFolderBtn_clicked(); + void modItemActivated(const QModelIndex &index); + void on_filterTextChanged(const QString & newContents); + void on_RunningState_changed(bool running); + void on_actionAdd_triggered(); + void on_actionRemove_triggered(); + void on_actionEnable_triggered(); + void on_actionDisable_triggered(); + void on_actionView_Folder_triggered(); + void on_actionView_configs_triggered(); + void ShowContextMenu(const QPoint &pos); }; class CoreModFolderPage : public ModFolderPage { public: - explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModList> mods, QString id, - QString iconName, QString displayName, QString helpPage = "", - QWidget *parent = 0); - virtual ~CoreModFolderPage() - { - } - virtual bool shouldDisplay() const; + explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods, QString id, + QString iconName, QString displayName, QString helpPage = "", + QWidget *parent = 0); + virtual ~CoreModFolderPage() + { + } + virtual bool shouldDisplay() const; }; diff --git a/application/pages/instance/ModFolderPage.ui b/application/pages/instance/ModFolderPage.ui index b5597bdc..052df602 100644 --- a/application/pages/instance/ModFolderPage.ui +++ b/application/pages/instance/ModFolderPage.ui @@ -1,155 +1,144 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ModFolderPage</class> - <widget class="QWidget" name="ModFolderPage"> + <widget class="QMainWindow" name="ModFolderPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>723</width> - <height>532</height> + <width>1042</width> + <height>501</height> </rect> </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="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <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="4" column="1" colspan="3"> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="1"> + <widget class="QLineEdit" name="filterEdit"> + <property name="clearButtonEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="filterLabel"> + <property name="text"> + <string>Filter:</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="1" colspan="3"> + <widget class="MCModInfoFrame" name="frame"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="1" column="1" colspan="3"> + <widget class="ModListView" name="modTreeView"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <attribute name="title"> - <string notr="true">Tab 1</string> - </attribute> - <layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0"> - <item row="0" column="2"> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QPushButton" name="addModBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmModBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="enableModBtn"> - <property name="text"> - <string>Enable</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="disableModBtn"> - <property name="text"> - <string>Disable</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="configFolderBtn"> - <property name="toolTip"> - <string>Open the 'config' folder in the system file manager.</string> - </property> - <property name="text"> - <string>View configs</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="QPushButton" name="viewModBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0" colspan="3"> - <widget class="MCModInfoFrame" name="frame"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - </widget> - </item> - <item row="0" column="0" colspan="2"> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="1" column="1"> - <widget class="QLineEdit" name="filterEdit"> - <property name="clearButtonEnabled"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="filterLabel"> - <property name="text"> - <string>Filter:</string> - </property> - </widget> - </item> - <item row="0" column="0" colspan="3"> - <widget class="ModListView" name="modTreeView"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="acceptDrops"> - <bool>true</bool> - </property> - <property name="dragDropMode"> - <enum>QAbstractItemView::DropOnly</enum> - </property> - </widget> - </item> - </layout> - </item> - </layout> + <property name="acceptDrops"> + <bool>true</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::DropOnly</enum> + </property> </widget> - </widget> - </item> - </layout> + </item> + </layout> + </widget> + <widget class="WideBar" name="actionsToolbar"> + <property name="windowTitle"> + <string>Actions</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> + <attribute name="toolBarArea"> + <enum>RightToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionAdd"/> + <addaction name="separator"/> + <addaction name="actionRemove"/> + <addaction name="actionEnable"/> + <addaction name="actionDisable"/> + <addaction name="actionView_configs"/> + <addaction name="actionView_Folder"/> + </widget> + <action name="actionAdd"> + <property name="text"> + <string>&Add</string> + </property> + <property name="toolTip"> + <string>Add mods</string> + </property> + </action> + <action name="actionRemove"> + <property name="text"> + <string>&Remove</string> + </property> + <property name="toolTip"> + <string>Remove selected mods</string> + </property> + </action> + <action name="actionEnable"> + <property name="text"> + <string>&Enable</string> + </property> + <property name="toolTip"> + <string>Enable selected mods</string> + </property> + </action> + <action name="actionDisable"> + <property name="text"> + <string>&Disable</string> + </property> + <property name="toolTip"> + <string>Disable selected mods</string> + </property> + </action> + <action name="actionView_configs"> + <property name="text"> + <string>View &Configs</string> + </property> + <property name="toolTip"> + <string>Open the 'config' folder in the system file manager.</string> + </property> + </action> + <action name="actionView_Folder"> + <property name="text"> + <string>View &Folder</string> + </property> + </action> </widget> <customwidgets> <customwidget> @@ -163,17 +152,15 @@ <header>widgets/MCModInfoFrame.h</header> <container>1</container> </customwidget> + <customwidget> + <class>WideBar</class> + <extends>QToolBar</extends> + <header>widgets/WideBar.h</header> + </customwidget> </customwidgets> <tabstops> - <tabstop>tabWidget</tabstop> <tabstop>modTreeView</tabstop> <tabstop>filterEdit</tabstop> - <tabstop>addModBtn</tabstop> - <tabstop>rmModBtn</tabstop> - <tabstop>enableModBtn</tabstop> - <tabstop>disableModBtn</tabstop> - <tabstop>configFolderBtn</tabstop> - <tabstop>viewModBtn</tabstop> </tabstops> <resources/> <connections/> diff --git a/application/pages/instance/NotesPage.cpp b/application/pages/instance/NotesPage.cpp index 3925fdfc..fa966c91 100644 --- a/application/pages/instance/NotesPage.cpp +++ b/application/pages/instance/NotesPage.cpp @@ -3,20 +3,19 @@ #include <QTabBar> NotesPage::NotesPage(BaseInstance *inst, QWidget *parent) - : QWidget(parent), ui(new Ui::NotesPage), m_inst(inst) + : QWidget(parent), ui(new Ui::NotesPage), m_inst(inst) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - ui->noteEditor->setText(m_inst->notes()); + ui->setupUi(this); + ui->noteEditor->setText(m_inst->notes()); } NotesPage::~NotesPage() { - delete ui; + delete ui; } bool NotesPage::apply() { - m_inst->setNotes(ui->noteEditor->toPlainText()); - return true; + m_inst->setNotes(ui->noteEditor->toPlainText()); + return true; } diff --git a/application/pages/instance/NotesPage.h b/application/pages/instance/NotesPage.h index 4a25f9b1..f96e5374 100644 --- a/application/pages/instance/NotesPage.h +++ b/application/pages/instance/NotesPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,33 +28,33 @@ class NotesPage; class NotesPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit NotesPage(BaseInstance *inst, QWidget *parent = 0); - virtual ~NotesPage(); - virtual QString displayName() const override - { - return tr("Notes"); - } - virtual QIcon icon() const override - { - auto icon = MMC->getThemedIcon("notes"); - if(icon.isNull()) - icon = MMC->getThemedIcon("news"); - return icon; - } - virtual QString id() const override - { - return "notes"; - } - virtual bool apply() override; - virtual QString helpPage() const override - { - return "Notes"; - } + explicit NotesPage(BaseInstance *inst, QWidget *parent = 0); + virtual ~NotesPage(); + virtual QString displayName() const override + { + return tr("Notes"); + } + virtual QIcon icon() const override + { + auto icon = MMC->getThemedIcon("notes"); + if(icon.isNull()) + icon = MMC->getThemedIcon("news"); + return icon; + } + virtual QString id() const override + { + return "notes"; + } + virtual bool apply() override; + virtual QString helpPage() const override + { + return "Notes"; + } private: - Ui::NotesPage *ui; - BaseInstance *m_inst; + Ui::NotesPage *ui; + BaseInstance *m_inst; }; diff --git a/application/pages/instance/NotesPage.ui b/application/pages/instance/NotesPage.ui index 88cca92f..67cb261c 100644 --- a/application/pages/instance/NotesPage.ui +++ b/application/pages/instance/NotesPage.ui @@ -10,7 +10,7 @@ <height>538</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> + <layout class="QVBoxLayout" name="verticalLayout"> <property name="leftMargin"> <number>0</number> </property> @@ -24,34 +24,26 @@ <number>0</number> </property> <item> - <widget class="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> + <widget class="QTextEdit" name="noteEditor"> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOn</enum> + </property> + <property name="tabChangesFocus"> + <bool>true</bool> + </property> + <property name="acceptRichText"> + <bool>false</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> </property> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string notr="true">Tab 1</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QTextEdit" name="noteEditor"> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOn</enum> - </property> - <property name="acceptRichText"> - <bool>false</bool> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - </layout> - </widget> </widget> </item> </layout> </widget> + <tabstops> + <tabstop>noteEditor</tabstop> + </tabstops> <resources/> <connections/> </ui> diff --git a/application/pages/instance/OtherLogsPage.cpp b/application/pages/instance/OtherLogsPage.cpp index 10cb1145..387db74f 100644 --- a/application/pages/instance/OtherLogsPage.cpp +++ b/application/pages/instance/OtherLogsPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,289 +25,289 @@ #include <QShortcut> OtherLogsPage::OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget *parent) - : QWidget(parent), ui(new Ui::OtherLogsPage), m_path(path), m_fileFilter(fileFilter), - m_watcher(new RecursiveFileSystemWatcher(this)) + : QWidget(parent), ui(new Ui::OtherLogsPage), m_path(path), m_fileFilter(fileFilter), + m_watcher(new RecursiveFileSystemWatcher(this)) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); - m_watcher->setMatcher(fileFilter); - m_watcher->setRootDir(QDir::current().absoluteFilePath(m_path)); + m_watcher->setMatcher(fileFilter); + m_watcher->setRootDir(QDir::current().absoluteFilePath(m_path)); - connect(m_watcher, &RecursiveFileSystemWatcher::filesChanged, this, &OtherLogsPage::populateSelectLogBox); - populateSelectLogBox(); + connect(m_watcher, &RecursiveFileSystemWatcher::filesChanged, this, &OtherLogsPage::populateSelectLogBox); + populateSelectLogBox(); - auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); - connect(findShortcut, &QShortcut::activated, this, &OtherLogsPage::findActivated); + auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); + connect(findShortcut, &QShortcut::activated, this, &OtherLogsPage::findActivated); - auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this); - connect(findNextShortcut, &QShortcut::activated, this, &OtherLogsPage::findNextActivated); + auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this); + connect(findNextShortcut, &QShortcut::activated, this, &OtherLogsPage::findNextActivated); - auto findPreviousShortcut = new QShortcut(QKeySequence(QKeySequence::FindPrevious), this); - connect(findPreviousShortcut, &QShortcut::activated, this, &OtherLogsPage::findPreviousActivated); + auto findPreviousShortcut = new QShortcut(QKeySequence(QKeySequence::FindPrevious), this); + connect(findPreviousShortcut, &QShortcut::activated, this, &OtherLogsPage::findPreviousActivated); - connect(ui->searchBar, &QLineEdit::returnPressed, this, &OtherLogsPage::on_findButton_clicked); + connect(ui->searchBar, &QLineEdit::returnPressed, this, &OtherLogsPage::on_findButton_clicked); } OtherLogsPage::~OtherLogsPage() { - delete ui; + delete ui; } void OtherLogsPage::openedImpl() { - m_watcher->enable(); + m_watcher->enable(); } void OtherLogsPage::closedImpl() { - m_watcher->disable(); + m_watcher->disable(); } void OtherLogsPage::populateSelectLogBox() { - ui->selectLogBox->clear(); - ui->selectLogBox->addItems(m_watcher->files()); - if (m_currentFile.isEmpty()) - { - setControlsEnabled(false); - ui->selectLogBox->setCurrentIndex(-1); - } - else - { - const int index = ui->selectLogBox->findText(m_currentFile); - if (index != -1) - { - ui->selectLogBox->setCurrentIndex(index); - setControlsEnabled(true); - } - else - { - setControlsEnabled(false); - } - } + ui->selectLogBox->clear(); + ui->selectLogBox->addItems(m_watcher->files()); + if (m_currentFile.isEmpty()) + { + setControlsEnabled(false); + ui->selectLogBox->setCurrentIndex(-1); + } + else + { + const int index = ui->selectLogBox->findText(m_currentFile); + if (index != -1) + { + ui->selectLogBox->setCurrentIndex(index); + setControlsEnabled(true); + } + else + { + setControlsEnabled(false); + } + } } void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) { - QString file; - if (index != -1) - { - file = ui->selectLogBox->itemText(index); - } + QString file; + if (index != -1) + { + file = ui->selectLogBox->itemText(index); + } - if (file.isEmpty() || !QFile::exists(FS::PathCombine(m_path, file))) - { - m_currentFile = QString(); - ui->text->clear(); - setControlsEnabled(false); - } - else - { - m_currentFile = file; - on_btnReload_clicked(); - setControlsEnabled(true); - } + if (file.isEmpty() || !QFile::exists(FS::PathCombine(m_path, file))) + { + m_currentFile = QString(); + ui->text->clear(); + setControlsEnabled(false); + } + else + { + m_currentFile = file; + on_btnReload_clicked(); + setControlsEnabled(true); + } } void OtherLogsPage::on_btnReload_clicked() { - if(m_currentFile.isEmpty()) - { - setControlsEnabled(false); - return; - } - QFile file(FS::PathCombine(m_path, m_currentFile)); - if (!file.open(QFile::ReadOnly)) - { - setControlsEnabled(false); - ui->btnReload->setEnabled(true); // allow reload - m_currentFile = QString(); - QMessageBox::critical(this, tr("Error"), tr("Unable to open %1 for reading: %2") - .arg(m_currentFile, file.errorString())); - } - else - { - auto setPlainText = [&](const QString & text) - { - QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); - bool conversionOk = false; - int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); - if(!conversionOk) - { - fontSize = 11; - } - QTextDocument *doc = ui->text->document(); - doc->setDefaultFont(QFont(fontFamily, fontSize)); - ui->text->setPlainText(text); - }; - auto showTooBig = [&]() - { - setPlainText( - tr("The file (%1) is too big. You may want to open it in a viewer optimized " - "for large files.").arg(file.fileName())); - }; - if(file.size() > (1024ll * 1024ll * 12ll)) - { - showTooBig(); - return; - } - QString content; - if(file.fileName().endsWith(".gz")) - { - QByteArray temp; - if(!GZip::unzip(file.readAll(), temp)) - { - setPlainText( - tr("The file (%1) is not readable.").arg(file.fileName())); - return; - } - content = QString::fromUtf8(temp); - } - else - { - content = QString::fromUtf8(file.readAll()); - } - if (content.size() >= 50000000ll) - { - showTooBig(); - return; - } - setPlainText(content); - } + if(m_currentFile.isEmpty()) + { + setControlsEnabled(false); + return; + } + QFile file(FS::PathCombine(m_path, m_currentFile)); + if (!file.open(QFile::ReadOnly)) + { + setControlsEnabled(false); + ui->btnReload->setEnabled(true); // allow reload + m_currentFile = QString(); + QMessageBox::critical(this, tr("Error"), tr("Unable to open %1 for reading: %2") + .arg(m_currentFile, file.errorString())); + } + else + { + auto setPlainText = [&](const QString & text) + { + QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); + bool conversionOk = false; + int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); + if(!conversionOk) + { + fontSize = 11; + } + QTextDocument *doc = ui->text->document(); + doc->setDefaultFont(QFont(fontFamily, fontSize)); + ui->text->setPlainText(text); + }; + auto showTooBig = [&]() + { + setPlainText( + tr("The file (%1) is too big. You may want to open it in a viewer optimized " + "for large files.").arg(file.fileName())); + }; + if(file.size() > (1024ll * 1024ll * 12ll)) + { + showTooBig(); + return; + } + QString content; + if(file.fileName().endsWith(".gz")) + { + QByteArray temp; + if(!GZip::unzip(file.readAll(), temp)) + { + setPlainText( + tr("The file (%1) is not readable.").arg(file.fileName())); + return; + } + content = QString::fromUtf8(temp); + } + else + { + content = QString::fromUtf8(file.readAll()); + } + if (content.size() >= 50000000ll) + { + showTooBig(); + return; + } + setPlainText(content); + } } void OtherLogsPage::on_btnPaste_clicked() { - GuiUtil::uploadPaste(ui->text->toPlainText(), this); + GuiUtil::uploadPaste(ui->text->toPlainText(), this); } void OtherLogsPage::on_btnCopy_clicked() { - GuiUtil::setClipboardText(ui->text->toPlainText()); + GuiUtil::setClipboardText(ui->text->toPlainText()); } void OtherLogsPage::on_btnDelete_clicked() { - if(m_currentFile.isEmpty()) - { - setControlsEnabled(false); - return; - } - if (QMessageBox::question(this, tr("Delete"), - tr("Do you really want to delete %1?").arg(m_currentFile), - QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) - { - return; - } - QFile file(FS::PathCombine(m_path, m_currentFile)); - if (!file.remove()) - { - QMessageBox::critical(this, tr("Error"), tr("Unable to delete %1: %2") - .arg(m_currentFile, file.errorString())); - } + if(m_currentFile.isEmpty()) + { + setControlsEnabled(false); + return; + } + if (QMessageBox::question(this, tr("Delete"), + tr("Do you really want to delete %1?").arg(m_currentFile), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) + { + return; + } + QFile file(FS::PathCombine(m_path, m_currentFile)); + if (!file.remove()) + { + QMessageBox::critical(this, tr("Error"), tr("Unable to delete %1: %2") + .arg(m_currentFile, file.errorString())); + } } void OtherLogsPage::on_btnClean_clicked() { - auto toDelete = m_watcher->files(); - if(toDelete.isEmpty()) - { - return; - } - QMessageBox *messageBox = new QMessageBox(this); - messageBox->setWindowTitle(tr("Clean up")); - if(toDelete.size() > 5) - { - messageBox->setText(tr("Do you really want to delete all log files?")); - messageBox->setDetailedText(toDelete.join('\n')); - } - else - { - messageBox->setText(tr("Do you really want to delete these files?\n%1").arg(toDelete.join('\n'))); - } - messageBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - messageBox->setDefaultButton(QMessageBox::Ok); - messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); - messageBox->setIcon(QMessageBox::Question); - messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); + auto toDelete = m_watcher->files(); + if(toDelete.isEmpty()) + { + return; + } + QMessageBox *messageBox = new QMessageBox(this); + messageBox->setWindowTitle(tr("Clean up")); + if(toDelete.size() > 5) + { + messageBox->setText(tr("Do you really want to delete all log files?")); + messageBox->setDetailedText(toDelete.join('\n')); + } + else + { + messageBox->setText(tr("Do you really want to delete these files?\n%1").arg(toDelete.join('\n'))); + } + messageBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + messageBox->setDefaultButton(QMessageBox::Ok); + messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); + messageBox->setIcon(QMessageBox::Question); + messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); - if (messageBox->exec() != QMessageBox::Ok) - { - return; - } - QStringList failed; - for(auto item: toDelete) - { - QFile file(FS::PathCombine(m_path, item)); - if (!file.remove()) - { - failed.push_back(item); - } - } - if(!failed.empty()) - { - QMessageBox *messageBox = new QMessageBox(this); - messageBox->setWindowTitle(tr("Error")); - if(failed.size() > 5) - { - messageBox->setText(tr("Couldn't delete some files!")); - messageBox->setDetailedText(failed.join('\n')); - } - else - { - messageBox->setText(tr("Couldn't delete some files:\n%1").arg(failed.join('\n'))); - } - messageBox->setStandardButtons(QMessageBox::Ok); - messageBox->setDefaultButton(QMessageBox::Ok); - messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); - messageBox->setIcon(QMessageBox::Critical); - messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); - messageBox->exec(); - } + if (messageBox->exec() != QMessageBox::Ok) + { + return; + } + QStringList failed; + for(auto item: toDelete) + { + QFile file(FS::PathCombine(m_path, item)); + if (!file.remove()) + { + failed.push_back(item); + } + } + if(!failed.empty()) + { + QMessageBox *messageBox = new QMessageBox(this); + messageBox->setWindowTitle(tr("Error")); + if(failed.size() > 5) + { + messageBox->setText(tr("Couldn't delete some files!")); + messageBox->setDetailedText(failed.join('\n')); + } + else + { + messageBox->setText(tr("Couldn't delete some files:\n%1").arg(failed.join('\n'))); + } + messageBox->setStandardButtons(QMessageBox::Ok); + messageBox->setDefaultButton(QMessageBox::Ok); + messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); + messageBox->setIcon(QMessageBox::Critical); + messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); + messageBox->exec(); + } } void OtherLogsPage::setControlsEnabled(const bool enabled) { - ui->btnReload->setEnabled(enabled); - ui->btnDelete->setEnabled(enabled); - ui->btnCopy->setEnabled(enabled); - ui->btnPaste->setEnabled(enabled); - ui->text->setEnabled(enabled); - ui->btnClean->setEnabled(enabled); + ui->btnReload->setEnabled(enabled); + ui->btnDelete->setEnabled(enabled); + ui->btnCopy->setEnabled(enabled); + ui->btnPaste->setEnabled(enabled); + ui->text->setEnabled(enabled); + ui->btnClean->setEnabled(enabled); } // FIXME: HACK, use LogView instead? static void findNext(QPlainTextEdit * _this, const QString& what, bool reverse) { - _this->find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); + _this->find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); } void OtherLogsPage::on_findButton_clicked() { - auto modifiers = QApplication::keyboardModifiers(); - bool reverse = modifiers & Qt::ShiftModifier; - findNext(ui->text, ui->searchBar->text(), reverse); + auto modifiers = QApplication::keyboardModifiers(); + bool reverse = modifiers & Qt::ShiftModifier; + findNext(ui->text, ui->searchBar->text(), reverse); } void OtherLogsPage::findNextActivated() { - findNext(ui->text, ui->searchBar->text(), false); + findNext(ui->text, ui->searchBar->text(), false); } void OtherLogsPage::findPreviousActivated() { - findNext(ui->text, ui->searchBar->text(), true); + findNext(ui->text, ui->searchBar->text(), true); } void OtherLogsPage::findActivated() { - // focus the search bar if it doesn't have focus - if (!ui->searchBar->hasFocus()) - { - ui->searchBar->setFocus(); - ui->searchBar->selectAll(); - } + // focus the search bar if it doesn't have focus + if (!ui->searchBar->hasFocus()) + { + ui->searchBar->setFocus(); + ui->searchBar->selectAll(); + } } diff --git a/application/pages/instance/OtherLogsPage.h b/application/pages/instance/OtherLogsPage.h index ac01ef0a..226f3af3 100644 --- a/application/pages/instance/OtherLogsPage.h +++ b/application/pages/instance/OtherLogsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,52 +30,52 @@ class RecursiveFileSystemWatcher; class OtherLogsPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget *parent = 0); - ~OtherLogsPage(); + explicit OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget *parent = 0); + ~OtherLogsPage(); - QString id() const override - { - return "logs"; - } - QString displayName() const override - { - return tr("Other logs"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("log"); - } - QString helpPage() const override - { - return "Minecraft-Logs"; - } - void openedImpl() override; - void closedImpl() override; + QString id() const override + { + return "logs"; + } + QString displayName() const override + { + return tr("Other logs"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("log"); + } + QString helpPage() const override + { + return "Minecraft-Logs"; + } + void openedImpl() override; + void closedImpl() override; private slots: - void populateSelectLogBox(); - void on_selectLogBox_currentIndexChanged(const int index); - void on_btnReload_clicked(); - void on_btnPaste_clicked(); - void on_btnCopy_clicked(); - void on_btnDelete_clicked(); - void on_btnClean_clicked(); + void populateSelectLogBox(); + void on_selectLogBox_currentIndexChanged(const int index); + void on_btnReload_clicked(); + void on_btnPaste_clicked(); + void on_btnCopy_clicked(); + void on_btnDelete_clicked(); + void on_btnClean_clicked(); - void on_findButton_clicked(); - void findActivated(); - void findNextActivated(); - void findPreviousActivated(); + void on_findButton_clicked(); + void findActivated(); + void findNextActivated(); + void findPreviousActivated(); private: - void setControlsEnabled(const bool enabled); + void setControlsEnabled(const bool enabled); private: - Ui::OtherLogsPage *ui; - QString m_path; - QString m_currentFile; - IPathMatcher::Ptr m_fileFilter; - RecursiveFileSystemWatcher *m_watcher; + Ui::OtherLogsPage *ui; + QString m_path; + QString m_currentFile; + IPathMatcher::Ptr m_fileFilter; + RecursiveFileSystemWatcher *m_watcher; }; diff --git a/application/pages/instance/ResourcePackPage.h b/application/pages/instance/ResourcePackPage.h index 19dc78da..e11c78a3 100644 --- a/application/pages/instance/ResourcePackPage.h +++ b/application/pages/instance/ResourcePackPage.h @@ -4,18 +4,19 @@ class ResourcePackPage : public ModFolderPage { + Q_OBJECT public: - explicit ResourcePackPage(MinecraftInstance *instance, QWidget *parent = 0) - : ModFolderPage(instance, instance->resourcePackList(), "resourcepacks", - "resourcepacks", tr("Resource packs"), "Resource-packs", parent) - { - ui->configFolderBtn->setHidden(true); - } + explicit ResourcePackPage(MinecraftInstance *instance, QWidget *parent = 0) + : ModFolderPage(instance, instance->resourcePackList(), "resourcepacks", + "resourcepacks", tr("Resource packs"), "Resource-packs", parent) + { + ui->actionView_configs->setVisible(false); + } - virtual ~ResourcePackPage() {} - virtual bool shouldDisplay() const override - { - return !m_inst->traits().contains("no-texturepacks") && - !m_inst->traits().contains("texturepacks"); - } + virtual ~ResourcePackPage() {} + virtual bool shouldDisplay() const override + { + return !m_inst->traits().contains("no-texturepacks") && + !m_inst->traits().contains("texturepacks"); + } }; diff --git a/application/pages/instance/ScreenshotsPage.cpp b/application/pages/instance/ScreenshotsPage.cpp index 71458386..efa0f9f2 100644 --- a/application/pages/instance/ScreenshotsPage.cpp +++ b/application/pages/instance/ScreenshotsPage.cpp @@ -13,6 +13,7 @@ #include <QPainter> #include <QClipboard> #include <QKeyEvent> +#include <QMenu> #include <MultiMC.h> @@ -32,341 +33,390 @@ typedef std::shared_ptr<SharedIconCache> SharedIconCachePtr; class ThumbnailingResult : public QObject { - Q_OBJECT + Q_OBJECT public slots: - inline void emitResultsReady(const QString &path) { emit resultsReady(path); } - inline void emitResultsFailed(const QString &path) { emit resultsFailed(path); } + inline void emitResultsReady(const QString &path) { emit resultsReady(path); } + inline void emitResultsFailed(const QString &path) { emit resultsFailed(path); } signals: - void resultsReady(const QString &path); - void resultsFailed(const QString &path); + void resultsReady(const QString &path); + void resultsFailed(const QString &path); }; class ThumbnailRunnable : public QRunnable { public: - ThumbnailRunnable(QString path, SharedIconCachePtr cache) - { - m_path = path; - m_cache = cache; - } - void run() - { - QFileInfo info(m_path); - if (info.isDir()) - return; - if ((info.suffix().compare("png", Qt::CaseInsensitive) != 0)) - return; - int tries = 5; - while (tries) - { - if (!m_cache->stale(m_path)) - return; - QImage image(m_path); - if (image.isNull()) - { - QThread::msleep(500); - tries--; - continue; - } - QImage small; - if (image.width() > image.height()) - small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation); - else - small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation); - QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2); - QImage square(QSize(256, 256), QImage::Format_ARGB32); - square.fill(Qt::transparent); - - QPainter painter(&square); - painter.drawImage(offset, small); - painter.end(); - - QIcon icon(QPixmap::fromImage(square)); - m_cache->add(m_path, icon); - m_resultEmitter.emitResultsReady(m_path); - return; - } - m_resultEmitter.emitResultsFailed(m_path); - } - QString m_path; - SharedIconCachePtr m_cache; - ThumbnailingResult m_resultEmitter; + ThumbnailRunnable(QString path, SharedIconCachePtr cache) + { + m_path = path; + m_cache = cache; + } + void run() + { + QFileInfo info(m_path); + if (info.isDir()) + return; + if ((info.suffix().compare("png", Qt::CaseInsensitive) != 0)) + return; + int tries = 5; + while (tries) + { + if (!m_cache->stale(m_path)) + return; + QImage image(m_path); + if (image.isNull()) + { + QThread::msleep(500); + tries--; + continue; + } + QImage small; + if (image.width() > image.height()) + small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation); + else + small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation); + QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2); + QImage square(QSize(256, 256), QImage::Format_ARGB32); + square.fill(Qt::transparent); + + QPainter painter(&square); + painter.drawImage(offset, small); + painter.end(); + + QIcon icon(QPixmap::fromImage(square)); + m_cache->add(m_path, icon); + m_resultEmitter.emitResultsReady(m_path); + return; + } + m_resultEmitter.emitResultsFailed(m_path); + } + QString m_path; + SharedIconCachePtr m_cache; + ThumbnailingResult m_resultEmitter; }; // this is about as elegant and well written as a bag of bricks with scribbles done by insane // asylum patients. class FilterModel : public QIdentityProxyModel { - Q_OBJECT + Q_OBJECT public: - explicit FilterModel(QObject *parent = 0) : QIdentityProxyModel(parent) - { - m_thumbnailingPool.setMaxThreadCount(4); - m_thumbnailCache = std::make_shared<SharedIconCache>(); - m_thumbnailCache->add("placeholder", MMC->getThemedIcon("screenshot-placeholder")); - connect(&watcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); - // FIXME: the watched file set is not updated when files are removed - } - virtual ~FilterModel() { m_thumbnailingPool.waitForDone(500); } - virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const - { - auto model = sourceModel(); - if (!model) - return QVariant(); - if (role == Qt::DisplayRole || role == Qt::EditRole) - { - QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); - return result.toString().remove(QRegExp("\\.png$")); - } - if (role == Qt::DecorationRole) - { - QVariant result = - sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); - QString filePath = result.toString(); - QIcon temp; - if (!watched.contains(filePath)) - { - ((QFileSystemWatcher &)watcher).addPath(filePath); - ((QSet<QString> &)watched).insert(filePath); - } - if (m_thumbnailCache->get(filePath, temp)) - { - return temp; - } - if (!m_failed.contains(filePath)) - { - ((FilterModel *)this)->thumbnailImage(filePath); - } - return (m_thumbnailCache->get("placeholder")); - } - return sourceModel()->data(mapToSource(proxyIndex), role); - } - virtual bool setData(const QModelIndex &index, const QVariant &value, - int role = Qt::EditRole) - { - auto model = sourceModel(); - if (!model) - return false; - if (role != Qt::EditRole) - return false; - // FIXME: this is a workaround for a bug in QFileSystemModel, where it doesn't - // sort after renames - { - ((QFileSystemModel *)model)->setNameFilterDisables(true); - ((QFileSystemModel *)model)->setNameFilterDisables(false); - } - return model->setData(mapToSource(index), value.toString() + ".png", role); - } + explicit FilterModel(QObject *parent = 0) : QIdentityProxyModel(parent) + { + m_thumbnailingPool.setMaxThreadCount(4); + m_thumbnailCache = std::make_shared<SharedIconCache>(); + m_thumbnailCache->add("placeholder", MMC->getThemedIcon("screenshot-placeholder")); + connect(&watcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); + // FIXME: the watched file set is not updated when files are removed + } + virtual ~FilterModel() { m_thumbnailingPool.waitForDone(500); } + virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const + { + auto model = sourceModel(); + if (!model) + return QVariant(); + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); + return result.toString().remove(QRegExp("\\.png$")); + } + if (role == Qt::DecorationRole) + { + QVariant result = + sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); + QString filePath = result.toString(); + QIcon temp; + if (!watched.contains(filePath)) + { + ((QFileSystemWatcher &)watcher).addPath(filePath); + ((QSet<QString> &)watched).insert(filePath); + } + if (m_thumbnailCache->get(filePath, temp)) + { + return temp; + } + if (!m_failed.contains(filePath)) + { + ((FilterModel *)this)->thumbnailImage(filePath); + } + return (m_thumbnailCache->get("placeholder")); + } + return sourceModel()->data(mapToSource(proxyIndex), role); + } + virtual bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) + { + auto model = sourceModel(); + if (!model) + return false; + if (role != Qt::EditRole) + return false; + // FIXME: this is a workaround for a bug in QFileSystemModel, where it doesn't + // sort after renames + { + ((QFileSystemModel *)model)->setNameFilterDisables(true); + ((QFileSystemModel *)model)->setNameFilterDisables(false); + } + return model->setData(mapToSource(index), value.toString() + ".png", role); + } private: - void thumbnailImage(QString path) - { - auto runnable = new ThumbnailRunnable(path, m_thumbnailCache); - connect(&(runnable->m_resultEmitter), SIGNAL(resultsReady(QString)), - SLOT(thumbnailReady(QString))); - connect(&(runnable->m_resultEmitter), SIGNAL(resultsFailed(QString)), - SLOT(thumbnailFailed(QString))); - ((QThreadPool &)m_thumbnailingPool).start(runnable); - } + void thumbnailImage(QString path) + { + auto runnable = new ThumbnailRunnable(path, m_thumbnailCache); + connect(&(runnable->m_resultEmitter), SIGNAL(resultsReady(QString)), + SLOT(thumbnailReady(QString))); + connect(&(runnable->m_resultEmitter), SIGNAL(resultsFailed(QString)), + SLOT(thumbnailFailed(QString))); + ((QThreadPool &)m_thumbnailingPool).start(runnable); + } private slots: - void thumbnailReady(QString path) { emit layoutChanged(); } - void thumbnailFailed(QString path) { m_failed.insert(path); } - void fileChanged(QString filepath) - { - m_thumbnailCache->setStale(filepath); - thumbnailImage(filepath); - // reinsert the path... - watcher.removePath(filepath); - watcher.addPath(filepath); - } + void thumbnailReady(QString path) { emit layoutChanged(); } + void thumbnailFailed(QString path) { m_failed.insert(path); } + void fileChanged(QString filepath) + { + m_thumbnailCache->setStale(filepath); + thumbnailImage(filepath); + // reinsert the path... + watcher.removePath(filepath); + watcher.addPath(filepath); + } private: - SharedIconCachePtr m_thumbnailCache; - QThreadPool m_thumbnailingPool; - QSet<QString> m_failed; - QSet<QString> watched; - QFileSystemWatcher watcher; + SharedIconCachePtr m_thumbnailCache; + QThreadPool m_thumbnailingPool; + QSet<QString> m_failed; + QSet<QString> watched; + QFileSystemWatcher watcher; }; class CenteredEditingDelegate : public QStyledItemDelegate { public: - explicit CenteredEditingDelegate(QObject *parent = 0) : QStyledItemDelegate(parent) {} - virtual ~CenteredEditingDelegate() {} - virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, - const QModelIndex &index) const - { - auto widget = QStyledItemDelegate::createEditor(parent, option, index); - auto foo = dynamic_cast<QLineEdit *>(widget); - if (foo) - { - foo->setAlignment(Qt::AlignHCenter); - foo->setFrame(true); - foo->setMaximumWidth(192); - } - return widget; - } + explicit CenteredEditingDelegate(QObject *parent = 0) : QStyledItemDelegate(parent) {} + virtual ~CenteredEditingDelegate() {} + virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const + { + auto widget = QStyledItemDelegate::createEditor(parent, option, index); + auto foo = dynamic_cast<QLineEdit *>(widget); + if (foo) + { + foo->setAlignment(Qt::AlignHCenter); + foo->setFrame(true); + foo->setMaximumWidth(192); + } + return widget; + } }; ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent) - : QWidget(parent), ui(new Ui::ScreenshotsPage) + : QMainWindow(parent), ui(new Ui::ScreenshotsPage) { - m_model.reset(new QFileSystemModel()); - m_filterModel.reset(new FilterModel()); - m_filterModel->setSourceModel(m_model.get()); - m_model->setFilter(QDir::Files | QDir::Writable | QDir::Readable); - m_model->setReadOnly(false); - m_model->setNameFilters({"*.png"}); - m_model->setNameFilterDisables(false); - m_folder = path; - m_valid = FS::ensureFolderPathExists(m_folder); - - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - ui->listView->setIconSize(QSize(128, 128)); - ui->listView->setGridSize(QSize(192, 160)); - ui->listView->setSpacing(9); - // ui->listView->setUniformItemSizes(true); - ui->listView->setLayoutMode(QListView::Batched); - ui->listView->setViewMode(QListView::IconMode); - ui->listView->setResizeMode(QListView::Adjust); - ui->listView->installEventFilter(this); - ui->listView->setEditTriggers(0); - ui->listView->setItemDelegate(new CenteredEditingDelegate(this)); - connect(ui->listView, SIGNAL(activated(QModelIndex)), SLOT(onItemActivated(QModelIndex))); + m_model.reset(new QFileSystemModel()); + m_filterModel.reset(new FilterModel()); + m_filterModel->setSourceModel(m_model.get()); + m_model->setFilter(QDir::Files | QDir::Writable | QDir::Readable); + m_model->setReadOnly(false); + m_model->setNameFilters({"*.png"}); + m_model->setNameFilterDisables(false); + m_folder = path; + m_valid = FS::ensureFolderPathExists(m_folder); + + ui->setupUi(this); + ui->toolBar->insertSpacer(ui->actionView_Folder); + + ui->listView->setIconSize(QSize(128, 128)); + ui->listView->setGridSize(QSize(192, 160)); + ui->listView->setSpacing(9); + // ui->listView->setUniformItemSizes(true); + ui->listView->setLayoutMode(QListView::Batched); + ui->listView->setViewMode(QListView::IconMode); + ui->listView->setResizeMode(QListView::Adjust); + ui->listView->installEventFilter(this); + ui->listView->setEditTriggers(0); + ui->listView->setItemDelegate(new CenteredEditingDelegate(this)); + ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu); + connect(ui->listView, SIGNAL(activated(QModelIndex)), SLOT(onItemActivated(QModelIndex))); } bool ScreenshotsPage::eventFilter(QObject *obj, QEvent *evt) { - if (obj != ui->listView) - return QWidget::eventFilter(obj, evt); - if (evt->type() != QEvent::KeyPress) - { - return QWidget::eventFilter(obj, evt); - } - QKeyEvent *keyEvent = static_cast<QKeyEvent *>(evt); - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_deleteBtn_clicked(); - return true; - case Qt::Key_F2: - on_renameBtn_clicked(); - return true; - default: - break; - } - return QWidget::eventFilter(obj, evt); + if (obj != ui->listView) + return QWidget::eventFilter(obj, evt); + if (evt->type() != QEvent::KeyPress) + { + return QWidget::eventFilter(obj, evt); + } + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(evt); + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_actionDelete_triggered(); + return true; + case Qt::Key_F2: + on_actionRename_triggered(); + return true; + default: + break; + } + return QWidget::eventFilter(obj, evt); } ScreenshotsPage::~ScreenshotsPage() { - delete ui; + delete ui; +} + +void ScreenshotsPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->listView->mapToGlobal(pos)); + delete menu; +} + +QMenu * ScreenshotsPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->toolBar->toggleViewAction() ); + return filteredMenu; } void ScreenshotsPage::onItemActivated(QModelIndex index) { - if (!index.isValid()) - return; - auto info = m_model->fileInfo(index); - QString fileName = info.absoluteFilePath(); - DesktopServices::openFile(info.absoluteFilePath()); + if (!index.isValid()) + return; + auto info = m_model->fileInfo(index); + QString fileName = info.absoluteFilePath(); + DesktopServices::openFile(info.absoluteFilePath()); } -void ScreenshotsPage::on_viewFolderBtn_clicked() +void ScreenshotsPage::on_actionView_Folder_triggered() { - DesktopServices::openDirectory(m_folder, true); + DesktopServices::openDirectory(m_folder, true); } -void ScreenshotsPage::on_uploadBtn_clicked() +void ScreenshotsPage::on_actionUpload_triggered() { - auto selection = ui->listView->selectionModel()->selectedRows(); - if (selection.isEmpty()) - return; - - QList<ScreenshotPtr> uploaded; - auto job = NetJobPtr(new NetJob("Screenshot Upload")); - for (auto item : selection) - { - auto info = m_model->fileInfo(item); - auto screenshot = std::make_shared<ScreenShot>(info); - uploaded.push_back(screenshot); - job->addNetAction(ImgurUpload::make(screenshot)); - } - SequentialTask task; - auto albumTask = NetJobPtr(new NetJob("Imgur Album Creation")); - auto imgurAlbum = ImgurAlbumCreation::make(uploaded); - albumTask->addNetAction(imgurAlbum); - task.addTask(job.unwrap()); - task.addTask(albumTask.unwrap()); - m_uploadActive = true; - ProgressDialog prog(this); - if (prog.execWithTask(&task) != QDialog::Accepted) - { - CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), - tr("Unknown error"), QMessageBox::Warning)->exec(); - } - else - { - auto link = QString("https://imgur.com/a/%1").arg(imgurAlbum->id()); - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(link); - CustomMessageBox::selectable( - this, - tr("Upload finished"), - tr("The <a href=\"%1\">link to the uploaded album</a> has been placed in your clipboard.") .arg(link), - QMessageBox::Information - )->exec(); - } - m_uploadActive = false; + auto selection = ui->listView->selectionModel()->selectedRows(); + if (selection.isEmpty()) + return; + + QList<ScreenshotPtr> uploaded; + auto job = NetJobPtr(new NetJob("Screenshot Upload")); + if(selection.size() < 2) + { + auto item = selection.at(0); + auto info = m_model->fileInfo(item); + auto screenshot = std::make_shared<ScreenShot>(info); + job->addNetAction(ImgurUpload::make(screenshot)); + + m_uploadActive = true; + ProgressDialog dialog(this); + if(dialog.execWithTask(job.get()) != QDialog::Accepted) + { + CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), + tr("Unknown error"), QMessageBox::Warning)->exec(); + } + else + { + auto link = screenshot->m_url; + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(link); + CustomMessageBox::selectable( + this, + tr("Upload finished"), + tr("The <a href=\"%1\">link to the uploaded screenshot</a> has been placed in your clipboard.") + .arg(link), + QMessageBox::Information + )->exec(); + } + + m_uploadActive = false; + return; + } + + for (auto item : selection) + { + auto info = m_model->fileInfo(item); + auto screenshot = std::make_shared<ScreenShot>(info); + uploaded.push_back(screenshot); + job->addNetAction(ImgurUpload::make(screenshot)); + } + SequentialTask task; + auto albumTask = NetJobPtr(new NetJob("Imgur Album Creation")); + auto imgurAlbum = ImgurAlbumCreation::make(uploaded); + albumTask->addNetAction(imgurAlbum); + task.addTask(job.unwrap()); + task.addTask(albumTask.unwrap()); + m_uploadActive = true; + ProgressDialog prog(this); + if (prog.execWithTask(&task) != QDialog::Accepted) + { + CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), + tr("Unknown error"), QMessageBox::Warning)->exec(); + } + else + { + auto link = QString("https://imgur.com/a/%1").arg(imgurAlbum->id()); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(link); + CustomMessageBox::selectable( + this, + tr("Upload finished"), + tr("The <a href=\"%1\">link to the uploaded album</a> has been placed in your clipboard.") .arg(link), + QMessageBox::Information + )->exec(); + } + m_uploadActive = false; } -void ScreenshotsPage::on_deleteBtn_clicked() +void ScreenshotsPage::on_actionDelete_triggered() { - auto mbox = CustomMessageBox::selectable( - this, tr("Are you sure?"), tr("This will delete all selected screenshots."), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No); - std::unique_ptr<QMessageBox> box(mbox); - - if (box->exec() != QMessageBox::Yes) - return; - - auto selected = ui->listView->selectionModel()->selectedIndexes(); - for (auto item : selected) - { - m_model->remove(item); - } + auto mbox = CustomMessageBox::selectable( + this, tr("Are you sure?"), tr("This will delete all selected screenshots."), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No); + std::unique_ptr<QMessageBox> box(mbox); + + if (box->exec() != QMessageBox::Yes) + return; + + auto selected = ui->listView->selectionModel()->selectedIndexes(); + for (auto item : selected) + { + m_model->remove(item); + } } -void ScreenshotsPage::on_renameBtn_clicked() +void ScreenshotsPage::on_actionRename_triggered() { - auto selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.isEmpty()) - return; - ui->listView->edit(selection[0]); - // TODO: mass renaming + auto selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.isEmpty()) + return; + ui->listView->edit(selection[0]); + // TODO: mass renaming } void ScreenshotsPage::openedImpl() { - if(!m_valid) - { - m_valid = FS::ensureFolderPathExists(m_folder); - } - if (m_valid) - { - QString path = QDir(m_folder).absolutePath(); - auto idx = m_model->setRootPath(path); - if(idx.isValid()) - { - ui->listView->setModel(m_filterModel.get()); - ui->listView->setRootIndex(m_filterModel->mapFromSource(idx)); - } - else - { - ui->listView->setModel(nullptr); - } - } + if(!m_valid) + { + m_valid = FS::ensureFolderPathExists(m_folder); + } + if (m_valid) + { + QString path = QDir(m_folder).absolutePath(); + auto idx = m_model->setRootPath(path); + if(idx.isValid()) + { + ui->listView->setModel(m_filterModel.get()); + ui->listView->setRootIndex(m_filterModel->mapFromSource(idx)); + } + else + { + ui->listView->setModel(nullptr); + } + } } #include "ScreenshotsPage.moc" diff --git a/application/pages/instance/ScreenshotsPage.h b/application/pages/instance/ScreenshotsPage.h index e31fb8b4..9adf79af 100644 --- a/application/pages/instance/ScreenshotsPage.h +++ b/application/pages/instance/ScreenshotsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ #pragma once -#include <QWidget> +#include <QMainWindow> #include "pages/BasePage.h" #include <MultiMC.h> @@ -31,54 +31,59 @@ struct ScreenShot; class ScreenshotList; class ImgurAlbumCreation; -class ScreenshotsPage : public QWidget, public BasePage +class ScreenshotsPage : public QMainWindow, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit ScreenshotsPage(QString path, QWidget *parent = 0); - virtual ~ScreenshotsPage(); + explicit ScreenshotsPage(QString path, QWidget *parent = 0); + virtual ~ScreenshotsPage(); - virtual void openedImpl() override; + virtual void openedImpl() override; - enum - { - NothingDone = 0x42 - }; + enum + { + NothingDone = 0x42 + }; + + virtual bool eventFilter(QObject *, QEvent *) override; + virtual QString displayName() const override + { + return tr("Screenshots"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("screenshots"); + } + virtual QString id() const override + { + return "screenshots"; + } + virtual QString helpPage() const override + { + return "Screenshots-management"; + } + virtual bool apply() override + { + return !m_uploadActive; + } + +protected: + QMenu * createPopupMenu() override; - virtual bool eventFilter(QObject *, QEvent *) override; - virtual QString displayName() const override - { - return tr("Screenshots"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("screenshots"); - } - virtual QString id() const override - { - return "screenshots"; - } - virtual QString helpPage() const override - { - return "Screenshots-management"; - } - virtual bool apply() override - { - return !m_uploadActive; - } private slots: - void on_uploadBtn_clicked(); - void on_deleteBtn_clicked(); - void on_renameBtn_clicked(); - void on_viewFolderBtn_clicked(); - void onItemActivated(QModelIndex); + void on_actionUpload_triggered(); + void on_actionDelete_triggered(); + void on_actionRename_triggered(); + void on_actionView_Folder_triggered(); + void onItemActivated(QModelIndex); + void ShowContextMenu(const QPoint &pos); private: - Ui::ScreenshotsPage *ui; - std::shared_ptr<QFileSystemModel> m_model; - std::shared_ptr<QIdentityProxyModel> m_filterModel; - QString m_folder; - bool m_valid = false; - bool m_uploadActive = false; + Ui::ScreenshotsPage *ui; + std::shared_ptr<QFileSystemModel> m_model; + std::shared_ptr<QIdentityProxyModel> m_filterModel; + QString m_folder; + bool m_valid = false; + bool m_uploadActive = false; }; diff --git a/application/pages/instance/ScreenshotsPage.ui b/application/pages/instance/ScreenshotsPage.ui index d05c4384..42d4db64 100644 --- a/application/pages/instance/ScreenshotsPage.ui +++ b/application/pages/instance/ScreenshotsPage.ui @@ -1,106 +1,90 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ScreenshotsPage</class> - <widget class="QWidget" name="ScreenshotsPage"> + <widget class="QMainWindow" name="ScreenshotsPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>723</width> - <height>532</height> + <width>800</width> + <height>600</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <property name="leftMargin"> - <number>0</number> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <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="QListView" name="listView"> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="WideBar" name="toolBar"> + <property name="windowTitle"> + <string>toolBar</string> </property> - <property name="topMargin"> - <number>0</number> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> </property> - <property name="rightMargin"> - <number>0</number> + <attribute name="toolBarArea"> + <enum>RightToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionUpload"/> + <addaction name="actionDelete"/> + <addaction name="actionRename"/> + <addaction name="actionView_Folder"/> + </widget> + <action name="actionUpload"> + <property name="text"> + <string>Upload</string> </property> - <property name="bottomMargin"> - <number>0</number> + </action> + <action name="actionDelete"> + <property name="text"> + <string>Delete</string> </property> - <item> - <widget class="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string notr="true">Tab 1</string> - </attribute> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QListView" name="listView"> - <property name="selectionMode"> - <enum>QAbstractItemView::ExtendedSelection</enum> - </property> - <property name="selectionBehavior"> - <enum>QAbstractItemView::SelectRows</enum> - </property> - </widget> - </item> - <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QPushButton" name="uploadBtn"> - <property name="text"> - <string>&Upload</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="deleteBtn"> - <property name="text"> - <string>&Delete</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="renameBtn"> - <property name="text"> - <string>&Rename</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="QPushButton" name="viewFolderBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </widget> - </item> - </layout> + </action> + <action name="actionRename"> + <property name="text"> + <string>Rename</string> + </property> + </action> + <action name="actionView_Folder"> + <property name="text"> + <string>View Folder</string> + </property> + </action> </widget> - <tabstops> - <tabstop>listView</tabstop> - <tabstop>uploadBtn</tabstop> - <tabstop>deleteBtn</tabstop> - <tabstop>renameBtn</tabstop> - <tabstop>viewFolderBtn</tabstop> - </tabstops> + <customwidgets> + <customwidget> + <class>WideBar</class> + <extends>QToolBar</extends> + <header>widgets/WideBar.h</header> + </customwidget> + </customwidgets> <resources/> <connections/> </ui> diff --git a/application/pages/instance/ServersPage.cpp b/application/pages/instance/ServersPage.cpp new file mode 100644 index 00000000..c33eef1f --- /dev/null +++ b/application/pages/instance/ServersPage.cpp @@ -0,0 +1,760 @@ +#include "ServersPage.h" +#include "ui_ServersPage.h" + +#include <FileSystem.h> +#include <sstream> +#include <io/stream_reader.h> +#include <tag_string.h> +#include <tag_primitive.h> +#include <tag_list.h> +#include <tag_compound.h> +#include <minecraft/MinecraftInstance.h> + +#include <QFileSystemWatcher> +#include <QMenu> + +static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things. + +struct Server +{ + // Types + enum class AcceptsTextures : int + { + ASK = 0, + ALWAYS = 1, + NEVER = 2 + }; + + // Methods + Server() + { + m_name = QObject::tr("Minecraft Server"); + } + Server(const QString & name, const QString & address) + { + m_name = name; + m_address = address; + } + Server(nbt::tag_compound& server) + { + std::string addressStr(server["ip"]); + m_address = QString::fromUtf8(addressStr.c_str()); + + std::string nameStr(server["name"]); + m_name = QString::fromUtf8(nameStr.c_str()); + + if(server["icon"]) + { + std::string base64str(server["icon"]); + m_icon = QByteArray::fromBase64(base64str.c_str()); + } + + if(server.has_key("acceptTextures", nbt::tag_type::Byte)) + { + bool value = server["acceptTextures"].as<nbt::tag_byte>().get(); + if(value) + { + m_acceptsTextures = AcceptsTextures::ALWAYS; + } + else + { + m_acceptsTextures = AcceptsTextures::NEVER; + } + } + } + + void serialize(nbt::tag_compound& server) + { + server.insert("name", m_name.trimmed().toUtf8().toStdString()); + server.insert("ip", m_address.trimmed().toUtf8().toStdString()); + if(m_icon.size()) + { + server.insert("icon", m_icon.toBase64().toStdString()); + } + if(m_acceptsTextures != AcceptsTextures::ASK) + { + server.insert("acceptTextures", nbt::tag_byte(m_acceptsTextures == AcceptsTextures::ALWAYS)); + } + } + + // Data - persistent and user changeable + QString m_name; + QString m_address; + AcceptsTextures m_acceptsTextures = AcceptsTextures::ASK; + + // Data - persistent and automatically updated + QByteArray m_icon; + + // Data - temporary + bool m_checked = false; + bool m_up = false; + QString m_motd; // https://mctools.org/motd-creator + int m_ping = 0; + int m_currentPlayers = 0; + int m_maxPlayers = 0; +}; + +static std::unique_ptr <nbt::tag_compound> parseServersDat(const QString& filename) +{ + try + { + QByteArray input = FS::read(filename); + std::istringstream foo(std::string(input.constData(), input.size())); + auto pair = nbt::io::read_compound(foo); + + if(pair.first != "") + return nullptr; + + if(pair.second == nullptr) + return nullptr; + + return std::move(pair.second); + } + catch (...) + { + return nullptr; + } +} + +static bool serializeServerDat(const QString& filename, nbt::tag_compound * levelInfo) +{ + try + { + if(!FS::ensureFilePathExists(filename)) + { + return false; + } + std::ostringstream s; + nbt::io::write_tag("", *levelInfo, s); + QByteArray val(s.str().data(), (int) s.str().size() ); + FS::write(filename, val); + return true; + } + catch (...) + { + return false; + } +} + +class ServersModel: public QAbstractListModel +{ + Q_OBJECT +public: + enum Roles + { + ServerPtrRole = Qt::UserRole, + }; + explicit ServersModel(const QString &path, QObject *parent = 0) + : QAbstractListModel(parent) + { + m_path = path; + m_watcher = new QFileSystemWatcher(this); + connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &ServersModel::fileChanged); + connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &ServersModel::dirChanged); + m_saveTimer.setSingleShot(true); + m_saveTimer.setInterval(5000); + connect(&m_saveTimer, &QTimer::timeout, this, &ServersModel::save_internal); + } + virtual ~ServersModel() {}; + + void observe() + { + if(m_observed) + { + return; + } + m_observed = true; + + if(!m_loaded) + { + load(); + } + + updateFSObserver(); + } + + void unobserve() + { + if(!m_observed) + { + return; + } + m_observed = false; + + updateFSObserver(); + } + + void lock() + { + if(m_locked) + { + return; + } + saveNow(); + + m_locked = true; + updateFSObserver(); + } + + void unlock() + { + if(!m_locked) + { + return; + } + m_locked = false; + + updateFSObserver(); + } + + int addEmptyRow(int position) + { + if(m_locked) + { + return -1; + } + if(position < 0 || position >= rowCount()) + { + position = rowCount(); + } + beginInsertRows(QModelIndex(), position, position); + m_servers.insert(position, Server()); + endInsertRows(); + scheduleSave(); + return position; + } + + bool removeRow(int row) + { + if(m_locked) + { + return false; + } + if(row < 0 || row >= rowCount()) + { + return false; + } + beginRemoveRows(QModelIndex(), row, row); + m_servers.removeAt(row); + endRemoveRows(); // does absolutely nothing, the selected server stays as the next line... + scheduleSave(); + return true; + } + + bool moveUp(int row) + { + if(m_locked) + { + return false; + } + if(row <= 0) + { + return false; + } + beginMoveRows(QModelIndex(), row, row, QModelIndex(), row - 1); + m_servers.swap(row-1, row); + endMoveRows(); + scheduleSave(); + return true; + } + + bool moveDown(int row) + { + if(m_locked) + { + return false; + } + int count = rowCount(); + if(row + 1 >= count) + { + return false; + } + beginMoveRows(QModelIndex(), row, row, QModelIndex(), row + 2); + m_servers.swap(row+1, row); + endMoveRows(); + scheduleSave(); + return true; + } + + QVariant headerData(int section, Qt::Orientation orientation, int role) const override + { + if (section < 0 || section >= COLUMN_COUNT) + return QVariant(); + + if(role == Qt::DisplayRole) + { + switch(section) + { + case 0: + return tr("Name"); + case 1: + return tr("Address"); + case 2: + return tr("Latency"); + } + } + + return QAbstractListModel::headerData(section, orientation, role); + } + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + if(column < 0 || column >= COLUMN_COUNT) + return QVariant(); + + if (row < 0 || row >= m_servers.size()) + return QVariant(); + + switch(column) + { + case 0: + switch (role) + { + case Qt::DecorationRole: + { + auto & bytes = m_servers[row].m_icon; + if(bytes.size()) + { + QPixmap px; + if(px.loadFromData(bytes)) + return QIcon(px); + } + return MMC->getThemedIcon("unknown_server"); + } + case Qt::DisplayRole: + return m_servers[row].m_name; + case ServerPtrRole: + return QVariant::fromValue<void *>((void *)&m_servers[row]); + default: + return QVariant(); + } + case 1: + switch (role) + { + case Qt::DisplayRole: + return m_servers[row].m_address; + default: + return QVariant(); + } + case 2: + switch (role) + { + case Qt::DisplayRole: + return m_servers[row].m_ping; + default: + return QVariant(); + } + default: + return QVariant(); + } + } + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + return m_servers.size(); + } + int columnCount(const QModelIndex & parent) const override + { + return COLUMN_COUNT; + } + + Server * at(int index) + { + if(index < 0 || index >= rowCount()) + { + return nullptr; + } + return &m_servers[index]; + } + + void setName(int row, const QString & name) + { + if(m_locked) + { + return; + } + auto server = at(row); + if(!server || server->m_name == name) + { + return; + } + server->m_name = name; + emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); + scheduleSave(); + } + + void setAddress(int row, const QString & address) + { + if(m_locked) + { + return; + } + auto server = at(row); + if(!server || server->m_address == address) + { + return; + } + server->m_address = address; + emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); + scheduleSave(); + } + + void setAcceptsTextures(int row, Server::AcceptsTextures textures) + { + if(m_locked) + { + return; + } + auto server = at(row); + if(!server || server->m_acceptsTextures == textures) + { + return; + } + server->m_acceptsTextures = textures; + emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); + scheduleSave(); + } + + void load() + { + cancelSave(); + beginResetModel(); + QList<Server> servers; + auto serversDat = parseServersDat(serversPath()); + if(serversDat) + { + auto &serversList = serversDat->at("servers").as<nbt::tag_list>(); + for(auto iter = serversList.begin(); iter != serversList.end(); iter++) + { + auto & serverTag = (*iter).as<nbt::tag_compound>(); + Server s(serverTag); + servers.append(s); + } + } + m_servers.swap(servers); + m_loaded = true; + endResetModel(); + } + + void saveNow() + { + if(saveIsScheduled()) + { + save_internal(); + } + } + + +public slots: + void dirChanged(const QString& path) + { + qDebug() << "Changed:" << path; + load(); + } + void fileChanged(const QString& path) + { + qDebug() << "Changed:" << path; + } + +private slots: + void save_internal() + { + cancelSave(); + QString path = serversPath(); + qDebug() << "Server list about to be saved to" << path; + + nbt::tag_compound out; + nbt::tag_list list; + for(auto & server: m_servers) + { + nbt::tag_compound serverNbt; + server.serialize(serverNbt); + list.push_back(std::move(serverNbt)); + } + out.insert("servers", nbt::value(std::move(list))); + + if(!serializeServerDat(path, &out)) + { + qDebug() << "Failed to save server list:" << path << "Will try again."; + scheduleSave(); + } + } + +private: + void scheduleSave() + { + if(!m_loaded) + { + qDebug() << "Server list should never save if it didn't successfully load, path:" << m_path; + return; + } + if(!m_dirty) + { + m_dirty = true; + qDebug() << "Server list save is scheduled for" << m_path; + } + m_saveTimer.start(); + } + + void cancelSave() + { + m_dirty = false; + m_saveTimer.stop(); + } + + bool saveIsScheduled() const + { + return m_dirty; + } + + void updateFSObserver() + { + bool observingFS = m_watcher->directories().contains(m_path); + if(m_observed && m_locked) + { + if(!observingFS) + { + qWarning() << "Will watch" << m_path; + if(!m_watcher->addPath(m_path)) + { + qWarning() << "Failed to start watching" << m_path; + } + } + } + else + { + if(observingFS) + { + qWarning() << "Will stop watching" << m_path; + if(!m_watcher->removePath(m_path)) + { + qWarning() << "Failed to stop watching" << m_path; + } + } + } + } + + QString serversPath() + { + QFileInfo foo(FS::PathCombine(m_path, "servers.dat")); + return foo.filePath(); + } + +private: + bool m_loaded = false; + bool m_locked = false; + bool m_observed = false; + bool m_dirty = false; + QString m_path; + QList<Server> m_servers; + QFileSystemWatcher *m_watcher = nullptr; + QTimer m_saveTimer; +}; + +ServersPage::ServersPage(MinecraftInstance * inst, QWidget* parent) + : QMainWindow(parent), ui(new Ui::ServersPage) +{ + ui->setupUi(this); + m_inst = inst; + m_model = new ServersModel(inst->gameRoot(), this); + ui->serversView->setIconSize(QSize(64,64)); + ui->serversView->setModel(m_model); + ui->serversView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->serversView, &QTreeView::customContextMenuRequested, this, &ServersPage::ShowContextMenu); + + auto head = ui->serversView->header(); + if(head->count()) + { + head->setSectionResizeMode(0, QHeaderView::Stretch); + for(int i = 1; i < head->count(); i++) + { + head->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + } + + auto selectionModel = ui->serversView->selectionModel(); + connect(selectionModel, &QItemSelectionModel::currentChanged, this, &ServersPage::currentChanged); + connect(m_inst, &MinecraftInstance::runningStatusChanged, this, &ServersPage::on_RunningState_changed); + connect(ui->nameLine, &QLineEdit::textEdited, this, &ServersPage::nameEdited); + connect(ui->addressLine, &QLineEdit::textEdited, this, &ServersPage::addressEdited); + connect(ui->resourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resourceIndexChanged(int))); + connect(m_model, &QAbstractItemModel::rowsRemoved, this, &ServersPage::rowsRemoved); + + m_locked = m_inst->isRunning(); + if(m_locked) + { + m_model->lock(); + } + + updateState(); +} + +ServersPage::~ServersPage() +{ + m_model->saveNow(); +} + +void ServersPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->serversView->mapToGlobal(pos)); + delete menu; +} + +QMenu * ServersPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->toolBar->toggleViewAction() ); + return filteredMenu; +} + +void ServersPage::on_RunningState_changed(bool running) +{ + if(m_locked == running) + { + return; + } + m_locked = running; + if(m_locked) + { + m_model->lock(); + } + else + { + m_model->unlock(); + } + updateState(); +} + +void ServersPage::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + int nextServer = -1; + if (!current.isValid()) + { + nextServer = -1; + } + else + { + nextServer = current.row(); + } + currentServer = nextServer; + updateState(); +} + +// WARNING: this is here because currentChanged is not accurate when removing rows. the current item needs to be fixed up after removal. +void ServersPage::rowsRemoved(const QModelIndex& parent, int first, int last) +{ + if(currentServer < first) + { + // current was before the removal + return; + } + else if(currentServer >= first && currentServer <= last) + { + // current got removed... + return; + } + else + { + // current was past the removal + int count = last - first + 1; + currentServer -= count; + } +} + +void ServersPage::nameEdited(const QString& name) +{ + m_model->setName(currentServer, name); +} + +void ServersPage::addressEdited(const QString& address) +{ + m_model->setAddress(currentServer, address); +} + +void ServersPage::resourceIndexChanged(int index) +{ + auto acceptsTextures = Server::AcceptsTextures(index); + m_model->setAcceptsTextures(currentServer, acceptsTextures); +} + +void ServersPage::updateState() +{ + auto server = m_model->at(currentServer); + + bool serverEditEnabled = server && !m_locked; + ui->addressLine->setEnabled(serverEditEnabled); + ui->nameLine->setEnabled(serverEditEnabled); + ui->resourceComboBox->setEnabled(serverEditEnabled); + ui->actionMove_Down->setEnabled(serverEditEnabled); + ui->actionMove_Up->setEnabled(serverEditEnabled); + ui->actionRemove->setEnabled(serverEditEnabled); + + if(server) + { + ui->addressLine->setText(server->m_address); + ui->nameLine->setText(server->m_name); + ui->resourceComboBox->setCurrentIndex(int(server->m_acceptsTextures)); + } + else + { + ui->addressLine->setText(QString()); + ui->nameLine->setText(QString()); + ui->resourceComboBox->setCurrentIndex(0); + } + + ui->actionAdd->setDisabled(m_locked); +} + +void ServersPage::openedImpl() +{ + m_model->observe(); +} + +void ServersPage::closedImpl() +{ + m_model->unobserve(); +} + +void ServersPage::on_actionAdd_triggered() +{ + int position = m_model->addEmptyRow(currentServer + 1); + if(position < 0) + { + return; + } + // select the new row + ui->serversView->selectionModel()->setCurrentIndex( + m_model->index(position), + QItemSelectionModel::SelectCurrent | QItemSelectionModel::Clear | QItemSelectionModel::Rows + ); + currentServer = position; +} + +void ServersPage::on_actionRemove_triggered() +{ + m_model->removeRow(currentServer); +} + +void ServersPage::on_actionMove_Up_triggered() +{ + if(m_model->moveUp(currentServer)) + { + currentServer --; + } +} + +void ServersPage::on_actionMove_Down_triggered() +{ + if(m_model->moveDown(currentServer)) + { + currentServer ++; + } +} + +#include "ServersPage.moc" diff --git a/application/pages/instance/ServersPage.h b/application/pages/instance/ServersPage.h new file mode 100644 index 00000000..c81f47be --- /dev/null +++ b/application/pages/instance/ServersPage.h @@ -0,0 +1,93 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QMainWindow> +#include <QString> + +#include "pages/BasePage.h" +#include <MultiMC.h> + +namespace Ui +{ +class ServersPage; +} + +struct Server; +class ServersModel; +class MinecraftInstance; + +class ServersPage : public QMainWindow, public BasePage +{ + Q_OBJECT + +public: + explicit ServersPage(MinecraftInstance *inst, QWidget *parent = 0); + virtual ~ServersPage(); + + void openedImpl() override; + void closedImpl() override; + + virtual QString displayName() const override + { + return tr("Servers"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("unknown_server"); + } + virtual QString id() const override + { + return "servers"; + } + virtual QString helpPage() const override + { + return "Servers-management"; + } + +protected: + QMenu * createPopupMenu() override; + +private: + void updateState(); + void scheduleSave(); + bool saveIsScheduled() const; + +private slots: + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + void rowsRemoved(const QModelIndex &parent, int first, int last); + + void on_actionAdd_triggered(); + void on_actionRemove_triggered(); + void on_actionMove_Up_triggered(); + void on_actionMove_Down_triggered(); + + void on_RunningState_changed(bool running); + + void nameEdited(const QString & name); + void addressEdited(const QString & address); + void resourceIndexChanged(int index);\ + + void ShowContextMenu(const QPoint &pos); + +private: // data + int currentServer = -1; + bool m_locked = true; + Ui::ServersPage *ui = nullptr; + ServersModel * m_model = nullptr; + MinecraftInstance * m_inst = nullptr; +}; + diff --git a/application/pages/instance/ServersPage.ui b/application/pages/instance/ServersPage.ui new file mode 100644 index 00000000..9c31abe3 --- /dev/null +++ b/application/pages/instance/ServersPage.ui @@ -0,0 +1,191 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ServersPage</class> + <widget class="QMainWindow" name="ServersPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1318</width> + <height>879</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <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="QTreeView" name="serversView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="acceptDrops"> + <bool>true</bool> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="iconSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <attribute name="headerStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="nameLabel"> + <property name="text"> + <string>&Name</string> + </property> + <property name="buddy"> + <cstring>nameLine</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="nameLine"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="addressLabel"> + <property name="text"> + <string>Address</string> + </property> + <property name="buddy"> + <cstring>addressLine</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="addressLine"/> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="resourcesLabel"> + <property name="text"> + <string>Reso&urces</string> + </property> + <property name="buddy"> + <cstring>resourceComboBox</cstring> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="resourceComboBox"> + <item> + <property name="text"> + <string>Ask to download</string> + </property> + </item> + <item> + <property name="text"> + <string>Always download</string> + </property> + </item> + <item> + <property name="text"> + <string>Never download</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="WideBar" name="toolBar"> + <property name="windowTitle"> + <string>toolBar</string> + </property> + <property name="allowedAreas"> + <set>Qt::LeftToolBarArea|Qt::RightToolBarArea</set> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> + <property name="floatable"> + <bool>false</bool> + </property> + <attribute name="toolBarArea"> + <enum>RightToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionAdd"/> + <addaction name="actionRemove"/> + <addaction name="actionMove_Up"/> + <addaction name="actionMove_Down"/> + </widget> + <action name="actionAdd"> + <property name="text"> + <string>Add</string> + </property> + </action> + <action name="actionRemove"> + <property name="text"> + <string>Remove</string> + </property> + </action> + <action name="actionMove_Up"> + <property name="text"> + <string>Move Up</string> + </property> + </action> + <action name="actionMove_Down"> + <property name="text"> + <string>Move Down</string> + </property> + </action> + </widget> + <customwidgets> + <customwidget> + <class>WideBar</class> + <extends>QToolBar</extends> + <header>widgets/WideBar.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>serversView</tabstop> + <tabstop>nameLine</tabstop> + <tabstop>addressLine</tabstop> + <tabstop>resourceComboBox</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/application/pages/instance/TexturePackPage.h b/application/pages/instance/TexturePackPage.h index b03614f0..a792ba07 100644 --- a/application/pages/instance/TexturePackPage.h +++ b/application/pages/instance/TexturePackPage.h @@ -4,16 +4,17 @@ class TexturePackPage : public ModFolderPage { + Q_OBJECT public: - explicit TexturePackPage(MinecraftInstance *instance, QWidget *parent = 0) - : ModFolderPage(instance, instance->texturePackList(), "texturepacks", "resourcepacks", - tr("Texture packs"), "Texture-packs", parent) - { - ui->configFolderBtn->setHidden(true); - } - virtual ~TexturePackPage() {} - virtual bool shouldDisplay() const override - { - return m_inst->traits().contains("texturepacks"); - } + explicit TexturePackPage(MinecraftInstance *instance, QWidget *parent = 0) + : ModFolderPage(instance, instance->texturePackList(), "texturepacks", "resourcepacks", + tr("Texture packs"), "Texture-packs", parent) + { + ui->actionView_configs->setVisible(false); + } + virtual ~TexturePackPage() {} + virtual bool shouldDisplay() const override + { + return m_inst->traits().contains("texturepacks"); + } }; diff --git a/application/pages/instance/VersionPage.cpp b/application/pages/instance/VersionPage.cpp index 00ae0a7e..2acc13c2 100644 --- a/application/pages/instance/VersionPage.cpp +++ b/application/pages/instance/VersionPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,10 @@ #include "MultiMC.h" #include <QMessageBox> +#include <QLabel> #include <QEvent> #include <QKeyEvent> +#include <QMenu> #include "VersionPage.h" #include "ui_VersionPage.h" @@ -25,7 +27,6 @@ #include "dialogs/CustomMessageBox.h" #include "dialogs/VersionSelectDialog.h" #include "dialogs/NewComponentDialog.h" -#include "dialogs/ModEditDialogCommon.h" #include "dialogs/ProgressDialog.h" #include <GuiUtil.h> @@ -38,532 +39,574 @@ #include "minecraft/ComponentList.h" #include "minecraft/auth/MojangAccountList.h" -#include "minecraft/Mod.h" +#include "minecraft/mod/Mod.h" #include "icons/IconList.h" #include "Exception.h" - -#include "MultiMC.h" +#include "Version.h" #include <meta/Index.h> #include <meta/VersionList.h> class IconProxy : public QIdentityProxyModel { - Q_OBJECT + Q_OBJECT public: - IconProxy(QWidget *parentWidget) : QIdentityProxyModel(parentWidget) - { - connect(parentWidget, &QObject::destroyed, this, &IconProxy::widgetGone); - m_parentWidget = parentWidget; - } - - virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override - { - QVariant var = QIdentityProxyModel::data(mapToSource(proxyIndex), role); - int column = proxyIndex.column(); - if(column == 0 && role == Qt::DecorationRole && m_parentWidget) - { - if(!var.isNull()) - { - auto string = var.toString(); - if(string == "warning") - { - return MMC->getThemedIcon("status-yellow"); - } - else if(string == "error") - { - return MMC->getThemedIcon("status-bad"); - } - } - return MMC->getThemedIcon("status-good"); - } - return var; - } + IconProxy(QWidget *parentWidget) : QIdentityProxyModel(parentWidget) + { + connect(parentWidget, &QObject::destroyed, this, &IconProxy::widgetGone); + m_parentWidget = parentWidget; + } + + virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override + { + QVariant var = QIdentityProxyModel::data(mapToSource(proxyIndex), role); + int column = proxyIndex.column(); + if(column == 0 && role == Qt::DecorationRole && m_parentWidget) + { + if(!var.isNull()) + { + auto string = var.toString(); + if(string == "warning") + { + return MMC->getThemedIcon("status-yellow"); + } + else if(string == "error") + { + return MMC->getThemedIcon("status-bad"); + } + } + return MMC->getThemedIcon("status-good"); + } + return var; + } private slots: - void widgetGone() - { - m_parentWidget = nullptr; - } + void widgetGone() + { + m_parentWidget = nullptr; + } private: - QWidget *m_parentWidget = nullptr; + QWidget *m_parentWidget = nullptr; }; QIcon VersionPage::icon() const { - return MMC->icons()->getIcon(m_inst->iconKey()); + return MMC->icons()->getIcon(m_inst->iconKey()); } bool VersionPage::shouldDisplay() const { - return !m_inst->isRunning(); + return true; +} + +QMenu * VersionPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->toolBar->toggleViewAction() ); + return filteredMenu; } VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent) - : QWidget(parent), ui(new Ui::VersionPage), m_inst(inst) -{ - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - m_profile = m_inst->getComponentList(); - - reloadComponentList(); - - if (m_profile) - { - auto proxy = new IconProxy(ui->packageView); - proxy->setSourceModel(m_profile.get()); - ui->packageView->setModel(proxy); - ui->packageView->installEventFilter(this); - ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection); - connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent); - auto smodel = ui->packageView->selectionModel(); - connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent); - updateVersionControls(); - // select first item. - preselect(0); - } - else - { - disableVersionControls(); - } - connect(m_inst, &MinecraftInstance::versionReloaded, this, - &VersionPage::updateVersionControls); + : QMainWindow(parent), ui(new Ui::VersionPage), m_inst(inst) +{ + ui->setupUi(this); + + ui->toolBar->insertSpacer(ui->actionReload); + + m_profile = m_inst->getComponentList(); + + reloadComponentList(); + + auto proxy = new IconProxy(ui->packageView); + proxy->setSourceModel(m_profile.get()); + ui->packageView->setModel(proxy); + ui->packageView->installEventFilter(this); + ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection); + ui->packageView->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent); + auto smodel = ui->packageView->selectionModel(); + connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent); + + connect(m_profile.get(), &ComponentList::minecraftChanged, this, &VersionPage::updateVersionControls); + controlsEnabled = !m_inst->isRunning(); + updateVersionControls(); + preselect(0); + connect(m_inst, &BaseInstance::runningStatusChanged, this, &VersionPage::updateRunningStatus); + connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::ShowContextMenu); } VersionPage::~VersionPage() { - delete ui; + delete ui; +} + +void VersionPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->packageView->mapToGlobal(pos)); + delete menu; } void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex &previous) { - if (!current.isValid()) - { - ui->frame->clear(); - return; - } - int row = current.row(); - auto patch = m_profile->getComponent(row); - auto severity = patch->getProblemSeverity(); - switch(severity) - { - case ProblemSeverity::Warning: - ui->frame->setModText(tr("%1 possibly has issues.").arg(patch->getName())); - break; - case ProblemSeverity::Error: - ui->frame->setModText(tr("%1 has issues!").arg(patch->getName())); - break; - default: - case ProblemSeverity::None: - ui->frame->clear(); - return; - } - - auto &problems = patch->getProblems(); - QString problemOut; - for (auto &problem: problems) - { - if(problem.m_severity == ProblemSeverity::Error) - { - problemOut += tr("Error: "); - } - else if(problem.m_severity == ProblemSeverity::Warning) - { - problemOut += tr("Warning: "); - } - problemOut += problem.m_description; - problemOut += "\n"; - } - ui->frame->setModDescription(problemOut); + if (!current.isValid()) + { + ui->frame->clear(); + return; + } + int row = current.row(); + auto patch = m_profile->getComponent(row); + auto severity = patch->getProblemSeverity(); + switch(severity) + { + case ProblemSeverity::Warning: + ui->frame->setModText(tr("%1 possibly has issues.").arg(patch->getName())); + break; + case ProblemSeverity::Error: + ui->frame->setModText(tr("%1 has issues!").arg(patch->getName())); + break; + default: + case ProblemSeverity::None: + ui->frame->clear(); + return; + } + + auto &problems = patch->getProblems(); + QString problemOut; + for (auto &problem: problems) + { + if(problem.m_severity == ProblemSeverity::Error) + { + problemOut += tr("Error: "); + } + else if(problem.m_severity == ProblemSeverity::Warning) + { + problemOut += tr("Warning: "); + } + problemOut += problem.m_description; + problemOut += "\n"; + } + ui->frame->setModDescription(problemOut); } +void VersionPage::updateRunningStatus(bool running) +{ + if(controlsEnabled == running) { + controlsEnabled = !running; + updateVersionControls(); + } +} void VersionPage::updateVersionControls() { - ui->forgeBtn->setEnabled(true); - ui->liteloaderBtn->setEnabled(true); - updateButtons(); + // FIXME: this is a dirty hack + auto minecraftVersion = Version(m_profile->getComponentVersion("net.minecraft")); + bool newCraft = controlsEnabled && (minecraftVersion >= Version("1.14")); + bool oldCraft = controlsEnabled && (minecraftVersion <= Version("1.12.2")); + ui->actionInstall_Fabric->setEnabled(newCraft); + ui->actionInstall_Forge->setEnabled(oldCraft); + ui->actionInstall_LiteLoader->setEnabled(oldCraft); + ui->actionReload->setEnabled(true); + updateButtons(); } -void VersionPage::disableVersionControls() +void VersionPage::updateButtons(int row) { - ui->forgeBtn->setEnabled(false); - ui->liteloaderBtn->setEnabled(false); - ui->reloadBtn->setEnabled(false); - updateButtons(); + if(row == -1) + row = currentRow(); + auto patch = m_profile->getComponent(row); + ui->actionRemove->setEnabled(controlsEnabled && patch && patch->isRemovable()); + ui->actionMove_down->setEnabled(controlsEnabled && patch && patch->isMoveable()); + ui->actionMove_up->setEnabled(controlsEnabled && patch && patch->isMoveable()); + ui->actionChange_version->setEnabled(controlsEnabled && patch && patch->isVersionChangeable()); + ui->actionEdit->setEnabled(controlsEnabled && patch && patch->isCustom()); + ui->actionCustomize->setEnabled(controlsEnabled && patch && patch->isCustomizable()); + ui->actionRevert->setEnabled(controlsEnabled && patch && patch->isRevertible()); + ui->actionDownload_All->setEnabled(controlsEnabled); + ui->actionAdd_Empty->setEnabled(controlsEnabled); + ui->actionReload->setEnabled(controlsEnabled); + ui->actionInstall_mods->setEnabled(controlsEnabled); + ui->actionReplace_Minecraft_jar->setEnabled(controlsEnabled); + ui->actionAdd_to_Minecraft_jar->setEnabled(controlsEnabled); } bool VersionPage::reloadComponentList() { - try - { - m_profile->reload(Net::Mode::Online); - return true; - } - catch (Exception &e) - { - QMessageBox::critical(this, tr("Error"), e.cause()); - return false; - } - catch (...) - { - QMessageBox::critical( - this, tr("Error"), - tr("Couldn't load the instance profile.")); - return false; - } -} - -void VersionPage::on_reloadBtn_clicked() -{ - reloadComponentList(); - m_container->refreshContainer(); -} - -void VersionPage::on_removeBtn_clicked() -{ - if (ui->packageView->currentIndex().isValid()) - { - // FIXME: use actual model, not reloading. - if (!m_profile->remove(ui->packageView->currentIndex().row())) - { - QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file")); - } - } - updateButtons(); - reloadComponentList(); - m_container->refreshContainer(); -} - -void VersionPage::on_modBtn_clicked() -{ - if(m_container) - { - m_container->selectPage("mods"); - } -} - -void VersionPage::on_jarmodBtn_clicked() -{ - auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget()); - if(!list.empty()) - { - m_profile->installJarMods(list); - } - updateButtons(); -} - -void VersionPage::on_jarBtn_clicked() -{ - auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement (*.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget()); - if(!jarPath.isEmpty()) - { - m_profile->installCustomJar(jarPath); - } - updateButtons(); -} - -void VersionPage::on_moveUpBtn_clicked() -{ - try - { - m_profile->move(currentRow(), ComponentList::MoveUp); - } - catch (Exception &e) - { - QMessageBox::critical(this, tr("Error"), e.cause()); - } - updateButtons(); -} - -void VersionPage::on_moveDownBtn_clicked() -{ - try - { - m_profile->move(currentRow(), ComponentList::MoveDown); - } - catch (Exception &e) - { - QMessageBox::critical(this, tr("Error"), e.cause()); - } - updateButtons(); -} - -void VersionPage::on_changeVersionBtn_clicked() -{ - auto versionRow = currentRow(); - if(versionRow == -1) - { - return; - } - auto patch = m_profile->getComponent(versionRow); - auto name = patch->getName(); - auto list = patch->getVersionList(); - if(!list) - { - return; - } - auto uid = list->uid(); - // FIXME: this is a horrible HACK. Get version filtering information from the actual metadata... - if(uid == "net.minecraftforge") - { - on_forgeBtn_clicked(); - return; - } - else if (uid == "com.mumfrey.liteloader") - { - on_liteloaderBtn_clicked(); - return; - } - VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this); - auto currentVersion = patch->getVersion(); - if(!currentVersion.isEmpty()) - { - vselect.setCurrentVersion(currentVersion); - } - if (!vselect.exec() || !vselect.selectedVersion()) - return; - - qDebug() << "Change" << uid << "to" << vselect.selectedVersion()->descriptor(); - bool important = false; - if(uid == "net.minecraft") - { - important = true; - } - m_profile->setComponentVersion(uid, vselect.selectedVersion()->descriptor(), important); - m_profile->resolve(Net::Mode::Online); - m_container->refreshContainer(); -} - -void VersionPage::on_downloadBtn_clicked() -{ - if (!MMC->accounts()->anyAccountIsValid()) - { - CustomMessageBox::selectable( - this, tr("Error"), - tr("MultiMC cannot download Minecraft or update instances unless you have at least " - "one account added.\nPlease add your Mojang or Minecraft account."), - QMessageBox::Warning)->show(); - return; - } - - auto updateTask = m_inst->createUpdateTask(Net::Mode::Online); - if (!updateTask) - { - return; - } - ProgressDialog tDialog(this); - connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); - // FIXME: unused return value - tDialog.execWithTask(updateTask.get()); - updateButtons(); - m_container->refreshContainer(); -} - -void VersionPage::on_forgeBtn_clicked() -{ - auto vlist = ENV.metadataIndex()->get("net.minecraftforge"); - if(!vlist) - { - return; - } - VersionSelectDialog vselect(vlist.get(), tr("Select Forge version"), this); - vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); - vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft")); - vselect.setEmptyErrorString(tr("Couldn't load or download the Forge version lists!")); - - auto currentVersion = m_profile->getComponentVersion("net.minecraftforge"); - if(!currentVersion.isEmpty()) - { - vselect.setCurrentVersion(currentVersion); - } - - if (vselect.exec() && vselect.selectedVersion()) - { - auto vsn = vselect.selectedVersion(); - m_profile->setComponentVersion("net.minecraftforge", vsn->descriptor()); - m_profile->resolve(Net::Mode::Online); - // m_profile->installVersion(); - preselect(m_profile->rowCount(QModelIndex())-1); - m_container->refreshContainer(); - } -} - -void VersionPage::on_addEmptyBtn_clicked() -{ - NewComponentDialog compdialog(QString(), QString(), this); - QStringList blacklist; - for(int i = 0; i < m_profile->rowCount(); i++) - { - auto comp = m_profile->getComponent(i); - blacklist.push_back(comp->getID()); - } - compdialog.setBlacklist(blacklist); - if (compdialog.exec()) - { - qDebug() << "name:" << compdialog.name(); - qDebug() << "uid:" << compdialog.uid(); - m_profile->installEmpty(compdialog.uid(), compdialog.name()); - } -} - -void VersionPage::on_liteloaderBtn_clicked() -{ - auto vlist = ENV.metadataIndex()->get("com.mumfrey.liteloader"); - if(!vlist) - { - return; - } - VersionSelectDialog vselect(vlist.get(), tr("Select LiteLoader version"), this); - vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); - vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft")); - vselect.setEmptyErrorString(tr("Couldn't load or download the LiteLoader version lists!")); - - auto currentVersion = m_profile->getComponentVersion("com.mumfrey.liteloader"); - if(!currentVersion.isEmpty()) - { - vselect.setCurrentVersion(currentVersion); - } - - if (vselect.exec() && vselect.selectedVersion()) - { - auto vsn = vselect.selectedVersion(); - m_profile->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor()); - m_profile->resolve(Net::Mode::Online); - // m_profile->installVersion(vselect.selectedVersion()); - preselect(m_profile->rowCount(QModelIndex())-1); - m_container->refreshContainer(); - } + try + { + m_profile->reload(Net::Mode::Online); + return true; + } + catch (const Exception &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + return false; + } + catch (...) + { + QMessageBox::critical( + this, tr("Error"), + tr("Couldn't load the instance profile.")); + return false; + } } -void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex &previous) +void VersionPage::on_actionReload_triggered() { - currentIdx = current.row(); - updateButtons(currentIdx); + reloadComponentList(); + m_container->refreshContainer(); } -void VersionPage::preselect(int row) +void VersionPage::on_actionRemove_triggered() { - if(row < 0) - { - row = 0; - } - if(row >= m_profile->rowCount(QModelIndex())) - { - row = m_profile->rowCount(QModelIndex()) - 1; - } - if(row < 0) - { - return; - } - auto model_index = m_profile->index(row); - ui->packageView->selectionModel()->select(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); - updateButtons(row); + if (ui->packageView->currentIndex().isValid()) + { + // FIXME: use actual model, not reloading. + if (!m_profile->remove(ui->packageView->currentIndex().row())) + { + QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file")); + } + } + updateButtons(); + reloadComponentList(); + m_container->refreshContainer(); } -void VersionPage::updateButtons(int row) +void VersionPage::on_actionInstall_mods_triggered() +{ + if(m_container) + { + m_container->selectPage("mods"); + } +} + +void VersionPage::on_actionAdd_to_Minecraft_jar_triggered() +{ + auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget()); + if(!list.empty()) + { + m_profile->installJarMods(list); + } + updateButtons(); +} + +void VersionPage::on_actionReplace_Minecraft_jar_triggered() +{ + auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement (*.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget()); + if(!jarPath.isEmpty()) + { + m_profile->installCustomJar(jarPath); + } + updateButtons(); +} + +void VersionPage::on_actionMove_up_triggered() +{ + try + { + m_profile->move(currentRow(), ComponentList::MoveUp); + } + catch (const Exception &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + } + updateButtons(); +} + +void VersionPage::on_actionMove_down_triggered() +{ + try + { + m_profile->move(currentRow(), ComponentList::MoveDown); + } + catch (const Exception &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + } + updateButtons(); +} + +void VersionPage::on_actionChange_version_triggered() +{ + auto versionRow = currentRow(); + if(versionRow == -1) + { + return; + } + auto patch = m_profile->getComponent(versionRow); + auto name = patch->getName(); + auto list = patch->getVersionList(); + if(!list) + { + return; + } + auto uid = list->uid(); + // FIXME: this is a horrible HACK. Get version filtering information from the actual metadata... + if(uid == "net.minecraftforge") + { + on_actionInstall_Forge_triggered(); + return; + } + else if (uid == "com.mumfrey.liteloader") + { + on_actionInstall_LiteLoader_triggered(); + return; + } + VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this); + if (uid == "net.fabricmc.intermediary") + { + vselect.setEmptyString(tr("No Fabric Loader versions are currently available.")); + vselect.setEmptyErrorString(tr("Couldn't load or download the Fabric Loader version lists!")); + vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); + } + auto currentVersion = patch->getVersion(); + if(!currentVersion.isEmpty()) + { + vselect.setCurrentVersion(currentVersion); + } + if (!vselect.exec() || !vselect.selectedVersion()) + return; + + qDebug() << "Change" << uid << "to" << vselect.selectedVersion()->descriptor(); + bool important = false; + if(uid == "net.minecraft") + { + important = true; + } + m_profile->setComponentVersion(uid, vselect.selectedVersion()->descriptor(), important); + m_profile->resolve(Net::Mode::Online); + m_container->refreshContainer(); +} + +void VersionPage::on_actionDownload_triggered() +{ + if (!MMC->accounts()->anyAccountIsValid()) + { + CustomMessageBox::selectable( + this, tr("Error"), + tr("MultiMC cannot download Minecraft or update instances unless you have at least " + "one account added.\nPlease add your Mojang or Minecraft account."), + QMessageBox::Warning)->show(); + return; + } + + auto updateTask = m_inst->createUpdateTask(Net::Mode::Online); + if (!updateTask) + { + return; + } + ProgressDialog tDialog(this); + connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); + // FIXME: unused return value + tDialog.execWithTask(updateTask.get()); + updateButtons(); + m_container->refreshContainer(); +} + +void VersionPage::on_actionInstall_Forge_triggered() +{ + auto vlist = ENV.metadataIndex()->get("net.minecraftforge"); + if(!vlist) + { + return; + } + VersionSelectDialog vselect(vlist.get(), tr("Select Forge version"), this); + vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); + vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft")); + vselect.setEmptyErrorString(tr("Couldn't load or download the Forge version lists!")); + + auto currentVersion = m_profile->getComponentVersion("net.minecraftforge"); + if(!currentVersion.isEmpty()) + { + vselect.setCurrentVersion(currentVersion); + } + + if (vselect.exec() && vselect.selectedVersion()) + { + auto vsn = vselect.selectedVersion(); + m_profile->setComponentVersion("net.minecraftforge", vsn->descriptor()); + m_profile->resolve(Net::Mode::Online); + // m_profile->installVersion(); + preselect(m_profile->rowCount(QModelIndex())-1); + m_container->refreshContainer(); + } +} + +void VersionPage::on_actionInstall_Fabric_triggered() +{ + auto vlist = ENV.metadataIndex()->get("net.fabricmc.fabric-loader"); + if(!vlist) + { + return; + } + VersionSelectDialog vselect(vlist.get(), tr("Select Fabric Loader version"), this); + vselect.setEmptyString(tr("No Fabric Loader versions are currently available.")); + vselect.setEmptyErrorString(tr("Couldn't load or download the Fabric Loader version lists!")); + + auto currentVersion = m_profile->getComponentVersion("net.fabricmc.fabric-loader"); + if(!currentVersion.isEmpty()) + { + vselect.setCurrentVersion(currentVersion); + } + + if (vselect.exec() && vselect.selectedVersion()) + { + auto vsn = vselect.selectedVersion(); + m_profile->setComponentVersion("net.fabricmc.fabric-loader", vsn->descriptor()); + m_profile->resolve(Net::Mode::Online); + preselect(m_profile->rowCount(QModelIndex())-1); + m_container->refreshContainer(); + } +} + +void VersionPage::on_actionAdd_Empty_triggered() +{ + NewComponentDialog compdialog(QString(), QString(), this); + QStringList blacklist; + for(int i = 0; i < m_profile->rowCount(); i++) + { + auto comp = m_profile->getComponent(i); + blacklist.push_back(comp->getID()); + } + compdialog.setBlacklist(blacklist); + if (compdialog.exec()) + { + qDebug() << "name:" << compdialog.name(); + qDebug() << "uid:" << compdialog.uid(); + m_profile->installEmpty(compdialog.uid(), compdialog.name()); + } +} + +void VersionPage::on_actionInstall_LiteLoader_triggered() { - if(row == -1) - row = currentRow(); - auto patch = m_profile->getComponent(row); - if (!patch) - { - ui->removeBtn->setDisabled(true); - ui->moveDownBtn->setDisabled(true); - ui->moveUpBtn->setDisabled(true); - ui->changeVersionBtn->setDisabled(true); - ui->editBtn->setDisabled(true); - ui->customizeBtn->setDisabled(true); - ui->revertBtn->setDisabled(true); - } - else - { - ui->removeBtn->setEnabled(patch->isRemovable()); - ui->moveDownBtn->setEnabled(patch->isMoveable()); - ui->moveUpBtn->setEnabled(patch->isMoveable()); - ui->changeVersionBtn->setEnabled(patch->isVersionChangeable()); - ui->editBtn->setEnabled(patch->isCustom()); - ui->customizeBtn->setEnabled(patch->isCustomizable()); - ui->revertBtn->setEnabled(patch->isRevertible()); - } + auto vlist = ENV.metadataIndex()->get("com.mumfrey.liteloader"); + if(!vlist) + { + return; + } + VersionSelectDialog vselect(vlist.get(), tr("Select LiteLoader version"), this); + vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); + vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft")); + vselect.setEmptyErrorString(tr("Couldn't load or download the LiteLoader version lists!")); + + auto currentVersion = m_profile->getComponentVersion("com.mumfrey.liteloader"); + if(!currentVersion.isEmpty()) + { + vselect.setCurrentVersion(currentVersion); + } + + if (vselect.exec() && vselect.selectedVersion()) + { + auto vsn = vselect.selectedVersion(); + m_profile->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor()); + m_profile->resolve(Net::Mode::Online); + // m_profile->installVersion(vselect.selectedVersion()); + preselect(m_profile->rowCount(QModelIndex())-1); + m_container->refreshContainer(); + } +} + +void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex &previous) +{ + currentIdx = current.row(); + updateButtons(currentIdx); +} + +void VersionPage::preselect(int row) +{ + if(row < 0) + { + row = 0; + } + if(row >= m_profile->rowCount(QModelIndex())) + { + row = m_profile->rowCount(QModelIndex()) - 1; + } + if(row < 0) + { + return; + } + auto model_index = m_profile->index(row); + ui->packageView->selectionModel()->select(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + updateButtons(row); } void VersionPage::onGameUpdateError(QString error) { - CustomMessageBox::selectable(this, tr("Error updating instance"), error, - QMessageBox::Warning)->show(); + CustomMessageBox::selectable(this, tr("Error updating instance"), error, QMessageBox::Warning)->show(); } Component * VersionPage::current() { - auto row = currentRow(); - if(row < 0) - { - return nullptr; - } - return m_profile->getComponent(row); + auto row = currentRow(); + if(row < 0) + { + return nullptr; + } + return m_profile->getComponent(row); } int VersionPage::currentRow() { - if (ui->packageView->selectionModel()->selectedRows().isEmpty()) - { - return -1; - } - return ui->packageView->selectionModel()->selectedRows().first().row(); -} - -void VersionPage::on_customizeBtn_clicked() -{ - auto version = currentRow(); - if(version == -1) - { - return; - } - auto patch = m_profile->getComponent(version); - if(!patch->getVersionFile()) - { - // TODO: wait for the update task to finish here... - return; - } - if(!m_profile->customize(version)) - { - // TODO: some error box here - } - updateButtons(); - preselect(currentIdx); -} - -void VersionPage::on_editBtn_clicked() -{ - auto version = current(); - if(!version) - { - return; - } - auto filename = version->getFilename(); - if(!QFileInfo::exists(filename)) - { - qWarning() << "file" << filename << "can't be opened for editing, doesn't exist!"; - return; - } - MMC->openJsonEditor(filename); -} - -void VersionPage::on_revertBtn_clicked() -{ - auto version = currentRow(); - if(version == -1) - { - return; - } - if(!m_profile->revertToBase(version)) - { - // TODO: some error box here - } - updateButtons(); - preselect(currentIdx); - m_container->refreshContainer(); + if (ui->packageView->selectionModel()->selectedRows().isEmpty()) + { + return -1; + } + return ui->packageView->selectionModel()->selectedRows().first().row(); +} + +void VersionPage::on_actionCustomize_triggered() +{ + auto version = currentRow(); + if(version == -1) + { + return; + } + auto patch = m_profile->getComponent(version); + if(!patch->getVersionFile()) + { + // TODO: wait for the update task to finish here... + return; + } + if(!m_profile->customize(version)) + { + // TODO: some error box here + } + updateButtons(); + preselect(currentIdx); +} + +void VersionPage::on_actionEdit_triggered() +{ + auto version = current(); + if(!version) + { + return; + } + auto filename = version->getFilename(); + if(!QFileInfo::exists(filename)) + { + qWarning() << "file" << filename << "can't be opened for editing, doesn't exist!"; + return; + } + MMC->openJsonEditor(filename); +} + +void VersionPage::on_actionRevert_triggered() +{ + auto version = currentRow(); + if(version == -1) + { + return; + } + if(!m_profile->revertToBase(version)) + { + // TODO: some error box here + } + updateButtons(); + preselect(currentIdx); + m_container->refreshContainer(); } #include "VersionPage.moc" diff --git a/application/pages/instance/VersionPage.h b/application/pages/instance/VersionPage.h index 85304ea5..31344eb9 100644 --- a/application/pages/instance/VersionPage.h +++ b/application/pages/instance/VersionPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ #pragma once -#include <QWidget> +#include <QMainWindow> #include "minecraft/MinecraftInstance.h" #include "minecraft/ComponentList.h" @@ -26,70 +26,74 @@ namespace Ui class VersionPage; } -class VersionPage : public QWidget, public BasePage +class VersionPage : public QMainWindow, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit VersionPage(MinecraftInstance *inst, QWidget *parent = 0); - virtual ~VersionPage(); - virtual QString displayName() const override - { - return tr("Version"); - } - virtual QIcon icon() const override; - virtual QString id() const override - { - return "version"; - } - virtual QString helpPage() const override - { - return "Instance-Version"; - } - virtual bool shouldDisplay() const override; + explicit VersionPage(MinecraftInstance *inst, QWidget *parent = 0); + virtual ~VersionPage(); + virtual QString displayName() const override + { + return tr("Version"); + } + virtual QIcon icon() const override; + virtual QString id() const override + { + return "version"; + } + virtual QString helpPage() const override + { + return "Instance-Version"; + } + virtual bool shouldDisplay() const override; private slots: - void on_forgeBtn_clicked(); - void on_addEmptyBtn_clicked(); - void on_liteloaderBtn_clicked(); - void on_reloadBtn_clicked(); - void on_removeBtn_clicked(); - void on_moveUpBtn_clicked(); - void on_moveDownBtn_clicked(); - void on_jarmodBtn_clicked(); - void on_jarBtn_clicked(); - void on_revertBtn_clicked(); - void on_editBtn_clicked(); - void on_modBtn_clicked(); - void on_customizeBtn_clicked(); - void on_downloadBtn_clicked(); + void on_actionChange_version_triggered(); + void on_actionInstall_Forge_triggered(); + void on_actionInstall_Fabric_triggered(); + void on_actionAdd_Empty_triggered(); + void on_actionInstall_LiteLoader_triggered(); + void on_actionReload_triggered(); + void on_actionRemove_triggered(); + void on_actionMove_up_triggered(); + void on_actionMove_down_triggered(); + void on_actionAdd_to_Minecraft_jar_triggered(); + void on_actionReplace_Minecraft_jar_triggered(); + void on_actionRevert_triggered(); + void on_actionEdit_triggered(); + void on_actionInstall_mods_triggered(); + void on_actionCustomize_triggered(); + void on_actionDownload_triggered(); - void updateVersionControls(); - void disableVersionControls(); - void on_changeVersionBtn_clicked(); + void updateVersionControls(); private: - Component * current(); - int currentRow(); - void updateButtons(int row = -1); - void preselect(int row = 0); - int doUpdate(); + Component * current(); + int currentRow(); + void updateButtons(int row = -1); + void preselect(int row = 0); + int doUpdate(); protected: - /// FIXME: this shouldn't be necessary! - bool reloadComponentList(); + QMenu * createPopupMenu() override; + + /// FIXME: this shouldn't be necessary! + bool reloadComponentList(); private: - Ui::VersionPage *ui; - std::shared_ptr<ComponentList> m_profile; - MinecraftInstance *m_inst; - int currentIdx = 0; + Ui::VersionPage *ui; + std::shared_ptr<ComponentList> m_profile; + MinecraftInstance *m_inst; + int currentIdx = 0; + bool controlsEnabled = false; public slots: - void versionCurrent(const QModelIndex ¤t, const QModelIndex &previous); + void versionCurrent(const QModelIndex ¤t, const QModelIndex &previous); private slots: - void onGameUpdateError(QString error); - void packageCurrent(const QModelIndex ¤t, const QModelIndex &previous); - + void updateRunningStatus(bool running); + void onGameUpdateError(QString error); + void packageCurrent(const QModelIndex ¤t, const QModelIndex &previous); + void ShowContextMenu(const QPoint &pos); }; diff --git a/application/pages/instance/VersionPage.ui b/application/pages/instance/VersionPage.ui index d54dd840..e16f18bc 100644 --- a/application/pages/instance/VersionPage.ui +++ b/application/pages/instance/VersionPage.ui @@ -1,279 +1,231 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>VersionPage</class> - <widget class="QWidget" name="VersionPage"> + <widget class="QMainWindow" name="VersionPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>870</width> - <height>1008</height> + <width>961</width> + <height>1091</height> </rect> </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="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string notr="true">Tab 1</string> - </attribute> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="ModListView" name="packageView"> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOn</enum> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="headerHidden"> - <bool>false</bool> - </property> - <attribute name="headerVisible"> - <bool>true</bool> - </attribute> - </widget> - </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Selection</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="changeVersionBtn"> - <property name="toolTip"> - <string>Change version of the selected package.</string> - </property> - <property name="text"> - <string>Change version</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="moveUpBtn"> - <property name="toolTip"> - <string>Make the selected package apply sooner.</string> - </property> - <property name="text"> - <string>Move up</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="moveDownBtn"> - <property name="toolTip"> - <string>Make the selected package apply later.</string> - </property> - <property name="text"> - <string>Move down</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="removeBtn"> - <property name="toolTip"> - <string>Remove selected package from the instance.</string> - </property> - <property name="text"> - <string>Remove</string> - </property> - </widget> - </item> - <item> - <widget class="LineSeparator" name="separator_4" native="true"/> - </item> - <item> - <widget class="QLabel" name="label_10"> - <property name="text"> - <string>Edit</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="customizeBtn"> - <property name="toolTip"> - <string>Customize selected package.</string> - </property> - <property name="text"> - <string>Customize</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="editBtn"> - <property name="toolTip"> - <string>Edit selected package.</string> - </property> - <property name="text"> - <string>Edit</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="revertBtn"> - <property name="toolTip"> - <string>Revert the selected package to default.</string> - </property> - <property name="text"> - <string>Revert</string> - </property> - </widget> - </item> - <item> - <widget class="LineSeparator" name="separator" native="true"/> - </item> - <item> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Install</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="forgeBtn"> - <property name="toolTip"> - <string>Install the Minecraft Forge package.</string> - </property> - <property name="text"> - <string>Install Forge</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="liteloaderBtn"> - <property name="toolTip"> - <string>Install the LiteLoader package.</string> - </property> - <property name="text"> - <string>Install LiteLoader</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="modBtn"> - <property name="toolTip"> - <string>Install normal mods.</string> - </property> - <property name="text"> - <string>Install mods</string> - </property> - </widget> - </item> - <item> - <widget class="LineSeparator" name="widget" native="true"/> - </item> - <item> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>Advanced</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="jarmodBtn"> - <property name="toolTip"> - <string>Add a mod into the Minecraft jar file.</string> - </property> - <property name="text"> - <string>Add to Minecraft.jar</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="jarBtn"> - <property name="text"> - <string>Replace Minecraft.jar</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="addEmptyBtn"> - <property name="text"> - <string>Add Empty</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="reloadBtn"> - <property name="toolTip"> - <string>Reload all packages.</string> - </property> - <property name="text"> - <string>Reload</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="downloadBtn"> - <property name="toolTip"> - <string>Download the files needed to launch the instance now.</string> - </property> - <property name="text"> - <string>Download All</string> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer_7"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>111</width> - <height>13</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item row="1" column="0" colspan="2"> - <widget class="MCModInfoFrame" name="frame"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - </widget> - </item> - </layout> - </widget> - </widget> - </item> - </layout> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <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> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="ModListView" name="packageView"> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOn</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="sortingEnabled"> + <bool>false</bool> + </property> + <property name="headerHidden"> + <bool>false</bool> + </property> + <attribute name="headerVisible"> + <bool>true</bool> + </attribute> + </widget> + </item> + <item> + <widget class="MCModInfoFrame" name="frame"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="WideBar" name="toolBar"> + <property name="windowTitle"> + <string>toolBar</string> + </property> + <property name="allowedAreas"> + <set>Qt::LeftToolBarArea|Qt::RightToolBarArea</set> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> + <property name="floatable"> + <bool>false</bool> + </property> + <attribute name="toolBarArea"> + <enum>RightToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionChange_version"/> + <addaction name="actionMove_up"/> + <addaction name="actionMove_down"/> + <addaction name="actionRemove"/> + <addaction name="separator"/> + <addaction name="actionCustomize"/> + <addaction name="actionEdit"/> + <addaction name="actionRevert"/> + <addaction name="separator"/> + <addaction name="actionInstall_Forge"/> + <addaction name="actionInstall_Fabric"/> + <addaction name="actionInstall_LiteLoader"/> + <addaction name="actionInstall_mods"/> + <addaction name="separator"/> + <addaction name="actionAdd_to_Minecraft_jar"/> + <addaction name="actionReplace_Minecraft_jar"/> + <addaction name="actionAdd_Empty"/> + <addaction name="actionReload"/> + <addaction name="actionDownload_All"/> + </widget> + <action name="actionChange_version"> + <property name="text"> + <string>Change version</string> + </property> + <property name="toolTip"> + <string>Change version of the selected package.</string> + </property> + </action> + <action name="actionMove_up"> + <property name="text"> + <string>Move up</string> + </property> + <property name="toolTip"> + <string>Make the selected package apply sooner.</string> + </property> + </action> + <action name="actionMove_down"> + <property name="text"> + <string>Move down</string> + </property> + <property name="toolTip"> + <string>Make the selected package apply later.</string> + </property> + </action> + <action name="actionRemove"> + <property name="text"> + <string>Remove</string> + </property> + <property name="toolTip"> + <string>Remove selected package from the instance.</string> + </property> + </action> + <action name="actionCustomize"> + <property name="text"> + <string>Customize</string> + </property> + <property name="toolTip"> + <string>Customize selected package.</string> + </property> + </action> + <action name="actionEdit"> + <property name="text"> + <string>Edit</string> + </property> + <property name="toolTip"> + <string>Edit selected package.</string> + </property> + </action> + <action name="actionRevert"> + <property name="text"> + <string>Revert</string> + </property> + <property name="toolTip"> + <string>Revert the selected package to default.</string> + </property> + </action> + <action name="actionInstall_Forge"> + <property name="text"> + <string>Install Forge</string> + </property> + <property name="toolTip"> + <string>Install the Minecraft Forge package.</string> + </property> + </action> + <action name="actionInstall_Fabric"> + <property name="text"> + <string>Install Fabric</string> + </property> + <property name="toolTip"> + <string>Install the Fabric Loader package.</string> + </property> + </action> + <action name="actionInstall_LiteLoader"> + <property name="text"> + <string>Install LiteLoader</string> + </property> + <property name="toolTip"> + <string>Install the LiteLoader package.</string> + </property> + </action> + <action name="actionInstall_mods"> + <property name="text"> + <string>Install mods</string> + </property> + <property name="toolTip"> + <string>Install normal mods.</string> + </property> + </action> + <action name="actionAdd_to_Minecraft_jar"> + <property name="text"> + <string>Add to Minecraft.jar</string> + </property> + <property name="toolTip"> + <string>Add a mod into the Minecraft jar file.</string> + </property> + </action> + <action name="actionReplace_Minecraft_jar"> + <property name="text"> + <string>Replace Minecraft.jar</string> + </property> + </action> + <action name="actionAdd_Empty"> + <property name="text"> + <string>Add Empty</string> + </property> + <property name="toolTip"> + <string>Add an empty custom package.</string> + </property> + </action> + <action name="actionReload"> + <property name="text"> + <string>Reload</string> + </property> + <property name="toolTip"> + <string>Reload all packages.</string> + </property> + </action> + <action name="actionDownload_All"> + <property name="text"> + <string>Download All</string> + </property> + <property name="toolTip"> + <string>Download the files needed to launch the instance now.</string> + </property> + </action> </widget> <customwidgets> <customwidget> @@ -282,37 +234,17 @@ <header>widgets/ModListView.h</header> </customwidget> <customwidget> - <class>LineSeparator</class> - <extends>QWidget</extends> - <header>widgets/LineSeparator.h</header> - <container>1</container> - </customwidget> - <customwidget> <class>MCModInfoFrame</class> <extends>QFrame</extends> <header>widgets/MCModInfoFrame.h</header> <container>1</container> </customwidget> + <customwidget> + <class>WideBar</class> + <extends>QToolBar</extends> + <header>widgets/WideBar.h</header> + </customwidget> </customwidgets> - <tabstops> - <tabstop>packageView</tabstop> - <tabstop>changeVersionBtn</tabstop> - <tabstop>moveUpBtn</tabstop> - <tabstop>moveDownBtn</tabstop> - <tabstop>removeBtn</tabstop> - <tabstop>customizeBtn</tabstop> - <tabstop>editBtn</tabstop> - <tabstop>revertBtn</tabstop> - <tabstop>forgeBtn</tabstop> - <tabstop>liteloaderBtn</tabstop> - <tabstop>modBtn</tabstop> - <tabstop>jarmodBtn</tabstop> - <tabstop>jarBtn</tabstop> - <tabstop>addEmptyBtn</tabstop> - <tabstop>reloadBtn</tabstop> - <tabstop>downloadBtn</tabstop> - <tabstop>tabWidget</tabstop> - </tabstops> <resources/> <connections/> </ui> diff --git a/application/pages/instance/WorldListPage.cpp b/application/pages/instance/WorldListPage.cpp index 539d26a0..8358a0f1 100644 --- a/application/pages/instance/WorldListPage.cpp +++ b/application/pages/instance/WorldListPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ #include "ui_WorldListPage.h" #include "minecraft/WorldList.h" #include <DesktopServices.h> -#include "dialogs/ModEditDialogCommon.h" #include <QEvent> +#include <QMenu> #include <QKeyEvent> #include <QClipboard> #include <QMessageBox> @@ -31,300 +31,314 @@ #include <QProcess> #include <FileSystem.h> -WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worlds, QString id, - QString iconName, QString displayName, QString helpPage, - QWidget *parent) - : QWidget(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds), m_iconName(iconName), m_id(id), m_displayName(displayName), m_helpName(helpPage) +WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worlds, QWidget *parent) + : QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - QSortFilterProxyModel * proxy = new QSortFilterProxyModel(this); - proxy->setSortCaseSensitivity(Qt::CaseInsensitive); - proxy->setSourceModel(m_worlds.get()); - ui->worldTreeView->setSortingEnabled(true); - ui->worldTreeView->setModel(proxy); - ui->worldTreeView->installEventFilter(this); - - auto head = ui->worldTreeView->header(); - - head->setSectionResizeMode(0, QHeaderView::Stretch); - head->setSectionResizeMode(1, QHeaderView::ResizeToContents); - connect(ui->worldTreeView->selectionModel(), - SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, - SLOT(worldChanged(const QModelIndex &, const QModelIndex &))); - worldChanged(QModelIndex(), QModelIndex()); + ui->setupUi(this); + + ui->toolBar->insertSpacer(ui->actionRefresh); + + QSortFilterProxyModel * proxy = new QSortFilterProxyModel(this); + proxy->setSortCaseSensitivity(Qt::CaseInsensitive); + proxy->setSourceModel(m_worlds.get()); + ui->worldTreeView->setSortingEnabled(true); + ui->worldTreeView->setModel(proxy); + ui->worldTreeView->installEventFilter(this); + ui->worldTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->worldTreeView, &QTreeView::customContextMenuRequested, this, &WorldListPage::ShowContextMenu); + + auto head = ui->worldTreeView->header(); + head->setSectionResizeMode(0, QHeaderView::Stretch); + head->setSectionResizeMode(1, QHeaderView::ResizeToContents); + + connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &WorldListPage::worldChanged); + worldChanged(QModelIndex(), QModelIndex()); } void WorldListPage::openedImpl() { - m_worlds->startWatching(); + m_worlds->startWatching(); } void WorldListPage::closedImpl() { - m_worlds->stopWatching(); + m_worlds->stopWatching(); } WorldListPage::~WorldListPage() { - m_worlds->stopWatching(); - delete ui; + m_worlds->stopWatching(); + delete ui; +} + +void WorldListPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->worldTreeView->mapToGlobal(pos)); + delete menu; +} + +QMenu * WorldListPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->toolBar->toggleViewAction() ); + return filteredMenu; } bool WorldListPage::shouldDisplay() const { - return true; + return true; } bool WorldListPage::worldListFilter(QKeyEvent *keyEvent) { - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmWorldBtn_clicked(); - return true; - default: - break; - } - return QWidget::eventFilter(ui->worldTreeView, keyEvent); + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_actionRemove_triggered(); + return true; + default: + break; + } + return QWidget::eventFilter(ui->worldTreeView, keyEvent); } bool WorldListPage::eventFilter(QObject *obj, QEvent *ev) { - if (ev->type() != QEvent::KeyPress) - { - return QWidget::eventFilter(obj, ev); - } - QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); - if (obj == ui->worldTreeView) - return worldListFilter(keyEvent); - return QWidget::eventFilter(obj, ev); + if (ev->type() != QEvent::KeyPress) + { + return QWidget::eventFilter(obj, ev); + } + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); + if (obj == ui->worldTreeView) + return worldListFilter(keyEvent); + return QWidget::eventFilter(obj, ev); } -void WorldListPage::on_rmWorldBtn_clicked() +void WorldListPage::on_actionRemove_triggered() { - auto proxiedIndex = getSelectedWorld(); - - if(!proxiedIndex.isValid()) - return; - - auto result = QMessageBox::question(this, - tr("Are you sure?"), - tr("This will remove the selected world permenantly.\n" - "The world will be gone forever (A LONG TIME).\n" - "\n" - "Do you want to continue?")); - if(result != QMessageBox::Yes) - { - return; - } - m_worlds->stopWatching(); - m_worlds->deleteWorld(proxiedIndex.row()); - m_worlds->startWatching(); + auto proxiedIndex = getSelectedWorld(); + + if(!proxiedIndex.isValid()) + return; + + auto result = QMessageBox::question(this, + tr("Are you sure?"), + tr("This will remove the selected world permenantly.\n" + "The world will be gone forever (A LONG TIME).\n" + "\n" + "Do you want to continue?")); + if(result != QMessageBox::Yes) + { + return; + } + m_worlds->stopWatching(); + m_worlds->deleteWorld(proxiedIndex.row()); + m_worlds->startWatching(); } -void WorldListPage::on_viewFolderBtn_clicked() +void WorldListPage::on_actionView_Folder_triggered() { - DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true); + DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true); } QModelIndex WorldListPage::getSelectedWorld() { - auto index = ui->worldTreeView->selectionModel()->currentIndex(); + auto index = ui->worldTreeView->selectionModel()->currentIndex(); - auto proxy = (QSortFilterProxyModel *) ui->worldTreeView->model(); - return proxy->mapToSource(index); + auto proxy = (QSortFilterProxyModel *) ui->worldTreeView->model(); + return proxy->mapToSource(index); } -void WorldListPage::on_copySeedBtn_clicked() +void WorldListPage::on_actionCopy_Seed_triggered() { - QModelIndex index = getSelectedWorld(); - - if (!index.isValid()) - { - return; - } - int64_t seed = m_worlds->data(index, WorldList::SeedRole).toLongLong(); - MMC->clipboard()->setText(QString::number(seed)); + QModelIndex index = getSelectedWorld(); + + if (!index.isValid()) + { + return; + } + int64_t seed = m_worlds->data(index, WorldList::SeedRole).toLongLong(); + MMC->clipboard()->setText(QString::number(seed)); } -void WorldListPage::on_mcEditBtn_clicked() +void WorldListPage::on_actionMCEdit_triggered() { - if(m_mceditStarting) - return; + if(m_mceditStarting) + return; - auto mcedit = MMC->mcedit(); + auto mcedit = MMC->mcedit(); - const QString mceditPath = mcedit->path(); + const QString mceditPath = mcedit->path(); - QModelIndex index = getSelectedWorld(); + QModelIndex index = getSelectedWorld(); - if (!index.isValid()) - { - return; - } + if (!index.isValid()) + { + return; + } - if(!worldSafetyNagQuestion()) - return; + if(!worldSafetyNagQuestion()) + return; - auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); + auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); - auto program = mcedit->getProgramPath(); - if(program.size()) - { + auto program = mcedit->getProgramPath(); + if(program.size()) + { #ifdef Q_OS_WIN32 - if(!QProcess::startDetached(program, {fullPath}, mceditPath)) - { - mceditError(); - } + if(!QProcess::startDetached(program, {fullPath}, mceditPath)) + { + mceditError(); + } #else - m_mceditProcess.reset(new LoggedProcess()); - m_mceditProcess->setDetachable(true); - connect(m_mceditProcess.get(), &LoggedProcess::stateChanged, this, &WorldListPage::mceditState); - m_mceditProcess->start(program, {fullPath}); - m_mceditProcess->setWorkingDirectory(mceditPath); - m_mceditStarting = true; + m_mceditProcess.reset(new LoggedProcess()); + m_mceditProcess->setDetachable(true); + connect(m_mceditProcess.get(), &LoggedProcess::stateChanged, this, &WorldListPage::mceditState); + m_mceditProcess->start(program, {fullPath}); + m_mceditProcess->setWorkingDirectory(mceditPath); + m_mceditStarting = true; #endif - } - else - { - QMessageBox::warning( - this->parentWidget(), - tr("No MCEdit found or set up!"), - tr("You do not have MCEdit set up or it was moved.\nYou can set it up in the global settings.") - ); - } + } + else + { + QMessageBox::warning( + this->parentWidget(), + tr("No MCEdit found or set up!"), + tr("You do not have MCEdit set up or it was moved.\nYou can set it up in the global settings.") + ); + } } void WorldListPage::mceditError() { - QMessageBox::warning( - this->parentWidget(), - tr("MCEdit failed to start!"), - tr("MCEdit failed to start.\nIt may be necessary to reinstall it.") - ); + QMessageBox::warning( + this->parentWidget(), + tr("MCEdit failed to start!"), + tr("MCEdit failed to start.\nIt may be necessary to reinstall it.") + ); } void WorldListPage::mceditState(LoggedProcess::State state) { - bool failed = false; - switch(state) - { - case LoggedProcess::NotRunning: - case LoggedProcess::Starting: - return; - case LoggedProcess::FailedToStart: - case LoggedProcess::Crashed: - case LoggedProcess::Aborted: - { - failed = true; - } - case LoggedProcess::Running: - case LoggedProcess::Finished: - { - m_mceditStarting = false; - break; - } - } - if(failed) - { - mceditError(); - } + bool failed = false; + switch(state) + { + case LoggedProcess::NotRunning: + case LoggedProcess::Starting: + return; + case LoggedProcess::FailedToStart: + case LoggedProcess::Crashed: + case LoggedProcess::Aborted: + { + failed = true; + } + case LoggedProcess::Running: + case LoggedProcess::Finished: + { + m_mceditStarting = false; + break; + } + } + if(failed) + { + mceditError(); + } } void WorldListPage::worldChanged(const QModelIndex ¤t, const QModelIndex &previous) { - QModelIndex index = getSelectedWorld(); - bool enable = index.isValid(); - ui->copySeedBtn->setEnabled(enable); - ui->mcEditBtn->setEnabled(enable); - ui->rmWorldBtn->setEnabled(enable); - ui->copyBtn->setEnabled(enable); - ui->renameBtn->setEnabled(enable); + QModelIndex index = getSelectedWorld(); + bool enable = index.isValid(); + ui->actionCopy_Seed->setEnabled(enable); + ui->actionMCEdit->setEnabled(enable); + ui->actionRemove->setEnabled(enable); + ui->actionCopy->setEnabled(enable); + ui->actionRename->setEnabled(enable); } -void WorldListPage::on_addBtn_clicked() +void WorldListPage::on_actionAdd_triggered() { - auto list = GuiUtil::BrowseForFiles( - m_helpName, - tr("Select a Minecraft world zip"), - tr("Minecraft World Zip File (*.zip)"), QString(), this->parentWidget()); - if (!list.empty()) - { - m_worlds->stopWatching(); - for (auto filename : list) - { - m_worlds->installWorld(QFileInfo(filename)); - } - m_worlds->startWatching(); - } + auto list = GuiUtil::BrowseForFiles( + displayName(), + tr("Select a Minecraft world zip"), + tr("Minecraft World Zip File (*.zip)"), QString(), this->parentWidget()); + if (!list.empty()) + { + m_worlds->stopWatching(); + for (auto filename : list) + { + m_worlds->installWorld(QFileInfo(filename)); + } + m_worlds->startWatching(); + } } bool WorldListPage::isWorldSafe(QModelIndex) { - return !m_inst->isRunning(); + return !m_inst->isRunning(); } bool WorldListPage::worldSafetyNagQuestion() { - if(!isWorldSafe(getSelectedWorld())) - { - auto result = QMessageBox::question(this, tr("Copy World"), tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?")); - if(result == QMessageBox::No) - { - return false; - } - } - return true; + if(!isWorldSafe(getSelectedWorld())) + { + auto result = QMessageBox::question(this, tr("Copy World"), tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?")); + if(result == QMessageBox::No) + { + return false; + } + } + return true; } -void WorldListPage::on_copyBtn_clicked() +void WorldListPage::on_actionCopy_triggered() { - QModelIndex index = getSelectedWorld(); - if (!index.isValid()) - { - return; - } - - if(!worldSafetyNagQuestion()) - return; - - auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); - auto world = (World *) worldVariant.value<void *>(); - bool ok = false; - QString name = QInputDialog::getText(this, tr("World name"), tr("Enter a new name for the copy."), QLineEdit::Normal, world->name(), &ok); - - if (ok && name.length() > 0) - { - world->install(m_worlds->dir().absolutePath(), name); - } + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) + { + return; + } + + if(!worldSafetyNagQuestion()) + return; + + auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); + auto world = (World *) worldVariant.value<void *>(); + bool ok = false; + QString name = QInputDialog::getText(this, tr("World name"), tr("Enter a new name for the copy."), QLineEdit::Normal, world->name(), &ok); + + if (ok && name.length() > 0) + { + world->install(m_worlds->dir().absolutePath(), name); + } } -void WorldListPage::on_renameBtn_clicked() +void WorldListPage::on_actionRename_triggered() { - QModelIndex index = getSelectedWorld(); - if (!index.isValid()) - { - return; - } + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) + { + return; + } - if(!worldSafetyNagQuestion()) - return; + if(!worldSafetyNagQuestion()) + return; - auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); - auto world = (World *) worldVariant.value<void *>(); + auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); + auto world = (World *) worldVariant.value<void *>(); - bool ok = false; - QString name = QInputDialog::getText(this, tr("World name"), tr("Enter a new world name."), QLineEdit::Normal, world->name(), &ok); + bool ok = false; + QString name = QInputDialog::getText(this, tr("World name"), tr("Enter a new world name."), QLineEdit::Normal, world->name(), &ok); - if (ok && name.length() > 0) - { - world->rename(name); - } + if (ok && name.length() > 0) + { + world->rename(name); + } } -void WorldListPage::on_refreshBtn_clicked() +void WorldListPage::on_actionRefresh_triggered() { - m_worlds->update(); + m_worlds->update(); } diff --git a/application/pages/instance/WorldListPage.h b/application/pages/instance/WorldListPage.h index 71b87bda..c39420da 100644 --- a/application/pages/instance/WorldListPage.h +++ b/application/pages/instance/WorldListPage.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ #pragma once -#include <QWidget> +#include <QMainWindow> #include "minecraft/MinecraftInstance.h" #include "pages/BasePage.h" @@ -28,69 +28,70 @@ namespace Ui class WorldListPage; } -class WorldListPage : public QWidget, public BasePage +class WorldListPage : public QMainWindow, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worlds, QString id, - QString iconName, QString displayName, QString helpPage = "", - QWidget *parent = 0); - virtual ~WorldListPage(); + explicit WorldListPage( + BaseInstance *inst, + std::shared_ptr<WorldList> worlds, + QWidget *parent = 0 + ); + virtual ~WorldListPage(); - virtual QString displayName() const override - { - return m_displayName; - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon(m_iconName); - } - virtual QString id() const override - { - return m_id; - } - virtual QString helpPage() const override - { - return m_helpName; - } - virtual bool shouldDisplay() const override; + virtual QString displayName() const override + { + return tr("Worlds"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("worlds"); + } + virtual QString id() const override + { + return "worlds"; + } + virtual QString helpPage() const override + { + return "Worlds"; + } + virtual bool shouldDisplay() const override; - virtual void openedImpl() override; - virtual void closedImpl() override; + virtual void openedImpl() override; + virtual void closedImpl() override; protected: - bool eventFilter(QObject *obj, QEvent *ev) override; - bool worldListFilter(QKeyEvent *ev); + bool eventFilter(QObject *obj, QEvent *ev) override; + bool worldListFilter(QKeyEvent *ev); + QMenu * createPopupMenu() override; protected: - BaseInstance *m_inst; + BaseInstance *m_inst; private: - QModelIndex getSelectedWorld(); - bool isWorldSafe(QModelIndex index); - bool worldSafetyNagQuestion(); - void mceditError(); + QModelIndex getSelectedWorld(); + bool isWorldSafe(QModelIndex index); + bool worldSafetyNagQuestion(); + void mceditError(); private: - Ui::WorldListPage *ui; - std::shared_ptr<WorldList> m_worlds; - unique_qobject_ptr<LoggedProcess> m_mceditProcess; - bool m_mceditStarting = false; - QString m_iconName; - QString m_id; - QString m_displayName; - QString m_helpName; + Ui::WorldListPage *ui; + std::shared_ptr<WorldList> m_worlds; + unique_qobject_ptr<LoggedProcess> m_mceditProcess; + bool m_mceditStarting = false; private slots: - void on_copySeedBtn_clicked(); - void on_mcEditBtn_clicked(); - void on_rmWorldBtn_clicked(); - void on_addBtn_clicked(); - void on_copyBtn_clicked(); - void on_renameBtn_clicked(); - void on_refreshBtn_clicked(); - void on_viewFolderBtn_clicked(); - void worldChanged(const QModelIndex ¤t, const QModelIndex &previous); - void mceditState(LoggedProcess::State state); + void on_actionCopy_Seed_triggered(); + void on_actionMCEdit_triggered(); + void on_actionRemove_triggered(); + void on_actionAdd_triggered(); + void on_actionCopy_triggered(); + void on_actionRename_triggered(); + void on_actionRefresh_triggered(); + void on_actionView_Folder_triggered(); + void worldChanged(const QModelIndex ¤t, const QModelIndex &previous); + void mceditState(LoggedProcess::State state); + + void ShowContextMenu(const QPoint &pos); }; diff --git a/application/pages/instance/WorldListPage.ui b/application/pages/instance/WorldListPage.ui index 0018ddf3..b7a9e57a 100644 --- a/application/pages/instance/WorldListPage.ui +++ b/application/pages/instance/WorldListPage.ui @@ -1,168 +1,140 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>WorldListPage</class> - <widget class="QWidget" name="WorldListPage"> + <widget class="QMainWindow" name="WorldListPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>723</width> - <height>532</height> + <width>800</width> + <height>600</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <property name="leftMargin"> - <number>0</number> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <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="QTreeView" name="worldTreeView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="acceptDrops"> + <bool>true</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::DragDrop</enum> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + <property name="allColumnsShowFocus"> + <bool>true</bool> + </property> + <attribute name="headerStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + </item> + </layout> + </widget> + <widget class="WideBar" name="toolBar"> + <property name="windowTitle"> + <string>toolBar</string> </property> - <property name="topMargin"> - <number>0</number> + <property name="allowedAreas"> + <set>Qt::LeftToolBarArea|Qt::RightToolBarArea</set> </property> - <property name="rightMargin"> - <number>0</number> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> </property> - <property name="bottomMargin"> - <number>0</number> + <property name="floatable"> + <bool>false</bool> </property> - <item> - <widget class="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string notr="true">Tab 1</string> - </attribute> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="2"> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QPushButton" name="addBtn"> - <property name="text"> - <string>Add</string> - </property> - </widget> - </item> - <item> - <widget class="LineSeparator" name="separator" native="true"/> - </item> - <item> - <widget class="QPushButton" name="renameBtn"> - <property name="text"> - <string>Rename</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="copyBtn"> - <property name="text"> - <string>Copy</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmWorldBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="mcEditBtn"> - <property name="text"> - <string notr="true">MCEdit</string> - </property> - </widget> - </item> - <item> - <widget class="LineSeparator" name="separator_2" native="true"/> - </item> - <item> - <widget class="QPushButton" name="copySeedBtn"> - <property name="text"> - <string>Copy Seed</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="QPushButton" name="refreshBtn"> - <property name="text"> - <string>Refresh</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="viewFolderBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="1"> - <widget class="QTreeView" name="worldTreeView"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="acceptDrops"> - <bool>true</bool> - </property> - <property name="dragDropMode"> - <enum>QAbstractItemView::DragDrop</enum> - </property> - <property name="sortingEnabled"> - <bool>true</bool> - </property> - <property name="allColumnsShowFocus"> - <bool>true</bool> - </property> - <attribute name="headerStretchLastSection"> - <bool>false</bool> - </attribute> - </widget> - </item> - </layout> - </widget> - </widget> - </item> - </layout> + <attribute name="toolBarArea"> + <enum>RightToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionAdd"/> + <addaction name="separator"/> + <addaction name="actionRename"/> + <addaction name="actionCopy"/> + <addaction name="actionRemove"/> + <addaction name="actionMCEdit"/> + <addaction name="separator"/> + <addaction name="actionCopy_Seed"/> + <addaction name="actionRefresh"/> + <addaction name="actionView_Folder"/> + </widget> + <action name="actionAdd"> + <property name="text"> + <string>Add</string> + </property> + </action> + <action name="actionRename"> + <property name="text"> + <string>Rename</string> + </property> + </action> + <action name="actionCopy"> + <property name="text"> + <string>Copy</string> + </property> + </action> + <action name="actionRemove"> + <property name="text"> + <string>Remove</string> + </property> + </action> + <action name="actionMCEdit"> + <property name="text"> + <string>MCEdit</string> + </property> + </action> + <action name="actionCopy_Seed"> + <property name="text"> + <string>Copy Seed</string> + </property> + </action> + <action name="actionRefresh"> + <property name="text"> + <string>Refresh</string> + </property> + </action> + <action name="actionView_Folder"> + <property name="text"> + <string>View Folder</string> + </property> + </action> </widget> <customwidgets> <customwidget> - <class>LineSeparator</class> - <extends>QWidget</extends> - <header>widgets/LineSeparator.h</header> - <container>1</container> + <class>WideBar</class> + <extends>QToolBar</extends> + <header>widgets/WideBar.h</header> </customwidget> </customwidgets> - <tabstops> - <tabstop>tabWidget</tabstop> - <tabstop>worldTreeView</tabstop> - <tabstop>addBtn</tabstop> - <tabstop>renameBtn</tabstop> - <tabstop>copyBtn</tabstop> - <tabstop>rmWorldBtn</tabstop> - <tabstop>mcEditBtn</tabstop> - <tabstop>copySeedBtn</tabstop> - <tabstop>refreshBtn</tabstop> - <tabstop>viewFolderBtn</tabstop> - </tabstops> <resources/> <connections/> </ui> diff --git a/application/pages/modplatform/FTBPage.cpp b/application/pages/modplatform/FTBPage.cpp index f438fce7..dca86efd 100644 --- a/application/pages/modplatform/FTBPage.cpp +++ b/application/pages/modplatform/FTBPage.cpp @@ -1,222 +1,364 @@ #include "FTBPage.h" #include "ui_FTBPage.h" +#include <QInputDialog> + #include "MultiMC.h" -#include "FolderInstanceProvider.h" #include "dialogs/CustomMessageBox.h" #include "dialogs/NewInstanceDialog.h" #include "modplatform/ftb/FtbPackFetchTask.h" #include "modplatform/ftb/FtbPackInstallTask.h" +#include "modplatform/ftb/FtbPrivatePackManager.h" #include "FtbListModel.h" FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), dialog(dialog), ui(new Ui::FTBPage) + : QWidget(parent), dialog(dialog), ui(new Ui::FTBPage) { - ftbFetchTask = new FtbPackFetchTask(); + ftbFetchTask.reset(new FtbPackFetchTask()); + ftbPrivatePacks.reset(new FtbPrivatePackManager()); + + ui->setupUi(this); + + { + publicFilterModel = new FtbFilterModel(this); + publicListModel = new FtbListModel(this); + publicFilterModel->setSourceModel(publicListModel); + + ui->publicPackList->setModel(publicFilterModel); + ui->publicPackList->setSortingEnabled(true); + ui->publicPackList->header()->hide(); + ui->publicPackList->setIndentation(0); + ui->publicPackList->setIconSize(QSize(42, 42)); - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); + for(int i = 0; i < publicFilterModel->getAvailableSortings().size(); i++) + { + ui->sortByBox->addItem(publicFilterModel->getAvailableSortings().keys().at(i)); + } - { - publicFilterModel = new FtbFilterModel(this); - publicListModel = new FtbListModel(this); - publicFilterModel->setSourceModel(publicListModel); + ui->sortByBox->setCurrentText(publicFilterModel->translateCurrentSorting()); + } - ui->publicPackList->setModel(publicFilterModel); - ui->publicPackList->setSortingEnabled(true); - ui->publicPackList->header()->hide(); - ui->publicPackList->setIndentation(0); - ui->publicPackList->setIconSize(QSize(42, 42)); + { + thirdPartyFilterModel = new FtbFilterModel(this); + thirdPartyModel = new FtbListModel(this); + thirdPartyFilterModel->setSourceModel(thirdPartyModel); - for(int i = 0; i < publicFilterModel->getAvailableSortings().size(); i++) - { - ui->sortByBox->addItem(publicFilterModel->getAvailableSortings().keys().at(i)); - } + ui->thirdPartyPackList->setModel(thirdPartyFilterModel); + ui->thirdPartyPackList->setSortingEnabled(true); + ui->thirdPartyPackList->header()->hide(); + ui->thirdPartyPackList->setIndentation(0); + ui->thirdPartyPackList->setIconSize(QSize(42, 42)); - ui->sortByBox->setCurrentText(publicFilterModel->translateCurrentSorting()); - } + thirdPartyFilterModel->setSorting(publicFilterModel->getCurrentSorting()); + } - { - thirdPartyFilterModel = new FtbFilterModel(this); - thirdPartyModel = new FtbListModel(this); - thirdPartyFilterModel->setSourceModel(thirdPartyModel); + { + privateFilterModel = new FtbFilterModel(this); + privateListModel = new FtbListModel(this); + privateFilterModel->setSourceModel(privateListModel); - ui->thirdPartyPackList->setModel(thirdPartyFilterModel); - ui->thirdPartyPackList->setSortingEnabled(true); - ui->thirdPartyPackList->header()->hide(); - ui->thirdPartyPackList->setIndentation(0); - ui->thirdPartyPackList->setIconSize(QSize(42, 42)); + ui->privatePackList->setModel(privateFilterModel); + ui->privatePackList->setSortingEnabled(true); + ui->privatePackList->header()->hide(); + ui->privatePackList->setIndentation(0); + ui->privatePackList->setIconSize(QSize(42, 42)); - thirdPartyFilterModel->setSorting(publicFilterModel->getCurrentSorting()); - } + privateFilterModel->setSorting(publicFilterModel->getCurrentSorting()); + } - ui->packVersionSelection->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - ui->packVersionSelection->view()->parentWidget()->setMaximumHeight(300); + ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); - connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FTBPage::onSortingSelectionChanged); - connect(ui->packVersionSelection, &QComboBox::currentTextChanged, this, &FTBPage::onVersionSelectionItemChanged); + connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FTBPage::onSortingSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FTBPage::onVersionSelectionItemChanged); - connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onPublicPackSelectionChanged); - connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onThirdPartyPackSelectionChanged); + connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onPublicPackSelectionChanged); + connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onThirdPartyPackSelectionChanged); + connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onPrivatePackSelectionChanged); - connect(ui->ftbTabWidget, &QTabWidget::currentChanged, this, &FTBPage::onTabChanged); + connect(ui->addPackBtn, &QPushButton::pressed, this, &FTBPage::onAddPackClicked); + connect(ui->removePackBtn, &QPushButton::pressed, this, &FTBPage::onRemovePackClicked); - ui->modpackInfo->setOpenExternalLinks(true); + connect(ui->tabWidget, &QTabWidget::currentChanged, this, &FTBPage::onTabChanged); - ui->publicPackList->selectionModel()->reset(); - ui->thirdPartyPackList->selectionModel()->reset(); + // ui->modpackInfo->setOpenExternalLinks(true); + + ui->publicPackList->selectionModel()->reset(); + ui->thirdPartyPackList->selectionModel()->reset(); + ui->privatePackList->selectionModel()->reset(); + + onTabChanged(ui->tabWidget->currentIndex()); } FTBPage::~FTBPage() { - delete ui; - if(ftbFetchTask) { - ftbFetchTask->deleteLater(); - } + delete ui; } bool FTBPage::shouldDisplay() const { - return true; + return true; } void FTBPage::openedImpl() { - if(!initialized) - { - connect(ftbFetchTask, &FtbPackFetchTask::finished, this, &FTBPage::ftbPackDataDownloadSuccessfully); - connect(ftbFetchTask, &FtbPackFetchTask::failed, this, &FTBPage::ftbPackDataDownloadFailed); - ftbFetchTask->fetch(); - initialized = true; - } - suggestCurrent(); + if(!initialized) + { + connect(ftbFetchTask.get(), &FtbPackFetchTask::finished, this, &FTBPage::ftbPackDataDownloadSuccessfully); + connect(ftbFetchTask.get(), &FtbPackFetchTask::failed, this, &FTBPage::ftbPackDataDownloadFailed); + + connect(ftbFetchTask.get(), &FtbPackFetchTask::privateFileDownloadFinished, this, &FTBPage::ftbPrivatePackDataDownloadSuccessfully); + connect(ftbFetchTask.get(), &FtbPackFetchTask::privateFileDownloadFailed, this, &FTBPage::ftbPrivatePackDataDownloadFailed); + + ftbFetchTask->fetch(); + ftbPrivatePacks->load(); + ftbFetchTask->fetchPrivate(ftbPrivatePacks->getCurrentPackCodes().toList()); + initialized = true; + } + suggestCurrent(); } void FTBPage::suggestCurrent() { - if(isOpened) - { - if(!selected.broken) - { - dialog->setSuggestedPack(selected.name, new FtbPackInstallTask(selected, selectedVersion)); - if(selected.type == FtbPackType::Public) { - publicListModel->getLogo(selected.logo, [this](QString logo){ - dialog->setSuggestedIconFromFile(logo, "ftb_" + selected.name); - }); - } else if (selected.type == FtbPackType::ThirdParty) { - thirdPartyModel->getLogo(selected.logo, [this](QString logo){ - dialog->setSuggestedIconFromFile(logo, "ftb_" + selected.name); - }); - } - } - else - { - dialog->setSuggestedPack(); - } - } + if(isOpened) + { + if(!selected.broken) + { + dialog->setSuggestedPack(selected.name, new FtbPackInstallTask(selected, selectedVersion)); + QString editedLogoName; + if(selected.logo.toLower().startsWith("ftb")) + { + editedLogoName = selected.logo; + } + else + { + editedLogoName = "ftb_" + selected.logo; + } + + editedLogoName = editedLogoName.left(editedLogoName.lastIndexOf(".png")); + + if(selected.type == FtbPackType::Public) + { + publicListModel->getLogo(selected.logo, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); + } + else if (selected.type == FtbPackType::ThirdParty) + { + thirdPartyModel->getLogo(selected.logo, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); + } + else if (selected.type == FtbPackType::Private) + { + privateListModel->getLogo(selected.logo, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); + } + } + else + { + dialog->setSuggestedPack(); + } + } } void FTBPage::ftbPackDataDownloadSuccessfully(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks) { - publicListModel->fill(publicPacks); - thirdPartyModel->fill(thirdPartyPacks); + publicListModel->fill(publicPacks); + thirdPartyModel->fill(thirdPartyPacks); } void FTBPage::ftbPackDataDownloadFailed(QString reason) { - //TODO: Display the error + //TODO: Display the error +} + +void FTBPage::ftbPrivatePackDataDownloadSuccessfully(FtbModpack pack) +{ + privateListModel->addPack(pack); +} + +void FTBPage::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode) +{ + auto reply = QMessageBox::question( + this, + tr("FTB private packs"), + tr("Failed to download pack information for code %1.\nShould it be removed now?").arg(packCode) + ); + if(reply == QMessageBox::Yes) + { + ftbPrivatePacks->remove(packCode); + } } void FTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev) { - if(!now.isValid()) - { - onPackSelectionChanged(); - return; - } - FtbModpack selectedPack = publicFilterModel->data(now, Qt::UserRole).value<FtbModpack>(); - onPackSelectionChanged(&selectedPack); + if(!now.isValid()) + { + onPackSelectionChanged(); + return; + } + FtbModpack selectedPack = publicFilterModel->data(now, Qt::UserRole).value<FtbModpack>(); + onPackSelectionChanged(&selectedPack); } void FTBPage::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev) { - if(!now.isValid()) - { - onPackSelectionChanged(); - return; - } - FtbModpack selectedPack = thirdPartyFilterModel->data(now, Qt::UserRole).value<FtbModpack>(); - onPackSelectionChanged(&selectedPack); + if(!now.isValid()) + { + onPackSelectionChanged(); + return; + } + FtbModpack selectedPack = thirdPartyFilterModel->data(now, Qt::UserRole).value<FtbModpack>(); + onPackSelectionChanged(&selectedPack); +} + +void FTBPage::onPrivatePackSelectionChanged(QModelIndex now, QModelIndex prev) +{ + if(!now.isValid()) + { + onPackSelectionChanged(); + return; + } + FtbModpack selectedPack = privateFilterModel->data(now, Qt::UserRole).value<FtbModpack>(); + onPackSelectionChanged(&selectedPack); } void FTBPage::onPackSelectionChanged(FtbModpack* pack) { - ui->packVersionSelection->clear(); - if(pack) - { - ui->modpackInfo->setHtml("Pack by <b>" + pack->author + "</b>" + "<br>Minecraft " + pack->mcVersion + "<br>" - "<br>" + pack->description + "<ul><li>" + pack->mods.replace(";", "</li><li>") + "</li></ul>"); - bool currentAdded = false; - - for(int i = 0; i < pack->oldVersions.size(); i++) - { - if(pack->currentVersion == pack->oldVersions.at(i)) - { - currentAdded = true; - } - ui->packVersionSelection->addItem(pack->oldVersions.at(i)); - } - - if(!currentAdded) - { - ui->packVersionSelection->addItem(pack->currentVersion); - } - selected = *pack; - } - suggestCurrent(); + ui->versionSelectionBox->clear(); + if(pack) + { + currentModpackInfo->setHtml("Pack by <b>" + pack->author + "</b>" + + "<br>Minecraft " + pack->mcVersion + "<br>" + "<br>" + pack->description + "<ul><li>" + pack->mods.replace(";", "</li><li>") + + "</li></ul>"); + bool currentAdded = false; + + for(int i = 0; i < pack->oldVersions.size(); i++) + { + if(pack->currentVersion == pack->oldVersions.at(i)) + { + currentAdded = true; + } + ui->versionSelectionBox->addItem(pack->oldVersions.at(i)); + } + + if(!currentAdded) + { + ui->versionSelectionBox->addItem(pack->currentVersion); + } + selected = *pack; + } + else + { + currentModpackInfo->setHtml(""); + ui->versionSelectionBox->clear(); + if(isOpened) + { + dialog->setSuggestedPack(); + } + return; + } + suggestCurrent(); } void FTBPage::onVersionSelectionItemChanged(QString data) { - if(data.isNull() || data.isEmpty()) - { - selectedVersion = ""; - return; - } + if(data.isNull() || data.isEmpty()) + { + selectedVersion = ""; + return; + } - selectedVersion = data; - suggestCurrent(); + selectedVersion = data; + suggestCurrent(); } void FTBPage::onSortingSelectionChanged(QString data) { - FtbFilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data); - publicFilterModel->setSorting(toSet); - thirdPartyFilterModel->setSorting(toSet); + FtbFilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data); + publicFilterModel->setSorting(toSet); + thirdPartyFilterModel->setSorting(toSet); + privateFilterModel->setSorting(toSet); } void FTBPage::onTabChanged(int tab) { - FtbFilterModel* currentModel = nullptr; - QTreeView* currentList = nullptr; - if (tab == 0) - { - currentModel = publicFilterModel; - currentList = ui->publicPackList; - } - else - { - currentModel = thirdPartyFilterModel; - currentList = ui->thirdPartyPackList; - } - QModelIndex idx = currentList->currentIndex(); - if(idx.isValid()) - { - auto pack = currentModel->data(idx, Qt::UserRole).value<FtbModpack>(); - onPackSelectionChanged(&pack); - } - else - { - onPackSelectionChanged(); - } + if(tab == 1) + { + currentModel = thirdPartyFilterModel; + currentList = ui->thirdPartyPackList; + currentModpackInfo = ui->thirdPartyPackDescription; + } + else if(tab == 2) + { + currentModel = privateFilterModel; + currentList = ui->privatePackList; + currentModpackInfo = ui->privatePackDescription; + } + else + { + currentModel = publicFilterModel; + currentList = ui->publicPackList; + currentModpackInfo = ui->publicPackDescription; + } + + currentList->selectionModel()->reset(); + QModelIndex idx = currentList->currentIndex(); + if(idx.isValid()) + { + auto pack = currentModel->data(idx, Qt::UserRole).value<FtbModpack>(); + onPackSelectionChanged(&pack); + } + else + { + onPackSelectionChanged(); + } +} + +void FTBPage::onAddPackClicked() +{ + bool ok; + QString text = QInputDialog::getText( + this, + tr("Add FTB pack"), + tr("Enter pack code:"), + QLineEdit::Normal, + QString(), + &ok + ); + if(ok && !text.isEmpty()) + { + ftbPrivatePacks->add(text); + ftbFetchTask->fetchPrivate({text}); + } +} + +void FTBPage::onRemovePackClicked() +{ + auto index = ui->privatePackList->currentIndex(); + if(!index.isValid()) + { + return; + } + auto row = index.row(); + FtbModpack pack = privateListModel->at(row); + auto answer = QMessageBox::question( + this, + tr("Remove pack"), + tr("Are you sure you want to remove pack %1?").arg(pack.name), + QMessageBox::Yes | QMessageBox::No + ); + if(answer != QMessageBox::Yes) + { + return; + } + + ftbPrivatePacks->remove(pack.packCode); + privateListModel->remove(row); + onPackSelectionChanged(); } diff --git a/application/pages/modplatform/FTBPage.h b/application/pages/modplatform/FTBPage.h index 00c5e9c5..215252ee 100644 --- a/application/pages/modplatform/FTBPage.h +++ b/application/pages/modplatform/FTBPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,15 @@ #pragma once #include <QWidget> +#include <QTreeView> +#include <QTextBrowser> #include "pages/BasePage.h" #include <MultiMC.h> #include "tasks/Task.h" #include "modplatform/ftb/PackHelpers.h" #include "modplatform/ftb/FtbPackFetchTask.h" +#include "QObjectPtr.h" namespace Ui { @@ -31,62 +34,81 @@ class FTBPage; class FtbListModel; class FtbFilterModel; class NewInstanceDialog; +class FtbPrivatePackListModel; +class FtbPrivatePackFilterModel; +class FtbPrivatePackManager; class FTBPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit FTBPage(NewInstanceDialog * dialog, QWidget *parent = 0); - virtual ~FTBPage(); - QString displayName() const override - { - return tr("FTB Legacy"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("ftb_logo"); - } - QString id() const override - { - return "ftb"; - } - QString helpPage() const override - { - return "FTB-platform"; - } - bool shouldDisplay() const override; - void openedImpl() override; + explicit FTBPage(NewInstanceDialog * dialog, QWidget *parent = 0); + virtual ~FTBPage(); + QString displayName() const override + { + return tr("FTB Legacy"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("ftb_logo"); + } + QString id() const override + { + return "ftb"; + } + QString helpPage() const override + { + return "FTB-platform"; + } + bool shouldDisplay() const override; + void openedImpl() override; private: - void suggestCurrent(); - void onPackSelectionChanged(FtbModpack *pack = nullptr); + void suggestCurrent(); + void onPackSelectionChanged(FtbModpack *pack = nullptr); private slots: - void ftbPackDataDownloadSuccessfully(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks); - void ftbPackDataDownloadFailed(QString reason); + void ftbPackDataDownloadSuccessfully(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks); + void ftbPackDataDownloadFailed(QString reason); - void onSortingSelectionChanged(QString data); - void onVersionSelectionItemChanged(QString data); + void ftbPrivatePackDataDownloadSuccessfully(FtbModpack pack); + void ftbPrivatePackDataDownloadFailed(QString reason, QString packCode); - void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second); - void onThirdPartyPackSelectionChanged(QModelIndex first, QModelIndex second); + void onSortingSelectionChanged(QString data); + void onVersionSelectionItemChanged(QString data); - void onTabChanged(int tab); + void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second); + void onThirdPartyPackSelectionChanged(QModelIndex first, QModelIndex second); + void onPrivatePackSelectionChanged(QModelIndex first, QModelIndex second); + + void onTabChanged(int tab); + + void onAddPackClicked(); + void onRemovePackClicked(); private: - bool initialized = false; - FtbModpack selected; - QString selectedVersion; + FtbFilterModel* currentModel = nullptr; + QTreeView* currentList = nullptr; + QTextBrowser* currentModpackInfo = nullptr; + + bool initialized = false; + FtbModpack selected; + QString selectedVersion; + + FtbListModel* publicListModel = nullptr; + FtbFilterModel* publicFilterModel = nullptr; + + FtbListModel *thirdPartyModel = nullptr; + FtbFilterModel *thirdPartyFilterModel = nullptr; - FtbListModel* publicListModel = nullptr; - FtbFilterModel* publicFilterModel = nullptr; + FtbListModel *privateListModel = nullptr; + FtbFilterModel *privateFilterModel = nullptr; - FtbListModel *thirdPartyModel = nullptr; - FtbFilterModel *thirdPartyFilterModel = nullptr; + unique_qobject_ptr<FtbPackFetchTask> ftbFetchTask; + std::unique_ptr<FtbPrivatePackManager> ftbPrivatePacks; - FtbPackFetchTask *ftbFetchTask = nullptr; - NewInstanceDialog* dialog = nullptr; + NewInstanceDialog* dialog = nullptr; - Ui::FTBPage *ui = nullptr; + Ui::FTBPage *ui = nullptr; }; diff --git a/application/pages/modplatform/FTBPage.ui b/application/pages/modplatform/FTBPage.ui index cb085af2..e5ed78cb 100644 --- a/application/pages/modplatform/FTBPage.ui +++ b/application/pages/modplatform/FTBPage.ui @@ -11,18 +11,6 @@ </rect> </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="QTabWidget" name="tabWidget"> <property name="currentIndex"> @@ -30,86 +18,107 @@ </property> <widget class="QWidget" name="tab"> <attribute name="title"> - <string notr="true"/> + <string>Public</string> </attribute> - <layout class="QGridLayout" name="gridLayout" columnstretch="0,0,0,0"> - <item row="1" column="2" colspan="2"> - <widget class="QTextBrowser" name="modpackInfo"> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAsNeeded</enum> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QTreeView" name="publicPackList"> + <property name="maximumSize"> + <size> + <width>250</width> + <height>16777215</height> + </size> </property> </widget> </item> - <item row="2" column="2"> - <widget class="QLabel" name="selectedVersionLabel"> - <property name="text"> - <string>Version selected:</string> + <item row="0" column="1"> + <widget class="QTextBrowser" name="publicPackDescription"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_2"> + <attribute name="title"> + <string>3rd Party</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="1"> + <widget class="QTextBrowser" name="thirdPartyPackDescription"/> + </item> + <item row="0" column="0"> + <widget class="QTreeView" name="thirdPartyPackList"> + <property name="maximumSize"> + <size> + <width>250</width> + <height>16777215</height> + </size> </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_3"> + <attribute name="title"> + <string>Private</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QTreeView" name="privatePackList"> + <property name="maximumSize"> + <size> + <width>250</width> + <height>16777215</height> + </size> </property> </widget> </item> - <item row="2" column="3"> - <widget class="QComboBox" name="packVersionSelection"/> + <item row="1" column="0"> + <widget class="QPushButton" name="addPackBtn"> + <property name="text"> + <string>Add pack</string> + </property> + </widget> </item> <item row="2" column="0"> - <widget class="QComboBox" name="sortByBox"/> - </item> - <item row="0" column="0" rowspan="2"> - <widget class="QTabWidget" name="ftbTabWidget"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="tabShape"> - <enum>QTabWidget::Rounded</enum> - </property> - <property name="currentIndex"> - <number>0</number> + <widget class="QPushButton" name="removePackBtn"> + <property name="text"> + <string>Remove selected pack</string> </property> - <widget class="QWidget" name="tab_2"> - <attribute name="title"> - <string>Public Packs</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QTreeView" name="publicPackList"> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAsNeeded</enum> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="tab_3"> - <attribute name="title"> - <string>3rd Party Packs</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <widget class="QTreeView" name="thirdPartyPackList"> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAsNeeded</enum> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - </widget> - </item> - </layout> - </widget> </widget> </item> + <item row="0" column="1" rowspan="3"> + <widget class="QTextBrowser" name="privatePackDescription"/> + </item> </layout> </widget> </widget> </item> + <item> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="1"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Version selected:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QComboBox" name="versionSelectionBox"/> + </item> + <item row="0" column="0"> + <widget class="QComboBox" name="sortByBox"> + <property name="minimumSize"> + <size> + <width>265</width> + <height>0</height> + </size> + </property> + </widget> + </item> + </layout> + </item> </layout> </widget> <resources/> diff --git a/application/pages/modplatform/FtbListModel.cpp b/application/pages/modplatform/FtbListModel.cpp index c14907c6..51aec890 100644 --- a/application/pages/modplatform/FtbListModel.cpp +++ b/application/pages/modplatform/FtbListModel.cpp @@ -10,56 +10,58 @@ #include <RWStorage.h> #include <Env.h> +#include "net/URLConstants.h" + FtbFilterModel::FtbFilterModel(QObject *parent) : QSortFilterProxyModel(parent) { - currentSorting = Sorting::ByGameVersion; - sortings.insert(tr("Sort by name"), Sorting::ByName); - sortings.insert(tr("Sort by game version"), Sorting::ByGameVersion); + currentSorting = Sorting::ByGameVersion; + sortings.insert(tr("Sort by name"), Sorting::ByName); + sortings.insert(tr("Sort by game version"), Sorting::ByGameVersion); } bool FtbFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - FtbModpack leftPack = sourceModel()->data(left, Qt::UserRole).value<FtbModpack>(); - FtbModpack rightPack = sourceModel()->data(right, Qt::UserRole).value<FtbModpack>(); + FtbModpack leftPack = sourceModel()->data(left, Qt::UserRole).value<FtbModpack>(); + FtbModpack rightPack = sourceModel()->data(right, Qt::UserRole).value<FtbModpack>(); - if(currentSorting == Sorting::ByGameVersion) { - Version lv(leftPack.mcVersion); - Version rv(rightPack.mcVersion); - return lv < rv; + if(currentSorting == Sorting::ByGameVersion) { + Version lv(leftPack.mcVersion); + Version rv(rightPack.mcVersion); + return lv < rv; - } else if(currentSorting == Sorting::ByName) { - return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; - } + } else if(currentSorting == Sorting::ByName) { + return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; + } - //UHM, some inavlid value set?! - qWarning() << "Invalid sorting set!"; - return true; + //UHM, some inavlid value set?! + qWarning() << "Invalid sorting set!"; + return true; } bool FtbFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { - return true; + return true; } const QMap<QString, FtbFilterModel::Sorting> FtbFilterModel::getAvailableSortings() { - return sortings; + return sortings; } QString FtbFilterModel::translateCurrentSorting() { - return sortings.key(currentSorting); + return sortings.key(currentSorting); } void FtbFilterModel::setSorting(Sorting s) { - currentSorting = s; - invalidate(); + currentSorting = s; + invalidate(); } FtbFilterModel::Sorting FtbFilterModel::getCurrentSorting() { - return currentSorting; + return currentSorting; } FtbListModel::FtbListModel(QObject *parent) : QAbstractListModel(parent) @@ -72,128 +74,183 @@ FtbListModel::~FtbListModel() QString FtbListModel::translatePackType(FtbPackType type) const { - if(type == FtbPackType::Public) { - return tr("Public Modpack"); - } else if(type == FtbPackType::ThirdParty) { - return tr("Third Party Modpack"); - } else if(type == FtbPackType::Private) { - return tr("Private Modpack"); - } else { - return tr("Unknown Type"); - } + switch(type) + { + case FtbPackType::Public: + return tr("Public Modpack"); + case FtbPackType::ThirdParty: + return tr("Third Party Modpack"); + case FtbPackType::Private: + return tr("Private Modpack"); + } + qWarning() << "Unknown FTB modpack type:" << int(type); + return QString(); } int FtbListModel::rowCount(const QModelIndex &parent) const { - return modpacks.size(); + return modpacks.size(); } int FtbListModel::columnCount(const QModelIndex &parent) const { - return 1; + return 1; } QVariant FtbListModel::data(const QModelIndex &index, int role) const { - int pos = index.row(); - if(pos >= modpacks.size() || pos < 0 || !index.isValid()) { - return QString("INVALID INDEX %1").arg(pos); - } - - FtbModpack pack = modpacks.at(pos); - if(role == Qt::DisplayRole) { - return pack.name + "\n" + translatePackType(pack.type); - } else if (role == Qt::ToolTipRole) { - if(pack.description.length() > 100) { - //some magic to prevent to long tooltips and replace html linebreaks - QString edit = pack.description.left(97); - edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); - return edit; - - } - return pack.description; - } else if(role == Qt::DecorationRole) { - if(m_logoMap.contains(pack.logo)) { - return (m_logoMap.value(pack.logo)); - } - QIcon icon = MMC->getThemedIcon("screenshot-placeholder"); - ((FtbListModel *)this)->requestLogo(pack.logo); - return icon; - } else if(role == Qt::TextColorRole) { - if(pack.broken) { - //FIXME: Hardcoded color - return QColor(255, 0, 50); - } else if(pack.bugged) { - //FIXME: Hardcoded color - //bugged pack, currently only indicates bugged xml - return QColor(244, 229, 66); - } - } else if(role == Qt::UserRole) { - QVariant v; - v.setValue(pack); - return v; - } - - return QVariant(); + int pos = index.row(); + if(pos >= modpacks.size() || pos < 0 || !index.isValid()) + { + return QString("INVALID INDEX %1").arg(pos); + } + + FtbModpack pack = modpacks.at(pos); + if(role == Qt::DisplayRole) + { + return pack.name + "\n" + translatePackType(pack.type); + } + else if (role == Qt::ToolTipRole) + { + if(pack.description.length() > 100) + { + //some magic to prevent to long tooltips and replace html linebreaks + QString edit = pack.description.left(97); + edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); + return edit; + + } + return pack.description; + } + else if(role == Qt::DecorationRole) + { + if(m_logoMap.contains(pack.logo)) + { + return (m_logoMap.value(pack.logo)); + } + QIcon icon = MMC->getThemedIcon("screenshot-placeholder"); + ((FtbListModel *)this)->requestLogo(pack.logo); + return icon; + } + else if(role == Qt::TextColorRole) + { + if(pack.broken) + { + //FIXME: Hardcoded color + return QColor(255, 0, 50); + } + else if(pack.bugged) + { + //FIXME: Hardcoded color + //bugged pack, currently only indicates bugged xml + return QColor(244, 229, 66); + } + } + else if(role == Qt::UserRole) + { + QVariant v; + v.setValue(pack); + return v; + } + + return QVariant(); } void FtbListModel::fill(FtbModpackList modpacks) { - beginResetModel(); - this->modpacks = modpacks; - endResetModel(); + beginResetModel(); + this->modpacks = modpacks; + endResetModel(); +} + +void FtbListModel::addPack(FtbModpack modpack) +{ + beginResetModel(); + this->modpacks.append(modpack); + endResetModel(); +} + +void FtbListModel::clear() +{ + beginResetModel(); + modpacks.clear(); + endResetModel(); } FtbModpack FtbListModel::at(int row) { - return modpacks.at(row); + return modpacks.at(row); +} + +void FtbListModel::remove(int row) +{ + if(row < 0 || row >= modpacks.size()) + { + qWarning() << "Attempt to remove FTB modpacks with invalid row" << row; + return; + } + beginRemoveRows(QModelIndex(), row, row); + modpacks.removeAt(row); + endRemoveRows(); } void FtbListModel::logoLoaded(QString logo, QIcon out) { - m_loadingLogos.removeAll(logo); - m_logoMap.insert(logo, out); - emit dataChanged(createIndex(0, 0), createIndex(1, 0)); + m_loadingLogos.removeAll(logo); + m_logoMap.insert(logo, out); + emit dataChanged(createIndex(0, 0), createIndex(1, 0)); } void FtbListModel::logoFailed(QString logo) { - m_failedLogos.append(logo); - m_loadingLogos.removeAll(logo); + m_failedLogos.append(logo); + m_loadingLogos.removeAll(logo); } void FtbListModel::requestLogo(QString file) { - if(m_loadingLogos.contains(file) || m_failedLogos.contains(file)) { - return; - } - - MetaEntryPtr entry = ENV.metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0))); - NetJob *job = new NetJob(QString("FTB Icon Download for %1").arg(file)); - job->addNetAction(Net::Download::makeCached(QUrl(QString("https://ftb.cursecdn.com/FTB2/static/%1").arg(file)), entry)); - - auto fullPath = entry->getFullPath(); - QObject::connect(job, &NetJob::finished, this, [this, file, fullPath]{ - emit logoLoaded(file, QIcon(fullPath)); - if(waitingCallbacks.contains(file)) { - waitingCallbacks.value(file)(fullPath); - } - }); - - QObject::connect(job, &NetJob::failed, this, [this, file]{ - emit logoFailed(file); - }); - - job->start(); - - m_loadingLogos.append(file); + if(m_loadingLogos.contains(file) || m_failedLogos.contains(file)) + { + return; + } + + MetaEntryPtr entry = ENV.metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0))); + NetJob *job = new NetJob(QString("FTB Icon Download for %1").arg(file)); + job->addNetAction(Net::Download::makeCached(QUrl(QString(URLConstants::FTB_CDN_BASE_URL + "static/%1").arg(file)), entry)); + + auto fullPath = entry->getFullPath(); + QObject::connect(job, &NetJob::finished, this, [this, file, fullPath] + { + emit logoLoaded(file, QIcon(fullPath)); + if(waitingCallbacks.contains(file)) + { + waitingCallbacks.value(file)(fullPath); + } + }); + + QObject::connect(job, &NetJob::failed, this, [this, file] + { + emit logoFailed(file); + }); + + job->start(); + + m_loadingLogos.append(file); } void FtbListModel::getLogo(const QString &logo, LogoCallback callback) { - if(m_logoMap.contains(logo)) { - callback(ENV.metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); - } else { - requestLogo(logo); - } + if(m_logoMap.contains(logo)) + { + callback(ENV.metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); + } + else + { + requestLogo(logo); + } +} + +Qt::ItemFlags FtbListModel::flags(const QModelIndex &index) const +{ + return QAbstractListModel::flags(index); } diff --git a/application/pages/modplatform/FtbListModel.h b/application/pages/modplatform/FtbListModel.h index d3a82b73..34749b24 100644 --- a/application/pages/modplatform/FtbListModel.h +++ b/application/pages/modplatform/FtbListModel.h @@ -7,6 +7,7 @@ #include <QSortFilterProxyModel> #include <QThreadPool> #include <QIcon> +#include <QStyledItemDelegate> #include <functional> @@ -15,54 +16,59 @@ typedef std::function<void(QString)> LogoCallback; class FtbFilterModel : public QSortFilterProxyModel { + Q_OBJECT public: - FtbFilterModel(QObject* parent = Q_NULLPTR); - enum Sorting { - ByName, - ByGameVersion - }; - const QMap<QString, Sorting> getAvailableSortings(); - QString translateCurrentSorting(); - void setSorting(Sorting sorting); - Sorting getCurrentSorting(); + FtbFilterModel(QObject* parent = Q_NULLPTR); + enum Sorting { + ByName, + ByGameVersion + }; + const QMap<QString, Sorting> getAvailableSortings(); + QString translateCurrentSorting(); + void setSorting(Sorting sorting); + Sorting getCurrentSorting(); protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; private: - QMap<QString, Sorting> sortings; - Sorting currentSorting; + QMap<QString, Sorting> sortings; + Sorting currentSorting; }; class FtbListModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT private: - FtbModpackList modpacks; - QStringList m_failedLogos; - QStringList m_loadingLogos; - FtbLogoMap m_logoMap; - QMap<QString, LogoCallback> waitingCallbacks; + FtbModpackList modpacks; + QStringList m_failedLogos; + QStringList m_loadingLogos; + FtbLogoMap m_logoMap; + QMap<QString, LogoCallback> waitingCallbacks; - void requestLogo(QString file); - QString translatePackType(FtbPackType type) const; + void requestLogo(QString file); + QString translatePackType(FtbPackType type) const; private slots: - void logoFailed(QString logo); - void logoLoaded(QString logo, QIcon out); + void logoFailed(QString logo); + void logoLoaded(QString logo, QIcon out); public: - FtbListModel(QObject *parent); - ~FtbListModel(); - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const override; + FtbListModel(QObject *parent); + ~FtbListModel(); + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; - void fill(FtbModpackList modpacks); + void fill(FtbModpackList modpacks); + void addPack(FtbModpack modpack); + void clear(); + void remove(int row); - FtbModpack at(int row); - void getLogo(const QString &logo, LogoCallback callback); + FtbModpack at(int row); + void getLogo(const QString &logo, LogoCallback callback); }; diff --git a/application/pages/modplatform/ImportPage.cpp b/application/pages/modplatform/ImportPage.cpp index 75a51245..af243511 100644 --- a/application/pages/modplatform/ImportPage.cpp +++ b/application/pages/modplatform/ImportPage.cpp @@ -2,11 +2,9 @@ #include "ui_ImportPage.h" #include "MultiMC.h" -#include "FolderInstanceProvider.h" -#include "dialogs/CustomMessageBox.h" -#include "dialogs/ProgressDialog.h" #include "dialogs/NewInstanceDialog.h" #include <QFileDialog> +#include <QValidator> #include <InstanceImportTask.h> #include <QTabBar> #include <QValidator> @@ -14,114 +12,119 @@ class UrlValidator : public QValidator { public: - using QValidator::QValidator; + using QValidator::QValidator; - State validate(QString &in, int &pos) const - { - const QUrl url(in); - if (url.isValid() && !url.isRelative() && !url.isEmpty()) - { - return Acceptable; - } - else if (QFile::exists(in)) - { - return Acceptable; - } - else - { - return Intermediate; - } - } + State validate(QString &in, int &pos) const + { + const QUrl url(in); + if (url.isValid() && !url.isRelative() && !url.isEmpty()) + { + return Acceptable; + } + else if (QFile::exists(in)) + { + return Acceptable; + } + else + { + return Intermediate; + } + } }; ImportPage::ImportPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::ImportPage), dialog(dialog) + : QWidget(parent), ui(new Ui::ImportPage), dialog(dialog) { - ui->setupUi(this); - ui->modpackEdit->setValidator(new UrlValidator(ui->modpackEdit)); - connect(ui->modpackEdit, &QLineEdit::textChanged, this, &ImportPage::updateState); + ui->setupUi(this); + ui->modpackEdit->setValidator(new UrlValidator(ui->modpackEdit)); + connect(ui->modpackEdit, &QLineEdit::textChanged, this, &ImportPage::updateState); } ImportPage::~ImportPage() { - delete ui; + delete ui; } bool ImportPage::shouldDisplay() const { - return true; + return true; } void ImportPage::openedImpl() { - updateState(); + updateState(); } void ImportPage::updateState() { - if(!isOpened) - { - return; - } - if(ui->modpackEdit->hasAcceptableInput()) - { - QString input = ui->modpackEdit->text(); - auto url = QUrl::fromUserInput(input); - if(url.isLocalFile()) - { - // FIXME: actually do some validation of what's inside here... this is fake AF - QFileInfo fi(input); - if(fi.exists() && fi.suffix() == "zip") - { - QFileInfo fi(url.fileName()); - dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); - } - } - else - { - // hook, line and sinker. - QFileInfo fi(url.fileName()); - dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); - } - } - else - { - dialog->setSuggestedPack(); - } + if(!isOpened) + { + return; + } + if(ui->modpackEdit->hasAcceptableInput()) + { + QString input = ui->modpackEdit->text(); + auto url = QUrl::fromUserInput(input); + if(url.isLocalFile()) + { + // FIXME: actually do some validation of what's inside here... this is fake AF + QFileInfo fi(input); + if(fi.exists() && fi.suffix() == "zip") + { + QFileInfo fi(url.fileName()); + dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); + } + } + else + { + if(input.endsWith("?client=y")) { + input.chop(9); + input.append("/file"); + url = QUrl::fromUserInput(input); + } + // hook, line and sinker. + QFileInfo fi(url.fileName()); + dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); + } + } + else + { + dialog->setSuggestedPack(); + } } void ImportPage::setUrl(const QString& url) { - ui->modpackEdit->setText(url); - updateState(); + ui->modpackEdit->setText(url); + updateState(); } void ImportPage::on_modpackBtn_clicked() { - const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), tr("Zip (*.zip)")); - if (url.isValid()) - { - if (url.isLocalFile()) - { - ui->modpackEdit->setText(url.toLocalFile()); - } - else - { - ui->modpackEdit->setText(url.toString()); - } - } + const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), tr("Zip (*.zip)")); + if (url.isValid()) + { + if (url.isLocalFile()) + { + ui->modpackEdit->setText(url.toLocalFile()); + } + else + { + ui->modpackEdit->setText(url.toString()); + } + } } QUrl ImportPage::modpackUrl() const { - const QUrl url(ui->modpackEdit->text()); - if (url.isValid() && !url.isRelative() && !url.host().isEmpty()) - { - return url; - } - else - { - return QUrl::fromLocalFile(ui->modpackEdit->text()); - } + const QUrl url(ui->modpackEdit->text()); + if (url.isValid() && !url.isRelative() && !url.host().isEmpty()) + { + return url; + } + else + { + return QUrl::fromLocalFile(ui->modpackEdit->text()); + } } diff --git a/application/pages/modplatform/ImportPage.h b/application/pages/modplatform/ImportPage.h index 8f62e6b1..3afb0045 100644 --- a/application/pages/modplatform/ImportPage.h +++ b/application/pages/modplatform/ImportPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,41 +30,41 @@ class NewInstanceDialog; class ImportPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit ImportPage(NewInstanceDialog* dialog, QWidget *parent = 0); - virtual ~ImportPage(); - virtual QString displayName() const override - { - return tr("Import from zip"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("viewfolder"); - } - virtual QString id() const override - { - return "import"; - } - virtual QString helpPage() const override - { - return "Zip-import"; - } - virtual bool shouldDisplay() const override; + explicit ImportPage(NewInstanceDialog* dialog, QWidget *parent = 0); + virtual ~ImportPage(); + virtual QString displayName() const override + { + return tr("Import from zip"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("viewfolder"); + } + virtual QString id() const override + { + return "import"; + } + virtual QString helpPage() const override + { + return "Zip-import"; + } + virtual bool shouldDisplay() const override; - void setUrl(const QString & url); - void openedImpl() override; + void setUrl(const QString & url); + void openedImpl() override; private slots: - void on_modpackBtn_clicked(); - void updateState(); + void on_modpackBtn_clicked(); + void updateState(); private: - QUrl modpackUrl() const; + QUrl modpackUrl() const; private: - Ui::ImportPage *ui = nullptr; - NewInstanceDialog* dialog = nullptr; + Ui::ImportPage *ui = nullptr; + NewInstanceDialog* dialog = nullptr; }; diff --git a/application/pages/modplatform/TechnicPage.cpp b/application/pages/modplatform/TechnicPage.cpp index c0f4faa5..2f95bec8 100644 --- a/application/pages/modplatform/TechnicPage.cpp +++ b/application/pages/modplatform/TechnicPage.cpp @@ -2,28 +2,25 @@ #include "ui_TechnicPage.h" #include "MultiMC.h" -#include "FolderInstanceProvider.h" -#include "dialogs/CustomMessageBox.h" -#include "dialogs/ProgressDialog.h" #include "dialogs/NewInstanceDialog.h" TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) + : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) { - ui->setupUi(this); + ui->setupUi(this); } TechnicPage::~TechnicPage() { - delete ui; + delete ui; } bool TechnicPage::shouldDisplay() const { - return true; + return true; } void TechnicPage::openedImpl() { - dialog->setSuggestedPack(); + dialog->setSuggestedPack(); } diff --git a/application/pages/modplatform/TechnicPage.h b/application/pages/modplatform/TechnicPage.h index 5b0f16a6..cc82ddf3 100644 --- a/application/pages/modplatform/TechnicPage.h +++ b/application/pages/modplatform/TechnicPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,32 +30,32 @@ class NewInstanceDialog; class TechnicPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit TechnicPage(NewInstanceDialog* dialog, QWidget *parent = 0); - virtual ~TechnicPage(); - virtual QString displayName() const override - { - return tr("Technic"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("technic"); - } - virtual QString id() const override - { - return "technic"; - } - virtual QString helpPage() const override - { - return "Technic-platform"; - } - virtual bool shouldDisplay() const override; - - void openedImpl() override; + explicit TechnicPage(NewInstanceDialog* dialog, QWidget *parent = 0); + virtual ~TechnicPage(); + virtual QString displayName() const override + { + return tr("Technic"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("technic"); + } + virtual QString id() const override + { + return "technic"; + } + virtual QString helpPage() const override + { + return "Technic-platform"; + } + virtual bool shouldDisplay() const override; + + void openedImpl() override; private: - Ui::TechnicPage *ui = nullptr; - NewInstanceDialog* dialog = nullptr; + Ui::TechnicPage *ui = nullptr; + NewInstanceDialog* dialog = nullptr; }; diff --git a/application/pages/modplatform/TwitchPage.cpp b/application/pages/modplatform/TwitchPage.cpp index 42aa46be..ea0f9267 100644 --- a/application/pages/modplatform/TwitchPage.cpp +++ b/application/pages/modplatform/TwitchPage.cpp @@ -2,28 +2,59 @@ #include "ui_TwitchPage.h" #include "MultiMC.h" -#include "FolderInstanceProvider.h" -#include "dialogs/CustomMessageBox.h" -#include "dialogs/ProgressDialog.h" #include "dialogs/NewInstanceDialog.h" +#include <InstanceImportTask.h> TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog) + : QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog) { - ui->setupUi(this); + ui->setupUi(this); + connect(ui->checkButton, &QPushButton::clicked, this, &TwitchPage::triggerCheck); } TwitchPage::~TwitchPage() { - delete ui; + delete ui; } bool TwitchPage::shouldDisplay() const { - return false; + return true; } void TwitchPage::openedImpl() { - dialog->setSuggestedPack(); + dialog->setSuggestedPack(); +} + +void TwitchPage::triggerCheck(bool) +{ + if(m_modIdResolver) { + return; + } + auto task = new Flame::UrlResolvingTask(ui->lineEdit->text()); + connect(task, &Task::finished, this, &TwitchPage::checkDone); + m_modIdResolver.reset(task); + task->start(); +} + +void TwitchPage::setUrl(const QString& url) +{ + ui->lineEdit->setText(url); + triggerCheck(true); +} + +void TwitchPage::checkDone() +{ + auto result = m_modIdResolver->getResults(); + auto formatted = QString("Project %1, File %2").arg(result.projectId).arg(result.fileId); + if(result.resolved && result.type == Flame::File::Type::Modpack) { + ui->twitchLabel->setText(formatted); + QFileInfo fi(result.fileName); + dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(result.url)); + } else { + ui->twitchLabel->setPixmap(QPixmap(QString::fromUtf8(":/assets/deadglitch"))); + dialog->setSuggestedPack(); + } + m_modIdResolver.reset(); } diff --git a/application/pages/modplatform/TwitchPage.h b/application/pages/modplatform/TwitchPage.h index 8e072917..600913cd 100644 --- a/application/pages/modplatform/TwitchPage.h +++ b/application/pages/modplatform/TwitchPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ #include "pages/BasePage.h" #include <MultiMC.h> #include "tasks/Task.h" +#include "modplatform/flame/UrlResolvingTask.h" namespace Ui { @@ -30,32 +31,39 @@ class NewInstanceDialog; class TwitchPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit TwitchPage(NewInstanceDialog* dialog, QWidget *parent = 0); - virtual ~TwitchPage(); - virtual QString displayName() const override - { - return tr("Twitch"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("twitch"); - } - virtual QString id() const override - { - return "twitch"; - } - virtual QString helpPage() const override - { - return "Twitch-platform"; - } - virtual bool shouldDisplay() const override; - - void openedImpl() override; + explicit TwitchPage(NewInstanceDialog* dialog, QWidget *parent = 0); + virtual ~TwitchPage(); + virtual QString displayName() const override + { + return tr("Twitch URL"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("twitch"); + } + virtual QString id() const override + { + return "twitch"; + } + virtual QString helpPage() const override + { + return "Twitch-platform"; + } + virtual bool shouldDisplay() const override; + + void openedImpl() override; + + void setUrl(const QString & url); + +private slots: + void triggerCheck(bool checked); + void checkDone(); private: - Ui::TwitchPage *ui = nullptr; - NewInstanceDialog* dialog = nullptr; + Ui::TwitchPage *ui = nullptr; + NewInstanceDialog* dialog = nullptr; + shared_qobject_ptr<Flame::UrlResolvingTask> m_modIdResolver; }; diff --git a/application/pages/modplatform/TwitchPage.ui b/application/pages/modplatform/TwitchPage.ui index 0930f541..0db2484d 100644 --- a/application/pages/modplatform/TwitchPage.ui +++ b/application/pages/modplatform/TwitchPage.ui @@ -10,9 +10,25 @@ <height>405</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QLabel" name="label_3"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="1"> + <widget class="QLineEdit" name="lineEdit"/> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Twitch URL:</string> + </property> + </widget> + </item> + <item row="1" column="0" colspan="3"> + <widget class="QLabel" name="twitchLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="font"> <font> <pointsize>40</pointsize> @@ -26,8 +42,19 @@ </property> </widget> </item> + <item row="0" column="2"> + <widget class="QPushButton" name="checkButton"> + <property name="text"> + <string>Check</string> + </property> + </widget> + </item> </layout> </widget> + <tabstops> + <tabstop>lineEdit</tabstop> + <tabstop>checkButton</tabstop> + </tabstops> <resources> <include location="../../resources/assets/assets.qrc"/> </resources> diff --git a/application/pages/modplatform/VanillaPage.cpp b/application/pages/modplatform/VanillaPage.cpp index c355fccb..17535f1e 100644 --- a/application/pages/modplatform/VanillaPage.cpp +++ b/application/pages/modplatform/VanillaPage.cpp @@ -2,9 +2,6 @@ #include "ui_VanillaPage.h" #include "MultiMC.h" -#include "FolderInstanceProvider.h" -#include "dialogs/CustomMessageBox.h" -#include "dialogs/ProgressDialog.h" #include <meta/Index.h> #include <meta/VersionList.h> @@ -15,81 +12,84 @@ #include <QTabBar> VanillaPage::VanillaPage(NewInstanceDialog *dialog, QWidget *parent) - : QWidget(parent), dialog(dialog), ui(new Ui::VanillaPage) + : QWidget(parent), dialog(dialog), ui(new Ui::VanillaPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - connect(ui->versionList, &VersionSelectWidget::selectedVersionChanged, this, &VanillaPage::setSelectedVersion); - filterChanged(); - connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); - connect(ui->betaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); - connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); - connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); - connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); - connect(ui->refreshBtn, &QPushButton::clicked, this, &VanillaPage::refresh); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + connect(ui->versionList, &VersionSelectWidget::selectedVersionChanged, this, &VanillaPage::setSelectedVersion); + filterChanged(); + connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); + connect(ui->betaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); + connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); + connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); + connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); + connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); + connect(ui->refreshBtn, &QPushButton::clicked, this, &VanillaPage::refresh); } void VanillaPage::openedImpl() { - if(!initialized) - { - auto vlist = ENV.metadataIndex()->get("net.minecraft"); - ui->versionList->initialize(vlist.get()); - initialized = true; - } - else - { - suggestCurrent(); - } + if(!initialized) + { + auto vlist = ENV.metadataIndex()->get("net.minecraft"); + ui->versionList->initialize(vlist.get()); + initialized = true; + } + else + { + suggestCurrent(); + } } void VanillaPage::refresh() { - ui->versionList->loadList(); + ui->versionList->loadList(); } void VanillaPage::filterChanged() { - QStringList out; - if(ui->alphaFilter->isChecked()) - out << "(old_alpha)"; - if(ui->betaFilter->isChecked()) - out << "(old_beta)"; - if(ui->snapshotFilter->isChecked()) - out << "(snapshot)"; - if(ui->oldSnapshotFilter->isChecked()) - out << "(old_snapshot)"; - if(ui->releaseFilter->isChecked()) - out << "(release)"; - auto regexp = out.join('|'); - ui->versionList->setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false)); + QStringList out; + if(ui->alphaFilter->isChecked()) + out << "(old_alpha)"; + if(ui->betaFilter->isChecked()) + out << "(old_beta)"; + if(ui->snapshotFilter->isChecked()) + out << "(snapshot)"; + if(ui->oldSnapshotFilter->isChecked()) + out << "(old_snapshot)"; + if(ui->releaseFilter->isChecked()) + out << "(release)"; + if(ui->experimentsFilter->isChecked()) + out << "(experiment)"; + auto regexp = out.join('|'); + ui->versionList->setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false)); } VanillaPage::~VanillaPage() { - delete ui; + delete ui; } bool VanillaPage::shouldDisplay() const { - return true; + return true; } BaseVersionPtr VanillaPage::selectedVersion() const { - return m_selectedVersion; + return m_selectedVersion; } void VanillaPage::suggestCurrent() { - if(m_selectedVersion && isOpened) - { - dialog->setSuggestedPack(m_selectedVersion->descriptor(), new InstanceCreationTask(m_selectedVersion)); - } + if(m_selectedVersion && isOpened) + { + dialog->setSuggestedPack(m_selectedVersion->descriptor(), new InstanceCreationTask(m_selectedVersion)); + } } void VanillaPage::setSelectedVersion(BaseVersionPtr version) { - m_selectedVersion = version; - suggestCurrent(); + m_selectedVersion = version; + suggestCurrent(); } diff --git a/application/pages/modplatform/VanillaPage.h b/application/pages/modplatform/VanillaPage.h index 91c65edf..cc77733c 100644 --- a/application/pages/modplatform/VanillaPage.h +++ b/application/pages/modplatform/VanillaPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,46 +30,46 @@ class NewInstanceDialog; class VanillaPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit VanillaPage(NewInstanceDialog *dialog, QWidget *parent = 0); - virtual ~VanillaPage(); - virtual QString displayName() const override - { - return tr("Vanilla"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("minecraft"); - } - virtual QString id() const override - { - return "vanilla"; - } - virtual QString helpPage() const override - { - return "Vanilla-platform"; - } - virtual bool shouldDisplay() const override; - void openedImpl() override; + explicit VanillaPage(NewInstanceDialog *dialog, QWidget *parent = 0); + virtual ~VanillaPage(); + virtual QString displayName() const override + { + return tr("Vanilla"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("minecraft"); + } + virtual QString id() const override + { + return "vanilla"; + } + virtual QString helpPage() const override + { + return "Vanilla-platform"; + } + virtual bool shouldDisplay() const override; + void openedImpl() override; - BaseVersionPtr selectedVersion() const; + BaseVersionPtr selectedVersion() const; public slots: - void setSelectedVersion(BaseVersionPtr version); + void setSelectedVersion(BaseVersionPtr version); private slots: - void filterChanged(); + void filterChanged(); private: - void refresh(); - void suggestCurrent(); + void refresh(); + void suggestCurrent(); private: - bool initialized = false; - NewInstanceDialog *dialog = nullptr; - Ui::VanillaPage *ui = nullptr; - bool m_versionSetByUser = false; - BaseVersionPtr m_selectedVersion; + bool initialized = false; + NewInstanceDialog *dialog = nullptr; + Ui::VanillaPage *ui = nullptr; + bool m_versionSetByUser = false; + BaseVersionPtr m_selectedVersion; }; diff --git a/application/pages/modplatform/VanillaPage.ui b/application/pages/modplatform/VanillaPage.ui index ae9cab47..47effc86 100644 --- a/application/pages/modplatform/VanillaPage.ui +++ b/application/pages/modplatform/VanillaPage.ui @@ -99,6 +99,16 @@ </widget> </item> <item> + <widget class="QCheckBox" name="experimentsFilter"> + <property name="text"> + <string>Experiments</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -144,6 +154,16 @@ <container>1</container> </customwidget> </customwidgets> + <tabstops> + <tabstop>tabWidget</tabstop> + <tabstop>releaseFilter</tabstop> + <tabstop>snapshotFilter</tabstop> + <tabstop>oldSnapshotFilter</tabstop> + <tabstop>betaFilter</tabstop> + <tabstop>alphaFilter</tabstop> + <tabstop>experimentsFilter</tabstop> + <tabstop>refreshBtn</tabstop> + </tabstops> <resources/> <connections/> </ui> diff --git a/application/resources/OSX/OSX.qrc b/application/resources/OSX/OSX.qrc index a7d7be17..19fd4b6a 100644 --- a/application/resources/OSX/OSX.qrc +++ b/application/resources/OSX/OSX.qrc @@ -1,37 +1,38 @@ <!DOCTYPE RCC> <RCC version="1.0"> - <qresource prefix="/icons/OSX"> - <file>index.theme</file> - <file>scalable/about.svg</file> - <file>scalable/accounts.svg</file> - <file>scalable/bug.svg</file> - <file>scalable/centralmods.svg</file> - <file>scalable/checkupdate.svg</file> - <file>scalable/copy.svg</file> - <file>scalable/coremods.svg</file> - <file>scalable/externaltools.svg</file> - <file>scalable/help.svg</file> + <qresource prefix="/icons/OSX"> + <file>index.theme</file> + <file>scalable/about.svg</file> + <file>scalable/accounts.svg</file> + <file>scalable/bug.svg</file> + <file>scalable/centralmods.svg</file> + <file>scalable/checkupdate.svg</file> + <file>scalable/copy.svg</file> + <file>scalable/coremods.svg</file> + <file>scalable/externaltools.svg</file> + <file>scalable/help.svg</file> <file>scalable/instance-settings.svg</file> - <file>scalable/jarmods.svg</file> - <file>scalable/java.svg</file> - <file>scalable/loadermods.svg</file> - <file>scalable/log.svg</file> - <file>scalable/minecraft.svg</file> - <file>scalable/multimc.svg</file> - <file>scalable/new.svg</file> - <file>scalable/news.svg</file> - <file>scalable/notes.svg</file> - <file>scalable/patreon.svg</file> - <file>scalable/proxy.svg</file> - <file>scalable/quickmods.svg</file> - <file>scalable/refresh.svg</file> - <file>scalable/resourcepacks.svg</file> - <file>scalable/screenshots.svg</file> - <file>scalable/settings.svg</file> - <file>scalable/status-bad.svg</file> - <file>scalable/status-good.svg</file> - <file>scalable/status-yellow.svg</file> - <file>scalable/viewfolder.svg</file> - <file>scalable/worlds.svg</file> - </qresource> + <file>scalable/jarmods.svg</file> + <file>scalable/java.svg</file> + <file>scalable/language.svg</file> + <file>scalable/loadermods.svg</file> + <file>scalable/log.svg</file> + <file>scalable/minecraft.svg</file> + <file>scalable/multimc.svg</file> + <file>scalable/new.svg</file> + <file>scalable/news.svg</file> + <file>scalable/notes.svg</file> + <file>scalable/patreon.svg</file> + <file>scalable/proxy.svg</file> + <file>scalable/quickmods.svg</file> + <file>scalable/refresh.svg</file> + <file>scalable/resourcepacks.svg</file> + <file>scalable/screenshots.svg</file> + <file>scalable/settings.svg</file> + <file>scalable/status-bad.svg</file> + <file>scalable/status-good.svg</file> + <file>scalable/status-yellow.svg</file> + <file>scalable/viewfolder.svg</file> + <file>scalable/worlds.svg</file> + </qresource> </RCC> diff --git a/application/resources/OSX/scalable/language.svg b/application/resources/OSX/scalable/language.svg new file mode 100644 index 00000000..4f7d002a --- /dev/null +++ b/application/resources/OSX/scalable/language.svg @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + version="1.1" + id="Calque_1" + x="0px" + y="0px" + viewBox="0 0 24 24" + enable-background="new 0 0 24 24" + xml:space="preserve"><metadata + id="metadata22"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs20" /><path + fill-rule="evenodd" + clip-rule="evenodd" + fill="#E6E6E6" + d="M21,20H9c-1.1,0-2-0.9-2-2V6c0-1.1,0.9-2,2-2h12c1.1,0,2,0.9,2,2 v12C23,19.1,22.1,20,21,20z" + id="path2" /><rect + fill="none" + width="24" + height="24" + id="rect4" /><g + id="_x36__8_"><g + id="g8"><path + d="M 21,4 H 9 C 7.9,4 7,4.9 7,6 v 12 c 0,1.1 0.9,2 2,2 h 12 c 1.1,0 2,-0.9 2,-2 V 6 C 23,4.9 22.1,4 21,4 Z m 1,14 -1,1 H 9 L 8,18 V 6 C 8,5.4 8.4,5 9,5 h 12 c 0.6,0 1,0.4 1,1 z" + id="path6" + style="fill:#585858" /></g></g><circle + cx="-3.8492424" + cy="11.559504" + r="1" + id="circle11" + style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd" /><path + d="m 10.985086,8.5301236 c -0.285244,0 -0.531238,0.2015787 -0.587348,0.480998 L 9.2012867,14.998205 c -0.064883,0.324431 0.1450781,0.638482 0.4689122,0.703366 0.3279037,0.0672 0.6390781,-0.145078 0.7033681,-0.468911 l 0.384313,-1.91432 h 1.69195 l 0.381898,1.91432 c 0.06544,0.328662 0.388374,0.53424 0.705784,0.468911 0.323873,-0.06488 0.533795,-0.378935 0.468912,-0.703366 L 12.809973,9.0111216 c -0.05611,-0.2794193 -0.302101,-0.480998 -0.587347,-0.480998 z m 0.362559,1.3487256 h 0.256212 l 0.478579,2.3953168 h -1.21337 z m 6.429409,-1.5928481 c -0.330857,0 -0.599434,0.2698964 -0.599434,0.6018494 v 0.5994329 h -1.795884 c -0.330855,0 -0.597015,0.269898 -0.597015,0.6018506 0,0.331951 0.266159,0.599434 0.597015,0.599434 h 0.161945 c 0.340555,1.096391 0.840589,1.953495 1.401899,2.629772 -0.439717,0.403341 -0.871563,0.734012 -1.339055,1.104601 -0.25779,0.207035 -0.298799,0.58673 -0.09185,0.845975 0.205993,0.258844 0.58387,0.299404 0.841141,0.09185 0.507959,-0.402258 0.944446,-0.738141 1.421237,-1.177114 0.476792,0.438973 0.95437,0.774856 1.462328,1.177114 0.257273,0.207554 0.635147,0.166994 0.841141,-0.09185 0.206952,-0.259245 0.163525,-0.63894 -0.09426,-0.845975 -0.467495,-0.370589 -0.938012,-0.70126 -1.377731,-1.104601 0.561312,-0.676317 1.102396,-1.53342 1.442991,-2.629772 h 0.159526 c 0.330857,0 0.599433,-0.267483 0.599433,-0.599434 0,-0.3319526 -0.268576,-0.6018506 -0.599433,-0.6018506 H 18.376487 V 8.8878505 c 0,-0.331953 -0.268576,-0.6018494 -0.599433,-0.6018494 z m -0.983747,2.4025669 h 2.006168 c -0.25847,0.696576 -0.641961,1.260479 -1.022421,1.74029 -0.380459,-0.479811 -0.725279,-1.043674 -0.983747,-1.74029 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#585858;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.7126205;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect977" /></svg>
\ No newline at end of file diff --git a/application/resources/assets/assets.qrc b/application/resources/assets/assets.qrc index 8d213387..38638e7f 100644 --- a/application/resources/assets/assets.qrc +++ b/application/resources/assets/assets.qrc @@ -2,6 +2,6 @@ <RCC version="1.0"> <qresource prefix="/assets"> <file alias="underconstruction">underconstruction.png</file> - <file alias="deadglitch">deadglitch.svg</file> + <file alias="deadglitch">deadglitch.svg</file> </qresource> </RCC> diff --git a/application/resources/backgrounds/backgrounds.qrc b/application/resources/backgrounds/backgrounds.qrc index 55de139e..83505635 100644 --- a/application/resources/backgrounds/backgrounds.qrc +++ b/application/resources/backgrounds/backgrounds.qrc @@ -2,5 +2,6 @@ <RCC version="1.0"> <qresource prefix="/backgrounds"> <file alias="kitteh">catbgrnd2.png</file> + <file alias="catmas">catmas.png</file> </qresource> </RCC> diff --git a/application/resources/backgrounds/catbgrnd2.png b/application/resources/backgrounds/catbgrnd2.png Binary files differindex 2b949e0b..e9de7f27 100644 --- a/application/resources/backgrounds/catbgrnd2.png +++ b/application/resources/backgrounds/catbgrnd2.png diff --git a/application/resources/backgrounds/catmas.png b/application/resources/backgrounds/catmas.png Binary files differnew file mode 100644 index 00000000..8bdb1d5c --- /dev/null +++ b/application/resources/backgrounds/catmas.png diff --git a/application/resources/documents/documents.qrc b/application/resources/documents/documents.qrc index 77fc0cf4..007efcde 100644 --- a/application/resources/documents/documents.qrc +++ b/application/resources/documents/documents.qrc @@ -1,7 +1,7 @@ <!DOCTYPE RCC> <RCC version="1.0"> - <qresource prefix="/documents"> - <file>../../../COPYING.md</file> - </qresource> + <qresource prefix="/documents"> + <file>../../../COPYING.md</file> + </qresource> </RCC> diff --git a/application/resources/flat/flat.qrc b/application/resources/flat/flat.qrc index aee2e30c..b6e2ee38 100644 --- a/application/resources/flat/flat.qrc +++ b/application/resources/flat/flat.qrc @@ -1,44 +1,45 @@ <!DOCTYPE RCC> <RCC version="1.0"> - <qresource prefix="/icons/flat"> - <file>index.theme</file> - <file>scalable/about.svg</file> - <file>scalable/accounts.svg</file> - <file>scalable/bug.svg</file> - <file>scalable/cat.svg</file> - <file>scalable/centralmods.svg</file> - <file>scalable/checkupdate.svg</file> - <file>scalable/copy.svg</file> - <file>scalable/coremods.svg</file> - <file>scalable/discord.svg</file> - <file>scalable/externaltools.svg</file> - <file>scalable/help.svg</file> - <file>scalable/instance-settings.svg</file> - <file>scalable/jarmods.svg</file> - <file>scalable/java.svg</file> - <file>scalable/loadermods.svg</file> - <file>scalable/log.svg</file> - <file>scalable/minecraft.svg</file> - <file>scalable/multimc.svg</file> - <file>scalable/new.svg</file> - <file>scalable/news.svg</file> - <file>scalable/notes.svg</file> - <file>scalable/packages.svg</file> - <file>scalable/patreon.svg</file> - <file>scalable/proxy.svg</file> - <file>scalable/quickmods.svg</file> - <file>scalable/reddit-alien.svg</file> - <file>scalable/refresh.svg</file> - <file>scalable/resourcepacks.svg</file> - <file>scalable/screenshot-placeholder.svg</file> - <file>scalable/screenshots.svg</file> - <file>scalable/settings.svg</file> - <file>scalable/star.svg</file> - <file>scalable/status-bad.svg</file> - <file>scalable/status-good.svg</file> - <file>scalable/status-running.svg</file> - <file>scalable/status-yellow.svg</file> - <file>scalable/viewfolder.svg</file> - <file>scalable/worlds.svg</file> - </qresource> + <qresource prefix="/icons/flat"> + <file>index.theme</file> + <file>scalable/about.svg</file> + <file>scalable/accounts.svg</file> + <file>scalable/bug.svg</file> + <file>scalable/cat.svg</file> + <file>scalable/centralmods.svg</file> + <file>scalable/checkupdate.svg</file> + <file>scalable/copy.svg</file> + <file>scalable/coremods.svg</file> + <file>scalable/discord.svg</file> + <file>scalable/externaltools.svg</file> + <file>scalable/help.svg</file> + <file>scalable/instance-settings.svg</file> + <file>scalable/jarmods.svg</file> + <file>scalable/java.svg</file> + <file>scalable/language.svg</file> + <file>scalable/loadermods.svg</file> + <file>scalable/log.svg</file> + <file>scalable/minecraft.svg</file> + <file>scalable/multimc.svg</file> + <file>scalable/new.svg</file> + <file>scalable/news.svg</file> + <file>scalable/notes.svg</file> + <file>scalable/packages.svg</file> + <file>scalable/patreon.svg</file> + <file>scalable/proxy.svg</file> + <file>scalable/quickmods.svg</file> + <file>scalable/reddit-alien.svg</file> + <file>scalable/refresh.svg</file> + <file>scalable/resourcepacks.svg</file> + <file>scalable/screenshot-placeholder.svg</file> + <file>scalable/screenshots.svg</file> + <file>scalable/settings.svg</file> + <file>scalable/star.svg</file> + <file>scalable/status-bad.svg</file> + <file>scalable/status-good.svg</file> + <file>scalable/status-running.svg</file> + <file>scalable/status-yellow.svg</file> + <file>scalable/viewfolder.svg</file> + <file>scalable/worlds.svg</file> + </qresource> </RCC> diff --git a/application/resources/flat/scalable/language.svg b/application/resources/flat/scalable/language.svg new file mode 100644 index 00000000..f4d3f2f4 --- /dev/null +++ b/application/resources/flat/scalable/language.svg @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + fill="#757575" + height="24" + viewBox="0 0 24 24" + width="24" + version="1.1" + id="svg4" + sodipodi:docname="language.svg" + inkscape:version="0.92.2 2405546, 2018-03-11"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8"> + + + + + + + + + + + + + + + + + + + + + + + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="3840" + inkscape:window-height="2123" + id="namedview6" + showgrid="true" + showguides="true" + inkscape:guide-bbox="true" + inkscape:zoom="6.9532167" + inkscape:cx="-18.49351" + inkscape:cy="-12.477971" + inkscape:window-x="1200" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid981" /> + <sodipodi:guide + position="-8,11.440678" + orientation="1,0" + id="guide1060" + inkscape:locked="false" /> + <sodipodi:guide + position="-28.34375,24" + orientation="0,1" + id="guide1062" + inkscape:locked="false" /> + </sodipodi:namedview> + <path + d="M 5,3 C 3.89,3 3,3.89 3,5 v 14 c 0,1.104569 0.895431,2 2,2 h 14 c 1.104569,0 2,-0.895431 2,-2 V 5 C 21,3.89 20.1,3 19,3 Z m 10.359375,4.505859 c 0.400344,0 0.726563,0.326845 0.726563,0.728516 v 0.724609 h 2.21875 c 0.400344,0 0.726562,0.326845 0.726562,0.728516 0,0.401669 -0.326217,0.726562 -0.726562,0.726562 h -0.191407 c -0.412128,1.326612 -1.066893,2.363281 -1.746093,3.181641 0.53207,0.488052 1.100334,0.887517 1.666015,1.335938 0.311924,0.250518 0.365651,0.709744 0.115235,1.023437 -0.249259,0.313205 -0.708226,0.362473 -1.019532,0.111328 -0.614642,-0.486742 -1.192601,-0.892661 -1.769531,-1.423828 -0.576929,0.531167 -1.104107,0.937086 -1.71875,1.423828 -0.311303,0.251148 -0.768322,0.201879 -1.017578,-0.111328 -0.250416,-0.313693 -0.200604,-0.772919 0.111328,-1.023437 0.565677,-0.448421 1.087072,-0.847886 1.619141,-1.335938 -0.679199,-0.818312 -1.283234,-1.854981 -1.695313,-3.181641 h -0.197265 c -0.400344,0 -0.720704,-0.324893 -0.720704,-0.726562 0,-0.401671 0.320361,-0.728516 0.720704,-0.728516 h 2.173828 V 8.234375 c 0,-0.401671 0.324264,-0.728516 0.724609,-0.728516 z M 7.142578,7.800781 h 1.496094 c 0.345155,0 0.643047,0.243927 0.710937,0.582031 l 1.447266,7.244141 c 0.07851,0.39257 -0.174512,0.773053 -0.566406,0.851563 -0.384074,0.07905 -0.774336,-0.168718 -0.853516,-0.566407 L 8.914062,13.595703 H 6.867188 L 6.402344,15.912109 C 6.324551,16.303955 5.947553,16.559826 5.550781,16.478516 5.158934,16.400005 4.905865,16.019523 4.984375,15.626953 L 6.431641,8.382812 C 6.499536,8.044708 6.797425,7.800781 7.142578,7.800781 Z m 0.4375,1.632813 -0.578125,2.898437 H 8.46875 L 7.890625,9.433594 Z m 6.589844,0.980468 c 0.312752,0.842923 0.729088,1.524886 1.189453,2.105469 0.460366,-0.580583 0.925527,-1.262594 1.238281,-2.105469 z" + id="path1072" + inkscape:connector-curvature="0" /> + <g + style="fill:#000000" + id="g821" + transform="matrix(0.0322459,0,0,0.0322459,-17.878956,30.647558)"> + <g + style="fill:#000000" + id="g819" /> + </g> +</svg> diff --git a/application/resources/iOS/iOS.qrc b/application/resources/iOS/iOS.qrc index 0cb642f4..511e390b 100644 --- a/application/resources/iOS/iOS.qrc +++ b/application/resources/iOS/iOS.qrc @@ -1,37 +1,38 @@ <!DOCTYPE RCC> <RCC version="1.0"> - <qresource prefix="/icons/iOS"> - <file>index.theme</file> - <file>scalable/about.svg</file> - <file>scalable/accounts.svg</file> - <file>scalable/bug.svg</file> - <file>scalable/centralmods.svg</file> - <file>scalable/checkupdate.svg</file> - <file>scalable/copy.svg</file> - <file>scalable/coremods.svg</file> - <file>scalable/externaltools.svg</file> - <file>scalable/help.svg</file> - <file>scalable/instance-settings.svg</file> - <file>scalable/jarmods.svg</file> - <file>scalable/java.svg</file> - <file>scalable/loadermods.svg</file> - <file>scalable/log.svg</file> - <file>scalable/minecraft.svg</file> - <file>scalable/multimc.svg</file> - <file>scalable/new.svg</file> - <file>scalable/news.svg</file> - <file>scalable/notes.svg</file> - <file>scalable/patreon.svg</file> - <file>scalable/proxy.svg</file> - <file>scalable/quickmods.svg</file> - <file>scalable/refresh.svg</file> - <file>scalable/resourcepacks.svg</file> - <file>scalable/screenshots.svg</file> - <file>scalable/settings.svg</file> - <file>scalable/status-bad.svg</file> - <file>scalable/status-good.svg</file> - <file>scalable/status-yellow.svg</file> - <file>scalable/viewfolder.svg</file> - <file>scalable/worlds.svg</file> - </qresource> + <qresource prefix="/icons/iOS"> + <file>index.theme</file> + <file>scalable/about.svg</file> + <file>scalable/accounts.svg</file> + <file>scalable/bug.svg</file> + <file>scalable/centralmods.svg</file> + <file>scalable/checkupdate.svg</file> + <file>scalable/copy.svg</file> + <file>scalable/coremods.svg</file> + <file>scalable/externaltools.svg</file> + <file>scalable/help.svg</file> + <file>scalable/instance-settings.svg</file> + <file>scalable/jarmods.svg</file> + <file>scalable/java.svg</file> + <file>scalable/language.svg</file> + <file>scalable/loadermods.svg</file> + <file>scalable/log.svg</file> + <file>scalable/minecraft.svg</file> + <file>scalable/multimc.svg</file> + <file>scalable/new.svg</file> + <file>scalable/news.svg</file> + <file>scalable/notes.svg</file> + <file>scalable/patreon.svg</file> + <file>scalable/proxy.svg</file> + <file>scalable/quickmods.svg</file> + <file>scalable/refresh.svg</file> + <file>scalable/resourcepacks.svg</file> + <file>scalable/screenshots.svg</file> + <file>scalable/settings.svg</file> + <file>scalable/status-bad.svg</file> + <file>scalable/status-good.svg</file> + <file>scalable/status-yellow.svg</file> + <file>scalable/viewfolder.svg</file> + <file>scalable/worlds.svg</file> + </qresource> </RCC> diff --git a/application/resources/iOS/scalable/language.svg b/application/resources/iOS/scalable/language.svg new file mode 100644 index 00000000..fcc3436e --- /dev/null +++ b/application/resources/iOS/scalable/language.svg @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xml:space="preserve" + enable-background="new 0 0 32 32" + viewBox="0 0 32 32" + y="0px" + x="0px" + id="Calque_1" + version="1.1"><metadata + id="metadata15"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs13" /> +<g + id="g8"> + + <path + id="path6" + d="M28,32H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h24c2.2,0,4,1.8,4,4 v24C32,30.2,30.2,32,28,32z M30,4c0-1.1-0.9-2-2-2H4C2.9,2,2,2.9,2,4v24c0,1.1,0.9,2,2,2h24c1.1,0,2-0.9,2-2V4z" + fill="#3366CC" + clip-rule="evenodd" + fill-rule="evenodd" /> +</g> +<path + d="m 8.8837922,9.8498439 c -0.5055801,0 -0.9415895,0.3572871 -1.0410421,0.8525411 L 5.7221093,21.314148 c -0.1150014,0.575036 0.2571428,1.131673 0.8311203,1.246676 0.5811915,0.119103 1.1327283,-0.257142 1.2466794,-0.831119 l 0.6811731,-3.393021 h 2.9988849 l 0.676892,3.393021 c 0.115982,0.582535 0.688371,0.946911 1.250962,0.831119 0.574046,-0.115001 0.94612,-0.67164 0.831119,-1.246676 L 12.118299,10.702385 C 12.018854,10.207131 11.582843,9.8498439 11.07726,9.8498439 Z m 0.642615,2.3905391 h 0.4541203 l 0.8482535,4.245562 H 8.6781534 Z M 20.922167,9.4171507 c -0.586426,0 -1.062464,0.4783758 -1.062464,1.0667433 v 1.062461 h -3.183102 c -0.58642,0 -1.058175,0.478378 -1.058175,1.066747 0,0.588364 0.471753,1.062461 1.058175,1.062461 h 0.28704 c 0.603613,1.943292 1.489894,3.462457 2.484785,4.661121 -0.779374,0.714899 -1.544795,1.300994 -2.373399,1.957842 -0.456918,0.366958 -0.529604,1.039945 -0.162795,1.499442 0.36511,0.458786 1.034877,0.530676 1.490873,0.162795 0.900329,-0.71298 1.673977,-1.308312 2.519064,-2.086367 0.845087,0.778055 1.691565,1.373387 2.591893,2.086367 0.456001,0.367877 1.125761,0.295987 1.490873,-0.162795 0.366811,-0.459497 0.289838,-1.132484 -0.167067,-1.499442 -0.82861,-0.656848 -1.662574,-1.242943 -2.441951,-1.957842 0.994894,-1.198734 1.953935,-2.7179 2.55762,-4.661121 h 0.28275 c 0.586426,0 1.062461,-0.474097 1.062461,-1.062461 0,-0.588369 -0.476035,-1.066747 -1.062461,-1.066747 h -3.251659 v -1.062461 c 0,-0.5883675 -0.476037,-1.0667433 -1.062461,-1.0667433 z m -1.743636,4.2584123 h 3.555818 c -0.458122,1.234642 -1.137838,2.234128 -1.812182,3.084565 -0.674344,-0.850437 -1.285517,-1.849853 -1.743636,-3.084565 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#3366cc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.58040667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect977" /></svg>
\ No newline at end of file diff --git a/application/resources/multimc/128x128/unknown_server.png b/application/resources/multimc/128x128/unknown_server.png Binary files differnew file mode 100644 index 00000000..ec98382d --- /dev/null +++ b/application/resources/multimc/128x128/unknown_server.png diff --git a/application/resources/multimc/index.theme b/application/resources/multimc/index.theme index 0fe7e7d7..6061b7f8 100644 --- a/application/resources/multimc/index.theme +++ b/application/resources/multimc/index.theme @@ -2,7 +2,7 @@ Name=multimc Comment=MultiMC Default Icons Inherits=default -Directories=8x8,16x16,22x22,24x24,32x32,32x32/instances,48x48,50x50/instances,64x64,128x128/instances,256x256,scalable +Directories=8x8,16x16,22x22,24x24,32x32,32x32/instances,48x48,50x50/instances,64x64,128x128/instances,256x256,scalable,scalable/instances [8x8] Size=8 @@ -33,6 +33,11 @@ Size=50 [64x64] Size=64 +[128x128] +Size=128 +MinSize=33 +MaxSize=128 + [128x128/instances] Size=128 MinSize=33 @@ -46,3 +51,8 @@ Size=48 Type=Scalable MinSize=16 MaxSize=256 + +[scalable/instances] +Size=128 +MinSize=16 +MaxSize=256 diff --git a/application/resources/multimc/multimc.qrc b/application/resources/multimc/multimc.qrc index bea3a325..22e7af86 100644 --- a/application/resources/multimc/multimc.qrc +++ b/application/resources/multimc/multimc.qrc @@ -1,306 +1,314 @@ <!DOCTYPE RCC> <RCC version="1.0"> <qresource prefix="/icons/multimc"> - <file>index.theme</file> - <!-- Logo. Our own. For use in branding. --> - <file>scalable/logo.svg</file> - - <!-- Logo. Our own. For use within the application (Settings pages and similar). --> - <file>scalable/multimc.svg</file> - - <!-- REDDIT logo icon, needs reddit license! --> - <file>scalable/reddit-alien.svg</file> - - <!-- twitch logo icon --> - <file>scalable/twitch.svg</file> - - <!-- technic logo icon --> - <file>scalable/technic.svg</file> - - <!-- A proxy icon. Our own. SSSsss --> - <file>scalable/proxy.svg</file> - - <!-- Java icon. From Oracle, fixed because it was derpy. --> - <file>scalable/java.svg</file> - - <!-- Star, CC-BY-SA 3.0, Oxygen icons.--> - <file>16x16/star.png</file> - <file>24x24/star.png</file> - <file>32x32/star.png</file> - <file>48x48/star.png</file> - <file>64x64/star.png</file> - - <!-- "folder-remote", CC-BY-SA 3.0, Oxygen icons. Used for the worlds folder--> - <file>16x16/worlds.png</file> - <file>22x22/worlds.png</file> - <file>32x32/worlds.png</file> - <file>48x48/worlds.png</file> - <file>64x64/worlds.png</file> - - <!-- Minecraft icon. Source: http://www.minecraftforum.net/forums/show-your-creation/fan-art/1574882-icon-better-minecraft-icon --> - <file>16x16/minecraft.png</file> - <file>24x24/minecraft.png</file> - <file>32x32/minecraft.png</file> - <file>48x48/minecraft.png</file> - <file>256x256/minecraft.png</file> - - <!-- About dialog. GPLv2, http://openiconlibrary.sourceforge.net/gallery2/?./Icons/actions/help-contents.png --> - <file>16x16/about.png</file> - <file>22x22/about.png</file> - <file>32x32/about.png</file> - <file>48x48/about.png</file> - <file>64x64/about.png</file> - - <!-- Report bug. Our own. --> - <file>scalable/bug.svg</file> - <file>16x16/bug.png</file> - <file>22x22/bug.png</file> - <file>32x32/bug.png</file> - <file>48x48/bug.png</file> - <file>64x64/bug.png</file> - - <!-- Screenshots. Our own. --> - <!-- frame is adapted and simplified from http://www.wpclipart.com/page_frames/picture_frames/golden_picture_frame.png.html --> - <file>16x16/screenshots.png</file> - <file>22x22/screenshots.png</file> - <file>32x32/screenshots.png</file> - <file>48x48/screenshots.png</file> - <file>64x64/screenshots.png</file> - <file>scalable/screenshots.svg</file> - - <!-- Custom commands. --> - <file>scalable/custom-commands.svg</file> - - <!-- Patron logo. (C) 2014 Patreon, Inc., http://www.patreon.com/toolbox?ftyp=media --> - <file>16x16/patreon.png</file> - <file>22x22/patreon.png</file> - <file>24x24/patreon.png</file> - <file>32x32/patreon.png</file> - <file>48x48/patreon.png</file> - <file>64x64/patreon.png</file> - - <!-- The cat button. Freeware, http://findicons.com/icon/73096/black_cat --> - <file>16x16/cat.png</file> - <file>22x22/cat.png</file> - <file>24x24/cat.png</file> - <file>32x32/cat.png</file> - <file>48x48/cat.png</file> - <file>64x64/cat.png</file> - - <!-- Show mods folder. CC-BY-SA 3.0 http://openiconlibrary.sourceforge.net/gallery2/?./Icons/places/oxygen-style/folder-favorites.png --> - <file>scalable/centralmods.svg</file> - <file>16x16/centralmods.png</file> - <file>22x22/centralmods.png</file> - <file>32x32/centralmods.png</file> - <file>48x48/centralmods.png</file> - <file>64x64/centralmods.png</file> - - <!-- Update. GPLv2, https://code.google.com/p/gnome-colors/ --> - <file>scalable/checkupdate.svg</file> - <file>16x16/checkupdate.png</file> - <file>22x22/checkupdate.png</file> - <file>32x32/checkupdate.png</file> - <file>48x48/checkupdate.png</file> - <file>64x64/checkupdate.png</file> - - <!-- copy instance. CC-BY-SA 3.0, http://openiconlibrary.sourceforge.net/gallery2/?./Icons/actions/edit-copy-6.png --> - <file>16x16/copy.png</file> - <file>22x22/copy.png</file> - <file>32x32/copy.png</file> - <file>48x48/copy.png</file> - <file>64x64/copy.png</file> - - <!-- Help. CC-BY-SA 3.0, http://openiconlibrary.sourceforge.net/gallery2/?./Icons/actions/help.png --> - <file>16x16/help.png</file> - <file>22x22/help.png</file> - <file>32x32/help.png</file> - <file>48x48/help.png</file> - <file>64x64/help.png</file> - - <!-- New instance. GPLv2, http://openiconlibrary.sourceforge.net/gallery2/?./Icons/actions/document-new-3.png --> - <file>16x16/new.png</file> + <file>index.theme</file> + <!-- Logo. Our own. For use in branding. --> + <file>scalable/logo.svg</file> + + <!-- Logo. Our own. For use within the application (Settings pages and similar). --> + <file>scalable/multimc.svg</file> + + <!-- REDDIT logo icon, needs reddit license! --> + <file>scalable/reddit-alien.svg</file> + + <!-- twitch logo icon --> + <file>scalable/twitch.svg</file> + + <!-- technic logo icon --> + <file>scalable/technic.svg</file> + + <!-- A proxy icon. Our own. SSSsss --> + <file>scalable/proxy.svg</file> + + <!-- A free language icon. http://www.languageicon.org/ --> + <file>scalable/language.svg</file> + + <!-- Java icon. From Oracle, fixed because it was derpy. --> + <file>scalable/java.svg</file> + + <!-- Star, CC-BY-SA 3.0, Oxygen icons.--> + <file>16x16/star.png</file> + <file>24x24/star.png</file> + <file>32x32/star.png</file> + <file>48x48/star.png</file> + <file>64x64/star.png</file> + + <!-- "folder-remote", CC-BY-SA 3.0, Oxygen icons. Used for the worlds folder--> + <file>16x16/worlds.png</file> + <file>22x22/worlds.png</file> + <file>32x32/worlds.png</file> + <file>48x48/worlds.png</file> + <file>64x64/worlds.png</file> + + <!-- Minecraft icon. Source: http://www.minecraftforum.net/forums/show-your-creation/fan-art/1574882-icon-better-minecraft-icon --> + <file>16x16/minecraft.png</file> + <file>24x24/minecraft.png</file> + <file>32x32/minecraft.png</file> + <file>48x48/minecraft.png</file> + <file>256x256/minecraft.png</file> + + <!-- About dialog. GPLv2, http://openiconlibrary.sourceforge.net/gallery2/?./Icons/actions/help-contents.png --> + <file>16x16/about.png</file> + <file>22x22/about.png</file> + <file>32x32/about.png</file> + <file>48x48/about.png</file> + <file>64x64/about.png</file> + + <!-- Report bug. Our own. --> + <file>scalable/bug.svg</file> + <file>16x16/bug.png</file> + <file>22x22/bug.png</file> + <file>32x32/bug.png</file> + <file>48x48/bug.png</file> + <file>64x64/bug.png</file> + + <!-- Screenshots. Our own. --> + <!-- frame is adapted and simplified from http://www.wpclipart.com/page_frames/picture_frames/golden_picture_frame.png.html --> + <file>16x16/screenshots.png</file> + <file>22x22/screenshots.png</file> + <file>32x32/screenshots.png</file> + <file>48x48/screenshots.png</file> + <file>64x64/screenshots.png</file> + <file>scalable/screenshots.svg</file> + + <!-- Custom commands. --> + <file>scalable/custom-commands.svg</file> + + <!-- Patron logo. (C) 2014 Patreon, Inc., http://www.patreon.com/toolbox?ftyp=media --> + <file>16x16/patreon.png</file> + <file>22x22/patreon.png</file> + <file>24x24/patreon.png</file> + <file>32x32/patreon.png</file> + <file>48x48/patreon.png</file> + <file>64x64/patreon.png</file> + + <!-- The cat button. Freeware, http://findicons.com/icon/73096/black_cat --> + <file>16x16/cat.png</file> + <file>22x22/cat.png</file> + <file>24x24/cat.png</file> + <file>32x32/cat.png</file> + <file>48x48/cat.png</file> + <file>64x64/cat.png</file> + + <!-- Show mods folder. CC-BY-SA 3.0 http://openiconlibrary.sourceforge.net/gallery2/?./Icons/places/oxygen-style/folder-favorites.png --> + <file>scalable/centralmods.svg</file> + <file>16x16/centralmods.png</file> + <file>22x22/centralmods.png</file> + <file>32x32/centralmods.png</file> + <file>48x48/centralmods.png</file> + <file>64x64/centralmods.png</file> + + <!-- Update. GPLv2, https://code.google.com/p/gnome-colors/ --> + <file>scalable/checkupdate.svg</file> + <file>16x16/checkupdate.png</file> + <file>22x22/checkupdate.png</file> + <file>32x32/checkupdate.png</file> + <file>48x48/checkupdate.png</file> + <file>64x64/checkupdate.png</file> + + <!-- copy instance. CC-BY-SA 3.0, http://openiconlibrary.sourceforge.net/gallery2/?./Icons/actions/edit-copy-6.png --> + <file>16x16/copy.png</file> + <file>22x22/copy.png</file> + <file>32x32/copy.png</file> + <file>48x48/copy.png</file> + <file>64x64/copy.png</file> + + <!-- Help. CC-BY-SA 3.0, http://openiconlibrary.sourceforge.net/gallery2/?./Icons/actions/help.png --> + <file>16x16/help.png</file> + <file>22x22/help.png</file> + <file>32x32/help.png</file> + <file>48x48/help.png</file> + <file>64x64/help.png</file> + + <!-- New instance. GPLv2, http://openiconlibrary.sourceforge.net/gallery2/?./Icons/actions/document-new-3.png --> + <file>16x16/new.png</file> <file>22x22/new.png</file> <file>32x32/new.png</file> - <file>48x48/new.png</file> - <file>64x64/new.png</file> - - <!-- Open news. Our own. --> - <file>scalable/news.svg</file> - <file>16x16/news.png</file> - <file>22x22/news.png</file> - <file>32x32/news.png</file> - <file>48x48/news.png</file> - <file>64x64/news.png</file> - - <!-- Bad status. Our own. --> - <file>16x16/status-bad.png</file> - <file>24x24/status-bad.png</file> - <file>22x22/status-bad.png</file> - <file>32x32/status-bad.png</file> - <file>48x48/status-bad.png</file> - <file>64x64/status-bad.png</file> - - <!-- Good status. Our own. --> - <file>16x16/status-good.png</file> - <file>24x24/status-good.png</file> - <file>22x22/status-good.png</file> - <file>32x32/status-good.png</file> - <file>48x48/status-good.png</file> - <file>64x64/status-good.png</file> - - <!-- Yellow status. Whatever that means... Our own. --> - <file>16x16/status-yellow.png</file> - <file>24x24/status-yellow.png</file> - <file>22x22/status-yellow.png</file> - <file>32x32/status-yellow.png</file> - <file>48x48/status-yellow.png</file> - <file>64x64/status-yellow.png</file> - - <!-- A status icon for things that are in progress/running... Our own. --> - <file>16x16/status-running.png</file> - <file>24x24/status-running.png</file> - <file>22x22/status-running.png</file> - <file>32x32/status-running.png</file> - <file>48x48/status-running.png</file> - <file>64x64/status-running.png</file> - <file>scalable/status-running.svg</file> - - <!-- Plugin (blue recolor), CC-BY-SA 3.0, Oxygen icons. --> - <file>16x16/loadermods.png</file> - <file>24x24/loadermods.png</file> - <file>32x32/loadermods.png</file> - <file>64x64/loadermods.png</file> - - <!-- Plugin (red recolor), CC-BY-SA 3.0, Oxygen icons. --> - <file>16x16/jarmods.png</file> - <file>24x24/jarmods.png</file> - <file>32x32/jarmods.png</file> - <file>64x64/jarmods.png</file> - - <!-- Plugin (green original), CC-BY-SA 3.0, Oxygen icons. --> - <file>16x16/coremods.png</file> - <file>24x24/coremods.png</file> - <file>32x32/coremods.png</file> - <file>64x64/coremods.png</file> - - <!-- Resource packs, CC-BY-SA 3.0, Oxygen icons. --> - <file>16x16/resourcepacks.png</file> - <file>24x24/resourcepacks.png</file> - <file>32x32/resourcepacks.png</file> - <file>64x64/resourcepacks.png</file> - - <!-- Refresh, CC-BY-SA 3.0, Oxygen icons. --> - <file>16x16/refresh.png</file> - <file>22x22/refresh.png</file> - <file>32x32/refresh.png</file> - <file>48x48/refresh.png</file> - <file>64x64/refresh.png</file> - - <!-- Settings, LGPL-2.1, http://openiconlibrary.sourceforge.net/gallery2/?./Icons/apps/system-settings-3.png --> - <file>16x16/settings.png</file> + <file>48x48/new.png</file> + <file>64x64/new.png</file> + + <!-- Open news. Our own. --> + <file>scalable/news.svg</file> + <file>16x16/news.png</file> + <file>22x22/news.png</file> + <file>32x32/news.png</file> + <file>48x48/news.png</file> + <file>64x64/news.png</file> + + <!-- Bad status. Our own. --> + <file>16x16/status-bad.png</file> + <file>24x24/status-bad.png</file> + <file>22x22/status-bad.png</file> + <file>32x32/status-bad.png</file> + <file>48x48/status-bad.png</file> + <file>64x64/status-bad.png</file> + + <!-- Good status. Our own. --> + <file>16x16/status-good.png</file> + <file>24x24/status-good.png</file> + <file>22x22/status-good.png</file> + <file>32x32/status-good.png</file> + <file>48x48/status-good.png</file> + <file>64x64/status-good.png</file> + + <!-- Yellow status. Whatever that means... Our own. --> + <file>16x16/status-yellow.png</file> + <file>24x24/status-yellow.png</file> + <file>22x22/status-yellow.png</file> + <file>32x32/status-yellow.png</file> + <file>48x48/status-yellow.png</file> + <file>64x64/status-yellow.png</file> + + <!-- A status icon for things that are in progress/running... Our own. --> + <file>16x16/status-running.png</file> + <file>24x24/status-running.png</file> + <file>22x22/status-running.png</file> + <file>32x32/status-running.png</file> + <file>48x48/status-running.png</file> + <file>64x64/status-running.png</file> + <file>scalable/status-running.svg</file> + + <!-- Plugin (blue recolor), CC-BY-SA 3.0, Oxygen icons. --> + <file>16x16/loadermods.png</file> + <file>24x24/loadermods.png</file> + <file>32x32/loadermods.png</file> + <file>64x64/loadermods.png</file> + + <!-- Plugin (red recolor), CC-BY-SA 3.0, Oxygen icons. --> + <file>16x16/jarmods.png</file> + <file>24x24/jarmods.png</file> + <file>32x32/jarmods.png</file> + <file>64x64/jarmods.png</file> + + <!-- Plugin (green original), CC-BY-SA 3.0, Oxygen icons. --> + <file>16x16/coremods.png</file> + <file>24x24/coremods.png</file> + <file>32x32/coremods.png</file> + <file>64x64/coremods.png</file> + + <!-- Resource packs, CC-BY-SA 3.0, Oxygen icons. --> + <file>16x16/resourcepacks.png</file> + <file>24x24/resourcepacks.png</file> + <file>32x32/resourcepacks.png</file> + <file>64x64/resourcepacks.png</file> + + <!-- Refresh, CC-BY-SA 3.0, Oxygen icons. --> + <file>16x16/refresh.png</file> + <file>22x22/refresh.png</file> + <file>32x32/refresh.png</file> + <file>48x48/refresh.png</file> + <file>64x64/refresh.png</file> + + <!-- Settings, LGPL-2.1, http://openiconlibrary.sourceforge.net/gallery2/?./Icons/apps/system-settings-3.png --> + <file>16x16/settings.png</file> <file>22x22/settings.png</file> - <file>32x32/settings.png</file> - <file>48x48/settings.png</file> - <file>64x64/settings.png</file> - - <!-- Same, used for instance settings --> - <file>16x16/instance-settings.png</file> - <file>22x22/instance-settings.png</file> - <file>32x32/instance-settings.png</file> - <file>48x48/instance-settings.png</file> - <file>64x64/instance-settings.png</file> - - <!-- View folder. CC-BY-SA 3.0, http://openiconlibrary.sourceforge.net/gallery2/?./Icons/actions/document-open-folder.png --> - <file>scalable/viewfolder.svg</file> - <file>16x16/viewfolder.png</file> + <file>32x32/settings.png</file> + <file>48x48/settings.png</file> + <file>64x64/settings.png</file> + + <!-- Same, used for instance settings --> + <file>16x16/instance-settings.png</file> + <file>22x22/instance-settings.png</file> + <file>32x32/instance-settings.png</file> + <file>48x48/instance-settings.png</file> + <file>64x64/instance-settings.png</file> + + <!-- View folder. CC-BY-SA 3.0, http://openiconlibrary.sourceforge.net/gallery2/?./Icons/actions/document-open-folder.png --> + <file>scalable/viewfolder.svg</file> + <file>16x16/viewfolder.png</file> <file>22x22/viewfolder.png</file> - <file>32x32/viewfolder.png</file> - <file>48x48/viewfolder.png</file> - <file>64x64/viewfolder.png</file> + <file>32x32/viewfolder.png</file> + <file>48x48/viewfolder.png</file> + <file>64x64/viewfolder.png</file> - <!-- Default minecraft skin, desaturated, cutout of head, Mojang --> - <file>8x8/noaccount.png</file> - <file>16x16/noaccount.png</file> - <file>24x24/noaccount.png</file> - <file>32x32/noaccount.png</file> - <file>48x48/noaccount.png</file> + <!-- Default minecraft skin, desaturated, cutout of head, Mojang --> + <file>8x8/noaccount.png</file> + <file>16x16/noaccount.png</file> + <file>24x24/noaccount.png</file> + <file>32x32/noaccount.png</file> + <file>48x48/noaccount.png</file> - <!-- and the same again. TODO: make a nice accounts icon --> - <file alias="8x8/accounts.png">8x8/noaccount.png</file> - <file alias="16x16/accounts.png">16x16/noaccount.png</file> - <file alias="24x24/accounts.png">24x24/noaccount.png</file> - <file alias="32x32/accounts.png">32x32/noaccount.png</file> - <file alias="48x48/accounts.png">48x48/noaccount.png</file> + <!-- and the same again. TODO: make a nice accounts icon --> + <file alias="8x8/accounts.png">8x8/noaccount.png</file> + <file alias="16x16/accounts.png">16x16/noaccount.png</file> + <file alias="24x24/accounts.png">24x24/noaccount.png</file> + <file alias="32x32/accounts.png">32x32/noaccount.png</file> + <file alias="48x48/accounts.png">48x48/noaccount.png</file> - <!-- Log file, LGPL, http://www.iconarchive.com/show/crystal-clear-icons-by-everaldo/Mimetype-text-icon.html --> - <file>16x16/log.png</file> - <file>24x24/log.png</file> - <file>32x32/log.png</file> - <file>48x48/log.png</file> - <file>64x64/log.png</file> + <!-- Log file, LGPL, http://www.iconarchive.com/show/crystal-clear-icons-by-everaldo/Mimetype-text-icon.html --> + <file>16x16/log.png</file> + <file>24x24/log.png</file> + <file>32x32/log.png</file> + <file>48x48/log.png</file> + <file>64x64/log.png</file> - <!-- placeholder when loading screenshot images --> - <file>scalable/screenshot-placeholder.svg</file> + <!-- placeholder for minecraft servers --> + <file>128x128/unknown_server.png</file> - <!-- discord logo icon thing. from discord. traced from bitmap --> - <file>scalable/discord.svg</file> + <!-- placeholder when loading screenshot images --> + <file>scalable/screenshot-placeholder.svg</file> - <!-- instance icons --> - <file>32x32/instances/chicken.png</file> - <file>128x128/instances/chicken.png</file> + <!-- discord logo icon thing. from discord. traced from bitmap --> + <file>scalable/discord.svg</file> - <file>32x32/instances/creeper.png</file> - <file>128x128/instances/creeper.png</file> + <!-- instance icons --> + <file>32x32/instances/chicken.png</file> + <file>128x128/instances/chicken.png</file> - <file>32x32/instances/enderpearl.png</file> - <file>128x128/instances/enderpearl.png</file> + <file>32x32/instances/creeper.png</file> + <file>128x128/instances/creeper.png</file> - <file>32x32/instances/ftb_glow.png</file> - <file>128x128/instances/ftb_glow.png</file> + <file>32x32/instances/enderpearl.png</file> + <file>128x128/instances/enderpearl.png</file> - <file>32x32/instances/ftb_logo.png</file> - <file>128x128/instances/ftb_logo.png</file> + <file>32x32/instances/ftb_glow.png</file> + <file>128x128/instances/ftb_glow.png</file> - <file>32x32/instances/flame.png</file> - <file>128x128/instances/flame.png</file> + <file>32x32/instances/ftb_logo.png</file> + <file>128x128/instances/ftb_logo.png</file> - <file>32x32/instances/gear.png</file> - <file>128x128/instances/gear.png</file> + <file>32x32/instances/flame.png</file> + <file>128x128/instances/flame.png</file> - <file>32x32/instances/herobrine.png</file> - <file>128x128/instances/herobrine.png</file> + <file>32x32/instances/gear.png</file> + <file>128x128/instances/gear.png</file> - <file>32x32/instances/infinity.png</file> - <file>128x128/instances/infinity.png</file> + <file>32x32/instances/herobrine.png</file> + <file>128x128/instances/herobrine.png</file> - <file>32x32/instances/magitech.png</file> - <file>128x128/instances/magitech.png</file> + <file>32x32/instances/infinity.png</file> + <file>128x128/instances/infinity.png</file> - <file>32x32/instances/meat.png</file> - <file>128x128/instances/meat.png</file> + <file>32x32/instances/magitech.png</file> + <file>128x128/instances/magitech.png</file> - <file>32x32/instances/netherstar.png</file> - <file>128x128/instances/netherstar.png</file> + <file>32x32/instances/meat.png</file> + <file>128x128/instances/meat.png</file> - <file>32x32/instances/skeleton.png</file> - <file>128x128/instances/skeleton.png</file> + <file>32x32/instances/netherstar.png</file> + <file>128x128/instances/netherstar.png</file> - <file>32x32/instances/squarecreeper.png</file> - <file>128x128/instances/squarecreeper.png</file> + <file>32x32/instances/skeleton.png</file> + <file>128x128/instances/skeleton.png</file> - <file>32x32/instances/steve.png</file> - <file>128x128/instances/steve.png</file> + <file>32x32/instances/squarecreeper.png</file> + <file>128x128/instances/squarecreeper.png</file> - <file>32x32/instances/brick.png</file> - <file>32x32/instances/diamond.png</file> - <file>32x32/instances/dirt.png</file> - <file>32x32/instances/gold.png</file> - <file>32x32/instances/grass.png</file> - <file>32x32/instances/iron.png</file> - <file>32x32/instances/planks.png</file> - <file>32x32/instances/stone.png</file> - <file>32x32/instances/tnt.png</file> + <file>32x32/instances/steve.png</file> + <file>128x128/instances/steve.png</file> - <file>50x50/instances/enderman.png</file> - </qresource> + <file>32x32/instances/brick.png</file> + <file>32x32/instances/diamond.png</file> + <file>32x32/instances/dirt.png</file> + <file>32x32/instances/gold.png</file> + <file>32x32/instances/grass.png</file> + <file>32x32/instances/iron.png</file> + <file>32x32/instances/planks.png</file> + <file>32x32/instances/stone.png</file> + <file>32x32/instances/tnt.png</file> + + <file>50x50/instances/enderman.png</file> + + <file>scalable/instances/fox.svg</file> + </qresource> </RCC> diff --git a/application/resources/multimc/scalable/instances/fox.svg b/application/resources/multimc/scalable/instances/fox.svg new file mode 100644 index 00000000..fcf16b2f --- /dev/null +++ b/application/resources/multimc/scalable/instances/fox.svg @@ -0,0 +1,290 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + id="svg8" + version="1.1" + viewBox="0 0 33.866666 33.866666" + height="128" + width="128"> + <defs + id="defs2" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + transform="translate(0,-263.13334)" + id="layer1"> + <path + id="rect4750" + d="m 4.233333,267.36667 v 6.35 6.35 3.175 3.175 3.175 3.175 h 25.4 v -3.175 -3.175 -3.175 -3.175 -6.35 -6.35 h -6.35 v 6.35 h -12.7 v -3.175 -3.175 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:0.79375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <g + id="g4748"> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4553" + width="6.3500032" + height="6.3499975" + x="4.233326" + y="267.36667" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#b48f83;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4553-6-2" + width="3.1750014" + height="3.1749992" + x="7.4083276" + y="270.54166" /> + <rect + y="267.36667" + x="23.283335" + height="6.3499975" + width="6.3500032" + id="rect4623" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + y="270.54166" + x="23.283335" + height="3.1749992" + width="3.1750014" + id="rect4627" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#b48f83;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e27c21;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4672" + width="25.400013" + height="15.875009" + x="4.233326" + y="273.71667" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4629" + width="3.1750016" + height="6.3500032" + x="4.2333255" + y="273.71667" /> + <rect + y="273.71667" + x="26.458338" + height="6.3500032" + width="3.1750016" + id="rect4631" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e78f41;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4636" + width="6.3500032" + height="3.1750016" + x="13.758331" + y="283.24167" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#b05122;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4638" + width="3.1750016" + height="3.1750016" + x="4.2333255" + y="280.06668" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4640" + width="3.1750016" + height="3.1750016" + x="7.4083276" + y="280.06668" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.84189939;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4644" + width="6.3500028" + height="3.1750016" + x="4.233326" + y="283.24167" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#06040e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4642" + width="3.1750016" + height="3.1750016" + x="4.2333255" + y="283.24167" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.84189939;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4646" + width="6.3500028" + height="3.1750016" + x="23.283337" + y="283.24167" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#06040e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4648" + width="3.1750016" + height="3.1750016" + x="26.45834" + y="283.24167" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4650" + width="3.1750016" + height="3.1750016" + x="23.283337" + y="280.06668" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#b05122;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4652" + width="3.1750016" + height="3.1750016" + x="26.45834" + y="280.06668" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4654" + width="25.400013" + height="3.1750016" + x="4.2333255" + y="286.41666" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e7d9d3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4656" + width="25.400013" + height="3.1750016" + x="4.2333255" + y="289.59167" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5612604;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4917" + width="25.4" + height="0.26457807" + x="4.2333331" + y="273.71667" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.48607069;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4921" + width="0.2645835" + height="19.049997" + x="4.2333331" + y="273.71667" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062555;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4658" + width="12.700007" + height="6.3500013" + x="10.583333" + y="286.41666" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#06040e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062555;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4660" + width="6.3500037" + height="3.1750007" + x="13.758333" + y="286.41669" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e7d9d3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062555;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4662" + width="3.1750019" + height="3.1750007" + x="10.583333" + y="286.41669" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e7d9d3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062555;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4664" + width="3.1750019" + height="3.1750007" + x="20.108334" + y="286.41669" /> + <rect + y="286.41669" + x="10.583333" + height="6.3499832" + width="0.2645835" + id="rect4923" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2806327;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.39686465;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4951" + width="12.699996" + height="0.26456967" + x="10.583333" + y="286.41669" /> + <rect + y="273.71667" + x="29.36875" + height="19.049982" + width="0.26458356" + id="rect4953" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.48607057;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + y="292.50208" + x="23.283333" + height="0.26457682" + width="6.3499994" + id="rect4957" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28062955;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2806325;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4959" + width="0.2645838" + height="6.3499656" + x="23.018749" + y="286.41669" /> + <rect + y="292.50208" + x="4.2333331" + height="0.26457968" + width="6.3499999" + id="rect4961" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063107;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + y="267.36667" + x="4.2333331" + height="6.3499956" + width="0.2645835" + id="rect4963" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063297;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063536;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4965" + width="6.349998" + height="0.26458791" + x="4.2333331" + y="267.36667" /> + <rect + y="267.36667" + x="23.283333" + height="6.3499956" + width="0.2645835" + id="rect4963-9" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.280633;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063536;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4965-1" + width="6.349998" + height="0.26458791" + x="23.283333" + y="267.36667" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063306;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4985" + width="0.26458356" + height="6.3499975" + x="29.36875" + y="267.36667" /> + <rect + y="267.36667" + x="10.318749" + height="6.3499975" + width="0.26458356" + id="rect4987" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063306;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + </g> + </g> +</svg> diff --git a/application/resources/multimc/scalable/language.svg b/application/resources/multimc/scalable/language.svg new file mode 100644 index 00000000..968e3538 --- /dev/null +++ b/application/resources/multimc/scalable/language.svg @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + viewBox="0 0 128 128" + height="128" + width="128" + xml:space="preserve" + id="svg2" + version="1.1" + sodipodi:docname="language.svg" + inkscape:version="0.92.2 2405546, 2018-03-11"><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="3840" + inkscape:window-height="2123" + id="namedview30" + showgrid="false" + inkscape:zoom="1.84375" + inkscape:cx="325.0346" + inkscape:cy="134.81864" + inkscape:window-x="1200" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="g888" /><metadata + id="metadata8"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs6" /><g + transform="matrix(1.3333333,0,0,-1.3333333,0,128.00004)" + id="g10"><g + transform="scale(0.1)" + id="g12"><g + transform="matrix(0.0334181,0,0,0.0334181,77.111273,13.149509)" + id="g888"><g + id="g14" + transform="scale(1.69573)" + style="fill:#0066cc;fill-opacity:1"><path + d="M 7103.55,14358.7 1603.03,16300 V 4328.22 l 5500.52,1779.59 v 8250.89" + style="fill:#0066cc;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path16" /></g><g + id="g18" + transform="scale(1.69635)"><path + d="M 6968.96,14359.4 12678,16300 V 4332.6 L 6968.96,6111.53 v 8247.87" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path20" /></g><g + id="g22" + transform="scale(1.49453)"><path + d="M 200.466,2533.04 7910.06,5102.75 V 16300 L 200.466,13730.3 V 2533.04" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path24" /></g><g + id="g26" + transform="scale(1.20038)" + style="fill:#003399;fill-opacity:1"><path + d="M 14222.2,2943.15 15582.6,705.027 16300,2784.12 14222.2,2943.15" + style="fill:#003399;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path28" /></g><g + id="g30" + transform="scale(1.16442)" + style="fill:#003399;fill-opacity:1"><path + d="m 3621.88,15967.1 c -52.56,51.6 68.45,-421.7 236.85,-592 298.61,-301.3 531.85,-340.1 656.04,-345.1 274.81,-11 613.95,68.5 815.34,152.9 194.86,83.1 536.31,257.4 665.56,511.7 27.4,54.4 102.2,145.7 55.22,371.2 -35.64,173.5 -146.08,234.2 -280.74,224.6 -134.66,-9.1 -542.33,-117.8 -739.51,-178.5 -197.26,-59.8 -603.56,-183.5 -780.64,-221.9 -176.65,-38.3 -566.12,17.8 -628.12,77.1" + style="fill:#003399;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path32" /></g><g + id="g34" + transform="scale(1.0917)" + style="fill:#003399;fill-opacity:1"><path + d="m 9188.43,10995.1 c -83.18,30.2 -1803.98,743 -2047.91,859.8 -199.6,96 -689.02,302.9 -919.3,396.9 648.62,1000.1 1058.07,1754.8 1112.58,1869.8 100.85,210.3 787.39,1553.7 803.42,1636.4 15.57,83.8 35.08,393.4 19.97,467 -15.11,75 -266.83,-69.2 -608.59,-185.1 -342.31,-115.4 -992.86,-538.5 -1244.12,-591.5 -252.17,-52.6 -1058.07,-357.9 -1470.46,-494.7 -412.38,-136.9 -1192.45,-375 -1513.33,-461.6 -321.33,-86.7 -601.81,-93.5 -781.53,-148 0,0 23.91,-251.8 71.63,-327.2 47.18,-75.5 217.19,-260.5 414.86,-312.2 197.67,-52 524.87,-31.2 673.9,2.9 148.95,34.6 406.98,160.7 441.61,215.7 34.99,56 -18.05,228.4 40.85,280.5 59.45,51.6 844.83,235.2 1141.34,324.7 296.51,91.2 1431.53,482.1 1585.42,462.2 C 6860.04,14829 5947.06,13020.6 5653.02,12481.1 5358.89,11941.7 3650.37,9568.39 3286.62,9150.14 3010.54,8832.19 2341.49,8018.6 2109.74,7835.03 c 58.44,-16.12 472.75,19.42 548.23,66.14 470.36,289.73 1253.82,1265 1506.09,1562.06 749.84,879.37 1408.63,1803.07 1931.02,2595.77 h 0.56 c 101.76,-42.4 924.61,-712.8 1139.32,-861.4 214.71,-148.5 1062.01,-621.2 1245.58,-699.7 183.57,-79.4 889.07,-404.6 918.75,-294.5 29.68,111 -127.6,760.1 -210.86,791.7" + style="fill:#003399;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path36" /></g><g + id="g38" + transform="scale(1.12147)" + style="fill:#003399;fill-opacity:1"><path + d="m 5073.69,1979.54 c 160.5,-98.08 312.09,-178.34 481.51,-258.59 338.84,-169.42 722.26,-347.76 1087.85,-481.51 499.35,-187.25 998.69,-338.838 1498.03,-454.757 276.43,-62.418 579.6,-115.919 873.85,-160.504 26.76,0 820.35,-98.085 980.86,-98.085 h 802.51 c 312.1,26.751 606.4,44.584 918.4,89.169 249.7,35.667 526.1,80.251 793.6,142.669 196.2,44.584 401.3,89.169 597.5,151.587 187.2,53.501 401.2,124.831 606.3,196.171 133.8,44.58 276.4,107 419.1,160.5 115.9,53.5 258.6,115.92 392.3,169.42 160.6,71.34 347.8,169.42 526.1,258.59 142.7,71.34 303.2,160.5 454.8,249.67 115.9,62.42 383.4,267.51 526.1,267.51 160.5,0 267.5,-142.67 267.5,-267.51 0,-258.59 -347.8,-338.84 -508.3,-454.76 -169.4,-115.92 -374.5,-205.08 -552.8,-303.17 -356.7,-187.253 -722.3,-347.756 -1070,-481.509 -454.8,-169.42 -954.1,-329.923 -1400,-436.926 -169.4,-35.667 -338.8,-80.251 -508.2,-107.002 C 12171.5,142.67 11244.1,0 10985.6,0 H 9808.53 c -312.09,26.7505 -642.01,62.4179 -954.1,107.002 -276.42,44.584 -570.68,98.086 -847.1,160.503 -214,44.585 -445.84,107.003 -650.93,169.421 -356.67,98.085 -704.43,222.921 -1043.27,356.674 -615.26,231.84 -1257.28,535.01 -1863.62,936.27 -107,71.33 -115.92,142.67 -115.92,222.92 0,133.75 98.08,258.59 258.59,258.59 142.67,0 428.01,-205.09 481.51,-231.84" + style="fill:#003399;fill-opacity:1;fill-rule:evenodd;stroke:none" + id="path40" /></g><g + id="g42" + transform="scale(1.51227)" + style="fill:#003399;fill-opacity:1"><path + d="M 8014.44,16134.7 V 5025.56 c -6.61,-33.07 -19.84,-66.13 -46.29,-99.19 -13.22,-19.84 -39.67,-46.29 -59.51,-52.9 C 7743.33,4807.34 297.566,2307.79 198.377,2307.79 c -79.351,0 -152.089,52.9 -191.76442,138.86 0,6.62 -6.61258,13.23 -6.61258,26.45 v 11115.7 c 13.2252,33.1 19.8377,79.4 46.288,105.8 52.9006,72.8 145.477,86 204.99,105.8 112.414,39.7 7445.762,2499.6 7551.562,2499.6 66.13,0 211.6,-46.3 211.6,-165.3 z M 7611.07,5190.87 403.367,2790.51 V 13423.5 L 7611.07,15823.9 V 5190.87" + style="fill:#003399;fill-opacity:1;fill-rule:evenodd;stroke:none" + id="path44" /></g><g + id="g46" + transform="scale(1.71411)" + style="fill:#003399;fill-opacity:1"><path + d="M 12723.8,16113.3 V 4311.27 c -5.8,-134.18 -99.2,-192.52 -186.7,-192.52 -75.8,0 -624.2,186.69 -717.6,215.86 -735,227.52 -1475.9,455.05 -2205.18,682.57 -163.35,52.51 -332.54,105.01 -490.05,157.52 -140.02,40.83 -291.7,87.5 -431.71,134.18 -624.23,192.52 -1260.13,385.04 -1884.36,595.06 -23.34,5.83 -81.68,87.51 -81.68,105.01 v 8243.35 c 11.67,29.2 23.34,64.2 52.51,87.5 46.67,52.5 2047.71,717.6 2835.29,980.1 210.02,75.8 2841.08,980.1 2922.78,980.1 105,0 186.7,-75.8 186.7,-186.7 z M 12367.9,4532.96 7076.56,6178.13 V 14083.1 L 12367.9,15880 V 4532.96" + style="fill:#003399;fill-opacity:1;fill-rule:evenodd;stroke:none" + id="path48" /></g><g + id="g50" + transform="scale(1.48515)" + style="fill:#0066cc;fill-opacity:1"><path + d="M 16235.4,2378.81 8076.61,4979.28 8110.74,16300 16235.4,13714.1 V 2378.81" + style="fill:#0066cc;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path52" /></g><g + id="g54" + transform="scale(1.33098)"><path + d="m 12990.3,14581.8 1172.9,-355.3 2136.8,-7701.24 -1204.8,365.52 -432.8,1581.01 -2489.7,754.63 -535.4,-1287.92 -1205.1,365.6 z m 536.2,-2038.8 -893.6,-2159.8 1642.8,-497.94 -749.2,2657.74" + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none" + id="path56" /></g></g></g></g></svg>
\ No newline at end of file diff --git a/application/resources/pe_blue/pe_blue.qrc b/application/resources/pe_blue/pe_blue.qrc index 1706371a..98445d88 100644 --- a/application/resources/pe_blue/pe_blue.qrc +++ b/application/resources/pe_blue/pe_blue.qrc @@ -1,37 +1,38 @@ <!DOCTYPE RCC> <RCC version="1.0"> - <qresource prefix="/icons/pe_blue"> - <file>index.theme</file> - <file>scalable/about.svg</file> - <file>scalable/accounts.svg</file> - <file>scalable/bug.svg</file> - <file>scalable/centralmods.svg</file> - <file>scalable/checkupdate.svg</file> - <file>scalable/copy.svg</file> - <file>scalable/coremods.svg</file> - <file>scalable/externaltools.svg</file> - <file>scalable/help.svg</file> - <file>scalable/instance-settings.svg</file> - <file>scalable/jarmods.svg</file> - <file>scalable/java.svg</file> - <file>scalable/loadermods.svg</file> - <file>scalable/log.svg</file> - <file>scalable/minecraft.svg</file> - <file>scalable/multimc.svg</file> - <file>scalable/new.svg</file> - <file>scalable/news.svg</file> - <file>scalable/notes.svg</file> - <file>scalable/patreon.svg</file> - <file>scalable/proxy.svg</file> - <file>scalable/quickmods.svg</file> - <file>scalable/refresh.svg</file> - <file>scalable/resourcepacks.svg</file> - <file>scalable/screenshots.svg</file> - <file>scalable/settings.svg</file> - <file>scalable/status-bad.svg</file> - <file>scalable/status-good.svg</file> - <file>scalable/status-yellow.svg</file> - <file>scalable/viewfolder.svg</file> - <file>scalable/worlds.svg</file> - </qresource> + <qresource prefix="/icons/pe_blue"> + <file>index.theme</file> + <file>scalable/about.svg</file> + <file>scalable/accounts.svg</file> + <file>scalable/bug.svg</file> + <file>scalable/centralmods.svg</file> + <file>scalable/checkupdate.svg</file> + <file>scalable/copy.svg</file> + <file>scalable/coremods.svg</file> + <file>scalable/externaltools.svg</file> + <file>scalable/help.svg</file> + <file>scalable/instance-settings.svg</file> + <file>scalable/jarmods.svg</file> + <file>scalable/java.svg</file> + <file>scalable/language.svg</file> + <file>scalable/loadermods.svg</file> + <file>scalable/log.svg</file> + <file>scalable/minecraft.svg</file> + <file>scalable/multimc.svg</file> + <file>scalable/new.svg</file> + <file>scalable/news.svg</file> + <file>scalable/notes.svg</file> + <file>scalable/patreon.svg</file> + <file>scalable/proxy.svg</file> + <file>scalable/quickmods.svg</file> + <file>scalable/refresh.svg</file> + <file>scalable/resourcepacks.svg</file> + <file>scalable/screenshots.svg</file> + <file>scalable/settings.svg</file> + <file>scalable/status-bad.svg</file> + <file>scalable/status-good.svg</file> + <file>scalable/status-yellow.svg</file> + <file>scalable/viewfolder.svg</file> + <file>scalable/worlds.svg</file> + </qresource> </RCC> diff --git a/application/resources/pe_blue/scalable/language.svg b/application/resources/pe_blue/scalable/language.svg new file mode 100644 index 00000000..92868516 --- /dev/null +++ b/application/resources/pe_blue/scalable/language.svg @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + version="1.1" + id="Calque_1" + x="0px" + y="0px" + viewBox="0 0 32 32" + enable-background="new 0 0 32 32" + xml:space="preserve"><metadata + id="metadata45"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs43" /><path + fill-rule="evenodd" + clip-rule="evenodd" + fill="#3366CC" + d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6 v20C32,29.3,29.3,32,26,32z" + id="path2" /><path + fill="#DAEEFF" + fill-rule="evenodd" + clip-rule="evenodd" + d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z" + id="path4" /><g + id="g10" /><g + id="g12" /><g + id="g14" /><g + id="g16" /><g + id="g18" /><g + id="g20" /><g + id="g22" /><g + id="g24" /><g + id="g26" /><g + id="g28" /><g + id="g30" /><g + id="g32" /><g + id="g34" /><g + id="g36" /><g + id="g38" /><path + d="m 9.1897907,10.114301 c -0.4838401,0 -0.9011011,0.341924 -0.9962772,0.815883 L 6.1640606,21.085639 c -0.1100564,0.55031 0.2460855,1.083011 0.7953819,1.193069 0.5562003,0.113982 1.0840209,-0.246085 1.1930722,-0.79538 l 0.6518824,-3.247122 h 2.8699319 l 0.647785,3.247122 c 0.110996,0.557485 0.658771,0.906193 1.197171,0.79538 0.549363,-0.110056 0.905437,-0.642759 0.795381,-1.193069 L 12.285213,10.930184 c -0.09517,-0.473959 -0.512431,-0.815883 -0.996274,-0.815883 z m 0.6149825,2.287746 h 0.4345928 l 0.811779,4.063002 H 8.9929943 Z M 20.710512,9.7002137 c -0.561209,0 -1.016777,0.4578063 -1.016777,1.0208743 v 1.016774 h -3.046227 c -0.561205,0 -1.012673,0.457809 -1.012673,1.020876 0,0.563065 0.451466,1.016776 1.012673,1.016776 h 0.274696 c 0.577658,1.85973 1.425829,3.313572 2.37794,4.460692 -0.745862,0.684158 -1.478369,1.245052 -2.271343,1.873654 -0.437271,0.35118 -0.506831,0.995228 -0.155795,1.434967 0.349411,0.439057 0.990377,0.507857 1.426766,0.155794 0.861615,-0.682321 1.601995,-1.252055 2.410742,-1.996652 0.808748,0.744597 1.618828,1.314331 2.480441,1.996652 0.436393,0.352059 1.077353,0.283261 1.426766,-0.155794 0.351038,-0.439739 0.277375,-1.083787 -0.159884,-1.434967 -0.792979,-0.628602 -1.591082,-1.189496 -2.336946,-1.873654 0.952113,-1.147188 1.869915,-2.601029 2.447642,-4.460692 h 0.270592 c 0.561209,0 1.016774,-0.453711 1.016774,-1.016776 0,-0.563067 -0.455565,-1.020876 -1.016774,-1.020876 h -3.111838 v -1.016774 c 0,-0.563068 -0.455567,-1.0208743 -1.016775,-1.0208743 z m -1.668658,4.0753003 h 3.402916 c -0.438423,1.181552 -1.08891,2.13806 -1.734258,2.951929 -0.645345,-0.813869 -1.230238,-1.770309 -1.668658,-2.951929 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#c1272d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.29744864;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect977" /></svg>
\ No newline at end of file diff --git a/application/resources/pe_colored/pe_colored.qrc b/application/resources/pe_colored/pe_colored.qrc index 588e3ac0..fbaaf9e4 100644 --- a/application/resources/pe_colored/pe_colored.qrc +++ b/application/resources/pe_colored/pe_colored.qrc @@ -1,37 +1,38 @@ <!DOCTYPE RCC> <RCC version="1.0"> - <qresource prefix="/icons/pe_colored"> - <file>index.theme</file> - <file>scalable/about.svg</file> - <file>scalable/accounts.svg</file> - <file>scalable/bug.svg</file> - <file>scalable/centralmods.svg</file> - <file>scalable/checkupdate.svg</file> - <file>scalable/copy.svg</file> - <file>scalable/coremods.svg</file> - <file>scalable/externaltools.svg</file> - <file>scalable/help.svg</file> - <file>scalable/instance-settings.svg</file> - <file>scalable/jarmods.svg</file> - <file>scalable/java.svg</file> - <file>scalable/loadermods.svg</file> - <file>scalable/log.svg</file> - <file>scalable/minecraft.svg</file> - <file>scalable/multimc.svg</file> - <file>scalable/new.svg</file> - <file>scalable/news.svg</file> - <file>scalable/notes.svg</file> - <file>scalable/patreon.svg</file> - <file>scalable/proxy.svg</file> - <file>scalable/quickmods.svg</file> - <file>scalable/refresh.svg</file> - <file>scalable/resourcepacks.svg</file> - <file>scalable/screenshots.svg</file> - <file>scalable/settings.svg</file> - <file>scalable/status-bad.svg</file> - <file>scalable/status-good.svg</file> - <file>scalable/status-yellow.svg</file> - <file>scalable/viewfolder.svg</file> - <file>scalable/worlds.svg</file> - </qresource> + <qresource prefix="/icons/pe_colored"> + <file>index.theme</file> + <file>scalable/about.svg</file> + <file>scalable/accounts.svg</file> + <file>scalable/bug.svg</file> + <file>scalable/centralmods.svg</file> + <file>scalable/checkupdate.svg</file> + <file>scalable/copy.svg</file> + <file>scalable/coremods.svg</file> + <file>scalable/externaltools.svg</file> + <file>scalable/help.svg</file> + <file>scalable/instance-settings.svg</file> + <file>scalable/jarmods.svg</file> + <file>scalable/java.svg</file> + <file>scalable/language.svg</file> + <file>scalable/loadermods.svg</file> + <file>scalable/log.svg</file> + <file>scalable/minecraft.svg</file> + <file>scalable/multimc.svg</file> + <file>scalable/new.svg</file> + <file>scalable/news.svg</file> + <file>scalable/notes.svg</file> + <file>scalable/patreon.svg</file> + <file>scalable/proxy.svg</file> + <file>scalable/quickmods.svg</file> + <file>scalable/refresh.svg</file> + <file>scalable/resourcepacks.svg</file> + <file>scalable/screenshots.svg</file> + <file>scalable/settings.svg</file> + <file>scalable/status-bad.svg</file> + <file>scalable/status-good.svg</file> + <file>scalable/status-yellow.svg</file> + <file>scalable/viewfolder.svg</file> + <file>scalable/worlds.svg</file> + </qresource> </RCC> diff --git a/application/resources/pe_colored/scalable/language.svg b/application/resources/pe_colored/scalable/language.svg new file mode 100644 index 00000000..80c1dcad --- /dev/null +++ b/application/resources/pe_colored/scalable/language.svg @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xml:space="preserve" + enable-background="new 0 0 32 32" + viewBox="0 0 32 32" + y="0px" + x="0px" + id="Calque_1" + version="1.1"><metadata + id="metadata19"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs17" /> +<path + id="path2" + d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z" + fill="#F2F2F2" + clip-rule="evenodd" + fill-rule="evenodd" /> + +<g + id="g12"> + <path + id="path6" + d="M6,28h20c1.1,0,2-0.9,2-2V9V6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v3v17C4,27.1,4.9,28,6,28z" + fill="none" /> + <path + id="path8" + d="M26,0H6C2.7,0,0,2.7,0,6v3h4V6c0-1.1,0.9-2,2-2h20c1.1,0,2,0.9,2,2v3h4V6C32,2.7,29.3,0,26,0z" + fill="#39B54A" /> + <path + id="path10" + d="M28,26c0,1.1-0.9,2-2,2H6c-1.1,0-2-0.9-2-2V9H0v17c0,3.3,2.7,6,6,6h20c3.3,0,6-2.7,6-6V9h-4V26z" + fill="#8C6239" /> +</g> +<path + d="m 9.1897919,10.114302 c -0.483841,0 -0.901102,0.341924 -0.996278,0.815883 l -2.029453,10.155454 c -0.110056,0.55031 0.246086,1.083011 0.795382,1.193069 0.556201,0.113982 1.084021,-0.246085 1.193073,-0.79538 l 0.651882,-3.247121 H 11.67433 l 0.647785,3.247121 c 0.110996,0.557485 0.658771,0.906193 1.197171,0.79538 0.549363,-0.110056 0.905437,-0.642759 0.795381,-1.193069 L 12.285214,10.930185 c -0.09517,-0.473959 -0.512431,-0.815883 -0.996274,-0.815883 z m 0.614982,2.287746 h 0.4345931 l 0.811779,4.063002 H 8.9929949 Z M 20.710512,9.7002137 c -0.561209,0 -1.016777,0.4578073 -1.016777,1.0208753 v 1.016774 h -3.046227 c -0.561204,0 -1.012672,0.457809 -1.012672,1.020876 0,0.563065 0.451466,1.016776 1.012672,1.016776 h 0.274696 c 0.577658,1.85973 1.425829,3.313572 2.37794,4.460692 -0.745862,0.684158 -1.478369,1.245052 -2.271343,1.873653 -0.437271,0.35118 -0.506831,0.995228 -0.155795,1.434967 0.349411,0.439057 0.990377,0.507857 1.426766,0.155794 0.861615,-0.682321 1.601995,-1.252055 2.410742,-1.996652 0.808748,0.744597 1.618828,1.314331 2.480441,1.996652 0.436393,0.352059 1.077353,0.283261 1.426766,-0.155794 0.351038,-0.439739 0.277375,-1.083787 -0.159884,-1.434967 -0.792979,-0.628601 -1.591082,-1.189495 -2.336946,-1.873653 0.952113,-1.147188 1.869915,-2.601029 2.447642,-4.460692 h 0.270592 c 0.561209,0 1.016774,-0.453711 1.016774,-1.016776 0,-0.563067 -0.455565,-1.020876 -1.016774,-1.020876 h -3.111838 v -1.016774 c 0,-0.563068 -0.455567,-1.0208753 -1.016775,-1.0208753 z m -1.668658,4.0753013 h 3.402916 c -0.438423,1.181552 -1.08891,2.13806 -1.734258,2.951929 -0.645345,-0.813869 -1.230238,-1.770309 -1.668658,-2.951929 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#c1272d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.29744864;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect977" /></svg>
\ No newline at end of file diff --git a/application/resources/pe_dark/pe_dark.qrc b/application/resources/pe_dark/pe_dark.qrc index 3543f590..a57b6a14 100644 --- a/application/resources/pe_dark/pe_dark.qrc +++ b/application/resources/pe_dark/pe_dark.qrc @@ -1,37 +1,38 @@ <!DOCTYPE RCC> <RCC version="1.0"> - <qresource prefix="/icons/pe_dark"> - <file>index.theme</file> - <file>scalable/about.svg</file> - <file>scalable/accounts.svg</file> - <file>scalable/bug.svg</file> - <file>scalable/centralmods.svg</file> - <file>scalable/checkupdate.svg</file> - <file>scalable/copy.svg</file> - <file>scalable/coremods.svg</file> - <file>scalable/externaltools.svg</file> - <file>scalable/help.svg</file> - <file>scalable/instance-settings.svg</file> - <file>scalable/jarmods.svg</file> - <file>scalable/java.svg</file> - <file>scalable/loadermods.svg</file> - <file>scalable/log.svg</file> - <file>scalable/minecraft.svg</file> - <file>scalable/multimc.svg</file> - <file>scalable/new.svg</file> - <file>scalable/news.svg</file> - <file>scalable/notes.svg</file> - <file>scalable/patreon.svg</file> - <file>scalable/proxy.svg</file> - <file>scalable/quickmods.svg</file> - <file>scalable/refresh.svg</file> - <file>scalable/resourcepacks.svg</file> - <file>scalable/screenshots.svg</file> - <file>scalable/settings.svg</file> - <file>scalable/status-bad.svg</file> - <file>scalable/status-good.svg</file> - <file>scalable/status-yellow.svg</file> - <file>scalable/viewfolder.svg</file> - <file>scalable/worlds.svg</file> - </qresource> + <qresource prefix="/icons/pe_dark"> + <file>index.theme</file> + <file>scalable/about.svg</file> + <file>scalable/accounts.svg</file> + <file>scalable/bug.svg</file> + <file>scalable/centralmods.svg</file> + <file>scalable/checkupdate.svg</file> + <file>scalable/copy.svg</file> + <file>scalable/coremods.svg</file> + <file>scalable/externaltools.svg</file> + <file>scalable/help.svg</file> + <file>scalable/instance-settings.svg</file> + <file>scalable/jarmods.svg</file> + <file>scalable/java.svg</file> + <file>scalable/language.svg</file> + <file>scalable/loadermods.svg</file> + <file>scalable/log.svg</file> + <file>scalable/minecraft.svg</file> + <file>scalable/multimc.svg</file> + <file>scalable/new.svg</file> + <file>scalable/news.svg</file> + <file>scalable/notes.svg</file> + <file>scalable/patreon.svg</file> + <file>scalable/proxy.svg</file> + <file>scalable/quickmods.svg</file> + <file>scalable/refresh.svg</file> + <file>scalable/resourcepacks.svg</file> + <file>scalable/screenshots.svg</file> + <file>scalable/settings.svg</file> + <file>scalable/status-bad.svg</file> + <file>scalable/status-good.svg</file> + <file>scalable/status-yellow.svg</file> + <file>scalable/viewfolder.svg</file> + <file>scalable/worlds.svg</file> + </qresource> </RCC> diff --git a/application/resources/pe_dark/scalable/language.svg b/application/resources/pe_dark/scalable/language.svg new file mode 100644 index 00000000..1a9b4c5c --- /dev/null +++ b/application/resources/pe_dark/scalable/language.svg @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + version="1.1" + id="Calque_1" + x="0px" + y="0px" + viewBox="0 0 32 32" + enable-background="new 0 0 32 32" + xml:space="preserve"><metadata + id="metadata45"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs43" /><path + fill-rule="evenodd" + clip-rule="evenodd" + d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6v20 C32,29.3,29.3,32,26,32z" + id="path2" /><path + fill-rule="evenodd" + clip-rule="evenodd" + fill="#F2F2F2" + d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z" + id="path4" /><g + id="g10" /><g + id="g12" /><g + id="g14" /><g + id="g16" /><g + id="g18" /><g + id="g20" /><g + id="g22" /><g + id="g24" /><g + id="g26" /><g + id="g28" /><g + id="g30" /><g + id="g32" /><g + id="g34" /><g + id="g36" /><g + id="g38" /><path + d="m 9.1897911,10.114301 c -0.48384,0 -0.901101,0.341924 -0.996278,0.815883 l -2.029452,10.155455 c -0.110057,0.55031 0.246085,1.083011 0.795381,1.193069 0.556201,0.113982 1.084021,-0.246085 1.193073,-0.79538 l 0.651882,-3.247122 h 2.8699319 l 0.647785,3.247122 c 0.110996,0.557485 0.658771,0.906193 1.197171,0.79538 0.549363,-0.110056 0.905437,-0.642759 0.795381,-1.193069 L 12.285213,10.930184 c -0.09517,-0.473959 -0.512431,-0.815883 -0.996274,-0.815883 z m 0.614982,2.287746 h 0.4345929 l 0.811779,4.063002 H 8.9929941 Z M 20.710512,9.7002137 c -0.561209,0 -1.016777,0.4578063 -1.016777,1.0208743 v 1.016774 h -3.046227 c -0.561205,0 -1.012673,0.457809 -1.012673,1.020876 0,0.563065 0.451466,1.016776 1.012673,1.016776 h 0.274696 c 0.577658,1.85973 1.425829,3.313572 2.37794,4.460692 -0.745862,0.684158 -1.478369,1.245052 -2.271343,1.873654 -0.437271,0.35118 -0.506831,0.995228 -0.155795,1.434967 0.349411,0.439057 0.990377,0.507857 1.426766,0.155794 0.861615,-0.682321 1.601995,-1.252055 2.410742,-1.996652 0.808748,0.744597 1.618828,1.314331 2.480441,1.996652 0.436393,0.352059 1.077353,0.283261 1.426766,-0.155794 0.351038,-0.439739 0.277375,-1.083787 -0.159884,-1.434967 -0.792979,-0.628602 -1.591082,-1.189496 -2.336946,-1.873654 0.952113,-1.147188 1.869915,-2.601029 2.447642,-4.460692 h 0.270592 c 0.561209,0 1.016774,-0.453711 1.016774,-1.016776 0,-0.563067 -0.455565,-1.020876 -1.016774,-1.020876 h -3.111838 v -1.016774 c 0,-0.563068 -0.455567,-1.0208743 -1.016775,-1.0208743 z m -1.668658,4.0753003 h 3.402916 c -0.438423,1.181552 -1.08891,2.13806 -1.734258,2.951929 -0.645345,-0.813869 -1.230238,-1.770309 -1.668658,-2.951929 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.29744864;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect977" /></svg>
\ No newline at end of file diff --git a/application/resources/pe_light/pe_light.qrc b/application/resources/pe_light/pe_light.qrc index f83d6eba..6d77c835 100644 --- a/application/resources/pe_light/pe_light.qrc +++ b/application/resources/pe_light/pe_light.qrc @@ -1,38 +1,39 @@ <!DOCTYPE RCC> <RCC version="1.0"> - <qresource prefix="/icons/pe_light"> - <file>index.theme</file> - <file>scalable/about.svg</file> - <file>scalable/accounts.svg</file> - <file>scalable/bug.svg</file> - <file>scalable/centralmods.svg</file> - <file>scalable/checkupdate.svg</file> - <file>scalable/copy.svg</file> - <file>scalable/coremods.svg</file> - <file>scalable/externaltools.svg</file> - <file>scalable/help.svg</file> - <file>scalable/instance-settings.svg</file> - <file>scalable/jarmods.svg</file> - <file>scalable/java.svg</file> - <file>scalable/loadermods.svg</file> - <file>scalable/log.svg</file> - <file>scalable/minecraft.svg</file> - <file>scalable/multimc.svg</file> - <file>scalable/new.svg</file> - <file>scalable/news.svg</file> - <file>scalable/notes.svg</file> - <file>scalable/patreon.svg</file> - <file>scalable/proxy.svg</file> - <file>scalable/quickmods.svg</file> - <file>scalable/refresh.svg</file> - <file>scalable/resourcepacks.svg</file> - <file>scalable/screenshots.svg</file> - <file>scalable/settings.svg</file> - <file>scalable/status-bad.svg</file> - <file>scalable/status-good.svg</file> - <file>scalable/status-yellow.svg</file> - <file>scalable/viewfolder.svg</file> - <file>scalable/worlds.svg</file> - </qresource> + <qresource prefix="/icons/pe_light"> + <file>index.theme</file> + <file>scalable/about.svg</file> + <file>scalable/accounts.svg</file> + <file>scalable/bug.svg</file> + <file>scalable/centralmods.svg</file> + <file>scalable/checkupdate.svg</file> + <file>scalable/copy.svg</file> + <file>scalable/coremods.svg</file> + <file>scalable/externaltools.svg</file> + <file>scalable/help.svg</file> + <file>scalable/instance-settings.svg</file> + <file>scalable/jarmods.svg</file> + <file>scalable/java.svg</file> + <file>scalable/language.svg</file> + <file>scalable/loadermods.svg</file> + <file>scalable/log.svg</file> + <file>scalable/minecraft.svg</file> + <file>scalable/multimc.svg</file> + <file>scalable/new.svg</file> + <file>scalable/news.svg</file> + <file>scalable/notes.svg</file> + <file>scalable/patreon.svg</file> + <file>scalable/proxy.svg</file> + <file>scalable/quickmods.svg</file> + <file>scalable/refresh.svg</file> + <file>scalable/resourcepacks.svg</file> + <file>scalable/screenshots.svg</file> + <file>scalable/settings.svg</file> + <file>scalable/status-bad.svg</file> + <file>scalable/status-good.svg</file> + <file>scalable/status-yellow.svg</file> + <file>scalable/viewfolder.svg</file> + <file>scalable/worlds.svg</file> + </qresource> </RCC> diff --git a/application/resources/pe_light/scalable/language.svg b/application/resources/pe_light/scalable/language.svg new file mode 100644 index 00000000..57d5e3de --- /dev/null +++ b/application/resources/pe_light/scalable/language.svg @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xml:space="preserve" + enable-background="new 0 0 32 32" + viewBox="0 0 32 32" + y="0px" + x="0px" + id="Calque_1" + version="1.1"><metadata + id="metadata43"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs41" /> +<path + id="path2" + d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z" + fill="#4D4D4D" + clip-rule="evenodd" + fill-rule="evenodd" /> + +<path + id="path6" + d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6 v20C32,29.3,29.3,32,26,32z M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20c1.1,0,2-0.9,2-2V6z" + fill="#F2F2F2" + clip-rule="evenodd" + fill-rule="evenodd" /> +<g + id="g8"> +</g> +<g + id="g10"> +</g> +<g + id="g12"> +</g> +<g + id="g14"> +</g> +<g + id="g16"> +</g> +<g + id="g18"> +</g> +<g + id="g20"> +</g> +<g + id="g22"> +</g> +<g + id="g24"> +</g> +<g + id="g26"> +</g> +<g + id="g28"> +</g> +<g + id="g30"> +</g> +<g + id="g32"> +</g> +<g + id="g34"> +</g> +<g + id="g36"> +</g> +<path + d="m 9.1897911,10.114301 c -0.48384,0 -0.901101,0.341924 -0.996278,0.815883 l -2.029452,10.155455 c -0.110057,0.55031 0.246085,1.083011 0.795381,1.193069 0.556201,0.113982 1.084021,-0.246085 1.193073,-0.79538 l 0.651882,-3.247122 h 2.8699319 l 0.647785,3.247122 c 0.110996,0.557485 0.658771,0.906193 1.197171,0.79538 0.549363,-0.110056 0.905437,-0.642759 0.795381,-1.193069 L 12.285213,10.930184 c -0.09517,-0.473959 -0.512431,-0.815883 -0.996274,-0.815883 z m 0.614982,2.287746 h 0.4345929 l 0.811779,4.063002 H 8.9929941 Z M 20.710512,9.7002137 c -0.561209,0 -1.016777,0.4578063 -1.016777,1.0208743 v 1.016774 h -3.046227 c -0.561205,0 -1.012673,0.457809 -1.012673,1.020876 0,0.563065 0.451466,1.016776 1.012673,1.016776 h 0.274696 c 0.577658,1.85973 1.425829,3.313572 2.37794,4.460692 -0.745862,0.684158 -1.478369,1.245052 -2.271343,1.873654 -0.437271,0.35118 -0.506831,0.995228 -0.155795,1.434967 0.349411,0.439057 0.990377,0.507857 1.426766,0.155794 0.861615,-0.682321 1.601995,-1.252055 2.410742,-1.996652 0.808748,0.744597 1.618828,1.314331 2.480441,1.996652 0.436393,0.352059 1.077353,0.283261 1.426766,-0.155794 0.351038,-0.439739 0.277375,-1.083787 -0.159884,-1.434967 -0.792979,-0.628602 -1.591082,-1.189496 -2.336946,-1.873654 0.952113,-1.147188 1.869915,-2.601029 2.447642,-4.460692 h 0.270592 c 0.561209,0 1.016774,-0.453711 1.016774,-1.016776 0,-0.563067 -0.455565,-1.020876 -1.016774,-1.020876 h -3.111838 v -1.016774 c 0,-0.563068 -0.455567,-1.0208743 -1.016775,-1.0208743 z m -1.668658,4.0753003 h 3.402916 c -0.438423,1.181552 -1.08891,2.13806 -1.734258,2.951929 -0.645345,-0.813869 -1.230238,-1.770309 -1.668658,-2.951929 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.29744864;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect977" /></svg>
\ No newline at end of file diff --git a/application/setupwizard/AnalyticsWizardPage.cpp b/application/setupwizard/AnalyticsWizardPage.cpp index 1109249b..6e063e99 100644 --- a/application/setupwizard/AnalyticsWizardPage.cpp +++ b/application/setupwizard/AnalyticsWizardPage.cpp @@ -8,22 +8,22 @@ #include <BuildConfig.h> AnalyticsWizardPage::AnalyticsWizardPage(QWidget *parent) - : BaseWizardPage(parent) + : BaseWizardPage(parent) { - setObjectName(QStringLiteral("analyticsPage")); - verticalLayout_3 = new QVBoxLayout(this); - verticalLayout_3->setObjectName(QStringLiteral("verticalLayout_3")); - textBrowser = new QTextBrowser(this); - textBrowser->setObjectName(QStringLiteral("textBrowser")); - textBrowser->setAcceptRichText(false); - textBrowser->setOpenExternalLinks(true); - verticalLayout_3->addWidget(textBrowser); + setObjectName(QStringLiteral("analyticsPage")); + verticalLayout_3 = new QVBoxLayout(this); + verticalLayout_3->setObjectName(QStringLiteral("verticalLayout_3")); + textBrowser = new QTextBrowser(this); + textBrowser->setObjectName(QStringLiteral("textBrowser")); + textBrowser->setAcceptRichText(false); + textBrowser->setOpenExternalLinks(true); + verticalLayout_3->addWidget(textBrowser); - checkBox = new QCheckBox(this); - checkBox->setObjectName(QStringLiteral("checkBox")); - checkBox->setChecked(true); - verticalLayout_3->addWidget(checkBox); - retranslate(); + checkBox = new QCheckBox(this); + checkBox->setObjectName(QStringLiteral("checkBox")); + checkBox->setChecked(true); + verticalLayout_3->addWidget(checkBox); + retranslate(); } AnalyticsWizardPage::~AnalyticsWizardPage() @@ -32,29 +32,29 @@ AnalyticsWizardPage::~AnalyticsWizardPage() bool AnalyticsWizardPage::validatePage() { - auto settings = MMC->settings(); - auto status = checkBox->isChecked(); - settings->set("Analytics", status); - return true; + auto settings = MMC->settings(); + auto status = checkBox->isChecked(); + settings->set("Analytics", status); + return true; } void AnalyticsWizardPage::retranslate() { - setTitle(tr("Analytics")); - setSubTitle(tr("We track some anonymous statistics about users.")); - textBrowser->setHtml(tr( - "<html><body>" - "<p>MultiMC sends anonymous usage statistics on every start of the application. This helps us decide what platforms and issues to focus on.</p>" - "<p>The data is processed by Google Analytics, see their <a href=\"https://support.google.com/analytics/answer/6004245?hl=en\">article on the " - "matter</a>.</p>" - "<p>The following data is collected:</p>" - "<ul><li>A random unique ID of the MultiMC installation.<br />It is stored in the application settings (multimc.cfg).</li>" - "<li>Anonymized (partial) IP address.</li>" - "<li>MultiMC version.</li>" - "<li>Operating system name, version and architecture.</li>" - "<li>CPU architecture (kernel architecture on linux).</li>" - "<li>Size of system memory.</li>" - "<li>Java version, architecture and memory settings.</li></ul>" - "<p>If we change the tracked information, you will see this page again.</p></body></html>")); - checkBox->setText(tr("Enable Analytics")); + setTitle(tr("Analytics")); + setSubTitle(tr("We track some anonymous statistics about users.")); + textBrowser->setHtml(tr( + "<html><body>" + "<p>MultiMC sends anonymous usage statistics on every start of the application. This helps us decide what platforms and issues to focus on.</p>" + "<p>The data is processed by Google Analytics, see their <a href=\"https://support.google.com/analytics/answer/6004245?hl=en\">article on the " + "matter</a>.</p>" + "<p>The following data is collected:</p>" + "<ul><li>A random unique ID of the MultiMC installation.<br />It is stored in the application settings (multimc.cfg).</li>" + "<li>Anonymized (partial) IP address.</li>" + "<li>MultiMC version.</li>" + "<li>Operating system name, version and architecture.</li>" + "<li>CPU architecture (kernel architecture on linux).</li>" + "<li>Size of system memory.</li>" + "<li>Java version, architecture and memory settings.</li></ul>" + "<p>If we change the tracked information, you will see this page again.</p></body></html>")); + checkBox->setText(tr("Enable Analytics")); } diff --git a/application/setupwizard/AnalyticsWizardPage.h b/application/setupwizard/AnalyticsWizardPage.h index 03de9cb0..c451db2c 100644 --- a/application/setupwizard/AnalyticsWizardPage.h +++ b/application/setupwizard/AnalyticsWizardPage.h @@ -8,18 +8,18 @@ class QCheckBox; class AnalyticsWizardPage : public BaseWizardPage { - Q_OBJECT; + Q_OBJECT public: - explicit AnalyticsWizardPage(QWidget *parent = Q_NULLPTR); - virtual ~AnalyticsWizardPage(); + explicit AnalyticsWizardPage(QWidget *parent = Q_NULLPTR); + virtual ~AnalyticsWizardPage(); - bool validatePage() override; + bool validatePage() override; protected: - void retranslate() override; + void retranslate() override; private: - QVBoxLayout *verticalLayout_3 = nullptr; - QTextBrowser *textBrowser = nullptr; - QCheckBox *checkBox = nullptr; + QVBoxLayout *verticalLayout_3 = nullptr; + QTextBrowser *textBrowser = nullptr; + QCheckBox *checkBox = nullptr; };
\ No newline at end of file diff --git a/application/setupwizard/BaseWizardPage.h b/application/setupwizard/BaseWizardPage.h index 9ad54e09..72dbecfd 100644 --- a/application/setupwizard/BaseWizardPage.h +++ b/application/setupwizard/BaseWizardPage.h @@ -6,28 +6,28 @@ class BaseWizardPage : public QWizardPage { public: - explicit BaseWizardPage(QWidget *parent = Q_NULLPTR) - : QWizardPage(parent) - { - } - virtual ~BaseWizardPage() {}; + explicit BaseWizardPage(QWidget *parent = Q_NULLPTR) + : QWizardPage(parent) + { + } + virtual ~BaseWizardPage() {}; - virtual bool wantsRefreshButton() - { - return false; - } - virtual void refresh() - { - } + virtual bool wantsRefreshButton() + { + return false; + } + virtual void refresh() + { + } protected: - virtual void retranslate() = 0; - void changeEvent(QEvent * event) override - { - if (event->type() == QEvent::LanguageChange) - { - retranslate(); - } - QWizardPage::changeEvent(event); - } + virtual void retranslate() = 0; + void changeEvent(QEvent * event) override + { + if (event->type() == QEvent::LanguageChange) + { + retranslate(); + } + QWizardPage::changeEvent(event); + } }; diff --git a/application/setupwizard/JavaWizardPage.cpp b/application/setupwizard/JavaWizardPage.cpp index cd2bca46..ad571c09 100644 --- a/application/setupwizard/JavaWizardPage.cpp +++ b/application/setupwizard/JavaWizardPage.cpp @@ -21,76 +21,76 @@ JavaWizardPage::JavaWizardPage(QWidget *parent) - :BaseWizardPage(parent) + :BaseWizardPage(parent) { - setupUi(); + setupUi(); } void JavaWizardPage::setupUi() { - setObjectName(QStringLiteral("javaPage")); - QVBoxLayout * layout = new QVBoxLayout(this); + setObjectName(QStringLiteral("javaPage")); + QVBoxLayout * layout = new QVBoxLayout(this); - m_java_widget = new JavaSettingsWidget(this); - layout->addWidget(m_java_widget); - setLayout(layout); + m_java_widget = new JavaSettingsWidget(this); + layout->addWidget(m_java_widget); + setLayout(layout); - retranslate(); + retranslate(); } void JavaWizardPage::refresh() { - m_java_widget->refresh(); + m_java_widget->refresh(); } void JavaWizardPage::initializePage() { - m_java_widget->initialize(); + m_java_widget->initialize(); } bool JavaWizardPage::wantsRefreshButton() { - return true; + return true; } bool JavaWizardPage::validatePage() { - auto settings = MMC->settings(); - auto result = m_java_widget->validate(); - switch(result) - { - default: - case JavaSettingsWidget::ValidationStatus::Bad: - { - return false; - } - case JavaSettingsWidget::ValidationStatus::AllOK: - { - settings->set("JavaPath", m_java_widget->javaPath()); - } - case JavaSettingsWidget::ValidationStatus::JavaBad: - { - // Memory - auto s = MMC->settings(); - s->set("MinMemAlloc", m_java_widget->minHeapSize()); - s->set("MaxMemAlloc", m_java_widget->maxHeapSize()); - if (m_java_widget->permGenEnabled()) - { - s->set("PermGen", m_java_widget->permGenSize()); - } - else - { - s->reset("PermGen"); - } - return true; - } - } + auto settings = MMC->settings(); + auto result = m_java_widget->validate(); + switch(result) + { + default: + case JavaSettingsWidget::ValidationStatus::Bad: + { + return false; + } + case JavaSettingsWidget::ValidationStatus::AllOK: + { + settings->set("JavaPath", m_java_widget->javaPath()); + } + case JavaSettingsWidget::ValidationStatus::JavaBad: + { + // Memory + auto s = MMC->settings(); + s->set("MinMemAlloc", m_java_widget->minHeapSize()); + s->set("MaxMemAlloc", m_java_widget->maxHeapSize()); + if (m_java_widget->permGenEnabled()) + { + s->set("PermGen", m_java_widget->permGenSize()); + } + else + { + s->reset("PermGen"); + } + return true; + } + } } void JavaWizardPage::retranslate() { - setTitle(tr("Java")); - setSubTitle(tr("You do not have a working Java set up yet or it went missing.\n" - "Please select one of the following or browse for a java executable.")); - m_java_widget->retranslate(); + setTitle(tr("Java")); + setSubTitle(tr("You do not have a working Java set up yet or it went missing.\n" + "Please select one of the following or browse for a java executable.")); + m_java_widget->retranslate(); } diff --git a/application/setupwizard/JavaWizardPage.h b/application/setupwizard/JavaWizardPage.h index 4ea31d24..0d749039 100644 --- a/application/setupwizard/JavaWizardPage.h +++ b/application/setupwizard/JavaWizardPage.h @@ -6,24 +6,24 @@ class JavaSettingsWidget; class JavaWizardPage : public BaseWizardPage { - Q_OBJECT; + Q_OBJECT public: - explicit JavaWizardPage(QWidget *parent = Q_NULLPTR); + explicit JavaWizardPage(QWidget *parent = Q_NULLPTR); - virtual ~JavaWizardPage() - { - }; + virtual ~JavaWizardPage() + { + }; - bool wantsRefreshButton() override; - void refresh() override; - void initializePage() override; - bool validatePage() override; + bool wantsRefreshButton() override; + void refresh() override; + void initializePage() override; + bool validatePage() override; protected: /* methods */ - void setupUi(); - void retranslate() override; + void setupUi(); + void retranslate() override; private: /* data */ - JavaSettingsWidget *m_java_widget = nullptr; + JavaSettingsWidget *m_java_widget = nullptr; }; diff --git a/application/setupwizard/LanguageWizardPage.cpp b/application/setupwizard/LanguageWizardPage.cpp index b884c91a..ca93c6f5 100644 --- a/application/setupwizard/LanguageWizardPage.cpp +++ b/application/setupwizard/LanguageWizardPage.cpp @@ -2,25 +2,19 @@ #include <MultiMC.h> #include <translations/TranslationsModel.h> +#include "widgets/LanguageSelectionWidget.h" #include <QVBoxLayout> -#include <QListView> LanguageWizardPage::LanguageWizardPage(QWidget *parent) - : BaseWizardPage(parent) + : BaseWizardPage(parent) { - setObjectName(QStringLiteral("languagePage")); - verticalLayout = new QVBoxLayout(this); - verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - languageView = new QListView(this); - languageView->setObjectName(QStringLiteral("languageView")); - verticalLayout->addWidget(languageView); - retranslate(); + setObjectName(QStringLiteral("languagePage")); + auto layout = new QVBoxLayout(this); + mainWidget = new LanguageSelectionWidget(this); + layout->setContentsMargins(0,0,0,0); + layout->addWidget(mainWidget); - auto translations = MMC->translations(); - auto index = translations->selectedIndex(); - languageView->setModel(translations.get()); - languageView->setCurrentIndex(index); - connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageWizardPage::languageRowChanged); + retranslate(); } LanguageWizardPage::~LanguageWizardPage() @@ -29,38 +23,26 @@ LanguageWizardPage::~LanguageWizardPage() bool LanguageWizardPage::wantsRefreshButton() { - return true; + return true; } void LanguageWizardPage::refresh() { - auto translations = MMC->translations(); - translations->downloadIndex(); + auto translations = MMC->translations(); + translations->downloadIndex(); } bool LanguageWizardPage::validatePage() { - auto settings = MMC->settings(); - auto translations = MMC->translations(); - QString key = translations->data(languageView->currentIndex(), Qt::UserRole).toString(); - settings->set("Language", key); - return true; + auto settings = MMC->settings(); + QString key = mainWidget->getSelectedLanguageKey(); + settings->set("Language", key); + return true; } void LanguageWizardPage::retranslate() { - setTitle(tr("Language")); - setSubTitle(tr("Select the language to use in MultiMC")); -} - -void LanguageWizardPage::languageRowChanged(const QModelIndex ¤t, const QModelIndex &previous) -{ - if (current == previous) - { - return; - } - auto translations = MMC->translations(); - QString key = translations->data(current, Qt::UserRole).toString(); - translations->selectLanguage(key); - translations->updateLanguage(key); + setTitle(tr("Language")); + setSubTitle(tr("Select the language to use in MultiMC")); + mainWidget->retranslate(); } diff --git a/application/setupwizard/LanguageWizardPage.h b/application/setupwizard/LanguageWizardPage.h index 8b55d57f..45a0e5c0 100644 --- a/application/setupwizard/LanguageWizardPage.h +++ b/application/setupwizard/LanguageWizardPage.h @@ -2,30 +2,25 @@ #include "BaseWizardPage.h" -class QVBoxLayout; -class QListView; +class LanguageSelectionWidget; class LanguageWizardPage : public BaseWizardPage { - Q_OBJECT; + Q_OBJECT public: - explicit LanguageWizardPage(QWidget *parent = Q_NULLPTR); + explicit LanguageWizardPage(QWidget *parent = Q_NULLPTR); - virtual ~LanguageWizardPage(); + virtual ~LanguageWizardPage(); - bool wantsRefreshButton() override; + bool wantsRefreshButton() override; - void refresh() override; + void refresh() override; - bool validatePage() override; + bool validatePage() override; protected: - void retranslate() override; - -protected slots: - void languageRowChanged(const QModelIndex ¤t, const QModelIndex &previous); + void retranslate() override; private: - QVBoxLayout *verticalLayout = nullptr; - QListView *languageView = nullptr; + LanguageSelectionWidget *mainWidget = nullptr; }; diff --git a/application/setupwizard/SetupWizard.cpp b/application/setupwizard/SetupWizard.cpp index c4b21713..5c2d60ac 100644 --- a/application/setupwizard/SetupWizard.cpp +++ b/application/setupwizard/SetupWizard.cpp @@ -12,74 +12,74 @@ SetupWizard::SetupWizard(QWidget *parent) : QWizard(parent) { - setObjectName(QStringLiteral("SetupWizard")); - resize(615, 659); - // make it ugly everywhere to avoid variability in theming - setWizardStyle(QWizard::ClassicStyle); - setOptions(QWizard::NoCancelButton | QWizard::IndependentPages | QWizard::HaveCustomButton1); + setObjectName(QStringLiteral("SetupWizard")); + resize(615, 659); + // make it ugly everywhere to avoid variability in theming + setWizardStyle(QWizard::ClassicStyle); + setOptions(QWizard::NoCancelButton | QWizard::IndependentPages | QWizard::HaveCustomButton1); - retranslate(); + retranslate(); - connect(this, &QWizard::currentIdChanged, this, &SetupWizard::pageChanged); + connect(this, &QWizard::currentIdChanged, this, &SetupWizard::pageChanged); } void SetupWizard::retranslate() { - setButtonText(QWizard::NextButton, tr("&Next >")); - setButtonText(QWizard::BackButton, tr("< &Back")); - setButtonText(QWizard::FinishButton, tr("&Finish")); - setButtonText(QWizard::CustomButton1, tr("&Refresh")); - setWindowTitle(tr("MultiMC Quick Setup")); + setButtonText(QWizard::NextButton, tr("&Next >")); + setButtonText(QWizard::BackButton, tr("< &Back")); + setButtonText(QWizard::FinishButton, tr("&Finish")); + setButtonText(QWizard::CustomButton1, tr("&Refresh")); + setWindowTitle(tr("MultiMC Quick Setup")); } BaseWizardPage * SetupWizard::getBasePage(int id) { - if(id == -1) - return nullptr; - auto pagePtr = page(id); - if(!pagePtr) - return nullptr; - return dynamic_cast<BaseWizardPage *>(pagePtr); + if(id == -1) + return nullptr; + auto pagePtr = page(id); + if(!pagePtr) + return nullptr; + return dynamic_cast<BaseWizardPage *>(pagePtr); } BaseWizardPage * SetupWizard::getCurrentBasePage() { - return getBasePage(currentId()); + return getBasePage(currentId()); } void SetupWizard::pageChanged(int id) { - auto basePagePtr = getBasePage(id); - if(!basePagePtr) - { - return; - } - if(basePagePtr->wantsRefreshButton()) - { - setButtonLayout({QWizard::CustomButton1, QWizard::Stretch, QWizard::BackButton, QWizard::NextButton, QWizard::FinishButton}); - auto customButton = button(QWizard::CustomButton1); - connect(customButton, &QAbstractButton::pressed, [&](){ - auto basePagePtr = getCurrentBasePage(); - if(basePagePtr) - { - basePagePtr->refresh(); - } - }); - } - else - { - setButtonLayout({QWizard::Stretch, QWizard::BackButton, QWizard::NextButton, QWizard::FinishButton}); - } + auto basePagePtr = getBasePage(id); + if(!basePagePtr) + { + return; + } + if(basePagePtr->wantsRefreshButton()) + { + setButtonLayout({QWizard::CustomButton1, QWizard::Stretch, QWizard::BackButton, QWizard::NextButton, QWizard::FinishButton}); + auto customButton = button(QWizard::CustomButton1); + connect(customButton, &QAbstractButton::pressed, [&](){ + auto basePagePtr = getCurrentBasePage(); + if(basePagePtr) + { + basePagePtr->refresh(); + } + }); + } + else + { + setButtonLayout({QWizard::Stretch, QWizard::BackButton, QWizard::NextButton, QWizard::FinishButton}); + } } void SetupWizard::changeEvent(QEvent *event) { - if (event->type() == QEvent::LanguageChange) - { - retranslate(); - } - QWizard::changeEvent(event); + if (event->type() == QEvent::LanguageChange) + { + retranslate(); + } + QWizard::changeEvent(event); } SetupWizard::~SetupWizard() diff --git a/application/setupwizard/SetupWizard.h b/application/setupwizard/SetupWizard.h index e3292997..08b0d805 100644 --- a/application/setupwizard/SetupWizard.h +++ b/application/setupwizard/SetupWizard.h @@ -1,4 +1,4 @@ -/* Copyright 2017-2018 MultiMC Contributors +/* Copyright 2017-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,20 +26,20 @@ class BaseWizardPage; class SetupWizard : public QWizard { - Q_OBJECT + Q_OBJECT public: /* con/destructors */ - explicit SetupWizard(QWidget *parent = 0); - virtual ~SetupWizard(); + explicit SetupWizard(QWidget *parent = 0); + virtual ~SetupWizard(); - void changeEvent(QEvent * event) override; - BaseWizardPage *getBasePage(int id); - BaseWizardPage *getCurrentBasePage(); + void changeEvent(QEvent * event) override; + BaseWizardPage *getBasePage(int id); + BaseWizardPage *getCurrentBasePage(); private slots: - void pageChanged(int id); + void pageChanged(int id); private: /* methods */ - void retranslate(); + void retranslate(); }; diff --git a/application/themes/BrightTheme.cpp b/application/themes/BrightTheme.cpp index 2763c982..b9188bdd 100644 --- a/application/themes/BrightTheme.cpp +++ b/application/themes/BrightTheme.cpp @@ -2,55 +2,55 @@ QString BrightTheme::id() { - return "bright"; + return "bright"; } QString BrightTheme::name() { - return QObject::tr("Bright"); + return QObject::tr("Bright"); } bool BrightTheme::hasColorScheme() { - return true; + return true; } QPalette BrightTheme::colorScheme() { - QPalette brightPalette; - brightPalette.setColor(QPalette::Window, QColor(239,240,241)); - brightPalette.setColor(QPalette::WindowText, QColor(49,54,59)); - brightPalette.setColor(QPalette::Base, QColor(252,252,252)); - brightPalette.setColor(QPalette::AlternateBase, QColor(239,240,241)); - brightPalette.setColor(QPalette::ToolTipBase, QColor(49,54,59)); - brightPalette.setColor(QPalette::ToolTipText, QColor(239,240,241)); - brightPalette.setColor(QPalette::Text, QColor(49,54,59)); - brightPalette.setColor(QPalette::Button, QColor(239,240,241)); - brightPalette.setColor(QPalette::ButtonText, QColor(49,54,59)); - brightPalette.setColor(QPalette::BrightText, Qt::red); - brightPalette.setColor(QPalette::Link, QColor(41, 128, 185)); - brightPalette.setColor(QPalette::Highlight, QColor(61, 174, 233)); - brightPalette.setColor(QPalette::HighlightedText, QColor(239,240,241)); - return fadeInactive(brightPalette, fadeAmount(), fadeColor()); + QPalette brightPalette; + brightPalette.setColor(QPalette::Window, QColor(239,240,241)); + brightPalette.setColor(QPalette::WindowText, QColor(49,54,59)); + brightPalette.setColor(QPalette::Base, QColor(252,252,252)); + brightPalette.setColor(QPalette::AlternateBase, QColor(239,240,241)); + brightPalette.setColor(QPalette::ToolTipBase, QColor(49,54,59)); + brightPalette.setColor(QPalette::ToolTipText, QColor(239,240,241)); + brightPalette.setColor(QPalette::Text, QColor(49,54,59)); + brightPalette.setColor(QPalette::Button, QColor(239,240,241)); + brightPalette.setColor(QPalette::ButtonText, QColor(49,54,59)); + brightPalette.setColor(QPalette::BrightText, Qt::red); + brightPalette.setColor(QPalette::Link, QColor(41, 128, 185)); + brightPalette.setColor(QPalette::Highlight, QColor(61, 174, 233)); + brightPalette.setColor(QPalette::HighlightedText, QColor(239,240,241)); + return fadeInactive(brightPalette, fadeAmount(), fadeColor()); } double BrightTheme::fadeAmount() { - return 0.5; + return 0.5; } QColor BrightTheme::fadeColor() { - return QColor(239,240,241); + return QColor(239,240,241); } bool BrightTheme::hasStyleSheet() { - return false; + return false; } QString BrightTheme::appStyleSheet() { - return QString(); + return QString(); } diff --git a/application/themes/BrightTheme.h b/application/themes/BrightTheme.h index 31b31b2d..c61f52d5 100644 --- a/application/themes/BrightTheme.h +++ b/application/themes/BrightTheme.h @@ -5,15 +5,15 @@ class BrightTheme: public FusionTheme { public: - virtual ~BrightTheme() {} + virtual ~BrightTheme() {} - QString id() override; - QString name() override; - bool hasStyleSheet() override; - QString appStyleSheet() override; - bool hasColorScheme() override; - QPalette colorScheme() override; - double fadeAmount() override; - QColor fadeColor() override; + QString id() override; + QString name() override; + bool hasStyleSheet() override; + QString appStyleSheet() override; + bool hasColorScheme() override; + QPalette colorScheme() override; + double fadeAmount() override; + QColor fadeColor() override; }; diff --git a/application/themes/CustomTheme.cpp b/application/themes/CustomTheme.cpp index a6c7bf8c..3e3e27de 100644 --- a/application/themes/CustomTheme.cpp +++ b/application/themes/CustomTheme.cpp @@ -8,237 +8,237 @@ const char * styleFile = "themeStyle.css"; static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAmount, QColor &fadeColor, QString &name, QString &widgets) { - QFileInfo pathInfo(path); - if(pathInfo.exists() && pathInfo.isFile()) - { - try - { - auto doc = Json::requireDocument(path, "Theme JSON file"); - const QJsonObject root = doc.object(); - name = Json::requireString(root, "name", "Theme name"); - widgets = Json::requireString(root, "widgets", "Qt widget theme"); - auto colorsRoot = Json::requireObject(root, "colors", "colors object"); - auto readColor = [&](QString colorName) -> QColor - { - auto colorValue = Json::ensureString(colorsRoot, colorName, QString()); - if(!colorValue.isEmpty()) - { - QColor color(colorValue); - if(!color.isValid()) - { - qWarning() << "Color value" << colorValue << "for" << colorName << "was not recognized."; - return QColor(); - } - return color; - } - return QColor(); - }; - auto readAndSetColor = [&](QPalette::ColorRole role, QString colorName) - { - auto color = readColor(colorName); - if(color.isValid()) - { - palette.setColor(role, color); - } - else - { - qDebug() << "Color value for" << colorName << "was not present."; - } - }; - - // palette - readAndSetColor(QPalette::Window, "Window"); - readAndSetColor(QPalette::WindowText, "WindowText"); - readAndSetColor(QPalette::Base, "Base"); - readAndSetColor(QPalette::AlternateBase, "AlternateBase"); - readAndSetColor(QPalette::ToolTipBase, "ToolTipBase"); - readAndSetColor(QPalette::ToolTipText, "ToolTipText"); - readAndSetColor(QPalette::Text, "Text"); - readAndSetColor(QPalette::Button, "Button"); - readAndSetColor(QPalette::ButtonText, "ButtonText"); - readAndSetColor(QPalette::BrightText, "BrightText"); - readAndSetColor(QPalette::Link, "Link"); - readAndSetColor(QPalette::Highlight, "Highlight"); - readAndSetColor(QPalette::HighlightedText, "HighlightedText"); - - //fade - fadeColor = readColor("fadeColor"); - fadeAmount = Json::ensureDouble(colorsRoot, "fadeAmount", 0.5, "fade amount"); - - } - catch(Exception e) - { - qWarning() << "Couldn't load theme json: " << e.cause(); - return false; - } - } - else - { - qDebug() << "No theme json present."; - return false; - } - return true; + QFileInfo pathInfo(path); + if(pathInfo.exists() && pathInfo.isFile()) + { + try + { + auto doc = Json::requireDocument(path, "Theme JSON file"); + const QJsonObject root = doc.object(); + name = Json::requireString(root, "name", "Theme name"); + widgets = Json::requireString(root, "widgets", "Qt widget theme"); + auto colorsRoot = Json::requireObject(root, "colors", "colors object"); + auto readColor = [&](QString colorName) -> QColor + { + auto colorValue = Json::ensureString(colorsRoot, colorName, QString()); + if(!colorValue.isEmpty()) + { + QColor color(colorValue); + if(!color.isValid()) + { + qWarning() << "Color value" << colorValue << "for" << colorName << "was not recognized."; + return QColor(); + } + return color; + } + return QColor(); + }; + auto readAndSetColor = [&](QPalette::ColorRole role, QString colorName) + { + auto color = readColor(colorName); + if(color.isValid()) + { + palette.setColor(role, color); + } + else + { + qDebug() << "Color value for" << colorName << "was not present."; + } + }; + + // palette + readAndSetColor(QPalette::Window, "Window"); + readAndSetColor(QPalette::WindowText, "WindowText"); + readAndSetColor(QPalette::Base, "Base"); + readAndSetColor(QPalette::AlternateBase, "AlternateBase"); + readAndSetColor(QPalette::ToolTipBase, "ToolTipBase"); + readAndSetColor(QPalette::ToolTipText, "ToolTipText"); + readAndSetColor(QPalette::Text, "Text"); + readAndSetColor(QPalette::Button, "Button"); + readAndSetColor(QPalette::ButtonText, "ButtonText"); + readAndSetColor(QPalette::BrightText, "BrightText"); + readAndSetColor(QPalette::Link, "Link"); + readAndSetColor(QPalette::Highlight, "Highlight"); + readAndSetColor(QPalette::HighlightedText, "HighlightedText"); + + //fade + fadeColor = readColor("fadeColor"); + fadeAmount = Json::ensureDouble(colorsRoot, "fadeAmount", 0.5, "fade amount"); + + } + catch (const Exception &e) + { + qWarning() << "Couldn't load theme json: " << e.cause(); + return false; + } + } + else + { + qDebug() << "No theme json present."; + return false; + } + return true; } static bool writeThemeJson(const QString &path, const QPalette &palette, double fadeAmount, QColor fadeColor, QString name, QString widgets) { - QJsonObject rootObj; - rootObj.insert("name", name); - rootObj.insert("widgets", widgets); - - QJsonObject colorsObj; - auto insertColor = [&](QPalette::ColorRole role, QString colorName) - { - colorsObj.insert(colorName, palette.color(role).name()); - }; - - // palette - insertColor(QPalette::Window, "Window"); - insertColor(QPalette::WindowText, "WindowText"); - insertColor(QPalette::Base, "Base"); - insertColor(QPalette::AlternateBase, "AlternateBase"); - insertColor(QPalette::ToolTipBase, "ToolTipBase"); - insertColor(QPalette::ToolTipText, "ToolTipText"); - insertColor(QPalette::Text, "Text"); - insertColor(QPalette::Button, "Button"); - insertColor(QPalette::ButtonText, "ButtonText"); - insertColor(QPalette::BrightText, "BrightText"); - insertColor(QPalette::Link, "Link"); - insertColor(QPalette::Highlight, "Highlight"); - insertColor(QPalette::HighlightedText, "HighlightedText"); - - // fade - colorsObj.insert("fadeColor", fadeColor.name()); - colorsObj.insert("fadeAmount", fadeAmount); - - rootObj.insert("colors", colorsObj); - try - { - Json::write(rootObj, path); - return true; - } - catch (Exception e) - { - qWarning() << "Failed to write theme json to" << path; - return false; - } + QJsonObject rootObj; + rootObj.insert("name", name); + rootObj.insert("widgets", widgets); + + QJsonObject colorsObj; + auto insertColor = [&](QPalette::ColorRole role, QString colorName) + { + colorsObj.insert(colorName, palette.color(role).name()); + }; + + // palette + insertColor(QPalette::Window, "Window"); + insertColor(QPalette::WindowText, "WindowText"); + insertColor(QPalette::Base, "Base"); + insertColor(QPalette::AlternateBase, "AlternateBase"); + insertColor(QPalette::ToolTipBase, "ToolTipBase"); + insertColor(QPalette::ToolTipText, "ToolTipText"); + insertColor(QPalette::Text, "Text"); + insertColor(QPalette::Button, "Button"); + insertColor(QPalette::ButtonText, "ButtonText"); + insertColor(QPalette::BrightText, "BrightText"); + insertColor(QPalette::Link, "Link"); + insertColor(QPalette::Highlight, "Highlight"); + insertColor(QPalette::HighlightedText, "HighlightedText"); + + // fade + colorsObj.insert("fadeColor", fadeColor.name()); + colorsObj.insert("fadeAmount", fadeAmount); + + rootObj.insert("colors", colorsObj); + try + { + Json::write(rootObj, path); + return true; + } + catch (const Exception &e) + { + qWarning() << "Failed to write theme json to" << path; + return false; + } } CustomTheme::CustomTheme(ITheme* baseTheme, QString folder) { - m_id = folder; - QString path = FS::PathCombine("themes", m_id); - QString pathResources = FS::PathCombine("themes", m_id, "resources"); - - qDebug() << "Loading theme" << m_id; - - if(!FS::ensureFolderPathExists(path) || !FS::ensureFolderPathExists(pathResources)) - { - qWarning() << "couldn't create folder for theme!"; - m_palette = baseTheme->colorScheme(); - m_styleSheet = baseTheme->appStyleSheet(); - return; - } - - auto themeFilePath = FS::PathCombine(path, themeFile); - - m_palette = baseTheme->colorScheme(); - if (!readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets)) - { - m_name = "Custom"; - m_palette = baseTheme->colorScheme(); - m_fadeColor = baseTheme->fadeColor(); - m_fadeAmount = baseTheme->fadeAmount(); - m_widgets = baseTheme->qtTheme(); - - QFileInfo info(themeFilePath); - if(!info.exists()) - { - writeThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, "Custom", m_widgets); - } - } - else - { - m_palette = fadeInactive(m_palette, m_fadeAmount, m_fadeColor); - } - - auto cssFilePath = FS::PathCombine(path, styleFile); - QFileInfo info (cssFilePath); - if(info.isFile()) - { - try - { - // TODO: validate css? - m_styleSheet = QString::fromUtf8(FS::read(cssFilePath)); - } - catch(Exception e) - { - qWarning() << "Couldn't load css:" << e.cause() << "from" << cssFilePath; - m_styleSheet = baseTheme->appStyleSheet(); - } - } - else - { - qDebug() << "No theme css present."; - m_styleSheet = baseTheme->appStyleSheet(); - try - { - FS::write(cssFilePath, m_styleSheet.toUtf8()); - } - catch(Exception e) - { - qWarning() << "Couldn't write css:" << e.cause() << "to" << cssFilePath; - } - } + m_id = folder; + QString path = FS::PathCombine("themes", m_id); + QString pathResources = FS::PathCombine("themes", m_id, "resources"); + + qDebug() << "Loading theme" << m_id; + + if(!FS::ensureFolderPathExists(path) || !FS::ensureFolderPathExists(pathResources)) + { + qWarning() << "couldn't create folder for theme!"; + m_palette = baseTheme->colorScheme(); + m_styleSheet = baseTheme->appStyleSheet(); + return; + } + + auto themeFilePath = FS::PathCombine(path, themeFile); + + m_palette = baseTheme->colorScheme(); + if (!readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets)) + { + m_name = "Custom"; + m_palette = baseTheme->colorScheme(); + m_fadeColor = baseTheme->fadeColor(); + m_fadeAmount = baseTheme->fadeAmount(); + m_widgets = baseTheme->qtTheme(); + + QFileInfo info(themeFilePath); + if(!info.exists()) + { + writeThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, "Custom", m_widgets); + } + } + else + { + m_palette = fadeInactive(m_palette, m_fadeAmount, m_fadeColor); + } + + auto cssFilePath = FS::PathCombine(path, styleFile); + QFileInfo info (cssFilePath); + if(info.isFile()) + { + try + { + // TODO: validate css? + m_styleSheet = QString::fromUtf8(FS::read(cssFilePath)); + } + catch (const Exception &e) + { + qWarning() << "Couldn't load css:" << e.cause() << "from" << cssFilePath; + m_styleSheet = baseTheme->appStyleSheet(); + } + } + else + { + qDebug() << "No theme css present."; + m_styleSheet = baseTheme->appStyleSheet(); + try + { + FS::write(cssFilePath, m_styleSheet.toUtf8()); + } + catch (const Exception &e) + { + qWarning() << "Couldn't write css:" << e.cause() << "to" << cssFilePath; + } + } } QStringList CustomTheme::searchPaths() { - return { FS::PathCombine("themes", m_id, "resources") }; + return { FS::PathCombine("themes", m_id, "resources") }; } QString CustomTheme::id() { - return m_id; + return m_id; } QString CustomTheme::name() { - return m_name; + return m_name; } bool CustomTheme::hasColorScheme() { - return true; + return true; } QPalette CustomTheme::colorScheme() { - return m_palette; + return m_palette; } bool CustomTheme::hasStyleSheet() { - return true; + return true; } QString CustomTheme::appStyleSheet() { - return m_styleSheet; + return m_styleSheet; } double CustomTheme::fadeAmount() { - return m_fadeAmount; + return m_fadeAmount; } QColor CustomTheme::fadeColor() { - return m_fadeColor; + return m_fadeColor; } QString CustomTheme::qtTheme() { - return m_widgets; + return m_widgets; } diff --git a/application/themes/CustomTheme.h b/application/themes/CustomTheme.h index 9023b13e..d216895d 100644 --- a/application/themes/CustomTheme.h +++ b/application/themes/CustomTheme.h @@ -5,27 +5,27 @@ class CustomTheme: public ITheme { public: - CustomTheme(ITheme * baseTheme, QString folder); - virtual ~CustomTheme() {} + CustomTheme(ITheme * baseTheme, QString folder); + virtual ~CustomTheme() {} - QString id() override; - QString name() override; - bool hasStyleSheet() override; - QString appStyleSheet() override; - bool hasColorScheme() override; - QPalette colorScheme() override; - double fadeAmount() override; - QColor fadeColor() override; - QString qtTheme() override; - QStringList searchPaths() override; + QString id() override; + QString name() override; + bool hasStyleSheet() override; + QString appStyleSheet() override; + bool hasColorScheme() override; + QPalette colorScheme() override; + double fadeAmount() override; + QColor fadeColor() override; + QString qtTheme() override; + QStringList searchPaths() override; private: /* data */ - QPalette m_palette; - QColor m_fadeColor; - double m_fadeAmount; - QString m_styleSheet; - QString m_name; - QString m_id; - QString m_widgets; + QPalette m_palette; + QColor m_fadeColor; + double m_fadeAmount; + QString m_styleSheet; + QString m_name; + QString m_id; + QString m_widgets; }; diff --git a/application/themes/DarkTheme.cpp b/application/themes/DarkTheme.cpp index cf4a81e1..31ecd559 100644 --- a/application/themes/DarkTheme.cpp +++ b/application/themes/DarkTheme.cpp @@ -2,54 +2,54 @@ QString DarkTheme::id() { - return "dark"; + return "dark"; } QString DarkTheme::name() { - return QObject::tr("Dark"); + return QObject::tr("Dark"); } bool DarkTheme::hasColorScheme() { - return true; + return true; } QPalette DarkTheme::colorScheme() { - QPalette darkPalette; - darkPalette.setColor(QPalette::Window, QColor(49,54,59)); - darkPalette.setColor(QPalette::WindowText, Qt::white); - darkPalette.setColor(QPalette::Base, QColor(35,38,41)); - darkPalette.setColor(QPalette::AlternateBase, QColor(49,54,59)); - darkPalette.setColor(QPalette::ToolTipBase, Qt::white); - darkPalette.setColor(QPalette::ToolTipText, Qt::white); - darkPalette.setColor(QPalette::Text, Qt::white); - darkPalette.setColor(QPalette::Button, QColor(49,54,59)); - darkPalette.setColor(QPalette::ButtonText, Qt::white); - darkPalette.setColor(QPalette::BrightText, Qt::red); - darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); - darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); - darkPalette.setColor(QPalette::HighlightedText, Qt::black); - return fadeInactive(darkPalette, fadeAmount(), fadeColor()); + QPalette darkPalette; + darkPalette.setColor(QPalette::Window, QColor(49,54,59)); + darkPalette.setColor(QPalette::WindowText, Qt::white); + darkPalette.setColor(QPalette::Base, QColor(35,38,41)); + darkPalette.setColor(QPalette::AlternateBase, QColor(49,54,59)); + darkPalette.setColor(QPalette::ToolTipBase, Qt::white); + darkPalette.setColor(QPalette::ToolTipText, Qt::white); + darkPalette.setColor(QPalette::Text, Qt::white); + darkPalette.setColor(QPalette::Button, QColor(49,54,59)); + darkPalette.setColor(QPalette::ButtonText, Qt::white); + darkPalette.setColor(QPalette::BrightText, Qt::red); + darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); + darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); + darkPalette.setColor(QPalette::HighlightedText, Qt::black); + return fadeInactive(darkPalette, fadeAmount(), fadeColor()); } double DarkTheme::fadeAmount() { - return 0.5; + return 0.5; } QColor DarkTheme::fadeColor() { - return QColor(49,54,59); + return QColor(49,54,59); } bool DarkTheme::hasStyleSheet() { - return true; + return true; } QString DarkTheme::appStyleSheet() { - return "QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }"; + return "QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }"; } diff --git a/application/themes/DarkTheme.h b/application/themes/DarkTheme.h index f3c18e22..9bd2f343 100644 --- a/application/themes/DarkTheme.h +++ b/application/themes/DarkTheme.h @@ -5,14 +5,14 @@ class DarkTheme: public FusionTheme { public: - virtual ~DarkTheme() {} + virtual ~DarkTheme() {} - QString id() override; - QString name() override; - bool hasStyleSheet() override; - QString appStyleSheet() override; - bool hasColorScheme() override; - QPalette colorScheme() override; - double fadeAmount() override; - QColor fadeColor() override; + QString id() override; + QString name() override; + bool hasStyleSheet() override; + QString appStyleSheet() override; + bool hasColorScheme() override; + QPalette colorScheme() override; + double fadeAmount() override; + QColor fadeColor() override; }; diff --git a/application/themes/FusionTheme.cpp b/application/themes/FusionTheme.cpp index aaf7b3d5..cf3286ba 100644 --- a/application/themes/FusionTheme.cpp +++ b/application/themes/FusionTheme.cpp @@ -2,5 +2,5 @@ QString FusionTheme::qtTheme() { - return "Fusion"; + return "Fusion"; } diff --git a/application/themes/FusionTheme.h b/application/themes/FusionTheme.h index e2d9014c..ee34245a 100644 --- a/application/themes/FusionTheme.h +++ b/application/themes/FusionTheme.h @@ -5,7 +5,7 @@ class FusionTheme: public ITheme { public: - virtual ~FusionTheme() {} + virtual ~FusionTheme() {} - QString qtTheme() override; + QString qtTheme() override; }; diff --git a/application/themes/ITheme.cpp b/application/themes/ITheme.cpp index b1cecf57..bfec87e7 100644 --- a/application/themes/ITheme.cpp +++ b/application/themes/ITheme.cpp @@ -6,42 +6,42 @@ void ITheme::apply(bool) { - QApplication::setStyle(QStyleFactory::create(qtTheme())); - if(hasColorScheme()) - { - QApplication::setPalette(colorScheme()); - } - if(hasStyleSheet()) - { - MMC->setStyleSheet(appStyleSheet()); - } - else - { - MMC->setStyleSheet(QString()); - } - QDir::setSearchPaths("theme", searchPaths()); + QApplication::setStyle(QStyleFactory::create(qtTheme())); + if(hasColorScheme()) + { + QApplication::setPalette(colorScheme()); + } + if(hasStyleSheet()) + { + MMC->setStyleSheet(appStyleSheet()); + } + else + { + MMC->setStyleSheet(QString()); + } + QDir::setSearchPaths("theme", searchPaths()); } QPalette ITheme::fadeInactive(QPalette in, qreal bias, QColor color) { - auto blend = [&in, bias, color](QPalette::ColorRole role) - { - QColor from = in.color(QPalette::Active, role); - QColor blended = Rainbow::mix(from, color, bias); - in.setColor(QPalette::Disabled, role, blended); - }; - blend(QPalette::Window); - blend(QPalette::WindowText); - blend(QPalette::Base); - blend(QPalette::AlternateBase); - blend(QPalette::ToolTipBase); - blend(QPalette::ToolTipText); - blend(QPalette::Text); - blend(QPalette::Button); - blend(QPalette::ButtonText); - blend(QPalette::BrightText); - blend(QPalette::Link); - blend(QPalette::Highlight); - blend(QPalette::HighlightedText); - return in; + auto blend = [&in, bias, color](QPalette::ColorRole role) + { + QColor from = in.color(QPalette::Active, role); + QColor blended = Rainbow::mix(from, color, bias); + in.setColor(QPalette::Disabled, role, blended); + }; + blend(QPalette::Window); + blend(QPalette::WindowText); + blend(QPalette::Base); + blend(QPalette::AlternateBase); + blend(QPalette::ToolTipBase); + blend(QPalette::ToolTipText); + blend(QPalette::Text); + blend(QPalette::Button); + blend(QPalette::ButtonText); + blend(QPalette::BrightText); + blend(QPalette::Link); + blend(QPalette::Highlight); + blend(QPalette::HighlightedText); + return in; } diff --git a/application/themes/ITheme.h b/application/themes/ITheme.h index b75001c2..c2347cf6 100644 --- a/application/themes/ITheme.h +++ b/application/themes/ITheme.h @@ -7,21 +7,21 @@ class QStyle; class ITheme { public: - virtual ~ITheme() {} - virtual void apply(bool initial); - virtual QString id() = 0; - virtual QString name() = 0; - virtual bool hasStyleSheet() = 0; - virtual QString appStyleSheet() = 0; - virtual QString qtTheme() = 0; - virtual bool hasColorScheme() = 0; - virtual QPalette colorScheme() = 0; - virtual QColor fadeColor() = 0; - virtual double fadeAmount() = 0; - virtual QStringList searchPaths() - { - return {}; - } + virtual ~ITheme() {} + virtual void apply(bool initial); + virtual QString id() = 0; + virtual QString name() = 0; + virtual bool hasStyleSheet() = 0; + virtual QString appStyleSheet() = 0; + virtual QString qtTheme() = 0; + virtual bool hasColorScheme() = 0; + virtual QPalette colorScheme() = 0; + virtual QColor fadeColor() = 0; + virtual double fadeAmount() = 0; + virtual QStringList searchPaths() + { + return {}; + } - static QPalette fadeInactive(QPalette in, qreal bias, QColor color); + static QPalette fadeInactive(QPalette in, qreal bias, QColor color); }; diff --git a/application/themes/SystemTheme.cpp b/application/themes/SystemTheme.cpp index a9ee853a..00b2300d 100644 --- a/application/themes/SystemTheme.cpp +++ b/application/themes/SystemTheme.cpp @@ -6,75 +6,75 @@ SystemTheme::SystemTheme() { - const auto & style = QApplication::style(); - systemPalette = style->standardPalette(); - QString lowerThemeName = style->objectName(); - qDebug() << systemTheme; - QStringList styles = QStyleFactory::keys(); - for(auto &st: styles) - { - if(st.toLower() == lowerThemeName) - { - systemTheme = st; - return; - } - } - // fall back to fusion if we can't find the current theme. - systemTheme = "Fusion"; - qDebug() << "System theme not found, defaulted to Fusion"; + const auto & style = QApplication::style(); + systemPalette = style->standardPalette(); + QString lowerThemeName = style->objectName(); + qDebug() << systemTheme; + QStringList styles = QStyleFactory::keys(); + for(auto &st: styles) + { + if(st.toLower() == lowerThemeName) + { + systemTheme = st; + return; + } + } + // fall back to fusion if we can't find the current theme. + systemTheme = "Fusion"; + qDebug() << "System theme not found, defaulted to Fusion"; } void SystemTheme::apply(bool initial) { - // if we are applying the system theme as the first theme, just don't touch anything. it's for the better... - if(initial) - { - return; - } - ITheme::apply(initial); + // if we are applying the system theme as the first theme, just don't touch anything. it's for the better... + if(initial) + { + return; + } + ITheme::apply(initial); } QString SystemTheme::id() { - return "system"; + return "system"; } QString SystemTheme::name() { - return QObject::tr("System"); + return QObject::tr("System"); } QString SystemTheme::qtTheme() { - return systemTheme; + return systemTheme; } QPalette SystemTheme::colorScheme() { - return systemPalette; + return systemPalette; } QString SystemTheme::appStyleSheet() { - return QString(); + return QString(); } double SystemTheme::fadeAmount() { - return 0.5; + return 0.5; } QColor SystemTheme::fadeColor() { - return QColor(128,128,128); + return QColor(128,128,128); } bool SystemTheme::hasStyleSheet() { - return false; + return false; } bool SystemTheme::hasColorScheme() { - return true; + return true; } diff --git a/application/themes/SystemTheme.h b/application/themes/SystemTheme.h index 18291b68..fe450600 100644 --- a/application/themes/SystemTheme.h +++ b/application/themes/SystemTheme.h @@ -5,20 +5,20 @@ class SystemTheme: public ITheme { public: - SystemTheme(); - virtual ~SystemTheme() {} - void apply(bool initial) override; + SystemTheme(); + virtual ~SystemTheme() {} + void apply(bool initial) override; - QString id() override; - QString name() override; - QString qtTheme() override; - bool hasStyleSheet() override; - QString appStyleSheet() override; - bool hasColorScheme() override; - QPalette colorScheme() override; - double fadeAmount() override; - QColor fadeColor() override; + QString id() override; + QString name() override; + QString qtTheme() override; + bool hasStyleSheet() override; + QString appStyleSheet() override; + bool hasColorScheme() override; + QPalette colorScheme() override; + double fadeAmount() override; + QColor fadeColor() override; private: - QPalette systemPalette; - QString systemTheme; + QPalette systemPalette; + QString systemTheme; }; diff --git a/application/widgets/Common.cpp b/application/widgets/Common.cpp index 9b730d6c..f72f3596 100644 --- a/application/widgets/Common.cpp +++ b/application/widgets/Common.cpp @@ -2,26 +2,26 @@ // Origin: Qt QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, - qreal &widthUsed) + qreal &widthUsed) { - QStringList lines; - height = 0; - widthUsed = 0; - textLayout.beginLayout(); - QString str = textLayout.text(); - while (true) - { - QTextLine line = textLayout.createLine(); - if (!line.isValid()) - break; - if (line.textLength() == 0) - break; - line.setLineWidth(lineWidth); - line.setPosition(QPointF(0, height)); - height += line.height(); - lines.append(str.mid(line.textStart(), line.textLength())); - widthUsed = qMax(widthUsed, line.naturalTextWidth()); - } - textLayout.endLayout(); - return lines; + QStringList lines; + height = 0; + widthUsed = 0; + textLayout.beginLayout(); + QString str = textLayout.text(); + while (true) + { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + if (line.textLength() == 0) + break; + line.setLineWidth(lineWidth); + line.setPosition(QPointF(0, height)); + height += line.height(); + lines.append(str.mid(line.textStart(), line.textLength())); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout.endLayout(); + return lines; } diff --git a/application/widgets/Common.h b/application/widgets/Common.h index fc46e08f..b3fbe1a0 100644 --- a/application/widgets/Common.h +++ b/application/widgets/Common.h @@ -3,4 +3,4 @@ #include <QTextLayout> QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, - qreal &widthUsed);
\ No newline at end of file + qreal &widthUsed);
\ No newline at end of file diff --git a/application/widgets/CustomCommands.cpp b/application/widgets/CustomCommands.cpp index 9f11e344..9e7673fd 100644 --- a/application/widgets/CustomCommands.cpp +++ b/application/widgets/CustomCommands.cpp @@ -6,43 +6,43 @@ CustomCommands::~CustomCommands() } CustomCommands::CustomCommands(QWidget* parent): - QWidget(parent), - ui(new Ui::CustomCommands) + QWidget(parent), + ui(new Ui::CustomCommands) { - ui->setupUi(this); + ui->setupUi(this); } void CustomCommands::initialize(bool checkable, bool checked, const QString& prelaunch, const QString& wrapper, const QString& postexit) { - ui->customCommandsGroupBox->setCheckable(checkable); - if(checkable) - { - ui->customCommandsGroupBox->setChecked(checked); - } - ui->preLaunchCmdTextBox->setText(prelaunch); - ui->wrapperCmdTextBox->setText(wrapper); - ui->postExitCmdTextBox->setText(postexit); + ui->customCommandsGroupBox->setCheckable(checkable); + if(checkable) + { + ui->customCommandsGroupBox->setChecked(checked); + } + ui->preLaunchCmdTextBox->setText(prelaunch); + ui->wrapperCmdTextBox->setText(wrapper); + ui->postExitCmdTextBox->setText(postexit); } bool CustomCommands::checked() const { - if(!ui->customCommandsGroupBox->isCheckable()) - return true; - return ui->customCommandsGroupBox->isChecked(); + if(!ui->customCommandsGroupBox->isCheckable()) + return true; + return ui->customCommandsGroupBox->isChecked(); } QString CustomCommands::prelaunchCommand() const { - return ui->preLaunchCmdTextBox->text(); + return ui->preLaunchCmdTextBox->text(); } QString CustomCommands::wrapperCommand() const { - return ui->wrapperCmdTextBox->text(); + return ui->wrapperCmdTextBox->text(); } QString CustomCommands::postexitCommand() const { - return ui->postExitCmdTextBox->text(); + return ui->postExitCmdTextBox->text(); } diff --git a/application/widgets/CustomCommands.h b/application/widgets/CustomCommands.h index 2bc7cb1a..451b2cc0 100644 --- a/application/widgets/CustomCommands.h +++ b/application/widgets/CustomCommands.h @@ -1,4 +1,4 @@ -/* Copyright 2018-2018 MultiMC Contributors +/* Copyright 2018-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,20 +24,20 @@ class CustomCommands; class CustomCommands : public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit CustomCommands(QWidget *parent = 0); - ~CustomCommands(); - void initialize(bool checkable, bool checked, const QString & prelaunch, const QString & wrapper, const QString & postexit); + explicit CustomCommands(QWidget *parent = 0); + ~CustomCommands(); + void initialize(bool checkable, bool checked, const QString & prelaunch, const QString & wrapper, const QString & postexit); - bool checked() const; - QString prelaunchCommand() const; - QString wrapperCommand() const; - QString postexitCommand() const; + bool checked() const; + QString prelaunchCommand() const; + QString wrapperCommand() const; + QString postexitCommand() const; private: - Ui::CustomCommands *ui; + Ui::CustomCommands *ui; }; diff --git a/application/widgets/CustomCommands.ui b/application/widgets/CustomCommands.ui index d3bc86b8..c302fe39 100644 --- a/application/widgets/CustomCommands.ui +++ b/application/widgets/CustomCommands.ui @@ -14,6 +14,18 @@ <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="QGroupBox" name="customCommandsGroupBox"> <property name="enabled"> diff --git a/application/widgets/FocusLineEdit.cpp b/application/widgets/FocusLineEdit.cpp index 139126c8..b272100c 100644 --- a/application/widgets/FocusLineEdit.cpp +++ b/application/widgets/FocusLineEdit.cpp @@ -3,23 +3,23 @@ FocusLineEdit::FocusLineEdit(QWidget *parent) : QLineEdit(parent) { - _selectOnMousePress = false; + _selectOnMousePress = false; } void FocusLineEdit::focusInEvent(QFocusEvent *e) { - QLineEdit::focusInEvent(e); - selectAll(); - _selectOnMousePress = true; + QLineEdit::focusInEvent(e); + selectAll(); + _selectOnMousePress = true; } void FocusLineEdit::mousePressEvent(QMouseEvent *me) { - QLineEdit::mousePressEvent(me); - if (_selectOnMousePress) - { - selectAll(); - _selectOnMousePress = false; - } - qDebug() << selectedText(); + QLineEdit::mousePressEvent(me); + if (_selectOnMousePress) + { + selectAll(); + _selectOnMousePress = false; + } + qDebug() << selectedText(); } diff --git a/application/widgets/FocusLineEdit.h b/application/widgets/FocusLineEdit.h index 6d1c78a8..71b4f140 100644 --- a/application/widgets/FocusLineEdit.h +++ b/application/widgets/FocusLineEdit.h @@ -2,16 +2,16 @@ class FocusLineEdit : public QLineEdit { - Q_OBJECT + Q_OBJECT public: - FocusLineEdit(QWidget *parent); - virtual ~FocusLineEdit() - { - } + FocusLineEdit(QWidget *parent); + virtual ~FocusLineEdit() + { + } protected: - void focusInEvent(QFocusEvent *e); - void mousePressEvent(QMouseEvent *me); + void focusInEvent(QFocusEvent *e); + void mousePressEvent(QMouseEvent *me); - bool _selectOnMousePress; + bool _selectOnMousePress; }; diff --git a/application/widgets/IconLabel.cpp b/application/widgets/IconLabel.cpp index 86c8a431..bf1c2358 100644 --- a/application/widgets/IconLabel.cpp +++ b/application/widgets/IconLabel.cpp @@ -7,37 +7,37 @@ #include <QRect> IconLabel::IconLabel(QWidget *parent, QIcon icon, QSize size) - : QWidget(parent), m_size(size), m_icon(icon) + : QWidget(parent), m_size(size), m_icon(icon) { - setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); } QSize IconLabel::sizeHint() const { - return m_size; + return m_size; } void IconLabel::setIcon(QIcon icon) { - m_icon = icon; - update(); + m_icon = icon; + update(); } void IconLabel::paintEvent(QPaintEvent *) { - QPainter p(this); - QRect rect = contentsRect(); - int width = rect.width(); - int height = rect.height(); - if(width < height) - { - rect.setHeight(width); - rect.translate(0, (height - width) / 2); - } - else if (width > height) - { - rect.setWidth(height); - rect.translate((width - height) / 2, 0); - } - m_icon.paint(&p, rect); + QPainter p(this); + QRect rect = contentsRect(); + int width = rect.width(); + int height = rect.height(); + if(width < height) + { + rect.setHeight(width); + rect.translate(0, (height - width) / 2); + } + else if (width > height) + { + rect.setWidth(height); + rect.translate((width - height) / 2, 0); + } + m_icon.paint(&p, rect); } diff --git a/application/widgets/IconLabel.h b/application/widgets/IconLabel.h index a2f1eef3..6d212c4c 100644 --- a/application/widgets/IconLabel.h +++ b/application/widgets/IconLabel.h @@ -9,18 +9,18 @@ class QStyleOption; */ class IconLabel : public QWidget { - Q_OBJECT + Q_OBJECT public: - /// Create a line separator. orientation is the orientation of the line. - explicit IconLabel(QWidget *parent, QIcon icon, QSize size); + /// Create a line separator. orientation is the orientation of the line. + explicit IconLabel(QWidget *parent, QIcon icon, QSize size); - virtual QSize sizeHint() const; - virtual void paintEvent(QPaintEvent *); + virtual QSize sizeHint() const; + virtual void paintEvent(QPaintEvent *); - void setIcon(QIcon icon); + void setIcon(QIcon icon); private: - QSize m_size; - QIcon m_icon; + QSize m_size; + QIcon m_icon; }; diff --git a/application/widgets/JavaSettingsWidget.cpp b/application/widgets/JavaSettingsWidget.cpp index 1e9d5546..a11dd1aa 100644 --- a/application/widgets/JavaSettingsWidget.cpp +++ b/application/widgets/JavaSettingsWidget.cpp @@ -19,410 +19,410 @@ JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent) { - m_availableMemory = Sys::getSystemRam() / Sys::megabyte; - - goodIcon = MMC->getThemedIcon("status-good"); - yellowIcon = MMC->getThemedIcon("status-yellow"); - badIcon = MMC->getThemedIcon("status-bad"); - setupUi(); - - connect(m_minMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); - connect(m_maxMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); - connect(m_permGenSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); - connect(m_versionWidget, &VersionSelectWidget::selectedVersionChanged, this, &JavaSettingsWidget::javaVersionSelected); - connect(m_javaBrowseBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaBrowseBtn_clicked); - connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited); - connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaSettingsWidget::on_javaStatusBtn_clicked); + m_availableMemory = Sys::getSystemRam() / Sys::megabyte; + + goodIcon = MMC->getThemedIcon("status-good"); + yellowIcon = MMC->getThemedIcon("status-yellow"); + badIcon = MMC->getThemedIcon("status-bad"); + setupUi(); + + connect(m_minMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); + connect(m_maxMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); + connect(m_permGenSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); + connect(m_versionWidget, &VersionSelectWidget::selectedVersionChanged, this, &JavaSettingsWidget::javaVersionSelected); + connect(m_javaBrowseBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaBrowseBtn_clicked); + connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited); + connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaSettingsWidget::on_javaStatusBtn_clicked); } void JavaSettingsWidget::setupUi() { - setObjectName(QStringLiteral("javaSettingsWidget")); - m_verticalLayout = new QVBoxLayout(this); - m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - - m_versionWidget = new VersionSelectWidget(this); - m_verticalLayout->addWidget(m_versionWidget); - - m_horizontalLayout = new QHBoxLayout(); - m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); - m_javaPathTextBox = new QLineEdit(this); - m_javaPathTextBox->setObjectName(QStringLiteral("javaPathTextBox")); - - m_horizontalLayout->addWidget(m_javaPathTextBox); - - m_javaBrowseBtn = new QPushButton(this); - m_javaBrowseBtn->setObjectName(QStringLiteral("javaBrowseBtn")); - - m_horizontalLayout->addWidget(m_javaBrowseBtn); - - m_javaStatusBtn = new QToolButton(this); - m_javaStatusBtn->setIcon(yellowIcon); - m_horizontalLayout->addWidget(m_javaStatusBtn); - - m_verticalLayout->addLayout(m_horizontalLayout); - - m_memoryGroupBox = new QGroupBox(this); - m_memoryGroupBox->setObjectName(QStringLiteral("memoryGroupBox")); - m_gridLayout_2 = new QGridLayout(m_memoryGroupBox); - m_gridLayout_2->setObjectName(QStringLiteral("gridLayout_2")); - - m_labelMinMem = new QLabel(m_memoryGroupBox); - m_labelMinMem->setObjectName(QStringLiteral("labelMinMem")); - m_gridLayout_2->addWidget(m_labelMinMem, 0, 0, 1, 1); - - m_minMemSpinBox = new QSpinBox(m_memoryGroupBox); - m_minMemSpinBox->setObjectName(QStringLiteral("minMemSpinBox")); - m_minMemSpinBox->setSuffix(QStringLiteral(" MB")); - m_minMemSpinBox->setMinimum(128); - m_minMemSpinBox->setMaximum(m_availableMemory); - m_minMemSpinBox->setSingleStep(128); - m_labelMinMem->setBuddy(m_minMemSpinBox); - m_gridLayout_2->addWidget(m_minMemSpinBox, 0, 1, 1, 1); - - m_labelMaxMem = new QLabel(m_memoryGroupBox); - m_labelMaxMem->setObjectName(QStringLiteral("labelMaxMem")); - m_gridLayout_2->addWidget(m_labelMaxMem, 1, 0, 1, 1); - - m_maxMemSpinBox = new QSpinBox(m_memoryGroupBox); - m_maxMemSpinBox->setObjectName(QStringLiteral("maxMemSpinBox")); - m_maxMemSpinBox->setSuffix(QStringLiteral(" MB")); - m_maxMemSpinBox->setMinimum(128); - m_maxMemSpinBox->setMaximum(m_availableMemory); - m_maxMemSpinBox->setSingleStep(128); - m_labelMaxMem->setBuddy(m_maxMemSpinBox); - m_gridLayout_2->addWidget(m_maxMemSpinBox, 1, 1, 1, 1); - - m_labelPermGen = new QLabel(m_memoryGroupBox); - m_labelPermGen->setObjectName(QStringLiteral("labelPermGen")); - m_labelPermGen->setText(QStringLiteral("PermGen:")); - m_gridLayout_2->addWidget(m_labelPermGen, 2, 0, 1, 1); - m_labelPermGen->setVisible(false); - - m_permGenSpinBox = new QSpinBox(m_memoryGroupBox); - m_permGenSpinBox->setObjectName(QStringLiteral("permGenSpinBox")); - m_permGenSpinBox->setSuffix(QStringLiteral(" MB")); - m_permGenSpinBox->setMinimum(64); - m_permGenSpinBox->setMaximum(m_availableMemory); - m_permGenSpinBox->setSingleStep(8); - m_gridLayout_2->addWidget(m_permGenSpinBox, 2, 1, 1, 1); - m_permGenSpinBox->setVisible(false); - - m_verticalLayout->addWidget(m_memoryGroupBox); - - retranslate(); + setObjectName(QStringLiteral("javaSettingsWidget")); + m_verticalLayout = new QVBoxLayout(this); + m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + + m_versionWidget = new VersionSelectWidget(this); + m_verticalLayout->addWidget(m_versionWidget); + + m_horizontalLayout = new QHBoxLayout(); + m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + m_javaPathTextBox = new QLineEdit(this); + m_javaPathTextBox->setObjectName(QStringLiteral("javaPathTextBox")); + + m_horizontalLayout->addWidget(m_javaPathTextBox); + + m_javaBrowseBtn = new QPushButton(this); + m_javaBrowseBtn->setObjectName(QStringLiteral("javaBrowseBtn")); + + m_horizontalLayout->addWidget(m_javaBrowseBtn); + + m_javaStatusBtn = new QToolButton(this); + m_javaStatusBtn->setIcon(yellowIcon); + m_horizontalLayout->addWidget(m_javaStatusBtn); + + m_verticalLayout->addLayout(m_horizontalLayout); + + m_memoryGroupBox = new QGroupBox(this); + m_memoryGroupBox->setObjectName(QStringLiteral("memoryGroupBox")); + m_gridLayout_2 = new QGridLayout(m_memoryGroupBox); + m_gridLayout_2->setObjectName(QStringLiteral("gridLayout_2")); + + m_labelMinMem = new QLabel(m_memoryGroupBox); + m_labelMinMem->setObjectName(QStringLiteral("labelMinMem")); + m_gridLayout_2->addWidget(m_labelMinMem, 0, 0, 1, 1); + + m_minMemSpinBox = new QSpinBox(m_memoryGroupBox); + m_minMemSpinBox->setObjectName(QStringLiteral("minMemSpinBox")); + m_minMemSpinBox->setSuffix(QStringLiteral(" MB")); + m_minMemSpinBox->setMinimum(128); + m_minMemSpinBox->setMaximum(m_availableMemory); + m_minMemSpinBox->setSingleStep(128); + m_labelMinMem->setBuddy(m_minMemSpinBox); + m_gridLayout_2->addWidget(m_minMemSpinBox, 0, 1, 1, 1); + + m_labelMaxMem = new QLabel(m_memoryGroupBox); + m_labelMaxMem->setObjectName(QStringLiteral("labelMaxMem")); + m_gridLayout_2->addWidget(m_labelMaxMem, 1, 0, 1, 1); + + m_maxMemSpinBox = new QSpinBox(m_memoryGroupBox); + m_maxMemSpinBox->setObjectName(QStringLiteral("maxMemSpinBox")); + m_maxMemSpinBox->setSuffix(QStringLiteral(" MB")); + m_maxMemSpinBox->setMinimum(128); + m_maxMemSpinBox->setMaximum(m_availableMemory); + m_maxMemSpinBox->setSingleStep(128); + m_labelMaxMem->setBuddy(m_maxMemSpinBox); + m_gridLayout_2->addWidget(m_maxMemSpinBox, 1, 1, 1, 1); + + m_labelPermGen = new QLabel(m_memoryGroupBox); + m_labelPermGen->setObjectName(QStringLiteral("labelPermGen")); + m_labelPermGen->setText(QStringLiteral("PermGen:")); + m_gridLayout_2->addWidget(m_labelPermGen, 2, 0, 1, 1); + m_labelPermGen->setVisible(false); + + m_permGenSpinBox = new QSpinBox(m_memoryGroupBox); + m_permGenSpinBox->setObjectName(QStringLiteral("permGenSpinBox")); + m_permGenSpinBox->setSuffix(QStringLiteral(" MB")); + m_permGenSpinBox->setMinimum(64); + m_permGenSpinBox->setMaximum(m_availableMemory); + m_permGenSpinBox->setSingleStep(8); + m_gridLayout_2->addWidget(m_permGenSpinBox, 2, 1, 1, 1); + m_permGenSpinBox->setVisible(false); + + m_verticalLayout->addWidget(m_memoryGroupBox); + + retranslate(); } void JavaSettingsWidget::initialize() { - m_versionWidget->initialize(MMC->javalist().get()); - m_versionWidget->setResizeOn(2); - auto s = MMC->settings(); - // Memory - observedMinMemory = s->get("MinMemAlloc").toInt(); - observedMaxMemory = s->get("MaxMemAlloc").toInt(); - observedPermGenMemory = s->get("PermGen").toInt(); - m_minMemSpinBox->setValue(observedMinMemory); - m_maxMemSpinBox->setValue(observedMaxMemory); - m_permGenSpinBox->setValue(observedPermGenMemory); + m_versionWidget->initialize(MMC->javalist().get()); + m_versionWidget->setResizeOn(2); + auto s = MMC->settings(); + // Memory + observedMinMemory = s->get("MinMemAlloc").toInt(); + observedMaxMemory = s->get("MaxMemAlloc").toInt(); + observedPermGenMemory = s->get("PermGen").toInt(); + m_minMemSpinBox->setValue(observedMinMemory); + m_maxMemSpinBox->setValue(observedMaxMemory); + m_permGenSpinBox->setValue(observedPermGenMemory); } void JavaSettingsWidget::refresh() { - m_versionWidget->loadList(); + m_versionWidget->loadList(); } JavaSettingsWidget::ValidationStatus JavaSettingsWidget::validate() { - switch(javaStatus) - { - default: - case JavaStatus::NotSet: - case JavaStatus::DoesNotExist: - case JavaStatus::DoesNotStart: - case JavaStatus::ReturnedInvalidData: - { - int button = CustomMessageBox::selectable( - this, - tr("No Java version selected"), - tr("You didn't select a Java version or selected something that doesn't work.\n" - "MultiMC will not be able to start Minecraft.\n" - "Do you wish to proceed without any Java?" - "\n\n" - "You can change the Java version in the settings later.\n" - ), - QMessageBox::Warning, - QMessageBox::Yes | QMessageBox::No, - QMessageBox::NoButton - )->exec(); - if(button == QMessageBox::No) - { - return ValidationStatus::Bad; - } - return ValidationStatus::JavaBad; - } - break; - case JavaStatus::Pending: - { - return ValidationStatus::Bad; - } - case JavaStatus::Good: - { - return ValidationStatus::AllOK; - } - } + switch(javaStatus) + { + default: + case JavaStatus::NotSet: + case JavaStatus::DoesNotExist: + case JavaStatus::DoesNotStart: + case JavaStatus::ReturnedInvalidData: + { + int button = CustomMessageBox::selectable( + this, + tr("No Java version selected"), + tr("You didn't select a Java version or selected something that doesn't work.\n" + "MultiMC will not be able to start Minecraft.\n" + "Do you wish to proceed without any Java?" + "\n\n" + "You can change the Java version in the settings later.\n" + ), + QMessageBox::Warning, + QMessageBox::Yes | QMessageBox::No, + QMessageBox::NoButton + )->exec(); + if(button == QMessageBox::No) + { + return ValidationStatus::Bad; + } + return ValidationStatus::JavaBad; + } + break; + case JavaStatus::Pending: + { + return ValidationStatus::Bad; + } + case JavaStatus::Good: + { + return ValidationStatus::AllOK; + } + } } QString JavaSettingsWidget::javaPath() const { - return m_javaPathTextBox->text(); + return m_javaPathTextBox->text(); } int JavaSettingsWidget::maxHeapSize() const { - return m_maxMemSpinBox->value(); + return m_maxMemSpinBox->value(); } int JavaSettingsWidget::minHeapSize() const { - return m_minMemSpinBox->value(); + return m_minMemSpinBox->value(); } bool JavaSettingsWidget::permGenEnabled() const { - return m_permGenSpinBox->isVisible(); + return m_permGenSpinBox->isVisible(); } int JavaSettingsWidget::permGenSize() const { - return m_permGenSpinBox->value(); + return m_permGenSpinBox->value(); } void JavaSettingsWidget::memoryValueChanged(int) { - bool actuallyChanged = false; - int min = m_minMemSpinBox->value(); - int max = m_maxMemSpinBox->value(); - int permgen = m_permGenSpinBox->value(); - QObject *obj = sender(); - if (obj == m_minMemSpinBox && min != observedMinMemory) - { - observedMinMemory = min; - actuallyChanged = true; - if (min > max) - { - observedMaxMemory = min; - m_maxMemSpinBox->setValue(min); - } - } - else if (obj == m_maxMemSpinBox && max != observedMaxMemory) - { - observedMaxMemory = max; - actuallyChanged = true; - if (min > max) - { - observedMinMemory = max; - m_minMemSpinBox->setValue(max); - } - } - else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory) - { - observedPermGenMemory = permgen; - actuallyChanged = true; - } - if(actuallyChanged) - { - checkJavaPathOnEdit(m_javaPathTextBox->text()); - } + bool actuallyChanged = false; + int min = m_minMemSpinBox->value(); + int max = m_maxMemSpinBox->value(); + int permgen = m_permGenSpinBox->value(); + QObject *obj = sender(); + if (obj == m_minMemSpinBox && min != observedMinMemory) + { + observedMinMemory = min; + actuallyChanged = true; + if (min > max) + { + observedMaxMemory = min; + m_maxMemSpinBox->setValue(min); + } + } + else if (obj == m_maxMemSpinBox && max != observedMaxMemory) + { + observedMaxMemory = max; + actuallyChanged = true; + if (min > max) + { + observedMinMemory = max; + m_minMemSpinBox->setValue(max); + } + } + else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory) + { + observedPermGenMemory = permgen; + actuallyChanged = true; + } + if(actuallyChanged) + { + checkJavaPathOnEdit(m_javaPathTextBox->text()); + } } void JavaSettingsWidget::javaVersionSelected(BaseVersionPtr version) { - auto java = std::dynamic_pointer_cast<JavaInstall>(version); - if(!java) - { - return; - } - auto visible = java->id.requiresPermGen(); - m_labelPermGen->setVisible(visible); - m_permGenSpinBox->setVisible(visible); - m_javaPathTextBox->setText(java->path); - checkJavaPath(java->path); + auto java = std::dynamic_pointer_cast<JavaInstall>(version); + if(!java) + { + return; + } + auto visible = java->id.requiresPermGen(); + m_labelPermGen->setVisible(visible); + m_permGenSpinBox->setVisible(visible); + m_javaPathTextBox->setText(java->path); + checkJavaPath(java->path); } void JavaSettingsWidget::on_javaBrowseBtn_clicked() { - QString filter; + QString filter; #if defined Q_OS_WIN32 - filter = "Java (javaw.exe)"; + filter = "Java (javaw.exe)"; #else - filter = "Java (java)"; + filter = "Java (java)"; #endif - QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"), QString(), filter); - if(raw_path.isEmpty()) - { - return; - } - QString cooked_path = FS::NormalizePath(raw_path); - m_javaPathTextBox->setText(cooked_path); - checkJavaPath(cooked_path); + QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"), QString(), filter); + if(raw_path.isEmpty()) + { + return; + } + QString cooked_path = FS::NormalizePath(raw_path); + m_javaPathTextBox->setText(cooked_path); + checkJavaPath(cooked_path); } void JavaSettingsWidget::on_javaStatusBtn_clicked() { - QString text; - bool failed = false; - switch(javaStatus) - { - case JavaStatus::NotSet: - checkJavaPath(m_javaPathTextBox->text()); - return; - case JavaStatus::DoesNotExist: - text += QObject::tr("The specified file either doesn't exist or is not a proper executable."); - failed = true; - break; - case JavaStatus::DoesNotStart: - { - text += QObject::tr("The specified java binary didn't start properly.<br />"); - auto htmlError = m_result.errorLog; - if(!htmlError.isEmpty()) - { - htmlError.replace('\n', "<br />"); - text += QString("<font color=\"red\">%1</font>").arg(htmlError); - } - failed = true; - break; - } - case JavaStatus::ReturnedInvalidData: - { - text += QObject::tr("The specified java binary returned unexpected results:<br />"); - auto htmlOut = m_result.outLog; - if(!htmlOut.isEmpty()) - { - htmlOut.replace('\n', "<br />"); - text += QString("<font color=\"red\">%1</font>").arg(htmlOut); - } - failed = true; - break; - } - case JavaStatus::Good: - text += QObject::tr("Java test succeeded!<br />Platform reported: %1<br />Java version " - "reported: %2<br />").arg(m_result.realPlatform, m_result.javaVersion.toString()); - break; - case JavaStatus::Pending: - // TODO: abort here? - return; - } - CustomMessageBox::selectable( - this, - failed ? QObject::tr("Java test success") : QObject::tr("Java test failure"), - text, - failed ? QMessageBox::Critical : QMessageBox::Information - )->show(); + QString text; + bool failed = false; + switch(javaStatus) + { + case JavaStatus::NotSet: + checkJavaPath(m_javaPathTextBox->text()); + return; + case JavaStatus::DoesNotExist: + text += QObject::tr("The specified file either doesn't exist or is not a proper executable."); + failed = true; + break; + case JavaStatus::DoesNotStart: + { + text += QObject::tr("The specified java binary didn't start properly.<br />"); + auto htmlError = m_result.errorLog; + if(!htmlError.isEmpty()) + { + htmlError.replace('\n', "<br />"); + text += QString("<font color=\"red\">%1</font>").arg(htmlError); + } + failed = true; + break; + } + case JavaStatus::ReturnedInvalidData: + { + text += QObject::tr("The specified java binary returned unexpected results:<br />"); + auto htmlOut = m_result.outLog; + if(!htmlOut.isEmpty()) + { + htmlOut.replace('\n', "<br />"); + text += QString("<font color=\"red\">%1</font>").arg(htmlOut); + } + failed = true; + break; + } + case JavaStatus::Good: + text += QObject::tr("Java test succeeded!<br />Platform reported: %1<br />Java version " + "reported: %2<br />").arg(m_result.realPlatform, m_result.javaVersion.toString()); + break; + case JavaStatus::Pending: + // TODO: abort here? + return; + } + CustomMessageBox::selectable( + this, + failed ? QObject::tr("Java test success") : QObject::tr("Java test failure"), + text, + failed ? QMessageBox::Critical : QMessageBox::Information + )->show(); } void JavaSettingsWidget::setJavaStatus(JavaSettingsWidget::JavaStatus status) { - javaStatus = status; - switch(javaStatus) - { - case JavaStatus::Good: - m_javaStatusBtn->setIcon(goodIcon); - break; - case JavaStatus::NotSet: - case JavaStatus::Pending: - m_javaStatusBtn->setIcon(yellowIcon); - break; - default: - m_javaStatusBtn->setIcon(badIcon); - break; - } + javaStatus = status; + switch(javaStatus) + { + case JavaStatus::Good: + m_javaStatusBtn->setIcon(goodIcon); + break; + case JavaStatus::NotSet: + case JavaStatus::Pending: + m_javaStatusBtn->setIcon(yellowIcon); + break; + default: + m_javaStatusBtn->setIcon(badIcon); + break; + } } void JavaSettingsWidget::javaPathEdited(const QString& path) { - checkJavaPathOnEdit(path); + checkJavaPathOnEdit(path); } void JavaSettingsWidget::checkJavaPathOnEdit(const QString& path) { - auto realPath = FS::ResolveExecutable(path); - QFileInfo pathInfo(realPath); - if (pathInfo.baseName().toLower().contains("java")) - { - checkJavaPath(path); - } - else - { - if(!m_checker) - { - setJavaStatus(JavaStatus::NotSet); - } - } + auto realPath = FS::ResolveExecutable(path); + QFileInfo pathInfo(realPath); + if (pathInfo.baseName().toLower().contains("java")) + { + checkJavaPath(path); + } + else + { + if(!m_checker) + { + setJavaStatus(JavaStatus::NotSet); + } + } } void JavaSettingsWidget::checkJavaPath(const QString &path) { - if(m_checker) - { - queuedCheck = path; - return; - } - auto realPath = FS::ResolveExecutable(path); - if(realPath.isNull()) - { - setJavaStatus(JavaStatus::DoesNotExist); - return; - } - setJavaStatus(JavaStatus::Pending); - m_checker.reset(new JavaChecker()); - m_checker->m_path = path; - m_checker->m_minMem = m_minMemSpinBox->value(); - m_checker->m_maxMem = m_maxMemSpinBox->value(); - if(m_permGenSpinBox->isVisible()) - { - m_checker->m_permGen = m_permGenSpinBox->value(); - } - connect(m_checker.get(), &JavaChecker::checkFinished, this, &JavaSettingsWidget::checkFinished); - m_checker->performCheck(); + if(m_checker) + { + queuedCheck = path; + return; + } + auto realPath = FS::ResolveExecutable(path); + if(realPath.isNull()) + { + setJavaStatus(JavaStatus::DoesNotExist); + return; + } + setJavaStatus(JavaStatus::Pending); + m_checker.reset(new JavaChecker()); + m_checker->m_path = path; + m_checker->m_minMem = m_minMemSpinBox->value(); + m_checker->m_maxMem = m_maxMemSpinBox->value(); + if(m_permGenSpinBox->isVisible()) + { + m_checker->m_permGen = m_permGenSpinBox->value(); + } + connect(m_checker.get(), &JavaChecker::checkFinished, this, &JavaSettingsWidget::checkFinished); + m_checker->performCheck(); } void JavaSettingsWidget::checkFinished(JavaCheckResult result) { - m_result = result; - switch(result.validity) - { - case JavaCheckResult::Validity::Valid: - { - setJavaStatus(JavaStatus::Good); - break; - } - case JavaCheckResult::Validity::ReturnedInvalidData: - { - setJavaStatus(JavaStatus::ReturnedInvalidData); - break; - } - case JavaCheckResult::Validity::Errored: - { - setJavaStatus(JavaStatus::DoesNotStart); - break; - } - } - m_checker.reset(); - if(!queuedCheck.isNull()) - { - checkJavaPath(queuedCheck); - queuedCheck.clear(); - } + m_result = result; + switch(result.validity) + { + case JavaCheckResult::Validity::Valid: + { + setJavaStatus(JavaStatus::Good); + break; + } + case JavaCheckResult::Validity::ReturnedInvalidData: + { + setJavaStatus(JavaStatus::ReturnedInvalidData); + break; + } + case JavaCheckResult::Validity::Errored: + { + setJavaStatus(JavaStatus::DoesNotStart); + break; + } + } + m_checker.reset(); + if(!queuedCheck.isNull()) + { + checkJavaPath(queuedCheck); + queuedCheck.clear(); + } } void JavaSettingsWidget::retranslate() { - m_memoryGroupBox->setTitle(tr("Memory")); - m_maxMemSpinBox->setToolTip(tr("The maximum amount of memory Minecraft is allowed to use.")); - m_labelMinMem->setText(tr("Minimum memory allocation:")); - m_labelMaxMem->setText(tr("Maximum memory allocation:")); - m_minMemSpinBox->setToolTip(tr("The amount of memory Minecraft is started with.")); - m_permGenSpinBox->setToolTip(tr("The amount of memory available to store loaded Java classes.")); - m_javaBrowseBtn->setText(tr("Browse")); + m_memoryGroupBox->setTitle(tr("Memory")); + m_maxMemSpinBox->setToolTip(tr("The maximum amount of memory Minecraft is allowed to use.")); + m_labelMinMem->setText(tr("Minimum memory allocation:")); + m_labelMaxMem->setText(tr("Maximum memory allocation:")); + m_minMemSpinBox->setToolTip(tr("The amount of memory Minecraft is started with.")); + m_permGenSpinBox->setToolTip(tr("The amount of memory available to store loaded Java classes.")); + m_javaBrowseBtn->setText(tr("Browse")); } diff --git a/application/widgets/JavaSettingsWidget.h b/application/widgets/JavaSettingsWidget.h index 3a94f851..0d280daf 100644 --- a/application/widgets/JavaSettingsWidget.h +++ b/application/widgets/JavaSettingsWidget.h @@ -22,81 +22,81 @@ class QToolButton; */ class JavaSettingsWidget : public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit JavaSettingsWidget(QWidget *parent); - virtual ~JavaSettingsWidget() {}; + explicit JavaSettingsWidget(QWidget *parent); + virtual ~JavaSettingsWidget() {}; - enum class JavaStatus - { - NotSet, - Pending, - Good, - DoesNotExist, - DoesNotStart, - ReturnedInvalidData - } javaStatus = JavaStatus::NotSet; + enum class JavaStatus + { + NotSet, + Pending, + Good, + DoesNotExist, + DoesNotStart, + ReturnedInvalidData + } javaStatus = JavaStatus::NotSet; - enum class ValidationStatus - { - Bad, - JavaBad, - AllOK - }; + enum class ValidationStatus + { + Bad, + JavaBad, + AllOK + }; - void refresh(); - void initialize(); - ValidationStatus validate(); - void retranslate(); + void refresh(); + void initialize(); + ValidationStatus validate(); + void retranslate(); - bool permGenEnabled() const; - int permGenSize() const; - int minHeapSize() const; - int maxHeapSize() const; - QString javaPath() const; + bool permGenEnabled() const; + int permGenSize() const; + int minHeapSize() const; + int maxHeapSize() const; + QString javaPath() const; protected slots: - void memoryValueChanged(int); - void javaPathEdited(const QString &path); - void javaVersionSelected(BaseVersionPtr version); - void on_javaBrowseBtn_clicked(); - void on_javaStatusBtn_clicked(); - void checkFinished(JavaCheckResult result); + void memoryValueChanged(int); + void javaPathEdited(const QString &path); + void javaVersionSelected(BaseVersionPtr version); + void on_javaBrowseBtn_clicked(); + void on_javaStatusBtn_clicked(); + void checkFinished(JavaCheckResult result); protected: /* methods */ - void checkJavaPathOnEdit(const QString &path); - void checkJavaPath(const QString &path); - void setJavaStatus(JavaStatus status); - void setupUi(); + void checkJavaPathOnEdit(const QString &path); + void checkJavaPath(const QString &path); + void setJavaStatus(JavaStatus status); + void setupUi(); private: /* data */ - VersionSelectWidget *m_versionWidget = nullptr; - QVBoxLayout *m_verticalLayout = nullptr; + VersionSelectWidget *m_versionWidget = nullptr; + QVBoxLayout *m_verticalLayout = nullptr; - QLineEdit * m_javaPathTextBox = nullptr; - QPushButton * m_javaBrowseBtn = nullptr; - QToolButton * m_javaStatusBtn = nullptr; - QHBoxLayout *m_horizontalLayout = nullptr; + QLineEdit * m_javaPathTextBox = nullptr; + QPushButton * m_javaBrowseBtn = nullptr; + QToolButton * m_javaStatusBtn = nullptr; + QHBoxLayout *m_horizontalLayout = nullptr; - QGroupBox *m_memoryGroupBox = nullptr; - QGridLayout *m_gridLayout_2 = nullptr; - QSpinBox *m_maxMemSpinBox = nullptr; - QLabel *m_labelMinMem = nullptr; - QLabel *m_labelMaxMem = nullptr; - QSpinBox *m_minMemSpinBox = nullptr; - QLabel *m_labelPermGen = nullptr; - QSpinBox *m_permGenSpinBox = nullptr; - QIcon goodIcon; - QIcon yellowIcon; - QIcon badIcon; + QGroupBox *m_memoryGroupBox = nullptr; + QGridLayout *m_gridLayout_2 = nullptr; + QSpinBox *m_maxMemSpinBox = nullptr; + QLabel *m_labelMinMem = nullptr; + QLabel *m_labelMaxMem = nullptr; + QSpinBox *m_minMemSpinBox = nullptr; + QLabel *m_labelPermGen = nullptr; + QSpinBox *m_permGenSpinBox = nullptr; + QIcon goodIcon; + QIcon yellowIcon; + QIcon badIcon; - int observedMinMemory = 0; - int observedMaxMemory = 0; - int observedPermGenMemory = 0; - QString queuedCheck; - uint64_t m_availableMemory = 0ull; - shared_qobject_ptr<JavaChecker> m_checker; - JavaCheckResult m_result; + int observedMinMemory = 0; + int observedMaxMemory = 0; + int observedPermGenMemory = 0; + QString queuedCheck; + uint64_t m_availableMemory = 0ull; + shared_qobject_ptr<JavaChecker> m_checker; + JavaCheckResult m_result; }; diff --git a/application/widgets/LabeledToolButton.cpp b/application/widgets/LabeledToolButton.cpp index 744d2e00..ab273b65 100644 --- a/application/widgets/LabeledToolButton.cpp +++ b/application/widgets/LabeledToolButton.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,32 +28,32 @@ */ LabeledToolButton::LabeledToolButton(QWidget * parent) - : QToolButton(parent) - , m_label(new QLabel(this)) + : QToolButton(parent) + , m_label(new QLabel(this)) { - //QToolButton::setText(" "); - m_label->setWordWrap(true); - m_label->setMouseTracking(false); - m_label->setAlignment(Qt::AlignCenter); - m_label->setTextInteractionFlags(Qt::NoTextInteraction); - // somehow, this makes word wrap work in the QLabel. yay. - //m_label->setMinimumWidth(100); + //QToolButton::setText(" "); + m_label->setWordWrap(true); + m_label->setMouseTracking(false); + m_label->setAlignment(Qt::AlignCenter); + m_label->setTextInteractionFlags(Qt::NoTextInteraction); + // somehow, this makes word wrap work in the QLabel. yay. + //m_label->setMinimumWidth(100); } QString LabeledToolButton::text() const { - return m_label->text(); + return m_label->text(); } void LabeledToolButton::setText(const QString & text) { - m_label->setText(text); + m_label->setText(text); } void LabeledToolButton::setIcon(QIcon icon) { - m_icon = icon; - resetIcon(); + m_icon = icon; + resetIcon(); } @@ -62,54 +62,54 @@ void LabeledToolButton::setIcon(QIcon icon) */ QSize LabeledToolButton::sizeHint() const { - /* - Q_D(const QToolButton); - if (d->sizeHint.isValid()) - return d->sizeHint; - */ - ensurePolished(); + /* + Q_D(const QToolButton); + if (d->sizeHint.isValid()) + return d->sizeHint; + */ + ensurePolished(); - int w = 0, h = 0; - QStyleOptionToolButton opt; - initStyleOption(&opt); - QSize sz =m_label->sizeHint(); - w = sz.width(); - h = sz.height(); + int w = 0, h = 0; + QStyleOptionToolButton opt; + initStyleOption(&opt); + QSize sz =m_label->sizeHint(); + w = sz.width(); + h = sz.height(); - opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height - if (popupMode() == MenuButtonPopup) - w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this); - - QSize rawSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this); - QSize sizeHint = rawSize.expandedTo(QApplication::globalStrut()); - return sizeHint; + opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height + if (popupMode() == MenuButtonPopup) + w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this); + + QSize rawSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this); + QSize sizeHint = rawSize.expandedTo(QApplication::globalStrut()); + return sizeHint; } void LabeledToolButton::resizeEvent(QResizeEvent * event) { - m_label->setGeometry(QRect(4, 4, width()-8, height()-8)); - if(!m_icon.isNull()) - { - resetIcon(); - } - QWidget::resizeEvent(event); + m_label->setGeometry(QRect(4, 4, width()-8, height()-8)); + if(!m_icon.isNull()) + { + resetIcon(); + } + QWidget::resizeEvent(event); } void LabeledToolButton::resetIcon() { - auto iconSz = m_icon.actualSize(QSize(160, 80)); - float w = iconSz.width(); - float h = iconSz.height(); - float ar = w/h; - // FIXME: hardcoded max size of 160x80 - int newW = 80 * ar; - if(newW > 160) - newW = 160; - QSize newSz (newW, 80); - auto pixmap = m_icon.pixmap(newSz); - m_label->setPixmap(pixmap); - m_label->setMinimumHeight(80); - m_label->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred ); + auto iconSz = m_icon.actualSize(QSize(160, 80)); + float w = iconSz.width(); + float h = iconSz.height(); + float ar = w/h; + // FIXME: hardcoded max size of 160x80 + int newW = 80 * ar; + if(newW > 160) + newW = 160; + QSize newSz (newW, 80); + auto pixmap = m_icon.pixmap(newSz); + m_label->setPixmap(pixmap); + m_label->setMinimumHeight(80); + m_label->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred ); } diff --git a/application/widgets/LabeledToolButton.h b/application/widgets/LabeledToolButton.h index 151a5c2c..136ebd23 100644 --- a/application/widgets/LabeledToolButton.h +++ b/application/widgets/LabeledToolButton.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,19 +22,19 @@ class QLabel; class LabeledToolButton : public QToolButton { - Q_OBJECT + Q_OBJECT - QLabel * m_label; - QIcon m_icon; + QLabel * m_label; + QIcon m_icon; public: - LabeledToolButton(QWidget * parent = 0); + LabeledToolButton(QWidget * parent = 0); - QString text() const; - void setText(const QString & text); - void setIcon(QIcon icon); - virtual QSize sizeHint() const; + QString text() const; + void setText(const QString & text); + void setIcon(QIcon icon); + virtual QSize sizeHint() const; protected: - void resizeEvent(QResizeEvent * event); - void resetIcon(); + void resizeEvent(QResizeEvent * event); + void resetIcon(); }; diff --git a/application/widgets/LanguageSelectionWidget.cpp b/application/widgets/LanguageSelectionWidget.cpp new file mode 100644 index 00000000..80e26b39 --- /dev/null +++ b/application/widgets/LanguageSelectionWidget.cpp @@ -0,0 +1,68 @@ +#include "LanguageSelectionWidget.h" + +#include <QVBoxLayout> +#include <QTreeView> +#include <QHeaderView> +#include <QLabel> +#include "MultiMC.h" +#include "translations/TranslationsModel.h" + +LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) : + QWidget(parent) +{ + verticalLayout = new QVBoxLayout(this); + verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + languageView = new QTreeView(this); + languageView->setObjectName(QStringLiteral("languageView")); + languageView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + languageView->setAlternatingRowColors(true); + languageView->setRootIsDecorated(false); + languageView->setItemsExpandable(false); + languageView->setWordWrap(true); + languageView->header()->setCascadingSectionResizes(true); + languageView->header()->setStretchLastSection(false); + verticalLayout->addWidget(languageView); + helpUsLabel = new QLabel(this); + helpUsLabel->setObjectName(QStringLiteral("helpUsLabel")); + helpUsLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse); + helpUsLabel->setOpenExternalLinks(true); + helpUsLabel->setWordWrap(true); + verticalLayout->addWidget(helpUsLabel); + + auto translations = MMC->translations(); + auto index = translations->selectedIndex(); + languageView->setModel(translations.get()); + languageView->setCurrentIndex(index); + languageView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + languageView->header()->setSectionResizeMode(0, QHeaderView::Stretch); + connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageSelectionWidget::languageRowChanged); + verticalLayout->setContentsMargins(0,0,0,0); +} + +QString LanguageSelectionWidget::getSelectedLanguageKey() const +{ + auto translations = MMC->translations(); + return translations->data(languageView->currentIndex(), Qt::UserRole).toString(); +} + +void LanguageSelectionWidget::retranslate() +{ + QString text = + tr("Don't see your language or the quality is poor?") + + "<br/>" + + QString("<a href=\"https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC\">%1</a>").arg(tr("Help us with translations!")); + helpUsLabel->setText(text); + +} + +void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, const QModelIndex& previous) +{ + if (current == previous) + { + return; + } + auto translations = MMC->translations(); + QString key = translations->data(current, Qt::UserRole).toString(); + translations->selectLanguage(key); + translations->updateLanguage(key); +} diff --git a/application/widgets/LanguageSelectionWidget.h b/application/widgets/LanguageSelectionWidget.h new file mode 100644 index 00000000..03e29bd8 --- /dev/null +++ b/application/widgets/LanguageSelectionWidget.h @@ -0,0 +1,41 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QWidget> + +class QVBoxLayout; +class QTreeView; +class QLabel; + +class LanguageSelectionWidget: public QWidget +{ + Q_OBJECT +public: + explicit LanguageSelectionWidget(QWidget *parent = 0); + virtual ~LanguageSelectionWidget() { }; + + QString getSelectedLanguageKey() const; + void retranslate(); + +protected slots: + void languageRowChanged(const QModelIndex ¤t, const QModelIndex &previous); + +private: + QVBoxLayout *verticalLayout = nullptr; + QTreeView *languageView = nullptr; + QLabel *helpUsLabel = nullptr; +}; diff --git a/application/widgets/LineSeparator.cpp b/application/widgets/LineSeparator.cpp index f4ee173d..d03e6762 100644 --- a/application/widgets/LineSeparator.cpp +++ b/application/widgets/LineSeparator.cpp @@ -7,31 +7,31 @@ void LineSeparator::initStyleOption(QStyleOption *option) const { - option->initFrom(this); - // in a horizontal layout, the line is vertical (and vice versa) - if (m_orientation == Qt::Vertical) - option->state |= QStyle::State_Horizontal; + option->initFrom(this); + // in a horizontal layout, the line is vertical (and vice versa) + if (m_orientation == Qt::Vertical) + option->state |= QStyle::State_Horizontal; } LineSeparator::LineSeparator(QWidget *parent, Qt::Orientation orientation) - : QWidget(parent), m_orientation(orientation) + : QWidget(parent), m_orientation(orientation) { - setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); } QSize LineSeparator::sizeHint() const { - QStyleOption opt; - initStyleOption(&opt); - const int extent = - style()->pixelMetric(QStyle::PM_ToolBarSeparatorExtent, &opt, parentWidget()); - return QSize(extent, extent); + QStyleOption opt; + initStyleOption(&opt); + const int extent = + style()->pixelMetric(QStyle::PM_ToolBarSeparatorExtent, &opt, parentWidget()); + return QSize(extent, extent); } void LineSeparator::paintEvent(QPaintEvent *) { - QPainter p(this); - QStyleOption opt; - initStyleOption(&opt); - style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, &p, parentWidget()); + QPainter p(this); + QStyleOption opt; + initStyleOption(&opt); + style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, &p, parentWidget()); } diff --git a/application/widgets/LineSeparator.h b/application/widgets/LineSeparator.h index 9546e747..22927b68 100644 --- a/application/widgets/LineSeparator.h +++ b/application/widgets/LineSeparator.h @@ -5,14 +5,14 @@ class QStyleOption; class LineSeparator : public QWidget { - Q_OBJECT + Q_OBJECT public: - /// Create a line separator. orientation is the orientation of the line. - explicit LineSeparator(QWidget *parent, Qt::Orientation orientation = Qt::Horizontal); - QSize sizeHint() const; - void paintEvent(QPaintEvent *); - void initStyleOption(QStyleOption *option) const; + /// Create a line separator. orientation is the orientation of the line. + explicit LineSeparator(QWidget *parent, Qt::Orientation orientation = Qt::Horizontal); + QSize sizeHint() const; + void paintEvent(QPaintEvent *); + void initStyleOption(QStyleOption *option) const; private: - Qt::Orientation m_orientation = Qt::Horizontal; + Qt::Orientation m_orientation = Qt::Horizontal; }; diff --git a/application/widgets/LogView.cpp b/application/widgets/LogView.cpp index 8e0346fa..26a2a527 100644 --- a/application/widgets/LogView.cpp +++ b/application/widgets/LogView.cpp @@ -4,141 +4,141 @@ LogView::LogView(QWidget* parent) : QPlainTextEdit(parent) { - setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - m_defaultFormat = new QTextCharFormat(currentCharFormat()); + setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + m_defaultFormat = new QTextCharFormat(currentCharFormat()); } LogView::~LogView() { - delete m_defaultFormat; + delete m_defaultFormat; } void LogView::setWordWrap(bool wrapping) { - if(wrapping) - { - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setLineWrapMode(QPlainTextEdit::WidgetWidth); - } - else - { - setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); - setLineWrapMode(QPlainTextEdit::NoWrap); - } + if(wrapping) + { + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setLineWrapMode(QPlainTextEdit::WidgetWidth); + } + else + { + setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setLineWrapMode(QPlainTextEdit::NoWrap); + } } void LogView::setModel(QAbstractItemModel* model) { - if(m_model) - { - disconnect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate); - disconnect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted); - disconnect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted); - disconnect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved); - } - m_model = model; - if(m_model) - { - connect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate); - connect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted); - connect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted); - connect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved); - connect(m_model, &QAbstractItemModel::destroyed, this, &LogView::modelDestroyed); - } - repopulate(); + if(m_model) + { + disconnect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate); + disconnect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted); + disconnect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted); + disconnect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved); + } + m_model = model; + if(m_model) + { + connect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate); + connect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted); + connect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted); + connect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved); + connect(m_model, &QAbstractItemModel::destroyed, this, &LogView::modelDestroyed); + } + repopulate(); } QAbstractItemModel * LogView::model() const { - return m_model; + return m_model; } void LogView::modelDestroyed(QObject* model) { - if(m_model == model) - { - setModel(nullptr); - } + if(m_model == model) + { + setModel(nullptr); + } } void LogView::repopulate() { - auto doc = document(); - doc->clear(); - if(!m_model) - { - return; - } - rowsInserted(QModelIndex(), 0, m_model->rowCount() - 1); + auto doc = document(); + doc->clear(); + if(!m_model) + { + return; + } + rowsInserted(QModelIndex(), 0, m_model->rowCount() - 1); } void LogView::rowsAboutToBeInserted(const QModelIndex& parent, int first, int last) { - Q_UNUSED(parent) - Q_UNUSED(first) - Q_UNUSED(last) - QScrollBar *bar = verticalScrollBar(); - int max_bar = bar->maximum(); - int val_bar = bar->value(); - if (m_scroll) - { - m_scroll = (max_bar - val_bar) <= 1; - } - else - { - m_scroll = val_bar == max_bar; - } + Q_UNUSED(parent) + Q_UNUSED(first) + Q_UNUSED(last) + QScrollBar *bar = verticalScrollBar(); + int max_bar = bar->maximum(); + int val_bar = bar->value(); + if (m_scroll) + { + m_scroll = (max_bar - val_bar) <= 1; + } + else + { + m_scroll = val_bar == max_bar; + } } void LogView::rowsInserted(const QModelIndex& parent, int first, int last) { - for(int i = first; i <= last; i++) - { - auto idx = m_model->index(i, 0, parent); - auto text = m_model->data(idx, Qt::DisplayRole).toString(); - QTextCharFormat format(*m_defaultFormat); - auto font = m_model->data(idx, Qt::FontRole); - if(font.isValid()) - { - format.setFont(font.value<QFont>()); - } - auto fg = m_model->data(idx, Qt::TextColorRole); - if(fg.isValid()) - { - format.setForeground(fg.value<QColor>()); - } - auto bg = m_model->data(idx, Qt::BackgroundRole); - if(bg.isValid()) - { - format.setBackground(bg.value<QColor>()); - } - auto workCursor = textCursor(); - workCursor.movePosition(QTextCursor::End); - workCursor.insertText(text, format); - workCursor.insertBlock(); - } - if(m_scroll && !m_scrolling) - { - m_scrolling = true; - QMetaObject::invokeMethod( this, "scrollToBottom", Qt::QueuedConnection); - } + for(int i = first; i <= last; i++) + { + auto idx = m_model->index(i, 0, parent); + auto text = m_model->data(idx, Qt::DisplayRole).toString(); + QTextCharFormat format(*m_defaultFormat); + auto font = m_model->data(idx, Qt::FontRole); + if(font.isValid()) + { + format.setFont(font.value<QFont>()); + } + auto fg = m_model->data(idx, Qt::TextColorRole); + if(fg.isValid()) + { + format.setForeground(fg.value<QColor>()); + } + auto bg = m_model->data(idx, Qt::BackgroundRole); + if(bg.isValid()) + { + format.setBackground(bg.value<QColor>()); + } + auto workCursor = textCursor(); + workCursor.movePosition(QTextCursor::End); + workCursor.insertText(text, format); + workCursor.insertBlock(); + } + if(m_scroll && !m_scrolling) + { + m_scrolling = true; + QMetaObject::invokeMethod( this, "scrollToBottom", Qt::QueuedConnection); + } } void LogView::rowsRemoved(const QModelIndex& parent, int first, int last) { - // TODO: some day... maybe - Q_UNUSED(parent) - Q_UNUSED(first) - Q_UNUSED(last) + // TODO: some day... maybe + Q_UNUSED(parent) + Q_UNUSED(first) + Q_UNUSED(last) } void LogView::scrollToBottom() { - m_scrolling = false; - verticalScrollBar()->setSliderPosition(verticalScrollBar()->maximum()); + m_scrolling = false; + verticalScrollBar()->setSliderPosition(verticalScrollBar()->maximum()); } void LogView::findNext(const QString& what, bool reverse) { - find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); + find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); } diff --git a/application/widgets/LogView.h b/application/widgets/LogView.h index bb6747cd..3143360a 100644 --- a/application/widgets/LogView.h +++ b/application/widgets/LogView.h @@ -6,31 +6,31 @@ class QAbstractItemModel; class LogView: public QPlainTextEdit { - Q_OBJECT + Q_OBJECT public: - explicit LogView(QWidget *parent = nullptr); - virtual ~LogView(); + explicit LogView(QWidget *parent = nullptr); + virtual ~LogView(); - virtual void setModel(QAbstractItemModel *model); - QAbstractItemModel *model() const; + virtual void setModel(QAbstractItemModel *model); + QAbstractItemModel *model() const; public slots: - void setWordWrap(bool wrapping); - void findNext(const QString & what, bool reverse); - void scrollToBottom(); + void setWordWrap(bool wrapping); + void findNext(const QString & what, bool reverse); + void scrollToBottom(); protected slots: - void repopulate(); - // note: this supports only appending - void rowsInserted(const QModelIndex &parent, int first, int last); - void rowsAboutToBeInserted(const QModelIndex &parent, int first, int last); - // note: this supports only removing from front - void rowsRemoved(const QModelIndex &parent, int first, int last); - void modelDestroyed(QObject * model); + void repopulate(); + // note: this supports only appending + void rowsInserted(const QModelIndex &parent, int first, int last); + void rowsAboutToBeInserted(const QModelIndex &parent, int first, int last); + // note: this supports only removing from front + void rowsRemoved(const QModelIndex &parent, int first, int last); + void modelDestroyed(QObject * model); protected: - QAbstractItemModel *m_model = nullptr; - QTextCharFormat *m_defaultFormat = nullptr; - bool m_scroll = false; - bool m_scrolling = false; + QAbstractItemModel *m_model = nullptr; + QTextCharFormat *m_defaultFormat = nullptr; + bool m_scroll = false; + bool m_scrolling = false; }; diff --git a/application/widgets/MCModInfoFrame.cpp b/application/widgets/MCModInfoFrame.cpp index 629c17e7..577b32a7 100644 --- a/application/widgets/MCModInfoFrame.cpp +++ b/application/widgets/MCModInfoFrame.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,145 +22,145 @@ void MCModInfoFrame::updateWithMod(Mod &m) { - if (m.type() == m.MOD_FOLDER) - { - clear(); - return; - } + if (m.type() == m.MOD_FOLDER) + { + clear(); + return; + } - QString text = ""; - QString name = ""; - if (m.name().isEmpty()) - name = m.mmc_id(); - else - name = m.name(); + QString text = ""; + QString name = ""; + if (m.name().isEmpty()) + name = m.mmc_id(); + else + name = m.name(); - if (m.homeurl().isEmpty()) - text = name; - else - text = "<a href=\"" + m.homeurl() + "\">" + name + "</a>"; - if (!m.authors().isEmpty()) - text += " by " + m.authors(); + if (m.homeurl().isEmpty()) + text = name; + else + text = "<a href=\"" + m.homeurl() + "\">" + name + "</a>"; + if (!m.authors().isEmpty()) + text += " by " + m.authors().join(", "); - setModText(text); + setModText(text); - if (m.description().isEmpty()) - { - setModDescription(QString()); - } - else - { - setModDescription(m.description()); - } + if (m.description().isEmpty()) + { + setModDescription(QString()); + } + else + { + setModDescription(m.description()); + } } void MCModInfoFrame::clear() { - setModText(QString()); - setModDescription(QString()); + setModText(QString()); + setModDescription(QString()); } MCModInfoFrame::MCModInfoFrame(QWidget *parent) : - QFrame(parent), - ui(new Ui::MCModInfoFrame) + QFrame(parent), + ui(new Ui::MCModInfoFrame) { - ui->setupUi(this); - ui->label_ModDescription->setHidden(true); - ui->label_ModText->setHidden(true); - updateHiddenState(); + ui->setupUi(this); + ui->label_ModDescription->setHidden(true); + ui->label_ModText->setHidden(true); + updateHiddenState(); } MCModInfoFrame::~MCModInfoFrame() { - delete ui; + delete ui; } void MCModInfoFrame::updateHiddenState() { - if(ui->label_ModDescription->isHidden() && ui->label_ModText->isHidden()) - { - setHidden(true); - } - else - { - setHidden(false); - } + if(ui->label_ModDescription->isHidden() && ui->label_ModText->isHidden()) + { + setHidden(true); + } + else + { + setHidden(false); + } } void MCModInfoFrame::setModText(QString text) { - if(text.isEmpty()) - { - ui->label_ModText->setHidden(true); - } - else - { - ui->label_ModText->setText(text); - ui->label_ModText->setHidden(false); - } - updateHiddenState(); + if(text.isEmpty()) + { + ui->label_ModText->setHidden(true); + } + else + { + ui->label_ModText->setText(text); + ui->label_ModText->setHidden(false); + } + updateHiddenState(); } void MCModInfoFrame::setModDescription(QString text) { - if(text.isEmpty()) - { - ui->label_ModDescription->setHidden(true); - updateHiddenState(); - return; - } - else - { - ui->label_ModDescription->setHidden(false); - updateHiddenState(); - } - ui->label_ModDescription->setToolTip(""); - QString intermediatetext = text.trimmed(); - bool prev(false); - QChar rem('\n'); - QString finaltext; - finaltext.reserve(intermediatetext.size()); - foreach(const QChar& c, intermediatetext) - { - if(c == rem && prev){ - continue; - } - prev = c == rem; - finaltext += c; - } - QString labeltext; - labeltext.reserve(300); - if(finaltext.length() > 290) - { - ui->label_ModDescription->setOpenExternalLinks(false); - ui->label_ModDescription->setTextFormat(Qt::TextFormat::RichText); - desc = text; - labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>"); - QObject::connect(ui->label_ModDescription, &QLabel::linkActivated, this, &MCModInfoFrame::modDescEllipsisHandler); - } - else - { - ui->label_ModDescription->setTextFormat(Qt::TextFormat::PlainText); - labeltext.append(finaltext); - } - ui->label_ModDescription->setText(labeltext); + if(text.isEmpty()) + { + ui->label_ModDescription->setHidden(true); + updateHiddenState(); + return; + } + else + { + ui->label_ModDescription->setHidden(false); + updateHiddenState(); + } + ui->label_ModDescription->setToolTip(""); + QString intermediatetext = text.trimmed(); + bool prev(false); + QChar rem('\n'); + QString finaltext; + finaltext.reserve(intermediatetext.size()); + foreach(const QChar& c, intermediatetext) + { + if(c == rem && prev){ + continue; + } + prev = c == rem; + finaltext += c; + } + QString labeltext; + labeltext.reserve(300); + if(finaltext.length() > 290) + { + ui->label_ModDescription->setOpenExternalLinks(false); + ui->label_ModDescription->setTextFormat(Qt::TextFormat::RichText); + desc = text; + labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>"); + QObject::connect(ui->label_ModDescription, &QLabel::linkActivated, this, &MCModInfoFrame::modDescEllipsisHandler); + } + else + { + ui->label_ModDescription->setTextFormat(Qt::TextFormat::PlainText); + labeltext.append(finaltext); + } + ui->label_ModDescription->setText(labeltext); } void MCModInfoFrame::modDescEllipsisHandler(const QString &link) { - if(!currentBox) - { - currentBox = CustomMessageBox::selectable(this, QString(), desc); - connect(currentBox, &QMessageBox::finished, this, &MCModInfoFrame::boxClosed); - currentBox->show(); - } - else - { - currentBox->setText(desc); - } + if(!currentBox) + { + currentBox = CustomMessageBox::selectable(this, QString(), desc); + connect(currentBox, &QMessageBox::finished, this, &MCModInfoFrame::boxClosed); + currentBox->show(); + } + else + { + currentBox->setText(desc); + } } void MCModInfoFrame::boxClosed(int result) { - currentBox = nullptr; + currentBox = nullptr; } diff --git a/application/widgets/MCModInfoFrame.h b/application/widgets/MCModInfoFrame.h index 6bcd345c..51aa4489 100644 --- a/application/widgets/MCModInfoFrame.h +++ b/application/widgets/MCModInfoFrame.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ #pragma once #include <QFrame> -#include "minecraft/Mod.h" +#include "minecraft/mod/Mod.h" namespace Ui { @@ -25,28 +25,28 @@ class MCModInfoFrame; class MCModInfoFrame : public QFrame { - Q_OBJECT + Q_OBJECT public: - explicit MCModInfoFrame(QWidget *parent = 0); - ~MCModInfoFrame(); + explicit MCModInfoFrame(QWidget *parent = 0); + ~MCModInfoFrame(); - void setModText(QString text); - void setModDescription(QString text); + void setModText(QString text); + void setModDescription(QString text); - void updateWithMod(Mod &m); - void clear(); + void updateWithMod(Mod &m); + void clear(); public slots: - void modDescEllipsisHandler(const QString& link ); - void boxClosed(int result); + void modDescEllipsisHandler(const QString& link ); + void boxClosed(int result); private: - void updateHiddenState(); + void updateHiddenState(); private: - Ui::MCModInfoFrame *ui; - QString desc; - class QMessageBox * currentBox = nullptr; + Ui::MCModInfoFrame *ui; + QString desc; + class QMessageBox * currentBox = nullptr; }; diff --git a/application/widgets/ModListView.cpp b/application/widgets/ModListView.cpp index 96e8d91b..99972a40 100644 --- a/application/widgets/ModListView.cpp +++ b/application/widgets/ModListView.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,46 +21,46 @@ #include <QRect> ModListView::ModListView ( QWidget* parent ) - :QTreeView ( parent ) + :QTreeView ( parent ) { - setAllColumnsShowFocus ( true ); - setExpandsOnDoubleClick ( false ); - setRootIsDecorated ( false ); - setSortingEnabled ( true ); - setAlternatingRowColors ( true ); - setSelectionMode ( QAbstractItemView::ExtendedSelection ); - setHeaderHidden ( false ); - setSelectionBehavior(QAbstractItemView::SelectRows); - setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOn ); - setHorizontalScrollBarPolicy ( Qt::ScrollBarAsNeeded ); - setDropIndicatorShown(true); - setDragEnabled(true); - setDragDropMode(QAbstractItemView::DropOnly); - viewport()->setAcceptDrops(true); + setAllColumnsShowFocus ( true ); + setExpandsOnDoubleClick ( false ); + setRootIsDecorated ( false ); + setSortingEnabled ( true ); + setAlternatingRowColors ( true ); + setSelectionMode ( QAbstractItemView::ExtendedSelection ); + setHeaderHidden ( false ); + setSelectionBehavior(QAbstractItemView::SelectRows); + setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOn ); + setHorizontalScrollBarPolicy ( Qt::ScrollBarAsNeeded ); + setDropIndicatorShown(true); + setDragEnabled(true); + setDragDropMode(QAbstractItemView::DropOnly); + viewport()->setAcceptDrops(true); } void ModListView::setModel ( QAbstractItemModel* model ) { - QTreeView::setModel ( model ); - auto head = header(); - head->setStretchLastSection(false); - // HACK: this is true for the checkbox column of mod lists - auto string = model->headerData(0,head->orientation()).toString(); - if(head->count() < 1) - { - return; - } - if(!string.size()) - { - head->setSectionResizeMode(0, QHeaderView::ResizeToContents); - head->setSectionResizeMode(1, QHeaderView::Stretch); - for(int i = 2; i < head->count(); i++) - head->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - else - { - head->setSectionResizeMode(0, QHeaderView::Stretch); - for(int i = 1; i < head->count(); i++) - head->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } + QTreeView::setModel ( model ); + auto head = header(); + head->setStretchLastSection(false); + // HACK: this is true for the checkbox column of mod lists + auto string = model->headerData(0,head->orientation()).toString(); + if(head->count() < 1) + { + return; + } + if(!string.size()) + { + head->setSectionResizeMode(0, QHeaderView::ResizeToContents); + head->setSectionResizeMode(1, QHeaderView::Stretch); + for(int i = 2; i < head->count(); i++) + head->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + else + { + head->setSectionResizeMode(0, QHeaderView::Stretch); + for(int i = 1; i < head->count(); i++) + head->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } } diff --git a/application/widgets/ModListView.h b/application/widgets/ModListView.h index baca23f4..5a07e868 100644 --- a/application/widgets/ModListView.h +++ b/application/widgets/ModListView.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,10 @@ #pragma once #include <QTreeView> -class Mod; - class ModListView: public QTreeView { - Q_OBJECT + Q_OBJECT public: - explicit ModListView ( QWidget* parent = 0 ); - virtual void setModel ( QAbstractItemModel* model ); + explicit ModListView ( QWidget* parent = 0 ); + virtual void setModel ( QAbstractItemModel* model ); }; diff --git a/application/widgets/PageContainer.cpp b/application/widgets/PageContainer.cpp index 98de57e8..376e119b 100644 --- a/application/widgets/PageContainer.cpp +++ b/application/widgets/PageContainer.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,196 +36,204 @@ class PageEntryFilterModel : public QSortFilterProxyModel { public: - explicit PageEntryFilterModel(QObject *parent = 0) : QSortFilterProxyModel(parent) - { - } + explicit PageEntryFilterModel(QObject *parent = 0) : QSortFilterProxyModel(parent) + { + } protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const - { - const QString pattern = filterRegExp().pattern(); - const auto model = static_cast<PageModel *>(sourceModel()); - const auto page = model->pages().at(sourceRow); - if (!page->shouldDisplay()) - return false; - // Regular contents check, then check page-filter. - return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); - } + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const + { + const QString pattern = filterRegExp().pattern(); + const auto model = static_cast<PageModel *>(sourceModel()); + const auto page = model->pages().at(sourceRow); + if (!page->shouldDisplay()) + return false; + // Regular contents check, then check page-filter. + return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); + } }; PageContainer::PageContainer(BasePageProvider *pageProvider, QString defaultId, - QWidget *parent) - : QWidget(parent) + QWidget *parent) + : QWidget(parent) { - createUI(); - m_model = new PageModel(this); - m_proxyModel = new PageEntryFilterModel(this); - int counter = 0; - auto pages = pageProvider->getPages(); - for (auto page : pages) - { - page->stackIndex = m_pageStack->addWidget(dynamic_cast<QWidget *>(page)); - page->listIndex = counter; - page->setParentContainer(this); - counter++; - } - m_model->setPages(pages); - - m_proxyModel->setSourceModel(m_model); - m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - - m_pageList->setIconSize(QSize(pageIconSize, pageIconSize)); - m_pageList->setSelectionMode(QAbstractItemView::SingleSelection); - m_pageList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - m_pageList->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); - m_pageList->setModel(m_proxyModel); - connect(m_pageList->selectionModel(), SIGNAL(currentRowChanged(QModelIndex, QModelIndex)), - this, SLOT(currentChanged(QModelIndex))); - m_pageStack->setStackingMode(QStackedLayout::StackOne); - m_pageList->setFocus(); - selectPage(defaultId); + createUI(); + m_model = new PageModel(this); + m_proxyModel = new PageEntryFilterModel(this); + int counter = 0; + auto pages = pageProvider->getPages(); + for (auto page : pages) + { + page->stackIndex = m_pageStack->addWidget(dynamic_cast<QWidget *>(page)); + page->listIndex = counter; + page->setParentContainer(this); + counter++; + } + m_model->setPages(pages); + + m_proxyModel->setSourceModel(m_model); + m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + + m_pageList->setIconSize(QSize(pageIconSize, pageIconSize)); + m_pageList->setSelectionMode(QAbstractItemView::SingleSelection); + m_pageList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + m_pageList->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); + m_pageList->setModel(m_proxyModel); + connect(m_pageList->selectionModel(), SIGNAL(currentRowChanged(QModelIndex, QModelIndex)), + this, SLOT(currentChanged(QModelIndex))); + m_pageStack->setStackingMode(QStackedLayout::StackOne); + m_pageList->setFocus(); + selectPage(defaultId); } bool PageContainer::selectPage(QString pageId) { - // now find what we want to have selected... - auto page = m_model->findPageEntryById(pageId); - QModelIndex index; - if (page) - { - index = m_proxyModel->mapFromSource(m_model->index(page->listIndex)); - } - if(!index.isValid()) - { - index = m_proxyModel->index(0, 0); - } - if (index.isValid()) - { - m_pageList->setCurrentIndex(index); - return true; - } - return false; + // now find what we want to have selected... + auto page = m_model->findPageEntryById(pageId); + QModelIndex index; + if (page) + { + index = m_proxyModel->mapFromSource(m_model->index(page->listIndex)); + } + if(!index.isValid()) + { + index = m_proxyModel->index(0, 0); + } + if (index.isValid()) + { + m_pageList->setCurrentIndex(index); + return true; + } + return false; } void PageContainer::refreshContainer() { - m_proxyModel->invalidate(); - if(!m_currentPage->shouldDisplay()) - { - auto index = m_proxyModel->index(0, 0); - if(index.isValid()) - { - m_pageList->setCurrentIndex(index); - } - else - { - // FIXME: unhandled corner case: what to do when there's no page to select? - } - } + m_proxyModel->invalidate(); + if(!m_currentPage->shouldDisplay()) + { + auto index = m_proxyModel->index(0, 0); + if(index.isValid()) + { + m_pageList->setCurrentIndex(index); + } + else + { + // FIXME: unhandled corner case: what to do when there's no page to select? + } + } } void PageContainer::createUI() { - m_pageStack = new QStackedLayout; - m_pageList = new PageView; - m_header = new QLabel(); - m_iconHeader = new IconLabel(this, QIcon(), QSize(24, 24)); - - QFont headerLabelFont = m_header->font(); - headerLabelFont.setBold(true); - const int pointSize = headerLabelFont.pointSize(); - if (pointSize > 0) - headerLabelFont.setPointSize(pointSize + 2); - m_header->setFont(headerLabelFont); - - QHBoxLayout *headerHLayout = new QHBoxLayout; - const int leftMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutLeftMargin); - headerHLayout->addSpacerItem( - new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); - headerHLayout->addWidget(m_header); - headerHLayout->addSpacerItem( - new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); - headerHLayout->addWidget(m_iconHeader); - const int rightMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutRightMargin); - headerHLayout->addSpacerItem( - new QSpacerItem(rightMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); - - m_pageStack->setMargin(0); - m_pageStack->addWidget(new QWidget(this)); - - m_layout = new QGridLayout; - m_layout->addLayout(headerHLayout, 0, 1, 1, 1); - m_layout->addWidget(m_pageList, 0, 0, 2, 1); - m_layout->addLayout(m_pageStack, 1, 1, 1, 1); - m_layout->setColumnStretch(1, 4); - setLayout(m_layout); + m_pageStack = new QStackedLayout; + m_pageList = new PageView; + m_header = new QLabel(); + m_iconHeader = new IconLabel(this, QIcon(), QSize(24, 24)); + + QFont headerLabelFont = m_header->font(); + headerLabelFont.setBold(true); + const int pointSize = headerLabelFont.pointSize(); + if (pointSize > 0) + headerLabelFont.setPointSize(pointSize + 2); + m_header->setFont(headerLabelFont); + + QHBoxLayout *headerHLayout = new QHBoxLayout; + const int leftMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutLeftMargin); + headerHLayout->addSpacerItem(new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); + headerHLayout->addWidget(m_header); + headerHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); + headerHLayout->addWidget(m_iconHeader); + const int rightMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutRightMargin); + headerHLayout->addSpacerItem(new QSpacerItem(rightMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); + headerHLayout->setContentsMargins(0, 6, 0, 0); + + m_pageStack->setMargin(0); + m_pageStack->addWidget(new QWidget(this)); + + m_layout = new QGridLayout; + m_layout->addLayout(headerHLayout, 0, 1, 1, 1); + m_layout->addWidget(m_pageList, 0, 0, 2, 1); + m_layout->addLayout(m_pageStack, 1, 1, 1, 1); + m_layout->setColumnStretch(1, 4); + m_layout->setContentsMargins(0,0,0,6); + setLayout(m_layout); } void PageContainer::addButtons(QWidget *buttons) { - m_layout->addWidget(buttons, 2, 0, 1, 2); + m_layout->addWidget(buttons, 2, 0, 1, 2); } void PageContainer::addButtons(QLayout *buttons) { - m_layout->addLayout(buttons, 2, 0, 1, 2); + m_layout->addLayout(buttons, 2, 0, 1, 2); } void PageContainer::showPage(int row) { - if (m_currentPage) - { - m_currentPage->closed(); - } - if (row != -1) - { - m_currentPage = m_model->pages().at(row); - } - else - { - m_currentPage = nullptr; - } - if (m_currentPage) - { - m_pageStack->setCurrentIndex(m_currentPage->stackIndex); - m_header->setText(m_currentPage->displayName()); - m_iconHeader->setIcon(m_currentPage->icon()); - m_currentPage->opened(); - } - else - { - m_pageStack->setCurrentIndex(0); - m_header->setText(QString()); - m_iconHeader->setIcon(MMC->getThemedIcon("bug")); - } + if (m_currentPage) + { + m_currentPage->closed(); + } + if (row != -1) + { + m_currentPage = m_model->pages().at(row); + } + else + { + m_currentPage = nullptr; + } + if (m_currentPage) + { + m_pageStack->setCurrentIndex(m_currentPage->stackIndex); + m_header->setText(m_currentPage->displayName()); + m_iconHeader->setIcon(m_currentPage->icon()); + m_currentPage->opened(); + } + else + { + m_pageStack->setCurrentIndex(0); + m_header->setText(QString()); + m_iconHeader->setIcon(MMC->getThemedIcon("bug")); + } } void PageContainer::help() { - if (m_currentPage) - { - QString pageId = m_currentPage->helpPage(); - if (pageId.isEmpty()) - return; - DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/wiki/" + pageId)); - } + if (m_currentPage) + { + QString pageId = m_currentPage->helpPage(); + if (pageId.isEmpty()) + return; + DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/wiki/" + pageId)); + } } void PageContainer::currentChanged(const QModelIndex ¤t) { - showPage(current.isValid() ? m_proxyModel->mapToSource(current).row() : -1); + showPage(current.isValid() ? m_proxyModel->mapToSource(current).row() : -1); } bool PageContainer::prepareToClose() { - for (auto page : m_model->pages()) - { - if (!page->apply()) - return false; - } - if (m_currentPage) - { - m_currentPage->closed(); - } - return true; + if(!saveAll()) + { + return false; + } + if (m_currentPage) + { + m_currentPage->closed(); + } + return true; +} + +bool PageContainer::saveAll() +{ + for (auto page : m_model->pages()) + { + if (!page->apply()) + return false; + } + return true; } diff --git a/application/widgets/PageContainer.h b/application/widgets/PageContainer.h index ea9f8ce1..925f4ba3 100644 --- a/application/widgets/PageContainer.h +++ b/application/widgets/PageContainer.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,56 +33,57 @@ class QGridLayout; class PageContainer : public QWidget, public BasePageContainer { - Q_OBJECT + Q_OBJECT public: - explicit PageContainer(BasePageProvider *pageProvider, QString defaultId = QString(), - QWidget *parent = 0); - virtual ~PageContainer() {} + explicit PageContainer(BasePageProvider *pageProvider, QString defaultId = QString(), + QWidget *parent = 0); + virtual ~PageContainer() {} - void addButtons(QWidget * buttons); - void addButtons(QLayout * buttons); - /* - * Save any unsaved state and prepare to be closed. - * @return true if everything can be saved, false if there is something that requires attention - */ - bool prepareToClose(); + void addButtons(QWidget * buttons); + void addButtons(QLayout * buttons); + /* + * Save any unsaved state and prepare to be closed. + * @return true if everything can be saved, false if there is something that requires attention + */ + bool prepareToClose(); + bool saveAll(); - /* request close - used by individual pages */ - bool requestClose() override - { - if(m_container) - { - return m_container->requestClose(); - } - return false; - } + /* request close - used by individual pages */ + bool requestClose() override + { + if(m_container) + { + return m_container->requestClose(); + } + return false; + } - virtual bool selectPage(QString pageId) override; + virtual bool selectPage(QString pageId) override; - void refreshContainer() override; - virtual void setParentContainer(BasePageContainer * container) - { - m_container = container; - }; + void refreshContainer() override; + virtual void setParentContainer(BasePageContainer * container) + { + m_container = container; + }; private: - void createUI(); + void createUI(); public slots: - void help(); + void help(); private slots: - void currentChanged(const QModelIndex ¤t); - void showPage(int row); + void currentChanged(const QModelIndex ¤t); + void showPage(int row); private: - BasePageContainer * m_container = nullptr; - BasePage * m_currentPage = 0; - QSortFilterProxyModel *m_proxyModel; - PageModel *m_model; - QStackedLayout *m_pageStack; - QListView *m_pageList; - QLabel *m_header; - IconLabel *m_iconHeader; - QGridLayout *m_layout; + BasePageContainer * m_container = nullptr; + BasePage * m_currentPage = 0; + QSortFilterProxyModel *m_proxyModel; + PageModel *m_model; + QStackedLayout *m_pageStack; + QListView *m_pageList; + QLabel *m_header; + IconLabel *m_iconHeader; + QGridLayout *m_layout; }; diff --git a/application/widgets/PageContainer_p.h b/application/widgets/PageContainer_p.h index ed8171f1..4a5a9239 100644 --- a/application/widgets/PageContainer_p.h +++ b/application/widgets/PageContainer_p.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,98 +26,98 @@ const int pageIconSize = 24; class PageViewDelegate : public QStyledItemDelegate { public: - PageViewDelegate(QObject *parent) : QStyledItemDelegate(parent) - { - } - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const - { - QSize size = QStyledItemDelegate::sizeHint(option, index); - size.setHeight(qMax(size.height(), 32)); - return size; - } + PageViewDelegate(QObject *parent) : QStyledItemDelegate(parent) + { + } + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QSize size = QStyledItemDelegate::sizeHint(option, index); + size.setHeight(qMax(size.height(), 32)); + return size; + } }; class PageModel : public QAbstractListModel { public: - PageModel(QObject *parent = 0) : QAbstractListModel(parent) - { - QPixmap empty(pageIconSize, pageIconSize); - empty.fill(Qt::transparent); - m_emptyIcon = QIcon(empty); - } - virtual ~PageModel() {} + PageModel(QObject *parent = 0) : QAbstractListModel(parent) + { + QPixmap empty(pageIconSize, pageIconSize); + empty.fill(Qt::transparent); + m_emptyIcon = QIcon(empty); + } + virtual ~PageModel() {} - int rowCount(const QModelIndex &parent = QModelIndex()) const - { - return parent.isValid() ? 0 : m_pages.size(); - } - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const - { - switch (role) - { - case Qt::DisplayRole: - return m_pages.at(index.row())->displayName(); - case Qt::DecorationRole: - { - QIcon icon = m_pages.at(index.row())->icon(); - if (icon.isNull()) - icon = m_emptyIcon; - // HACK: fixes icon stretching on windows. TODO: report Qt bug for this - return QIcon(icon.pixmap(QSize(48,48))); - } - } - return QVariant(); - } + int rowCount(const QModelIndex &parent = QModelIndex()) const + { + return parent.isValid() ? 0 : m_pages.size(); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const + { + switch (role) + { + case Qt::DisplayRole: + return m_pages.at(index.row())->displayName(); + case Qt::DecorationRole: + { + QIcon icon = m_pages.at(index.row())->icon(); + if (icon.isNull()) + icon = m_emptyIcon; + // HACK: fixes icon stretching on windows. TODO: report Qt bug for this + return QIcon(icon.pixmap(QSize(48,48))); + } + } + return QVariant(); + } - void setPages(const QList<BasePage *> &pages) - { - beginResetModel(); - m_pages = pages; - endResetModel(); - } - const QList<BasePage *> &pages() const - { - return m_pages; - } + void setPages(const QList<BasePage *> &pages) + { + beginResetModel(); + m_pages = pages; + endResetModel(); + } + const QList<BasePage *> &pages() const + { + return m_pages; + } - BasePage * findPageEntryById(QString id) - { - for(auto page: m_pages) - { - if (page->id() == id) - return page; - } - return nullptr; - } + BasePage * findPageEntryById(QString id) + { + for(auto page: m_pages) + { + if (page->id() == id) + return page; + } + return nullptr; + } - QList<BasePage *> m_pages; - QIcon m_emptyIcon; + QList<BasePage *> m_pages; + QIcon m_emptyIcon; }; class PageView : public QListView { public: - PageView(QWidget *parent = 0) : QListView(parent) - { - setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); - setItemDelegate(new PageViewDelegate(this)); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - } + PageView(QWidget *parent = 0) : QListView(parent) + { + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); + setItemDelegate(new PageViewDelegate(this)); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } - virtual QSize sizeHint() const - { - int width = sizeHintForColumn(0) + frameWidth() * 2 + 5; - if (verticalScrollBar()->isVisible()) - width += verticalScrollBar()->width(); - return QSize(width, 100); - } + virtual QSize sizeHint() const + { + int width = sizeHintForColumn(0) + frameWidth() * 2 + 5; + if (verticalScrollBar()->isVisible()) + width += verticalScrollBar()->width(); + return QSize(width, 100); + } - virtual bool eventFilter(QObject *obj, QEvent *event) - { - if (obj == verticalScrollBar() && - (event->type() == QEvent::Show || event->type() == QEvent::Hide)) - updateGeometry(); - return QListView::eventFilter(obj, event); - } + virtual bool eventFilter(QObject *obj, QEvent *event) + { + if (obj == verticalScrollBar() && + (event->type() == QEvent::Show || event->type() == QEvent::Hide)) + updateGeometry(); + return QListView::eventFilter(obj, event); + } }; diff --git a/application/widgets/ProgressWidget.cpp b/application/widgets/ProgressWidget.cpp index fab099a9..911e555d 100644 --- a/application/widgets/ProgressWidget.cpp +++ b/application/widgets/ProgressWidget.cpp @@ -9,65 +9,65 @@ #include "tasks/Task.h" ProgressWidget::ProgressWidget(QWidget *parent) - : QWidget(parent) + : QWidget(parent) { - m_label = new QLabel(this); - m_label->setWordWrap(true); - m_bar = new QProgressBar(this); - m_bar->setMinimum(0); - m_bar->setMaximum(100); - QVBoxLayout *layout = new QVBoxLayout(this); - layout->addWidget(m_label); - layout->addWidget(m_bar); - layout->addStretch(); - setLayout(layout); + m_label = new QLabel(this); + m_label->setWordWrap(true); + m_bar = new QProgressBar(this); + m_bar->setMinimum(0); + m_bar->setMaximum(100); + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(m_label); + layout->addWidget(m_bar); + layout->addStretch(); + setLayout(layout); } void ProgressWidget::start(std::shared_ptr<Task> task) { - if (m_task) - { - disconnect(m_task.get(), 0, this, 0); - } - m_task = task; - connect(m_task.get(), &Task::finished, this, &ProgressWidget::handleTaskFinish); - connect(m_task.get(), &Task::status, this, &ProgressWidget::handleTaskStatus); - connect(m_task.get(), &Task::progress, this, &ProgressWidget::handleTaskProgress); - connect(m_task.get(), &Task::destroyed, this, &ProgressWidget::taskDestroyed); - if (!m_task->isRunning()) - { - QMetaObject::invokeMethod(m_task.get(), "start", Qt::QueuedConnection); - } + if (m_task) + { + disconnect(m_task.get(), 0, this, 0); + } + m_task = task; + connect(m_task.get(), &Task::finished, this, &ProgressWidget::handleTaskFinish); + connect(m_task.get(), &Task::status, this, &ProgressWidget::handleTaskStatus); + connect(m_task.get(), &Task::progress, this, &ProgressWidget::handleTaskProgress); + connect(m_task.get(), &Task::destroyed, this, &ProgressWidget::taskDestroyed); + if (!m_task->isRunning()) + { + QMetaObject::invokeMethod(m_task.get(), "start", Qt::QueuedConnection); + } } bool ProgressWidget::exec(std::shared_ptr<Task> task) { - QEventLoop loop; - connect(task.get(), &Task::finished, &loop, &QEventLoop::quit); - start(task); - if (task->isRunning()) - { - loop.exec(); - } - return task->wasSuccessful(); + QEventLoop loop; + connect(task.get(), &Task::finished, &loop, &QEventLoop::quit); + start(task); + if (task->isRunning()) + { + loop.exec(); + } + return task->wasSuccessful(); } void ProgressWidget::handleTaskFinish() { - if (!m_task->wasSuccessful()) - { - m_label->setText(m_task->failReason()); - } + if (!m_task->wasSuccessful()) + { + m_label->setText(m_task->failReason()); + } } void ProgressWidget::handleTaskStatus(const QString &status) { - m_label->setText(status); + m_label->setText(status); } void ProgressWidget::handleTaskProgress(qint64 current, qint64 total) { - m_bar->setMaximum(total); - m_bar->setValue(current); + m_bar->setMaximum(total); + m_bar->setValue(current); } void ProgressWidget::taskDestroyed() { - m_task = nullptr; + m_task = nullptr; } diff --git a/application/widgets/ProgressWidget.h b/application/widgets/ProgressWidget.h index 08d8a157..fa67748a 100644 --- a/application/widgets/ProgressWidget.h +++ b/application/widgets/ProgressWidget.h @@ -11,22 +11,22 @@ class QLabel; class ProgressWidget : public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit ProgressWidget(QWidget *parent = nullptr); + explicit ProgressWidget(QWidget *parent = nullptr); public slots: - void start(std::shared_ptr<Task> task); - bool exec(std::shared_ptr<Task> task); + void start(std::shared_ptr<Task> task); + bool exec(std::shared_ptr<Task> task); private slots: - void handleTaskFinish(); - void handleTaskStatus(const QString &status); - void handleTaskProgress(qint64 current, qint64 total); - void taskDestroyed(); + void handleTaskFinish(); + void handleTaskStatus(const QString &status); + void handleTaskProgress(qint64 current, qint64 total); + void taskDestroyed(); private: - QLabel *m_label; - QProgressBar *m_bar; - std::shared_ptr<Task> m_task; + QLabel *m_label; + QProgressBar *m_bar; + std::shared_ptr<Task> m_task; }; diff --git a/application/widgets/ServerStatus.cpp b/application/widgets/ServerStatus.cpp index f1963b68..a7016c0c 100644 --- a/application/widgets/ServerStatus.cpp +++ b/application/widgets/ServerStatus.cpp @@ -15,80 +15,80 @@ class ClickableLabel : public QLabel { - Q_OBJECT + Q_OBJECT public: - ClickableLabel(QWidget *parent) : QLabel(parent) - { - setCursor(Qt::PointingHandCursor); - } + ClickableLabel(QWidget *parent) : QLabel(parent) + { + setCursor(Qt::PointingHandCursor); + } - ~ClickableLabel(){}; + ~ClickableLabel(){}; signals: - void clicked(); + void clicked(); protected: - void mousePressEvent(QMouseEvent *event) - { - emit clicked(); - } + void mousePressEvent(QMouseEvent *event) + { + emit clicked(); + } }; class ClickableIconLabel : public IconLabel { - Q_OBJECT + Q_OBJECT public: - ClickableIconLabel(QWidget *parent, QIcon icon, QSize size) : IconLabel(parent, icon, size) - { - setCursor(Qt::PointingHandCursor); - } + ClickableIconLabel(QWidget *parent, QIcon icon, QSize size) : IconLabel(parent, icon, size) + { + setCursor(Qt::PointingHandCursor); + } - ~ClickableIconLabel(){}; + ~ClickableIconLabel(){}; signals: - void clicked(); + void clicked(); protected: - void mousePressEvent(QMouseEvent *event) - { - emit clicked(); - } + void mousePressEvent(QMouseEvent *event) + { + emit clicked(); + } }; ServerStatus::ServerStatus(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) { - layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - goodIcon = MMC->getThemedIcon("status-good"); - yellowIcon = MMC->getThemedIcon("status-yellow"); - badIcon = MMC->getThemedIcon("status-bad"); - - addStatus("authserver.mojang.com", tr("Auth")); - addLine(); - addStatus("sessionserver.mojang.com", tr("Session")); - addLine(); - addStatus("textures.minecraft.net", tr("Skins")); - addLine(); - addStatus("api.mojang.com", tr("API")); - - m_statusRefresh = new QToolButton(this); - m_statusRefresh->setCheckable(true); - m_statusRefresh->setToolButtonStyle(Qt::ToolButtonIconOnly); - m_statusRefresh->setIcon(MMC->getThemedIcon("refresh")); - layout->addWidget(m_statusRefresh); - - setLayout(layout); - - // Start status checker - m_statusChecker.reset(new StatusChecker()); - { - auto reloader = m_statusChecker.get(); - connect(reloader, &StatusChecker::statusChanged, this, &ServerStatus::StatusChanged); - connect(reloader, &StatusChecker::statusLoading, this, &ServerStatus::StatusReloading); - connect(m_statusRefresh, &QAbstractButton::clicked, this, &ServerStatus::reloadStatus); - m_statusChecker->startTimer(60000); - reloadStatus(); - } + layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + goodIcon = MMC->getThemedIcon("status-good"); + yellowIcon = MMC->getThemedIcon("status-yellow"); + badIcon = MMC->getThemedIcon("status-bad"); + + addStatus("authserver.mojang.com", tr("Auth")); + addLine(); + addStatus("sessionserver.mojang.com", tr("Session")); + addLine(); + addStatus("textures.minecraft.net", tr("Skins")); + addLine(); + addStatus("api.mojang.com", tr("API")); + + m_statusRefresh = new QToolButton(this); + m_statusRefresh->setCheckable(true); + m_statusRefresh->setToolButtonStyle(Qt::ToolButtonIconOnly); + m_statusRefresh->setIcon(MMC->getThemedIcon("refresh")); + layout->addWidget(m_statusRefresh); + + setLayout(layout); + + // Start status checker + m_statusChecker.reset(new StatusChecker()); + { + auto reloader = m_statusChecker.get(); + connect(reloader, &StatusChecker::statusChanged, this, &ServerStatus::StatusChanged); + connect(reloader, &StatusChecker::statusLoading, this, &ServerStatus::StatusReloading); + connect(m_statusRefresh, &QAbstractButton::clicked, this, &ServerStatus::reloadStatus); + m_statusChecker->startTimer(60000); + reloadStatus(); + } } ServerStatus::~ServerStatus() @@ -97,83 +97,83 @@ ServerStatus::~ServerStatus() void ServerStatus::reloadStatus() { - m_statusChecker->reloadStatus(); + m_statusChecker->reloadStatus(); } void ServerStatus::addLine() { - layout->addWidget(new LineSeparator(this, Qt::Vertical)); + layout->addWidget(new LineSeparator(this, Qt::Vertical)); } void ServerStatus::addStatus(QString key, QString name) { - { - auto label = new ClickableIconLabel(this, badIcon, QSize(16, 16)); - label->setToolTip(key); - serverLabels[key] = label; - layout->addWidget(label); - connect(label,SIGNAL(clicked()),SLOT(clicked())); - } - { - auto label = new ClickableLabel(this); - label->setText(name); - label->setToolTip(key); - layout->addWidget(label); - connect(label,SIGNAL(clicked()),SLOT(clicked())); - } + { + auto label = new ClickableIconLabel(this, badIcon, QSize(16, 16)); + label->setToolTip(key); + serverLabels[key] = label; + layout->addWidget(label); + connect(label,SIGNAL(clicked()),SLOT(clicked())); + } + { + auto label = new ClickableLabel(this); + label->setText(name); + label->setToolTip(key); + layout->addWidget(label); + connect(label,SIGNAL(clicked()),SLOT(clicked())); + } } void ServerStatus::clicked() { - DesktopServices::openUrl(QUrl("https://help.mojang.com/")); + DesktopServices::openUrl(QUrl("https://help.mojang.com/")); } void ServerStatus::setStatus(QString key, int value) { - if (!serverLabels.contains(key)) - return; - IconLabel *label = serverLabels[key]; - switch(value) - { - case 0: - label->setIcon(goodIcon); - break; - case 1: - label->setIcon(yellowIcon); - break; - default: - case 2: - label->setIcon(badIcon); - break; - } + if (!serverLabels.contains(key)) + return; + IconLabel *label = serverLabels[key]; + switch(value) + { + case 0: + label->setIcon(goodIcon); + break; + case 1: + label->setIcon(yellowIcon); + break; + default: + case 2: + label->setIcon(badIcon); + break; + } } void ServerStatus::StatusChanged(const QMap<QString, QString> statusEntries) { - auto convertStatus = [&](QString status)->int - { - if (status == "green") - return 0; - else if (status == "yellow") - return 1; - else if (status == "red") - return 2; - return 2; - } - ; - auto iter = statusEntries.begin(); - while (iter != statusEntries.end()) - { - QString key = iter.key(); - auto value = convertStatus(iter.value()); - setStatus(key, value); - iter++; - } + auto convertStatus = [&](QString status)->int + { + if (status == "green") + return 0; + else if (status == "yellow") + return 1; + else if (status == "red") + return 2; + return 2; + } + ; + auto iter = statusEntries.begin(); + while (iter != statusEntries.end()) + { + QString key = iter.key(); + auto value = convertStatus(iter.value()); + setStatus(key, value); + iter++; + } } void ServerStatus::StatusReloading(bool is_reloading) { - m_statusRefresh->setChecked(is_reloading); + m_statusRefresh->setChecked(is_reloading); } #include "ServerStatus.moc" diff --git a/application/widgets/ServerStatus.h b/application/widgets/ServerStatus.h index 9beb8e4f..e1e70dfb 100644 --- a/application/widgets/ServerStatus.h +++ b/application/widgets/ServerStatus.h @@ -12,29 +12,29 @@ class StatusChecker; class ServerStatus: public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit ServerStatus(QWidget *parent = nullptr, Qt::WindowFlags f = 0); - virtual ~ServerStatus(); + explicit ServerStatus(QWidget *parent = nullptr, Qt::WindowFlags f = 0); + virtual ~ServerStatus(); public slots: - void reloadStatus(); - void StatusChanged(const QMap<QString, QString> statuses); - void StatusReloading(bool is_reloading); + void reloadStatus(); + void StatusChanged(const QMap<QString, QString> statuses); + void StatusReloading(bool is_reloading); private slots: - void clicked(); + void clicked(); private: /* methods */ - void addLine(); - void addStatus(QString key, QString name); - void setStatus(QString key, int value); + void addLine(); + void addStatus(QString key, QString name); + void setStatus(QString key, int value); private: /* data */ - QHBoxLayout * layout = nullptr; - QToolButton *m_statusRefresh = nullptr; - QMap<QString, IconLabel *> serverLabels; - QIcon goodIcon; - QIcon yellowIcon; - QIcon badIcon; - std::shared_ptr<StatusChecker> m_statusChecker; + QHBoxLayout * layout = nullptr; + QToolButton *m_statusRefresh = nullptr; + QMap<QString, IconLabel *> serverLabels; + QIcon goodIcon; + QIcon yellowIcon; + QIcon badIcon; + std::shared_ptr<StatusChecker> m_statusChecker; }; diff --git a/application/widgets/VersionListView.cpp b/application/widgets/VersionListView.cpp index 8c80ecf3..09df75b7 100644 --- a/application/widgets/VersionListView.cpp +++ b/application/widgets/VersionListView.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,155 +22,140 @@ #include "Common.h" VersionListView::VersionListView(QWidget *parent) - :QTreeView ( parent ) + :QTreeView ( parent ) { - m_emptyString = tr("No versions are currently available."); + m_emptyString = tr("No versions are currently available."); } void VersionListView::rowsInserted(const QModelIndex &parent, int start, int end) { - if(!m_itemCount) - viewport()->update(); - m_itemCount += end-start+1; - QTreeView::rowsInserted(parent, start, end); + m_itemCount += end-start+1; + updateEmptyViewPort(); + QTreeView::rowsInserted(parent, start, end); } void VersionListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { - m_itemCount -= end-start+1; - if(!m_itemCount) - viewport()->update(); - QTreeView::rowsInserted(parent, start, end); + m_itemCount -= end-start+1; + updateEmptyViewPort(); + QTreeView::rowsInserted(parent, start, end); } void VersionListView::setModel(QAbstractItemModel *model) { - m_itemCount = model->rowCount(); - if(!m_itemCount) - viewport()->update(); - QTreeView::setModel(model); + m_itemCount = model->rowCount(); + updateEmptyViewPort(); + QTreeView::setModel(model); } void VersionListView::reset() { - if(model()) - { - m_itemCount = model()->rowCount(); - } - viewport()->update(); - QTreeView::reset(); + if(model()) + { + m_itemCount = model()->rowCount(); + } + else { + m_itemCount = 0; + } + updateEmptyViewPort(); + QTreeView::reset(); } void VersionListView::setEmptyString(QString emptyString) { - m_emptyString = emptyString; - updateEmptyViewPort(); + m_emptyString = emptyString; + updateEmptyViewPort(); } void VersionListView::setEmptyErrorString(QString emptyErrorString) { - m_emptyErrorString = emptyErrorString; - updateEmptyViewPort(); + m_emptyErrorString = emptyErrorString; + updateEmptyViewPort(); } void VersionListView::setEmptyMode(VersionListView::EmptyMode mode) { - m_emptyMode = mode; - updateEmptyViewPort(); + m_emptyMode = mode; + updateEmptyViewPort(); } void VersionListView::updateEmptyViewPort() { - if(!m_itemCount) - { - viewport()->update(); - } + setAccessibleDescription(currentEmptyString()); + + if(!m_itemCount) + { + viewport()->update(); + } } void VersionListView::paintEvent(QPaintEvent *event) { - if(m_itemCount) - { - QTreeView::paintEvent(event); - } - else - { - paintInfoLabel(event); - } + if(m_itemCount) + { + QTreeView::paintEvent(event); + } + else + { + paintInfoLabel(event); + } } -void VersionListView::paintInfoLabel(QPaintEvent *event) +QString VersionListView::currentEmptyString() const { - QString emptyString; - switch(m_emptyMode) - { - case VersionListView::Empty: - return; - case VersionListView::String: - emptyString = m_emptyString; - break; - case VersionListView::ErrorString: - emptyString = m_emptyErrorString; - break; - } + if(m_itemCount) { + return QString(); + } + switch(m_emptyMode) + { + default: + case VersionListView::Empty: + return QString(); + case VersionListView::String: + return m_emptyString; + case VersionListView::ErrorString: + return m_emptyErrorString; + } +} + + +void VersionListView::paintInfoLabel(QPaintEvent *event) const +{ + QString emptyString = currentEmptyString(); + //calculate the rect for the overlay QPainter painter(viewport()); painter.setRenderHint(QPainter::Antialiasing, true); - QFont font("sans", 20); + QFont font("sans", 20); font.setBold(true); - - QRect bounds = viewport()->geometry(); - bounds.moveTop(0); - QTextLayout layout(emptyString, font); - qreal height = 0.0; - qreal widthUsed = 0.0; - QStringList lines = viewItemTextLayout(layout, bounds.width() - 20, height, widthUsed); - QRect rect (0,0, widthUsed, height); - rect.setWidth(rect.width()+20); - rect.setHeight(rect.height()+20); - rect.moveCenter(bounds.center()); + + QRect bounds = viewport()->geometry(); + bounds.moveTop(0); + auto innerBounds = bounds; + innerBounds.adjust(10, 10, -10, -10); + + QColor background = QApplication::palette().color(QPalette::Foreground); + QColor foreground = QApplication::palette().color(QPalette::Base); + foreground.setAlpha(190); + painter.setFont(font); + auto fontMetrics = painter.fontMetrics(); + auto textRect = fontMetrics.boundingRect(innerBounds, Qt::AlignHCenter | Qt::TextWordWrap, emptyString); + textRect.moveCenter(bounds.center()); + + auto wrapRect = textRect; + wrapRect.adjust(-10, -10, 10, 10); + //check if we are allowed to draw in our area - if (!event->rect().intersects(rect)) { + if (!event->rect().intersects(wrapRect)) { return; } - //draw the letter of the topmost item semitransparent in the middle - QColor background = QApplication::palette().color(QPalette::Foreground); - QColor foreground = QApplication::palette().color(QPalette::Base); - /* - background.setAlpha(128 - scrollFade); - foreground.setAlpha(128 - scrollFade); - */ + painter.setBrush(QBrush(background)); painter.setPen(foreground); - painter.drawRoundedRect(rect, 5.0, 5.0); - foreground.setAlpha(190); + painter.drawRoundedRect(wrapRect, 5.0, 5.0); + painter.setPen(foreground); painter.setFont(font); - painter.drawText(rect, Qt::AlignCenter, lines.join("\n")); - -} - -/* -void ModListView::setModel ( QAbstractItemModel* model ) -{ - QTreeView::setModel ( model ); - auto head = header(); - head->setStretchLastSection(false); - // HACK: this is true for the checkbox column of mod lists - auto string = model->headerData(0,head->orientation()).toString(); - if(!string.size()) - { - head->setSectionResizeMode(0, QHeaderView::ResizeToContents); - head->setSectionResizeMode(1, QHeaderView::Stretch); - for(int i = 2; i < head->count(); i++) - head->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - else - { - head->setSectionResizeMode(0, QHeaderView::Stretch); - for(int i = 1; i < head->count(); i++) - head->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } + painter.drawText(textRect, Qt::AlignHCenter | Qt::TextWordWrap, emptyString); } -*/ diff --git a/application/widgets/VersionListView.h b/application/widgets/VersionListView.h index b7a881e9..37f7b27e 100644 --- a/application/widgets/VersionListView.h +++ b/application/widgets/VersionListView.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,42 +16,41 @@ #pragma once #include <QTreeView> -class Mod; - class VersionListView : public QTreeView { - Q_OBJECT + Q_OBJECT public: - explicit VersionListView(QWidget *parent = 0); - virtual void paintEvent(QPaintEvent *event) override; - virtual void setModel(QAbstractItemModel* model) override; + explicit VersionListView(QWidget *parent = 0); + virtual void paintEvent(QPaintEvent *event) override; + virtual void setModel(QAbstractItemModel* model) override; - enum EmptyMode - { - Empty, - String, - ErrorString - }; + enum EmptyMode + { + Empty, + String, + ErrorString + }; - void setEmptyString(QString emptyString); - void setEmptyErrorString(QString emptyErrorString); - void setEmptyMode(EmptyMode mode); + void setEmptyString(QString emptyString); + void setEmptyErrorString(QString emptyErrorString); + void setEmptyMode(EmptyMode mode); public slots: - virtual void reset() override; + virtual void reset() override; protected slots: - virtual void rowsAboutToBeRemoved(const QModelIndex & parent, int start, int end) override; - virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; + virtual void rowsAboutToBeRemoved(const QModelIndex & parent, int start, int end) override; + virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; private: /* methods */ - void paintInfoLabel(QPaintEvent *event); - void updateEmptyViewPort(); + void paintInfoLabel(QPaintEvent *event) const; + void updateEmptyViewPort(); + QString currentEmptyString() const; private: /* variables */ - int m_itemCount = 0; - QString m_emptyString; - QString m_emptyErrorString; - EmptyMode m_emptyMode = Empty; + int m_itemCount = 0; + QString m_emptyString; + QString m_emptyErrorString; + EmptyMode m_emptyMode = Empty; }; diff --git a/application/widgets/VersionSelectWidget.cpp b/application/widgets/VersionSelectWidget.cpp index ce1141b6..9925a6b4 100644 --- a/application/widgets/VersionSelectWidget.cpp +++ b/application/widgets/VersionSelectWidget.cpp @@ -7,50 +7,51 @@ #include <dialogs/CustomMessageBox.h> VersionSelectWidget::VersionSelectWidget(QWidget* parent) - : QWidget(parent) + : QWidget(parent) { - setObjectName(QStringLiteral("VersionSelectWidget")); - verticalLayout = new QVBoxLayout(this); - verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - verticalLayout->setContentsMargins(0, 0, 0, 0); + setObjectName(QStringLiteral("VersionSelectWidget")); + verticalLayout = new QVBoxLayout(this); + verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + verticalLayout->setContentsMargins(0, 0, 0, 0); - m_proxyModel = new VersionProxyModel(this); + m_proxyModel = new VersionProxyModel(this); - listView = new VersionListView(this); - listView->setObjectName(QStringLiteral("listView")); - listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - listView->setAlternatingRowColors(true); - listView->setRootIsDecorated(false); - listView->setItemsExpandable(false); - listView->setWordWrap(true); - listView->header()->setCascadingSectionResizes(true); - listView->header()->setStretchLastSection(false); - listView->setModel(m_proxyModel); - verticalLayout->addWidget(listView); + listView = new VersionListView(this); + listView->setObjectName(QStringLiteral("listView")); + listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + listView->setAlternatingRowColors(true); + listView->setRootIsDecorated(false); + listView->setItemsExpandable(false); + listView->setWordWrap(true); + listView->header()->setCascadingSectionResizes(true); + listView->header()->setStretchLastSection(false); + listView->setModel(m_proxyModel); + verticalLayout->addWidget(listView); - sneakyProgressBar = new QProgressBar(this); - sneakyProgressBar->setObjectName(QStringLiteral("sneakyProgressBar")); - sneakyProgressBar->setFormat(QStringLiteral("%p%")); - verticalLayout->addWidget(sneakyProgressBar); - sneakyProgressBar->setHidden(true); - connect(listView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &VersionSelectWidget::currentRowChanged); + sneakyProgressBar = new QProgressBar(this); + sneakyProgressBar->setObjectName(QStringLiteral("sneakyProgressBar")); + sneakyProgressBar->setFormat(QStringLiteral("%p%")); + verticalLayout->addWidget(sneakyProgressBar); + sneakyProgressBar->setHidden(true); + connect(listView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &VersionSelectWidget::currentRowChanged); - QMetaObject::connectSlotsByName(this); + QMetaObject::connectSlotsByName(this); } void VersionSelectWidget::setCurrentVersion(const QString& version) { - m_currentVersion = version; + m_currentVersion = version; + m_proxyModel->setCurrentVersion(version); } void VersionSelectWidget::setEmptyString(QString emptyString) { - listView->setEmptyString(emptyString); + listView->setEmptyString(emptyString); } void VersionSelectWidget::setEmptyErrorString(QString emptyErrorString) { - listView->setEmptyErrorString(emptyErrorString); + listView->setEmptyErrorString(emptyErrorString); } VersionSelectWidget::~VersionSelectWidget() @@ -59,143 +60,143 @@ VersionSelectWidget::~VersionSelectWidget() void VersionSelectWidget::setResizeOn(int column) { - listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::ResizeToContents); - resizeOnColumn = column; - listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch); + listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::ResizeToContents); + resizeOnColumn = column; + listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch); } void VersionSelectWidget::initialize(BaseVersionList *vlist) { - m_vlist = vlist; - m_proxyModel->setSourceModel(vlist); - listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); - listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch); - - if (!m_vlist->isLoaded()) - { - loadList(); - } - else - { - if (m_proxyModel->rowCount() == 0) - { - listView->setEmptyMode(VersionListView::String); - } - preselect(); - } + m_vlist = vlist; + m_proxyModel->setSourceModel(vlist); + listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch); + + if (!m_vlist->isLoaded()) + { + loadList(); + } + else + { + if (m_proxyModel->rowCount() == 0) + { + listView->setEmptyMode(VersionListView::String); + } + preselect(); + } } void VersionSelectWidget::closeEvent(QCloseEvent * event) { - QWidget::closeEvent(event); + QWidget::closeEvent(event); } void VersionSelectWidget::loadList() { - auto newTask = m_vlist->getLoadTask(); - if (!newTask) - { - return; - } - loadTask = newTask.get(); - connect(loadTask, &Task::succeeded, this, &VersionSelectWidget::onTaskSucceeded); - connect(loadTask, &Task::failed, this, &VersionSelectWidget::onTaskFailed); - connect(loadTask, &Task::progress, this, &VersionSelectWidget::changeProgress); - if(!loadTask->isRunning()) - { - loadTask->start(); - } - sneakyProgressBar->setHidden(false); + auto newTask = m_vlist->getLoadTask(); + if (!newTask) + { + return; + } + loadTask = newTask.get(); + connect(loadTask, &Task::succeeded, this, &VersionSelectWidget::onTaskSucceeded); + connect(loadTask, &Task::failed, this, &VersionSelectWidget::onTaskFailed); + connect(loadTask, &Task::progress, this, &VersionSelectWidget::changeProgress); + if(!loadTask->isRunning()) + { + loadTask->start(); + } + sneakyProgressBar->setHidden(false); } void VersionSelectWidget::onTaskSucceeded() { - if (m_proxyModel->rowCount() == 0) - { - listView->setEmptyMode(VersionListView::String); - } - sneakyProgressBar->setHidden(true); - preselect(); - loadTask = nullptr; + if (m_proxyModel->rowCount() == 0) + { + listView->setEmptyMode(VersionListView::String); + } + sneakyProgressBar->setHidden(true); + preselect(); + loadTask = nullptr; } void VersionSelectWidget::onTaskFailed(const QString& reason) { - CustomMessageBox::selectable(this, tr("Error"), tr("List update failed:\n%1").arg(reason), QMessageBox::Warning)->show(); - onTaskSucceeded(); + CustomMessageBox::selectable(this, tr("Error"), tr("List update failed:\n%1").arg(reason), QMessageBox::Warning)->show(); + onTaskSucceeded(); } void VersionSelectWidget::changeProgress(qint64 current, qint64 total) { - sneakyProgressBar->setMaximum(total); - sneakyProgressBar->setValue(current); + sneakyProgressBar->setMaximum(total); + sneakyProgressBar->setValue(current); } void VersionSelectWidget::currentRowChanged(const QModelIndex& current, const QModelIndex&) { - auto variant = m_proxyModel->data(current, BaseVersionList::VersionPointerRole); - emit selectedVersionChanged(variant.value<BaseVersionPtr>()); + auto variant = m_proxyModel->data(current, BaseVersionList::VersionPointerRole); + emit selectedVersionChanged(variant.value<BaseVersionPtr>()); } void VersionSelectWidget::preselect() { - if(preselectedAlready) - return; - selectCurrent(); - if(preselectedAlready) - return; - selectRecommended(); + if(preselectedAlready) + return; + selectCurrent(); + if(preselectedAlready) + return; + selectRecommended(); } void VersionSelectWidget::selectCurrent() { - if(m_currentVersion.isEmpty()) - { - return; - } - auto idx = m_proxyModel->getVersion(m_currentVersion); - if(idx.isValid()) - { - preselectedAlready = true; - listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); - listView->scrollTo(idx, QAbstractItemView::PositionAtCenter); - } + if(m_currentVersion.isEmpty()) + { + return; + } + auto idx = m_proxyModel->getVersion(m_currentVersion); + if(idx.isValid()) + { + preselectedAlready = true; + listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + listView->scrollTo(idx, QAbstractItemView::PositionAtCenter); + } } void VersionSelectWidget::selectRecommended() { - auto idx = m_proxyModel->getRecommended(); - if(idx.isValid()) - { - preselectedAlready = true; - listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); - listView->scrollTo(idx, QAbstractItemView::PositionAtCenter); - } + auto idx = m_proxyModel->getRecommended(); + if(idx.isValid()) + { + preselectedAlready = true; + listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + listView->scrollTo(idx, QAbstractItemView::PositionAtCenter); + } } bool VersionSelectWidget::hasVersions() const { - return m_proxyModel->rowCount(QModelIndex()) != 0; + return m_proxyModel->rowCount(QModelIndex()) != 0; } BaseVersionPtr VersionSelectWidget::selectedVersion() const { - auto currentIndex = listView->selectionModel()->currentIndex(); - auto variant = m_proxyModel->data(currentIndex, BaseVersionList::VersionPointerRole); - return variant.value<BaseVersionPtr>(); + auto currentIndex = listView->selectionModel()->currentIndex(); + auto variant = m_proxyModel->data(currentIndex, BaseVersionList::VersionPointerRole); + return variant.value<BaseVersionPtr>(); } void VersionSelectWidget::setExactFilter(BaseVersionList::ModelRoles role, QString filter) { - m_proxyModel->setFilter(role, new ExactFilter(filter)); + m_proxyModel->setFilter(role, new ExactFilter(filter)); } void VersionSelectWidget::setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter) { - m_proxyModel->setFilter(role, new ContainsFilter(filter)); + m_proxyModel->setFilter(role, new ContainsFilter(filter)); } void VersionSelectWidget::setFilter(BaseVersionList::ModelRoles role, Filter *filter) { - m_proxyModel->setFilter(role, filter); + m_proxyModel->setFilter(role, filter); } diff --git a/application/widgets/VersionSelectWidget.h b/application/widgets/VersionSelectWidget.h index c134887f..701f568e 100644 --- a/application/widgets/VersionSelectWidget.h +++ b/application/widgets/VersionSelectWidget.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,55 +27,55 @@ class Filter; class VersionSelectWidget: public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit VersionSelectWidget(QWidget *parent = 0); - ~VersionSelectWidget(); + explicit VersionSelectWidget(QWidget *parent = 0); + ~VersionSelectWidget(); - //! loads the list if needed. - void initialize(BaseVersionList *vlist); + //! loads the list if needed. + void initialize(BaseVersionList *vlist); - //! Starts a task that loads the list. - void loadList(); + //! Starts a task that loads the list. + void loadList(); - bool hasVersions() const; - BaseVersionPtr selectedVersion() const; - void selectRecommended(); - void selectCurrent(); + bool hasVersions() const; + BaseVersionPtr selectedVersion() const; + void selectRecommended(); + void selectCurrent(); - void setCurrentVersion(const QString & version); - void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter); - void setExactFilter(BaseVersionList::ModelRoles role, QString filter); - void setFilter(BaseVersionList::ModelRoles role, Filter *filter); - void setEmptyString(QString emptyString); - void setEmptyErrorString(QString emptyErrorString); - void setResizeOn(int column); + void setCurrentVersion(const QString & version); + void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter); + void setExactFilter(BaseVersionList::ModelRoles role, QString filter); + void setFilter(BaseVersionList::ModelRoles role, Filter *filter); + void setEmptyString(QString emptyString); + void setEmptyErrorString(QString emptyErrorString); + void setResizeOn(int column); signals: - void selectedVersionChanged(BaseVersionPtr version); + void selectedVersionChanged(BaseVersionPtr version); protected: - virtual void closeEvent ( QCloseEvent* ); + virtual void closeEvent ( QCloseEvent* ); private slots: - void onTaskSucceeded(); - void onTaskFailed(const QString &reason); - void changeProgress(qint64 current, qint64 total); - void currentRowChanged(const QModelIndex ¤t, const QModelIndex &); + void onTaskSucceeded(); + void onTaskFailed(const QString &reason); + void changeProgress(qint64 current, qint64 total); + void currentRowChanged(const QModelIndex ¤t, const QModelIndex &); private: - void preselect(); + void preselect(); private: - QString m_currentVersion; - BaseVersionList *m_vlist = nullptr; - VersionProxyModel *m_proxyModel = nullptr; - int resizeOnColumn = 0; - Task * loadTask; - bool preselectedAlready = false; + QString m_currentVersion; + BaseVersionList *m_vlist = nullptr; + VersionProxyModel *m_proxyModel = nullptr; + int resizeOnColumn = 0; + Task * loadTask; + bool preselectedAlready = false; private: - QVBoxLayout *verticalLayout = nullptr; - VersionListView *listView = nullptr; - QProgressBar *sneakyProgressBar = nullptr; + QVBoxLayout *verticalLayout = nullptr; + VersionListView *listView = nullptr; + QProgressBar *sneakyProgressBar = nullptr; }; diff --git a/application/widgets/WideBar.cpp b/application/widgets/WideBar.cpp new file mode 100644 index 00000000..cbd6c617 --- /dev/null +++ b/application/widgets/WideBar.cpp @@ -0,0 +1,116 @@ +#include "WideBar.h" +#include <QToolButton> +#include <QMenu> + +class ActionButton : public QToolButton +{ + Q_OBJECT +public: + ActionButton(QAction * action, QWidget * parent = 0) : QToolButton(parent), m_action(action) { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + connect(action, &QAction::changed, this, &ActionButton::actionChanged); + connect(this, &ActionButton::clicked, action, &QAction::trigger); + actionChanged(); + }; +private slots: + void actionChanged() { + setEnabled(m_action->isEnabled()); + setChecked(m_action->isChecked()); + setCheckable(m_action->isCheckable()); + setText(m_action->text()); + setIcon(m_action->icon()); + setToolTip(m_action->toolTip()); + setHidden(!m_action->isVisible()); + setFocusPolicy(Qt::NoFocus); + } +private: + QAction * m_action; +}; + + +WideBar::WideBar(const QString& title, QWidget* parent) : QToolBar(title, parent) +{ + setFloatable(false); + setMovable(false); +} + +WideBar::WideBar(QWidget* parent) : QToolBar(parent) +{ + setFloatable(false); + setMovable(false); +} + +struct WideBar::BarEntry { + enum Type { + None, + Action, + Separator, + Spacer + } type = None; + QAction *qAction = nullptr; + QAction *wideAction = nullptr; +}; + + +WideBar::~WideBar() +{ + for(auto *iter: m_entries) { + delete iter; + } +} + +void WideBar::addAction(QAction* action) +{ + auto entry = new BarEntry(); + entry->qAction = addWidget(new ActionButton(action, this)); + entry->wideAction = action; + entry->type = BarEntry::Action; + m_entries.push_back(entry); +} + +void WideBar::addSeparator() +{ + auto entry = new BarEntry(); + entry->qAction = QToolBar::addSeparator(); + entry->type = BarEntry::Separator; + m_entries.push_back(entry); +} + +void WideBar::insertSpacer(QAction* action) +{ + auto iter = std::find_if(m_entries.begin(), m_entries.end(), [action](BarEntry * entry) { + return entry->wideAction == action; + }); + if(iter == m_entries.end()) { + return; + } + QWidget* spacer = new QWidget(); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + auto entry = new BarEntry(); + entry->qAction = insertWidget((*iter)->qAction, spacer); + entry->type = BarEntry::Spacer; + m_entries.insert(iter, entry); +} + +QMenu * WideBar::createContextMenu(QWidget *parent, const QString & title) +{ + QMenu *contextMenu = new QMenu(title, parent); + for(auto & item: m_entries) { + switch(item->type) { + default: + case BarEntry::None: + break; + case BarEntry::Separator: + case BarEntry::Spacer: + contextMenu->addSeparator(); + break; + case BarEntry::Action: + contextMenu->addAction(item->wideAction); + break; + } + } + return contextMenu; +} + +#include "WideBar.moc" diff --git a/application/widgets/WideBar.h b/application/widgets/WideBar.h new file mode 100644 index 00000000..d1b8cbe7 --- /dev/null +++ b/application/widgets/WideBar.h @@ -0,0 +1,26 @@ +#pragma once + +#include <QToolBar> +#include <QAction> +#include <QMap> + +class QMenu; + +class WideBar : public QToolBar +{ + Q_OBJECT + +public: + explicit WideBar(const QString &title, QWidget * parent = nullptr); + explicit WideBar(QWidget * parent = nullptr); + virtual ~WideBar(); + + void addAction(QAction *action); + void addSeparator(); + void insertSpacer(QAction *action); + QMenu *createContextMenu(QWidget *parent = nullptr, const QString & title = QString()); + +private: + struct BarEntry; + QList<BarEntry *> m_entries; +}; |