Merge branch 'Develop'

This commit is contained in:
Silarn
2019-07-22 01:00:42 -05:00
135 changed files with 20680 additions and 7141 deletions
+19 -1
View File
@@ -1,15 +1,33 @@
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12)
ADD_COMPILE_OPTIONS($<$<CXX_COMPILER_ID:MSVC>:/MP>)
ADD_COMPILE_OPTIONS($<$<CXX_COMPILER_ID:MSVC>:/MP> $<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELEASE>:/O2>> $<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELWITHDEBINFO>:/O2>>)
PROJECT(organizer)
# sets ModOrganizer as StartUp Project in Visual Studio
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY VS_STARTUP_PROJECT "ModOrganizer")
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
set(CMAKE_INSTALL_MESSAGE NEVER)
SET(DEPENDENCIES_DIR CACHE PATH "")
# hint to find qt in dependencies path
LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake)
LIST(APPEND CMAKE_PREFIX_PATH ${LZ4_ROOT}/dll)
ADD_SUBDIRECTORY(src)
set(vcxproj_user_file "${CMAKE_CURRENT_BINARY_DIR}/INSTALL.vcxproj.user")
if(NOT EXISTS ${vcxproj_user_file})
FILE(WRITE ${vcxproj_user_file}
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<Project ToolsVersion=\"Current\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n"
"<PropertyGroup>\n"
"<LocalDebuggerWorkingDirectory>${CMAKE_INSTALL_PREFIX}/bin</LocalDebuggerWorkingDirectory>\n"
"<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>"
"<LocalDebuggerCommand>${CMAKE_INSTALL_PREFIX}/bin/ModOrganizer.exe</LocalDebuggerCommand>\n"
"</PropertyGroup>\n"
"</Project>\n")
endif()
INSTALL(FILES dump_running_process.bat DESTINATION bin)
+8 -4
View File
@@ -1,9 +1,11 @@
version: 1.0.{build}
skip_branch_with_pr: true
image: Visual Studio 2017
image: Visual Studio 2019
environment:
WEBHOOK_URL:
secure: gOKbXaZM9ImtMD5XrYITvdyZUW/az082G9OIN1EC1VYoNNsKROB4s6VsQiL6qbLNLc4XQhc8Y9DhdU9Hfh49vhypcjUEQmMsENxnp+1hYMJctTqIYDunVSd767eez5wz6rKHqk+XOHmg5gyVNCV9u4DVcCZJSVe4UTKn1lc47H0=
build:
parallel: true
build_script:
- cmd: >-
git clone --depth=1 --branch=%APPVEYOR_REPO_BRANCH% https://github.com/ModOrganizer2/modorganizer-umbrella.git c:\projects\modorganizer-umbrella 2> $null
@@ -14,9 +16,9 @@ build_script:
C:\Python37-x64\python.exe unimake.py -d c:\projects\modorganizer-build -s Appveyor_Build=True %APPVEYOR_PROJECT_NAME%
artifacts:
- path: vsbuild\src\RelWithDebInfo\ModOrganizer.exe
- path: build\src\ModOrganizer.exe
name: modorganizer_exe
- path: vsbuild\src\RelWithDebInfo\ModOrganizer.pdb
- path: build\src\ModOrganizer.pdb
name: modorganizer_pdb
on_success:
- ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER
@@ -24,5 +26,7 @@ on_success:
- ps: ./send.ps1 success $env:WEBHOOK_URL
on_failure:
- ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER
- ps: Push-AppveyorArtifact ${env:APPVEYOR_BUILD_FOLDER}\stdout.log
- ps: Push-AppveyorArtifact ${env:APPVEYOR_BUILD_FOLDER}\stderr.log
- ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1
- ps: ./send.ps1 failure $env:WEBHOOK_URL
- ps: ./send.ps1 failure $env:WEBHOOK_URL
+2
View File
@@ -0,0 +1,2 @@
pushd "%~dp0"
start ModOrganizer.exe --crashdump
+245 -17
View File
@@ -9,6 +9,28 @@ IF(CMAKE_SIZEOF_VOID_P EQUAL 8)
SET(PROJ_ARCH x64)
ENDIF()
macro(add_msvc_precompiled_header PrecompiledHeader PrecompiledSource SourcesVar HeadersVar)
if(MSVC)
get_filename_component(PrecompiledBasename ${PrecompiledHeader} NAME_WE)
set(PrecompiledBinary "${CMAKE_CURRENT_BINARY_DIR}/${PrecompiledBasename}.pch")
set(Sources ${${SourcesVar}})
set_source_files_properties(
${PrecompiledSource} PROPERTIES
COMPILE_FLAGS "/Yc\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\""
OBJECT_OUTPUTS "${PrecompiledBinary}")
set_source_files_properties(
${Sources} PROPERTIES
COMPILE_FLAGS "/Yu\"${PrecompiledHeader}\" /FI\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\""
OBJECT_DEPENDS "${PrecompiledBinary}")
list(APPEND ${SourcesVar} ${PrecompiledSource})
list(APPEND ${HeadersVar} ${PrecompiledHeader})
endif(MSVC)
endmacro(add_msvc_precompiled_header)
SET(organizer_SRCS
transfersavesdialog.cpp
syncoverwritedialog.cpp
@@ -32,6 +54,14 @@ SET(organizer_SRCS
modlist.cpp
modidlineedit.cpp
modinfodialog.cpp
modinfodialogcategories.cpp
modinfodialogconflicts.cpp
modinfodialogesps.cpp
modinfodialogfiletree.cpp
modinfodialogimages.cpp
modinfodialognexus.cpp
modinfodialogtab.cpp
modinfodialogtextfiles.cpp
modinfo.cpp
modinfobackup.cpp
modinfoforeign.cpp
@@ -93,6 +123,12 @@ SET(organizer_SRCS
lcdnumber.cpp
forcedloaddialog.cpp
forcedloaddialogwidget.cpp
filterwidget.cpp
statusbar.cpp
apiuseraccount.cpp
filerenamer.cpp
texteditor.cpp
expanderwidget.cpp
shared/windows_error.cpp
shared/error_report.cpp
@@ -126,6 +162,15 @@ SET(organizer_HDRS
modlist.h
modidlineedit.h
modinfodialog.h
modinfodialogcategories.h
modinfodialogconflicts.h
modinfodialogesps.h
modinfodialogfiletree.h
modinfodialogfwd.h
modinfodialogimages.h
modinfodialognexus.h
modinfodialogtab.h
modinfodialogtextfiles.h
modinfo.h
modinfobackup.h
modinfoforeign.h
@@ -183,12 +228,17 @@ SET(organizer_HDRS
instancemanager.h
usvfsconnector.h
eventfilter.h
descriptionpage.h
moshortcut.h
listdialog.h
lcdnumber.h
forcedloaddialog.h
forcedloaddialogwidget.h
filterwidget.h
statusbar.h
apiuseraccount.h
filerenamer.h
texteditor.h
expanderwidget.h
shared/windows_error.h
shared/error_report.h
@@ -203,7 +253,6 @@ SET(organizer_HDRS
SET(organizer_UIS
transfersavesdialog.ui
syncoverwritedialog.ui
simpleinstalldialog.ui
settingsdialog.ui
selectiondialog.ui
queryoverwritedialog.ui
@@ -215,8 +264,6 @@ SET(organizer_UIS
mainwindow.ui
lockeddialog.ui
waitingonclosedialog.ui
installdialog.ui
finddialog.ui
editexecutablesdialog.ui
credentialsdialog.ui
categoriesdialog.ui
@@ -243,16 +290,191 @@ SET(organizer_RCS
)
SOURCE_GROUP(Source FILES ${organizer_SRCS})
SOURCE_GROUP(Headers FILES ${organizer_HDRS})
SOURCE_GROUP(UI FILES ${organizer_UIS})
source_group(src REGULAR_EXPRESSION ".*\\.(h|cpp|ui)")
set(application
iuserinterface
main
mainwindow
moapplication
moshortcut
selfupdater
singleinstance
statusbar
)
set(browser
browserdialog
browserview
)
set(core
categories
shared/directoryentry
directoryrefresher
installationmanager
instancemanager
loadmechanism
nexusinterface
nxmaccessmanager
organizercore
organizerproxy
apiuseraccount
)
set(dialogs
aboutdialog
activatemodsdialog
categoriesdialog
credentialsdialog
filedialogmemory
forcedloaddialog
forcedloaddialogwidget
listdialog
messagedialog
motddialog
overwriteinfodialog
queryoverwritedialog
problemsdialog
savetextasdialog
selectiondialog
syncoverwritedialog
transfersavesdialog
waitingonclosedialog
)
set(downloads
downloadlist
downloadlistsortproxy
downloadlistwidget
downloadmanager
)
set(executables
executableslist
editexecutablesdialog
)
set(locking
ilockedwaitingforprocess
lockeddialog
lockeddialogbase
)
set(modinfo
modinfo
modinfobackup
modinfoforeign
modinfooverwrite
modinforegular
modinfoseparator
modinfowithconflictinfo
)
set(modinfo\\dialog
modinfodialog
modinfodialogcategories
modinfodialogconflicts
modinfodialogesps
modinfodialogfiletree
modinfodialogfwd
modinfodialogimages
modinfodialognexus
modinfodialogtab
modinfodialogtextfiles
)
set(modlist
modlist
modlistsortproxy
modlistview
)
set(plugins
plugincontainer
pluginlist
pluginlistsortproxy
pluginlistview
)
set(previews
previewdialog
previewgenerator
)
set(profiles
profile
profileinputdialog
profilesdialog
)
set(settings
settings
settingsdialog
)
set(utilities
shared/appconfig
bbcode
csvbuilder
shared/error_report
eventfilter
helper
shared/leaktrace
persistentcookiejar
serverinfo
spawn
shared/stackdata
shared/util
usvfsconnector
shared/windows_error
)
set(widgets
expanderwidget
genericicondelegate
filerenamer
filterwidget
icondelegate
lcdnumber
logbuffer
loghighlighter
modflagicondelegate
modidlineedit
noeditdelegate
qtgroupingproxy
texteditor
viewmarkingscrollbar
)
set(src_filters
application core browser dialogs downloads executables locking modinfo modinfo\\dialog
modlist plugins previews profiles settings utilities widgets
)
foreach(filter in list ${src_filters})
set(files)
foreach(d in lists ${${filter}})
set(files ${files} ${d}.cpp ${d}.h ${d}.inc ${d}.ui)
endforeach()
source_group(src\\${filter} FILES ${files})
endforeach()
file(GLOB_RECURSE rule_files ../vsbuild/CMakeFiles/*.rule)
file(GLOB qm_files ../vsbuild/src/*.qm)
source_group(cmake FILES CMakeLists.txt)
source_group(autogen FILES ${rule_files} ${qm_files})
source_group(resources FILES ${organizer_RCS} ${organizer_QRCS})
# MO projects
SET(dependency_project_path "${DEPENDENCIES_DIR}")
SET(default_project_path "${DEPENDENCIES_DIR}/modorganizer_super")
GET_FILENAME_COMPONENT(${default_project_path} ${default_project_path} REALPATH)
SET(project_path "${default_project_path}" CACHE PATH "path to the other mo projects")
add_msvc_precompiled_header("pch.h" "pch.cpp" organizer_SRCS organizer_HDRS)
#TODO this should not be a hardcoded path
SET(lib_path "${project_path}/../../install/libs")
@@ -260,6 +482,8 @@ SET(lib_path "${project_path}/../../install/libs")
SET(CMAKE_INCLUDE_CURRENT_DIR ON)
SET(CMAKE_AUTOMOC ON)
SET(CMAKE_AUTOUIC ON)
SET(CMAKE_AUTORCC ON)
FIND_PACKAGE(Qt5Widgets REQUIRED)
FIND_PACKAGE(Qt5QuickWidgets REQUIRED)
FIND_PACKAGE(Qt5Quick REQUIRED)
@@ -269,10 +493,14 @@ FIND_PACKAGE(Qt5WebEngineWidgets REQUIRED)
FIND_PACKAGE(Qt5WebSockets REQUIRED)
FIND_PACKAGE(Qt5Qml REQUIRED)
FIND_PACKAGE(Qt5LinguistTools)
QT5_WRAP_UI(organizer_UIHDRS ${organizer_UIS})
QT5_ADD_RESOURCES(organizer_RCCPPS ${organizer_QRCS})
SET(mo_translation_sources ${CMAKE_SOURCE_DIR}/src ${project_path}/uibase/src)
QT5_CREATE_TRANSLATION(organizer_translations_qm ${mo_translation_sources} ${CMAKE_SOURCE_DIR}/src/organizer_en.ts)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_SOURCE_GROUP autogen)
set_property(GLOBAL PROPERTY AUTOMOC_SOURCE_GROUP autogen)
set_property(GLOBAL PROPERTY AUTORCC_SOURCE_GROUP autogen)
QT5_CREATE_TRANSLATION(organizer_translations_qm ${mo_translation_sources} ${CMAKE_SOURCE_DIR}/src/organizer_en.ts OPTIONS -silent)
ADD_CUSTOM_TARGET(translations DEPENDS ${organizer_translations_qm})
INCLUDE_DIRECTORIES(${Qt5Declarative_INCLUDES})
@@ -301,12 +529,12 @@ INCLUDE_DIRECTORIES(${project_path}/uibase/src
${project_path}/game_gamebryo/src/creation
${project_path}/game_features/src
${project_path}/githubpp/src
${LZ4_ROOT}/include)
${LZ4_ROOT}/lib)
INCLUDE_DIRECTORIES(shared ${ZLIB_INCLUDE_DIRS})
LINK_DIRECTORIES(${lib_path}
${ZLIB_ROOT}/lib
${LZ4_ROOT}/dll)
${LZ4_ROOT}/bin)
EXECUTE_PROCESS(
COMMAND git log -1 --format=%h
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
@@ -322,7 +550,7 @@ ELSE()
SET(usvfs_name usvfs_x86)
ENDIF()
ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIHDRS} ${organizer_RCS} ${organizer_RCCPPS} ${organizer_translations_qm})
ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIS} ${organizer_RCS} ${organizer_QRCS} ${organizer_translations_qm})
TARGET_LINK_LIBRARIES(ModOrganizer
Qt5::Widgets Qt5::WinExtras Qt5::WebEngineWidgets Qt5::Quick
Qt5::Qml Qt5::QuickWidgets Qt5::Network Qt5::WebSockets
@@ -333,7 +561,7 @@ TARGET_LINK_LIBRARIES(ModOrganizer
Dbghelp advapi32 Version Shlwapi liblz4 Crypt32)
IF (MSVC)
SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES COMPILE_FLAGS "/std:c++latest")
SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES COMPILE_FLAGS "/std:c++latest /permissive-")
ENDIF()
IF (MSVC AND "${CMAKE_SIZEOF_VOID_P}" EQUAL 4)
# 32 bits
@@ -375,11 +603,11 @@ SET(windeploy_parameters "--no-translations --plugindir qtplugins --libdir dlls
INSTALL(
CODE
"EXECUTE_PROCESS(COMMAND
${qt5bin}/windeployqt.exe ModOrganizer.exe --webenginewidgets --websockets ${windeploy_parameters}
${qt5bin}/windeployqt.exe --verbose 0 ModOrganizer.exe --webenginewidgets --websockets ${windeploy_parameters}
WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin
)
EXECUTE_PROCESS(COMMAND
${qt5bin}/windeployqt.exe uibase.dll ${windeploy_parameters}
${qt5bin}/windeployqt.exe --verbose 0 uibase.dll ${windeploy_parameters}
WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin
)
file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/platforms)
+2 -2
View File
@@ -115,7 +115,7 @@ void AboutDialog::on_creditsList_currentItemChanged(QListWidgetItem *current, QL
}
}
void AboutDialog::on_copyrightText_linkActivated(const QString &link)
void AboutDialog::on_sourceText_linkActivated(const QString &link)
{
emit linkClicked(link);
}
}
+1 -1
View File
@@ -74,7 +74,7 @@ private:
private slots:
void on_creditsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
void on_copyrightText_linkActivated(const QString &link);
void on_sourceText_linkActivated(const QString &link);
private:
+46 -13
View File
@@ -120,20 +120,43 @@
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Copyright 2011-2016 Sebastian Herbord&lt;/p&gt;&lt;p&gt;Copyright 2016-2018 Mod Organizer 2 contributors&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Copyright © 2011-2016 Sebastian Herbord&lt;br/&gt;Copyright © 2016-2019 Mod Organizer 2 Contributors&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="copyrightText">
<property name="text">
<string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.&lt;/p&gt;&lt;p&gt;See the GNU General Public License for more details.&lt;/p&gt;&lt;p&gt;Source code can be found at &lt;a href=&quot;https://github.com/Modorganizer2/modorganizer&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#007af4;&quot;&gt;GitHub&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.&lt;/p&gt;&lt;p&gt;See the GNU General Public License for more details.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="sourceText">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Source code can be found at &lt;a href=&quot;https://github.com/ModOrganizer2/modorganizer&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#007af4;&quot;&gt;GitHub&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="software">
@@ -157,7 +180,7 @@
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Lead Developers/ Maintainers</string>
<string>Lead Developers &amp;&amp; Maintainers</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
@@ -175,11 +198,6 @@
<string notr="true">AL12</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">erasmux</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">Silarn</string>
@@ -198,7 +216,7 @@
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Mo2 devs and Contributors</string>
<string>MO2 Developers &amp;&amp; Contributors</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7" stretch="0">
<item>
@@ -213,12 +231,22 @@
</item>
<item>
<property name="text">
<string>Project579</string>
<string notr="true">erasmux</string>
</property>
</item>
<item>
<property name="text">
<string>przester</string>
<string notr="true">isanae</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">Project579</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">przester</string>
</property>
</item>
</widget>
@@ -292,6 +320,11 @@
<string notr="true">pndrev (German)</string>
</property>
</item>
<item>
<property name="text">
<string>yohru (Japanese)</string>
</property>
</item>
<item>
<property name="text">
<string>Mordan (Greek)</string>
@@ -342,12 +375,12 @@
</item>
<item>
<property name="text">
<string>yohru (Japanese)</string>
<string>Nubbie (Swedish)</string>
</property>
</item>
<item>
<property name="text">
<string>...and all other Transifex contributors!</string>
<string>...and all other contributors!</string>
</property>
<property name="toolTip">
<string/>
+67
View File
@@ -0,0 +1,67 @@
#include "apiuseraccount.h"
APIUserAccount::APIUserAccount()
: m_type(APIUserAccountTypes::None)
{
}
const QString& APIUserAccount::id() const
{
return m_id;
}
const QString& APIUserAccount::name() const
{
return m_name;
}
APIUserAccountTypes APIUserAccount::type() const
{
return m_type;
}
const APILimits& APIUserAccount::limits() const
{
return m_limits;
}
APIUserAccount& APIUserAccount::id(const QString& id)
{
m_id = id;
return *this;
}
APIUserAccount& APIUserAccount::name(const QString& name)
{
m_name = name;
return *this;
}
APIUserAccount& APIUserAccount::type(APIUserAccountTypes type)
{
m_type = type;
return *this;
}
APIUserAccount& APIUserAccount::limits(const APILimits& limits)
{
m_limits = limits;
return *this;
}
int APIUserAccount::remainingRequests() const
{
return std::max(
m_limits.remainingDailyRequests,
m_limits.remainingHourlyRequests);
}
bool APIUserAccount::shouldThrottle() const
{
return (remainingRequests() < ThrottleThreshold);
}
bool APIUserAccount::exhausted() const
{
return (remainingRequests() <= 0);
}
+129
View File
@@ -0,0 +1,129 @@
#ifndef APIUSERACCOUNT_H
#define APIUSERACCOUNT_H
#include <QString>
/**
* represents user account types on a mod provider website such as nexus
*/
enum class APIUserAccountTypes
{
// not logged in
None = 0,
// regular account
Regular,
// premium account
Premium
};
/**
* current limits imposed on the user account
**/
struct APILimits
{
// maximum number of requests per day
int maxDailyRequests = 0;
// remaining number of requests today
int remainingDailyRequests = 0;
// maximum number of requests per hour
int maxHourlyRequests = 0;
// remaining number of requests this hour
int remainingHourlyRequests = 0;
};
/**
* API statistics
*/
struct APIStats
{
// number of API requests currently queued
int requestsQueued = 0;
};
/**
* represents a user account on the mod provier website
*/
class APIUserAccount
{
public:
// when the number of remanining requests is under this number, further
// requests will be throttled by avoiding non-critical ones
static const int ThrottleThreshold = 200;
APIUserAccount();
/**
* user id
*/
const QString& id() const;
/**
* user name
*/
const QString& name() const;
/**
* account type
*/
APIUserAccountTypes type() const;
/**
* current API limits
*/
const APILimits& limits() const;
/**
* sets the user id
*/
APIUserAccount& id(const QString& id);
/**
* sets the user name
**/
APIUserAccount& name(const QString& name);
/**
* sets the acount type
*/
APIUserAccount& type(APIUserAccountTypes type);
/**
* sets the current limits
*/
APIUserAccount& limits(const APILimits& limits);
/**
* returns the number of remaining requests
*/
int remainingRequests() const;
/**
* whether the number of remaining requests is low enough that further
* requests should be throttled
*/
bool shouldThrottle() const;
/**
* true if all the remaining requests have been used and the API will refuse
* further requests
*/
bool exhausted() const;
private:
QString m_id, m_name;
APIUserAccountTypes m_type;
APILimits m_limits;
APIStats m_stats;
};
#endif // APIUSERACCOUNT_H
+1 -1
View File
@@ -174,7 +174,7 @@ private:
"<a href=\"\\1\">\\1</a>");
m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"),
"<a href=\"\\1\">\\2</a>");
m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"),
m_TagMap["img"] = std::make_pair(QRegExp("\\[img(?:\\s*width=\\d+\\s*,?\\s*height=\\d+)?\\](.*)\\[/img\\]"),
"<img src=\"\\1\">");
m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"),
"<img src=\"\\2\" alt=\"\\1\">");
-28
View File
@@ -1,28 +0,0 @@
#include <QWebEnginePage>
#ifndef DESCRIPTIONPAGE_H
#define DESCRIPTIONPAGE_H
class DescriptionPage : public QWebEnginePage
{
Q_OBJECT
public:
DescriptionPage(QObject* parent = 0) : QWebEnginePage(parent){}
bool acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame)
{
if (type == QWebEnginePage::NavigationTypeLinkClicked)
{
emit linkClicked(url);
return false;
}
return true;
}
signals:
void linkClicked(const QUrl&);
};
#endif //DESCRIPTIONPAGE_H
+31 -4
View File
@@ -47,7 +47,7 @@ int DownloadList::rowCount(const QModelIndex&) const
int DownloadList::columnCount(const QModelIndex&) const
{
return 4;
return COL_COUNT;
}
@@ -69,6 +69,9 @@ QVariant DownloadList::headerData(int section, Qt::Orientation orientation, int
(orientation == Qt::Horizontal)) {
switch (section) {
case COL_NAME: return tr("Name");
case COL_MODNAME: return tr("Mod name");
case COL_VERSION: return tr("Version");
case COL_ID: return tr("Nexus ID");
case COL_SIZE: return tr("Size");
case COL_STATUS: return tr("Status");
case COL_FILETIME: return tr("Filetime");
@@ -93,6 +96,30 @@ QVariant DownloadList::data(const QModelIndex &index, int role) const
} else {
switch (index.column()) {
case COL_NAME: return m_MetaDisplay ? m_Manager->getDisplayName(index.row()) : m_Manager->getFileName(index.row());
case COL_MODNAME: {
if (m_Manager->isInfoIncomplete(index.row())) {
return {};
} else {
const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(index.row());
return info->modName;
}
}
case COL_VERSION: {
if (m_Manager->isInfoIncomplete(index.row())) {
return {};
} else {
const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(index.row());
return info->version.canonicalString();
}
}
case COL_ID: {
if (m_Manager->isInfoIncomplete(index.row())) {
return {};
} else {
const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(index.row());
return QString("%1").arg(m_Manager->getModID(index.row()));
}
}
case COL_SIZE: return sizeFormat(m_Manager->getFileSize(index.row()));
case COL_FILETIME: return m_Manager->getFileTime(index.row());
case COL_STATUS:
@@ -143,10 +170,10 @@ QVariant DownloadList::data(const QModelIndex &index, int role) const
&& m_Manager->isInfoIncomplete(index.row()))
return QIcon(":/MO/gui/warning_16");
} else if (role == Qt::TextAlignmentRole) {
if (index.column() == COL_NAME)
return QVariant(Qt::AlignVCenter | Qt::AlignLeft);
else
if (index.column() == COL_SIZE)
return QVariant(Qt::AlignVCenter | Qt::AlignRight);
else
return QVariant(Qt::AlignVCenter | Qt::AlignLeft);
}
return QVariant();
}
+7 -1
View File
@@ -39,7 +39,13 @@ public:
COL_NAME = 0,
COL_STATUS,
COL_SIZE,
COL_FILETIME
COL_FILETIME,
COL_MODNAME,
COL_VERSION,
COL_ID,
// number of columns
COL_COUNT
};
public:
+42
View File
@@ -43,6 +43,48 @@ bool DownloadListSortProxy::lessThan(const QModelIndex &left,
&& (rightIndex < m_Manager->numTotalDownloads())) {
if (left.column() == DownloadList::COL_NAME) {
return m_Manager->getFileName(left.row()).compare(m_Manager->getFileName(right.row()), Qt::CaseInsensitive) < 0;
} else if (left.column() == DownloadList::COL_MODNAME) {
QString leftName, rightName;
if (!m_Manager->isInfoIncomplete(left.row())) {
const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(left.row());
leftName = info->modName;
}
if (!m_Manager->isInfoIncomplete(right.row())) {
const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(right.row());
rightName = info->modName;
}
return leftName.compare(rightName, Qt::CaseInsensitive) < 0;
} else if (left.column() == DownloadList::COL_VERSION) {
MOBase::VersionInfo versionLeft, versionRight;
if (!m_Manager->isInfoIncomplete(left.row())) {
const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(left.row());
versionLeft = info->version;
}
if (!m_Manager->isInfoIncomplete(right.row())) {
const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(right.row());
versionRight = info->version;
}
return versionLeft < versionRight;
} else if (left.column() == DownloadList::COL_ID) {
int leftID=0, rightID=0;
if (!m_Manager->isInfoIncomplete(left.row())) {
const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(left.row());
leftID = info->modID;
}
if (!m_Manager->isInfoIncomplete(right.row())) {
const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(right.row());
rightID = info->modID;
}
return leftID < rightID;
} else if (left.column() == DownloadList::COL_STATUS) {
DownloadManager::DownloadState leftState = m_Manager->getState(left.row());
DownloadManager::DownloadState rightState = m_Manager->getState(right.row());
+24 -15
View File
@@ -123,6 +123,15 @@ DownloadListWidget::~DownloadListWidget()
void DownloadListWidget::setManager(DownloadManager *manager)
{
m_Manager = manager;
// hide these columns by default
//
// note that this is overridden by the ini if MO has been started at least
// once before, which is handled in MainWindow::processUpdates() for older
// versions
header()->hideSection(DownloadList::COL_MODNAME);
header()->hideSection(DownloadList::COL_VERSION);
header()->hideSection(DownloadList::COL_ID);
}
void DownloadListWidget::setSourceModel(DownloadList *sourceModel)
@@ -199,7 +208,7 @@ void DownloadListWidget::onCustomContextMenu(const QPoint &point)
if (state >= DownloadManager::STATE_READY) {
menu.addAction(tr("Install"), this, SLOT(issueInstall()));
if (m_Manager->isInfoIncomplete(m_ContextRow))
if (m_Manager->isInfoIncomplete(m_ContextRow))
menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfoMd5()));
else
menu.addAction(tr("Visit on Nexus"), this, SLOT(issueVisitOnNexus()));
@@ -226,9 +235,9 @@ void DownloadListWidget::onCustomContextMenu(const QPoint &point)
menu.addSeparator();
}
menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted()));
menu.addAction(tr("Delete Uninstalled..."), this, SLOT(issueDeleteUninstalled()));
menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll()));
menu.addAction(tr("Delete Installed Downloads..."), this, SLOT(issueDeleteCompleted()));
menu.addAction(tr("Delete Uninstalled Downloads..."), this, SLOT(issueDeleteUninstalled()));
menu.addAction(tr("Delete All Downloads..."), this, SLOT(issueDeleteAll()));
menu.addSeparator();
if (!hidden) {
@@ -259,8 +268,8 @@ void DownloadListWidget::issueQueryInfoMd5()
void DownloadListWidget::issueDelete()
{
if (QMessageBox::question(nullptr, tr("Delete Files?"),
tr("This will permanently delete the selected download."),
if (QMessageBox::warning(nullptr, tr("Delete Files?"),
tr("This will permanently delete the selected download.\n\nAre you absolutely sure you want to proceed?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
emit removeDownload(m_ContextRow, true);
}
@@ -314,8 +323,8 @@ void DownloadListWidget::issueResume()
void DownloadListWidget::issueDeleteAll()
{
if (QMessageBox::question(nullptr, tr("Delete Files?"),
tr("This will remove all finished downloads from this list and from disk."),
if (QMessageBox::warning(nullptr, tr("Delete Files?"),
tr("This will remove all finished downloads from this list and from disk.\n\nAre you absolutely sure you want to proceed?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
emit removeDownload(-1, true);
}
@@ -323,8 +332,8 @@ void DownloadListWidget::issueDeleteAll()
void DownloadListWidget::issueDeleteCompleted()
{
if (QMessageBox::question(nullptr, tr("Delete Files?"),
tr("This will remove all installed downloads from this list and from disk."),
if (QMessageBox::warning(nullptr, tr("Delete Files?"),
tr("This will remove all installed downloads from this list and from disk.\n\nAre you absolutely sure you want to proceed?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
emit removeDownload(-2, true);
}
@@ -332,8 +341,8 @@ void DownloadListWidget::issueDeleteCompleted()
void DownloadListWidget::issueDeleteUninstalled()
{
if (QMessageBox::question(nullptr, tr("Delete Files?"),
tr("This will remove all uninstalled downloads from this list and from disk."),
if (QMessageBox::warning(nullptr, tr("Delete Files?"),
tr("This will remove all uninstalled downloads from this list and from disk.\n\nAre you absolutely sure you want to proceed?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
emit removeDownload(-3, true);
}
@@ -341,7 +350,7 @@ void DownloadListWidget::issueDeleteUninstalled()
void DownloadListWidget::issueRemoveFromViewAll()
{
if (QMessageBox::question(nullptr, tr("Are you sure?"),
if (QMessageBox::question(nullptr, tr("Hide Files?"),
tr("This will remove all finished downloads from this list (but NOT from disk)."),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
emit removeDownload(-1, false);
@@ -350,7 +359,7 @@ void DownloadListWidget::issueRemoveFromViewAll()
void DownloadListWidget::issueRemoveFromViewCompleted()
{
if (QMessageBox::question(nullptr, tr("Are you sure?"),
if (QMessageBox::question(nullptr, tr("Hide Files?"),
tr("This will remove all installed downloads from this list (but NOT from disk)."),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
emit removeDownload(-2, false);
@@ -359,7 +368,7 @@ void DownloadListWidget::issueRemoveFromViewCompleted()
void DownloadListWidget::issueRemoveFromViewUninstalled()
{
if (QMessageBox::question(nullptr, tr("Are you sure?"),
if (QMessageBox::question(nullptr, tr("Hide Files?"),
tr("This will remove all uninstalled downloads from this list (but NOT from disk)."),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
emit removeDownload(-3, false);
+30 -19
View File
@@ -549,10 +549,21 @@ void DownloadManager::addNXMDownload(const QString &url)
NXMUrl nxmInfo(url);
QStringList validGames;
MOBase::IPluginGame* foundGame = nullptr;
validGames.append(m_ManagedGame->gameShortName());
validGames.append(m_ManagedGame->validShortNames());
for (auto game : validGames) {
MOBase::IPluginGame* gamePlugin = m_OrganizerCore->getGame(game);
if (
nxmInfo.game().compare(gamePlugin->gameShortName(), Qt::CaseInsensitive) == 0 ||
nxmInfo.game().compare(gamePlugin->gameNexusName(), Qt::CaseInsensitive) == 0
) {
foundGame = gamePlugin;
break;
}
}
qDebug("add nxm download: %s", qUtf8Printable(url));
if (!validGames.contains(nxmInfo.game(), Qt::CaseInsensitive)) {
if (foundGame == nullptr) {
qDebug("download requested for wrong game (game: %s, url: %s)", qUtf8Printable(m_ManagedGame->gameShortName()), qUtf8Printable(nxmInfo.game()));
QMessageBox::information(nullptr, tr("Wrong Game"), tr("The download link is for a mod for \"%1\" but this instance of MO "
"has been set up for \"%2\".").arg(nxmInfo.game()).arg(m_ManagedGame->gameShortName()), QMessageBox::Ok);
@@ -560,7 +571,7 @@ void DownloadManager::addNXMDownload(const QString &url)
}
for (auto tuple : m_PendingDownloads) {
if (std::get<0>(tuple).compare(nxmInfo.game(), Qt::CaseInsensitive) == 0, std::get<1>(tuple) == nxmInfo.modId() && std::get<2>(tuple) == nxmInfo.fileId()) {
if (std::get<0>(tuple).compare(foundGame->gameShortName(), Qt::CaseInsensitive) == 0, std::get<1>(tuple) == nxmInfo.modId() && std::get<2>(tuple) == nxmInfo.fileId()) {
QString debugStr("download requested is already queued (mod: %1, file: %2)");
QString infoStr(tr("There is already a download queued for this file.\n\nMod %1\nFile %2"));
@@ -620,7 +631,7 @@ void DownloadManager::addNXMDownload(const QString &url)
emit aboutToUpdate();
m_PendingDownloads.append(std::make_tuple(nxmInfo.game(), nxmInfo.modId(), nxmInfo.fileId()));
m_PendingDownloads.append(std::make_tuple(foundGame->gameShortName(), nxmInfo.modId(), nxmInfo.fileId()));
emit update(-1);
emit downloadAdded();
@@ -628,9 +639,10 @@ void DownloadManager::addNXMDownload(const QString &url)
info->nexusKey = nxmInfo.key();
info->nexusExpires = nxmInfo.expires();
info->nexusDownloadUser = nxmInfo.userId();
QObject *test = info;
m_RequestIDs.insert(m_NexusInterface->requestFileInfo(nxmInfo.game(), nxmInfo.modId(), nxmInfo.fileId(), this, qVariantFromValue(test), ""));
m_RequestIDs.insert(m_NexusInterface->requestFileInfo(foundGame->gameShortName(), nxmInfo.modId(), nxmInfo.fileId(), this, qVariantFromValue(test), ""));
}
@@ -1029,14 +1041,14 @@ void DownloadManager::openFile(int index)
reportError(tr("OpenFile: invalid download index %1").arg(index));
return;
}
QDir path = QDir(m_OutputDirectory);
if (path.exists(getFileName(index))) {
::ShellExecuteW(nullptr, L"open", ToWString(QDir::toNativeSeparators(getFilePath(index))).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
shell::OpenFile(getFilePath(index));
return;
}
::ShellExecuteW(nullptr, L"explore", ToWString(QDir::toNativeSeparators(m_OutputDirectory)).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
shell::ExploreFile(m_OutputDirectory);
return;
}
@@ -1046,23 +1058,22 @@ void DownloadManager::openInDownloadsFolder(int index)
reportError(tr("OpenFileInDownloadsFolder: invalid download index %1").arg(index));
return;
}
QString params = "/select,\"";
QDir path = QDir(m_OutputDirectory);
if (path.exists(getFileName(index))) {
params = params + QDir::toNativeSeparators(getFilePath(index)) + "\"";
::ShellExecuteW(nullptr, nullptr, L"explorer", ToWString(params).c_str(), nullptr, SW_SHOWNORMAL);
const auto path = getFilePath(index);
if (QFile::exists(path)) {
shell::ExploreFile(path);
return;
}
else if (path.exists(getFileName(index) + ".unfinished")) {
params = params + QDir::toNativeSeparators(getFilePath(index)+".unfinished") + "\"";
::ShellExecuteW(nullptr, nullptr, L"explorer", ToWString(params).c_str(), nullptr, SW_SHOWNORMAL);
return;
else {
const auto unfinished = path + ".unfinished";
if (QFile::exists(unfinished)) {
shell::ExploreFile(unfinished);
return;
}
}
::ShellExecuteW(nullptr, L"explore", ToWString(QDir::toNativeSeparators(m_OutputDirectory)).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
return;
shell::ExploreFile(m_OutputDirectory);
}
File diff suppressed because it is too large Load Diff
+154 -59
View File
@@ -22,17 +22,118 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "tutorabledialog.h"
#include <QListWidgetItem>
#include <QTimer>
#include "executableslist.h"
#include "profile.h"
#include "iplugingame.h"
#include <QTimer>
#include <QAbstractButton>
#include <optional>
namespace Ui {
class EditExecutablesDialog;
}
class ModList;
class OrganizerCore;
/** helper class to manage custom overwrites within the edit executables
* dialog, stores a T and a bool in map indexed by a QString
**/
template <class T>
class ToggableMap
{
public:
struct Value
{
bool enabled;
T value;
Value(bool b, T&& v)
: enabled(b), value(std::forward<T>(v))
{
}
};
/**
* returns the Value associated with the given title, or empty
**/
std::optional<Value> find(const QString& title) const
{
auto itor = m_map.find(title);
if (itor == m_map.end()) {
return {};
}
return itor->second;
}
/**
* sets the given value, adds it if not found
**/
void set(QString title, bool b, T value)
{
m_map.insert_or_assign(std::move(title), Value(b, std::move(value)));
}
/**
* sets whether the given value is enabled, inserts it if not found
**/
void setEnabled(const QString& title, bool b)
{
auto itor = m_map.find(title);
if (itor == m_map.end()) {
m_map.emplace(title, Value(b, {}));
} else {
itor->second.enabled = b;
}
}
/**
* sets the given value, inserts it enabled if not found
**/
void setValue(const QString& title, T value)
{
auto itor = m_map.find(title);
if (itor == m_map.end()) {
m_map.emplace(title, Value(true, std::move(value)));
} else {
itor->second.value = std::move(value);
}
}
/**
* renames the given value, ignored if not found
**/
void rename(const QString& oldTitle, QString newTitle)
{
auto itor = m_map.find(oldTitle);
if (itor == m_map.end()) {
return;
}
// move to new title, erase old
m_map.emplace(std::move(newTitle), std::move(itor->second));
m_map.erase(itor);
}
/**
* removes the given value, ignored if not found
**/
void remove(const QString& title)
{
auto itor = m_map.find(title);
if (itor == m_map.end()) {
return;
}
m_map.erase(itor);
}
private:
std::map<QString, Value> m_map;
};
/**
@@ -43,85 +144,79 @@ class EditExecutablesDialog : public MOBase::TutorableDialog
Q_OBJECT
public:
using CustomOverwrites = ToggableMap<QString>;
using ForcedLibraries = ToggableMap<QList<MOBase::ExecutableForcedLoadSetting>>;
/**
* @brief constructor
*
* @param executablesList current list of executables
* @param parent parent widget
**/
explicit EditExecutablesDialog(const ExecutablesList &executablesList,
const ModList &modList,
Profile *profile,
const MOBase::IPluginGame *game,
QWidget *parent = 0);
explicit EditExecutablesDialog(OrganizerCore& oc, QWidget* parent=nullptr);
~EditExecutablesDialog();
/**
* @brief retrieve the updated list of executables
*
* @return updated list of executables
**/
ExecutablesList getExecutablesList() const;
void saveExecutable();
const CustomOverwrites& getCustomOverwrites() const;
const ForcedLibraries& getForcedLibraries() const;
private slots:
void on_newFilesModCheckBox_toggled(bool checked);
void on_list_itemSelectionChanged();
private slots:
void on_reset_clicked();
void on_add_clicked();
void on_remove_clicked();
void on_up_clicked();
void on_down_clicked();
void on_binaryEdit_textChanged(const QString &arg1);
void on_title_textChanged(const QString& s);
void on_overwriteSteamAppID_toggled(bool checked);
void on_createFilesInMod_toggled(bool checked);
void on_forceLoadLibraries_toggled(bool checked);
void on_workingDirEdit_textChanged(const QString &arg1);
void on_browseBinary_clicked();
void on_browseWorkingDirectory_clicked();
void on_configureLibraries_clicked();
void on_addButton_clicked();
void on_browseButton_clicked();
void on_removeButton_clicked();
void on_titleEdit_textChanged(const QString &arg1);
void on_overwriteAppIDBox_toggled(bool checked);
void on_browseDirButton_clicked();
void on_closeButton_clicked();
void delayedRefresh();
void on_executablesListBox_itemSelectionChanged();
void on_executablesListBox_clicked(const QModelIndex &index);
void on_forceLoadButton_clicked();
void on_forceLoadCheckBox_toggled();
void on_buttons_clicked(QAbstractButton* b);
private:
std::unique_ptr<Ui::EditExecutablesDialog> ui;
OrganizerCore& m_organizerCore;
void resetInput();
// copy of the original executables, used to clear the current settings when
// committing changes
const ExecutablesList m_originalExecutables;
void refreshExecutablesWidget();
// current executable list
ExecutablesList m_executablesList;
bool executableChanged();
// custom overwrites set in the dialog
CustomOverwrites m_customOverwrites;
void updateButtonStates();
// forced libraries set in the dialog
ForcedLibraries m_forcedLibraries;
private:
Ui::EditExecutablesDialog *ui;
// true when the change events being triggered are in response to loading
// the executable's data into the UI, not from a user change
bool m_settingUI;
QListWidgetItem *m_CurrentItem;
ExecutablesList m_ExecutablesList;
void loadCustomOverwrites();
void loadForcedLibraries();
Profile *m_Profile;
QListWidgetItem* selectedItem();
Executable* selectedExe();
QList<MOBase::ExecutableForcedLoadSetting> m_ForcedLibraries;
const MOBase::IPluginGame *m_GamePlugin;
void fillList();
QListWidgetItem* createListItem(const Executable& exe);
void updateUI(const QListWidgetItem* item, const Executable* e);
void clearEdits();
void setEdits(const Executable& e);
void setButtons(const QListWidgetItem* item, const Executable* e);
void save();
void saveOrder();
bool canMove(const QListWidgetItem* item, int direction);
void move(QListWidgetItem* item, int direction);
void setJarBinary(const QString& binaryName);
bool isTitleConflicting(const QString& s);
void commitChanges();
void setDirty(bool b);
};
#endif // EDITEXECUTABLESDIALOG_H
File diff suppressed because it is too large Load Diff
+342 -117
View File
File diff suppressed because it is too large Load Diff

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