mirror of
https://github.com/ModOrganizer2/modorganizer-uibase.git
synced 2026-07-27 14:03:49 -07:00
Merge pull request #36 from ModOrganizer2/Develop
Stage for release 2.2.2
This commit is contained in:
+10
-2
@@ -1,13 +1,21 @@
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12)
|
||||
|
||||
ADD_COMPILE_OPTIONS($<$<CXX_COMPILER_ID:MSVC>:/MP> $<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELEASE>:/O2>> $<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELWITHDEBINFO>:/O2>>)
|
||||
ADD_COMPILE_OPTIONS(
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/MP>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/Wall>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/permissive->
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/wd4464> # relative path for include
|
||||
$<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELEASE>:/O2>>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELWITHDEBINFO>:/O2>>)
|
||||
|
||||
PROJECT(uibase)
|
||||
|
||||
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
|
||||
SET(DEPENDENCIES_DIR CACHE PATH "")
|
||||
|
||||
# hint to find qt in dependencies path
|
||||
# hint to find dependencies path
|
||||
LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake)
|
||||
LIST(APPEND CMAKE_PREFIX_PATH ${FMT_ROOT}/build)
|
||||
|
||||
ADD_DEFINITIONS(-DUNICODE -D_UNICODE)
|
||||
ADD_SUBDIRECTORY(src)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
version: 1.0.{build}
|
||||
skip_branch_with_pr: true
|
||||
image: Visual Studio 2019 Preview
|
||||
image: Visual Studio 2019
|
||||
environment:
|
||||
WEBHOOK_URL:
|
||||
secure: gOKbXaZM9ImtMD5XrYITvdyZUW/az082G9OIN1EC1Vbg57wBaeLhi49uGjxPw5GVujHku6kxN6ab89zhbS5GVeluR76GM83IbKV4Sh7udXzoYZZdg6YudtYHzdhCgUeiedpswbuczTq9ceIkkfSEWZuh/lMAAVVwvcGsJAnoPFw=
|
||||
|
||||
+109
-3
@@ -9,6 +9,27 @@ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
SET(PROJ_ARCH x64)
|
||||
endif()
|
||||
|
||||
macro(add_msvc_precompiled_header PrecompiledHeader PrecompiledSource SourcesVar HeadersVar MocSource)
|
||||
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} ${MocSource} 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(uibase_SRCS
|
||||
utility.cpp
|
||||
@@ -41,6 +62,10 @@ SET(uibase_SRCS
|
||||
safewritefile.cpp
|
||||
registry.cpp
|
||||
steamutility.cpp
|
||||
log.cpp
|
||||
expanderwidget.cpp
|
||||
errorcodes.cpp
|
||||
linklabel.cpp
|
||||
)
|
||||
|
||||
SET(uibase_HDRS
|
||||
@@ -56,6 +81,8 @@ SET(uibase_HDRS
|
||||
ipluginpreview.h
|
||||
iplugingame.h
|
||||
ipluginfilemapper.h
|
||||
isavegame.h
|
||||
isavegameinfowidget.h
|
||||
utility.h
|
||||
textviewer.h
|
||||
finddialog.h
|
||||
@@ -93,6 +120,10 @@ SET(uibase_HDRS
|
||||
safewritefile.h
|
||||
registry.h
|
||||
steamutility.h
|
||||
log.h
|
||||
expanderwidget.h
|
||||
errorcodes.h
|
||||
linklabel.h
|
||||
)
|
||||
|
||||
SET(UIS
|
||||
@@ -106,14 +137,86 @@ SET(uibase_RCS
|
||||
)
|
||||
|
||||
|
||||
source_group(src REGULAR_EXPRESSION ".*\\.(h|cpp|ui)")
|
||||
|
||||
set(interfaces
|
||||
imoinfo
|
||||
installationtester
|
||||
iplugin
|
||||
iplugindiagnose
|
||||
ipluginfilemapper
|
||||
iplugingame
|
||||
iplugininstaller
|
||||
iplugininstallercustom
|
||||
iplugininstallersimple
|
||||
ipluginlist
|
||||
ipluginmodpage
|
||||
ipluginpreview
|
||||
ipluginproxy
|
||||
iplugintool
|
||||
iprofile
|
||||
isavegame
|
||||
isavegameinfowidget
|
||||
iinstallationmanager
|
||||
imodinterface
|
||||
imodlist
|
||||
imodrepositorybridge
|
||||
)
|
||||
|
||||
set(tutorials
|
||||
tutorabledialog
|
||||
tutorialcontrol
|
||||
tutorialmanager
|
||||
)
|
||||
|
||||
set(widgets
|
||||
finddialog
|
||||
lineeditclear
|
||||
questionboxmemory
|
||||
sortabletreewidget
|
||||
taskprogressmanager
|
||||
textviewer
|
||||
expanderwidget
|
||||
linklabel
|
||||
)
|
||||
|
||||
set(src_filters interfaces tutorials 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()
|
||||
|
||||
source_group(cmake FILES CMakeLists.txt)
|
||||
source_group(resources FILES ${uibase_RCS})
|
||||
|
||||
add_msvc_precompiled_header(
|
||||
"pch.h" "pch.cpp"
|
||||
uibase_SRCS uibase_HDRS
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}_autogen/mocs_compilation.cpp")
|
||||
|
||||
message("${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}_autogen/mocs_compilation.cpp")
|
||||
|
||||
SET(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
SET(CMAKE_AUTOMOC ON)
|
||||
SET(CMAKE_AUTOUIC 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)
|
||||
|
||||
# make the automoc file include pch.h so warnings are disabled in it
|
||||
set(CMAKE_AUTOMOC_MOC_OPTIONS "-bpch.h")
|
||||
|
||||
FIND_PACKAGE(Qt5Widgets REQUIRED)
|
||||
FIND_PACKAGE(Qt5WinExtras REQUIRED)
|
||||
FIND_PACKAGE(Qt5Qml REQUIRED)
|
||||
FIND_PACKAGE(Qt5QuickWidgets REQUIRED)
|
||||
QT5_WRAP_UI(uibase_UIHDRS ${UIS})
|
||||
|
||||
INCLUDE_DIRECTORIES(${Qt5Declarative_INCLUDES})
|
||||
|
||||
@@ -123,10 +226,13 @@ SET(Boost_USE_STATIC_RUNTIME OFF)
|
||||
FIND_PACKAGE(Boost REQUIRED)
|
||||
INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS})
|
||||
|
||||
FIND_PACKAGE(fmt REQUIRED)
|
||||
INCLUDE_DIRECTORIES(${SPDLOG_ROOT}/include)
|
||||
|
||||
ADD_DEFINITIONS(-DUIBASE_EXPORT)
|
||||
|
||||
ADD_LIBRARY(uibase SHARED ${uibase_HDRS} ${uibase_SRCS} ${uibase_UIHDRS} ${uibase_RCS} ${UIS} ${RSCS} ${TRS} ${MOCS})
|
||||
TARGET_LINK_LIBRARIES(uibase Qt5::Widgets Qt5::WinExtras Qt5::Qml Qt5::QuickWidgets ${Boost_LIBRARIES})
|
||||
ADD_LIBRARY(uibase SHARED ${uibase_HDRS} ${uibase_SRCS} ${uibase_RCS} ${UIS} ${RSCS} ${TRS} ${MOCS})
|
||||
TARGET_LINK_LIBRARIES(uibase Qt5::Widgets Qt5::WinExtras Qt5::Qml Qt5::QuickWidgets ${Boost_LIBRARIES} fmt::fmt)
|
||||
|
||||
IF (MSVC)
|
||||
SET_TARGET_PROPERTIES(uibase PROPERTIES COMPILE_FLAGS "/std:c++latest")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "delayedfilewriter.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
using namespace MOBase;
|
||||
@@ -15,7 +16,7 @@ DelayedFileWriterBase::DelayedFileWriterBase(int delay)
|
||||
DelayedFileWriterBase::~DelayedFileWriterBase()
|
||||
{
|
||||
if (m_Timer.isActive()) {
|
||||
qCritical("delayed file save timer active at shutdown");
|
||||
log::error("delayed file save timer active at shutdown");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2362
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
#ifndef UIBASE_ERRORCODES_H
|
||||
#define UIBASE_ERRORCODES_H
|
||||
|
||||
#include "dllimport.h"
|
||||
#include <Windows.h>
|
||||
|
||||
namespace MOBase
|
||||
{
|
||||
|
||||
QDLLEXPORT const wchar_t* errorCodeName(DWORD code);
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif UIBASE_ERRORCODES_H
|
||||
@@ -5,10 +5,10 @@ using namespace MOBase;
|
||||
ExecutableForcedLoadSetting::ExecutableForcedLoadSetting(
|
||||
const QString &process,
|
||||
const QString &library)
|
||||
: m_Process(process)
|
||||
: m_Enabled(false)
|
||||
, m_Process(process)
|
||||
, m_Library(library)
|
||||
, m_Forced(false)
|
||||
, m_Enabled(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "expanderwidget.h"
|
||||
|
||||
namespace MOBase {
|
||||
|
||||
ExpanderWidget::ExpanderWidget()
|
||||
: m_button(nullptr), m_content(nullptr), opened_(false)
|
||||
{
|
||||
}
|
||||
|
||||
ExpanderWidget::ExpanderWidget(QToolButton* button, QWidget* content)
|
||||
: ExpanderWidget()
|
||||
{
|
||||
set(button, content);
|
||||
}
|
||||
|
||||
void ExpanderWidget::set(QToolButton* button, QWidget* content, bool o)
|
||||
{
|
||||
m_button = button;
|
||||
m_content = content;
|
||||
|
||||
m_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
QObject::connect(m_button, &QToolButton::clicked, [&]{ toggle(); });
|
||||
|
||||
toggle(o);
|
||||
}
|
||||
|
||||
void ExpanderWidget::toggle()
|
||||
{
|
||||
if (opened()) {
|
||||
toggle(false);
|
||||
}
|
||||
else {
|
||||
toggle(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ExpanderWidget::toggle(bool b)
|
||||
{
|
||||
if (b != opened_) {
|
||||
emit aboutToToggle(b);
|
||||
}
|
||||
|
||||
if (b) {
|
||||
m_button->setArrowType(Qt::DownArrow);
|
||||
m_content->show();
|
||||
} else {
|
||||
m_button->setArrowType(Qt::RightArrow);
|
||||
m_content->hide();
|
||||
}
|
||||
|
||||
if (b != opened_) {
|
||||
// the state has to be remembered instead of using m_content's visibility
|
||||
// because saving the state in saveConflictExpandersState() happens after the
|
||||
// dialog is closed, which marks all the widgets hidden
|
||||
opened_ = b;
|
||||
|
||||
emit toggled(b);
|
||||
}
|
||||
}
|
||||
|
||||
bool ExpanderWidget::opened() const
|
||||
{
|
||||
return opened_;
|
||||
}
|
||||
|
||||
QByteArray ExpanderWidget::saveState() const
|
||||
{
|
||||
QByteArray result;
|
||||
QDataStream stream(&result, QIODevice::WriteOnly);
|
||||
|
||||
stream << opened();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ExpanderWidget::restoreState(const QByteArray& a)
|
||||
{
|
||||
QDataStream stream(a);
|
||||
|
||||
bool opened = false;
|
||||
stream >> opened;
|
||||
|
||||
if (stream.status() == QDataStream::Ok) {
|
||||
toggle(opened);
|
||||
}
|
||||
}
|
||||
|
||||
QToolButton* ExpanderWidget::button() const
|
||||
{
|
||||
return m_button;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef EXPANDERWIDGET_H
|
||||
#define EXPANDERWIDGET_H
|
||||
|
||||
#include "dllimport.h"
|
||||
#include <QToolButton>
|
||||
|
||||
namespace MOBase {
|
||||
|
||||
/* Takes a QToolButton and a widget and creates an expandable widget.
|
||||
**/
|
||||
class QDLLEXPORT ExpanderWidget : public QObject
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
/** empty expander, use set()
|
||||
**/
|
||||
ExpanderWidget();
|
||||
|
||||
/** see set()
|
||||
**/
|
||||
ExpanderWidget(QToolButton* button, QWidget* content);
|
||||
|
||||
/** @brief sets the button and content widgets to use
|
||||
* the button will be given an arrow icon, clicking it will toggle the
|
||||
* visibility of the given widget
|
||||
* @param button the button that toggles the content
|
||||
* @param content the widget that will be shown or hidden
|
||||
* @param opened initial state, defaults to closed
|
||||
**/
|
||||
void set(QToolButton* button, QWidget* content, bool opened=false);
|
||||
|
||||
/** either opens or closes the expander depending on the current state
|
||||
**/
|
||||
void toggle();
|
||||
|
||||
/** sets the current state of the expander
|
||||
**/
|
||||
void toggle(bool b);
|
||||
|
||||
/** returns whether the expander is currently opened
|
||||
**/
|
||||
bool opened() const;
|
||||
|
||||
QByteArray saveState() const;
|
||||
void restoreState(const QByteArray& a);
|
||||
|
||||
QToolButton* button() const;
|
||||
|
||||
signals:
|
||||
void aboutToToggle(bool b);
|
||||
void toggled(bool b);
|
||||
|
||||
private:
|
||||
QToolButton* m_button;
|
||||
QWidget* m_content;
|
||||
bool opened_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // EXPANDERWIDGET_H
|
||||
+5
-1
@@ -59,7 +59,11 @@ public:
|
||||
try {
|
||||
return boost::any_cast<T*>(iter->second);
|
||||
} catch (const boost::bad_any_cast&) {
|
||||
qCritical("failed to retrieve feature type %s (got %s)", typeid(T).name(), typeid(iter->second).name());
|
||||
// don't use log::error() here so log.h and fmt aren't pulled into
|
||||
// plugins
|
||||
qCritical(
|
||||
"failed to retrieve feature type %s (got %s)",
|
||||
typeid(T).name(), typeid(iter->second).name());
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "linklabel.h"
|
||||
|
||||
QColor LinkLabel::m_linkColor;
|
||||
|
||||
LinkLabel::LinkLabel(QWidget* parent)
|
||||
: QLabel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QColor LinkLabel::linkColor() const
|
||||
{
|
||||
return m_linkColor;
|
||||
}
|
||||
|
||||
void LinkLabel::setLinkColor(const QColor& c)
|
||||
{
|
||||
if (m_linkColor != c) {
|
||||
m_linkColor = c;
|
||||
|
||||
// setting link color on the global palette; qt doesn't seem to support
|
||||
// per-widget colors for links
|
||||
if (qApp) {
|
||||
auto p = qApp->palette();
|
||||
p.setColor(QPalette::Link, c);
|
||||
p.setColor(QPalette::LinkVisited, c);
|
||||
qApp->setPalette(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef UIBASE_LINKLABEL_INCLUDED
|
||||
#define UIBASE_LINKLABEL_INCLUDED
|
||||
|
||||
#include "dllimport.h"
|
||||
#include <QLabel>
|
||||
|
||||
// this is a hack to allow .qss files to change the link color
|
||||
//
|
||||
// there's nothing in qt to change link colors from a qss file and the color
|
||||
// can't even be changed on individual widgets, they all use the _global_
|
||||
// palette on qApp
|
||||
//
|
||||
// so as soon as there's a LinkLabel present on screen, the global link color
|
||||
// will be set to whatever's in the qss file for:
|
||||
//
|
||||
// LinkLabel { qproperty-linkColor: cssColor; }
|
||||
//
|
||||
// this doesn't work for links that are visible, so changing the qss live won't
|
||||
// change the colors for those, MO has to be restarted
|
||||
//
|
||||
// apart from that, `LinkLabel` is just a `QLabel`
|
||||
//
|
||||
class QDLLEXPORT LinkLabel : public QLabel
|
||||
{
|
||||
Q_OBJECT;
|
||||
Q_PROPERTY(QColor linkColor READ linkColor WRITE setLinkColor);
|
||||
|
||||
public:
|
||||
LinkLabel(QWidget* parent=nullptr);
|
||||
|
||||
QColor linkColor() const;
|
||||
void setLinkColor(const QColor& c);
|
||||
|
||||
private:
|
||||
static QColor m_linkColor;
|
||||
};
|
||||
|
||||
#endif // UIBASE_LINKLABEL_INCLUDED
|
||||
+432
@@ -0,0 +1,432 @@
|
||||
#include "pch.h"
|
||||
#include "log.h"
|
||||
#include "utility.h"
|
||||
#include <iostream>
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4365)
|
||||
namespace spdlog { using wstring_view_t = fmt::basic_string_view<wchar_t>; }
|
||||
#define SPDLOG_WCHAR_FILENAMES 1
|
||||
#include <spdlog/logger.h>
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <spdlog/sinks/base_sink.h>
|
||||
#include <spdlog/sinks/dist_sink.h>
|
||||
#include <spdlog/sinks/daily_file_sink.h>
|
||||
#include <spdlog/sinks/rotating_file_sink.h>
|
||||
#include <spdlog/sinks/basic_file_sink.h>
|
||||
#pragma warning(pop)
|
||||
|
||||
|
||||
namespace MOBase::log
|
||||
{
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
static std::unique_ptr<Logger> g_default;
|
||||
|
||||
spdlog::level::level_enum toSpdlog(Levels lv)
|
||||
{
|
||||
switch (lv)
|
||||
{
|
||||
case Debug:
|
||||
return spdlog::level::debug;
|
||||
|
||||
case Warning:
|
||||
return spdlog::level::warn;
|
||||
|
||||
case Error:
|
||||
return spdlog::level::err;
|
||||
|
||||
case Info: // fall-through
|
||||
default:
|
||||
return spdlog::level::info;
|
||||
}
|
||||
}
|
||||
|
||||
Levels fromSpdlog(spdlog::level::level_enum lv)
|
||||
{
|
||||
switch (lv)
|
||||
{
|
||||
case spdlog::level::trace:
|
||||
case spdlog::level::debug:
|
||||
return Debug;
|
||||
|
||||
case spdlog::level::warn:
|
||||
return Warning;
|
||||
|
||||
case spdlog::level::critical: // fall-through
|
||||
case spdlog::level::err:
|
||||
return Error;
|
||||
|
||||
case spdlog::level::info: // fall-through
|
||||
case spdlog::level::off:
|
||||
default:
|
||||
return Info;
|
||||
}
|
||||
}
|
||||
|
||||
class CallbackSink : public spdlog::sinks::base_sink<std::mutex>
|
||||
{
|
||||
public:
|
||||
CallbackSink(Callback* f)
|
||||
: m_f(f)
|
||||
{
|
||||
}
|
||||
|
||||
void setCallback(Callback* f)
|
||||
{
|
||||
m_f = f;
|
||||
}
|
||||
|
||||
protected:
|
||||
void sink_it_(const spdlog::details::log_msg& m) override
|
||||
{
|
||||
thread_local bool active = false;
|
||||
|
||||
if (active) {
|
||||
// trying to log from a log callback, ignoring
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_f) {
|
||||
// disabled
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto g = Guard([&]{ active = false; });
|
||||
active = true;
|
||||
|
||||
Entry e;
|
||||
e.time = m.time;
|
||||
e.level = fromSpdlog(m.level);
|
||||
e.message = fmt::to_string(m.payload);
|
||||
|
||||
spdlog::memory_buf_t formatted;
|
||||
base_sink::formatter_->format(m, formatted);
|
||||
|
||||
if (formatted.size() >= 2) {
|
||||
// remove \r\n
|
||||
e.formattedMessage.assign(formatted.begin(), formatted.end() - 2);
|
||||
} else {
|
||||
e.formattedMessage = fmt::to_string(formatted);
|
||||
}
|
||||
|
||||
(*m_f)(std::move(e));
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
fprintf(
|
||||
stderr, "uncaugh exception in logging callback, %s\n",
|
||||
e.what());
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
fprintf(stderr, "uncaught exception in logging callback\n");
|
||||
}
|
||||
}
|
||||
|
||||
void flush_() override
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<Callback*> m_f;
|
||||
};
|
||||
|
||||
|
||||
File::File() :
|
||||
type(None),
|
||||
maxSize(0), maxFiles(0),
|
||||
dailyHour(0), dailyMinute(0)
|
||||
{
|
||||
}
|
||||
|
||||
File File::daily(fs::path file, int hour, int minute)
|
||||
{
|
||||
File fl;
|
||||
|
||||
fl.type = Daily;
|
||||
fl.file = std::move(file);
|
||||
fl.dailyHour = hour;
|
||||
fl.dailyMinute = minute;
|
||||
|
||||
return fl;
|
||||
}
|
||||
|
||||
File File::rotating(
|
||||
fs::path file, std::size_t maxSize, std::size_t maxFiles)
|
||||
{
|
||||
File fl;
|
||||
|
||||
fl.type = Rotating;
|
||||
fl.file = std::move(file);
|
||||
fl.maxSize = maxSize;
|
||||
fl.maxFiles = maxFiles;
|
||||
|
||||
return fl;
|
||||
}
|
||||
|
||||
File File::single(std::filesystem::path file)
|
||||
{
|
||||
File fl;
|
||||
|
||||
fl.type = Single;
|
||||
fl.file = std::move(file);
|
||||
|
||||
return fl;
|
||||
}
|
||||
|
||||
spdlog::sink_ptr createFileSink(const File& f)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (f.type)
|
||||
{
|
||||
case File::Daily:
|
||||
{
|
||||
return std::make_shared<spdlog::sinks::daily_file_sink_mt>(
|
||||
f.file.native(), f.dailyHour, f.dailyMinute);
|
||||
}
|
||||
|
||||
case File::Rotating:
|
||||
{
|
||||
return std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
|
||||
f.file.native(), f.maxSize, f.maxFiles);
|
||||
}
|
||||
|
||||
case File::Single:
|
||||
{
|
||||
return std::make_shared<spdlog::sinks::basic_file_sink_mt>(
|
||||
f.file.native(), true);
|
||||
}
|
||||
|
||||
case File::None: // fall-through
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
catch(spdlog::spdlog_ex& e)
|
||||
{
|
||||
std::cerr << "failed to create file log, " << e.what() << "\n";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Logger::Logger(LoggerConfiguration conf_moved)
|
||||
: m_conf(std::move(conf_moved))
|
||||
{
|
||||
createLogger(m_conf.name);
|
||||
|
||||
const auto timeType = m_conf.utc ?
|
||||
spdlog::pattern_time_type::utc : spdlog::pattern_time_type::local;
|
||||
|
||||
m_logger->set_level(toSpdlog(m_conf.maxLevel));
|
||||
m_logger->set_pattern(m_conf.pattern, timeType);
|
||||
m_logger->flush_on(spdlog::level::trace);
|
||||
}
|
||||
|
||||
// anchor
|
||||
Logger::~Logger() = default;
|
||||
|
||||
Levels Logger::level() const
|
||||
{
|
||||
return fromSpdlog(m_logger->level());
|
||||
}
|
||||
|
||||
void Logger::setLevel(Levels lv)
|
||||
{
|
||||
m_logger->set_level(toSpdlog(lv));
|
||||
}
|
||||
|
||||
void Logger::setPattern(const std::string& s)
|
||||
{
|
||||
m_logger->set_pattern(s);
|
||||
}
|
||||
|
||||
void Logger::setFile(const File& f)
|
||||
{
|
||||
|
||||
if (m_file) {
|
||||
auto* ds = static_cast<spdlog::sinks::dist_sink<std::mutex>*>(m_sinks.get());
|
||||
ds->remove_sink(m_file);
|
||||
m_file = {};
|
||||
}
|
||||
|
||||
if (f.type != File::None) {
|
||||
try
|
||||
{
|
||||
m_file = createFileSink(f);
|
||||
|
||||
if (m_file) {
|
||||
addSink(m_file);
|
||||
}
|
||||
}
|
||||
catch(spdlog::spdlog_ex& e)
|
||||
{
|
||||
error(e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::setCallback(Callback* f)
|
||||
{
|
||||
if (m_callback) {
|
||||
static_cast<CallbackSink*>(m_callback.get())->setCallback(f);
|
||||
} else {
|
||||
m_callback.reset(new CallbackSink(f));
|
||||
addSink(m_callback);
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::createLogger(const std::string& name)
|
||||
{
|
||||
m_sinks.reset(new spdlog::sinks::dist_sink<std::mutex>);
|
||||
|
||||
DWORD console_mode;
|
||||
if (::GetConsoleMode(::GetStdHandle(STD_ERROR_HANDLE), &console_mode) != 0) {
|
||||
using sink_type = spdlog::sinks::wincolor_stderr_sink_mt;
|
||||
m_console.reset(new sink_type);
|
||||
|
||||
if (auto* cs = dynamic_cast<sink_type*>(m_console.get())) {
|
||||
cs->set_color(spdlog::level::info, cs->WHITE);
|
||||
cs->set_color(spdlog::level::debug, cs->WHITE);
|
||||
}
|
||||
|
||||
addSink(m_console);
|
||||
}
|
||||
|
||||
m_logger.reset(new spdlog::logger(name, m_sinks));
|
||||
}
|
||||
|
||||
void Logger::addSink(std::shared_ptr<spdlog::sinks::sink> sink)
|
||||
{
|
||||
// this is called for both the file and callback sinks
|
||||
//
|
||||
// in createLogger(), the dist_sink that was just created will be given the
|
||||
// pattern that was set in Logger::Logger(), and will pass it to its children;
|
||||
// the log level is irrelevant in child sinks because dist_sink checks it
|
||||
// itself
|
||||
//
|
||||
// the problem then is that dist_sink doesn't have children yet, they're added
|
||||
// in setFile() and setCallback(), which can be called by the user much later
|
||||
// (or not at all)
|
||||
//
|
||||
// however, when a sink is added to dist_sink, it does _not_ set the pattern
|
||||
// on it, it merely adds it to the list
|
||||
//
|
||||
// this sets the formatter on the sink manually before adding it to dist_sink
|
||||
|
||||
auto* ds = static_cast<spdlog::sinks::dist_sink<std::mutex>*>(m_sinks.get());
|
||||
|
||||
const auto timeType = m_conf.utc ?
|
||||
spdlog::pattern_time_type::utc : spdlog::pattern_time_type::local;
|
||||
|
||||
sink->set_formatter(std::make_unique<spdlog::pattern_formatter>(
|
||||
m_conf.pattern, timeType));
|
||||
|
||||
ds->add_sink(sink);
|
||||
}
|
||||
|
||||
|
||||
QString levelToString(Levels level)
|
||||
{
|
||||
const auto spdlogLevel = toSpdlog(level);
|
||||
const auto sv = spdlog::level::to_string_view(spdlogLevel);
|
||||
const std::string s(sv.begin(), sv.end());
|
||||
|
||||
return QString::fromStdString(s);
|
||||
}
|
||||
|
||||
|
||||
void createDefault(LoggerConfiguration conf)
|
||||
{
|
||||
g_default = std::make_unique<Logger>(conf);
|
||||
}
|
||||
|
||||
Logger& getDefault()
|
||||
{
|
||||
Q_ASSERT(g_default);
|
||||
return *g_default;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
namespace MOBase::log::details
|
||||
{
|
||||
|
||||
std::string converter<std::wstring>::convert(const std::wstring& s)
|
||||
{
|
||||
return QString::fromStdWString(s).toStdString();
|
||||
}
|
||||
|
||||
std::string converter<QString>::convert(const QString& s)
|
||||
{
|
||||
return s.toStdString();
|
||||
}
|
||||
|
||||
std::string converter<QSize>::convert(const QSize& s)
|
||||
{
|
||||
return fmt::format("QSize({}, {})", s.width(), s.height());
|
||||
}
|
||||
|
||||
std::string converter<QRect>::convert(const QRect& r)
|
||||
{
|
||||
return fmt::format(
|
||||
"QRect({},{}-{},{})", r.left(), r.top(), r.right(), r.bottom());
|
||||
}
|
||||
|
||||
std::string converter<QColor>::convert(const QColor& c)
|
||||
{
|
||||
return fmt::format(
|
||||
"QColor({}, {}, {}, {})",
|
||||
c.red(), c.green(), c.blue(), c.alpha());
|
||||
}
|
||||
|
||||
std::string converter<QByteArray>::convert(const QByteArray& v)
|
||||
{
|
||||
return fmt::format("QByteArray({} bytes)", v.size());
|
||||
}
|
||||
|
||||
std::string converter<QVariant>::convert(const QVariant& v)
|
||||
{
|
||||
return fmt::format(
|
||||
"QVariant(type={}, value='{}')",
|
||||
v.typeName(), (v.type() == QVariant::ByteArray ?
|
||||
"(binary)" : v.toString().toStdString()));
|
||||
}
|
||||
|
||||
|
||||
void doLogImpl(spdlog::logger& lg, Levels lv, const std::string& s) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
const char* start = s.c_str();
|
||||
const char* p = start;
|
||||
|
||||
for (;;) {
|
||||
while (*p && *p != '\n') {
|
||||
++p;
|
||||
}
|
||||
|
||||
std::string_view sv(start, static_cast<std::size_t>(p - start));
|
||||
lg.log(toSpdlog(lv), "{}", sv);
|
||||
|
||||
if (!*p) {
|
||||
break;
|
||||
}
|
||||
|
||||
++p;
|
||||
start = p;
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
// eat it
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,272 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <QString>
|
||||
#include <fmt/format.h>
|
||||
#include "dllimport.h"
|
||||
|
||||
namespace spdlog { class logger; }
|
||||
namespace spdlog::sinks { class sink; }
|
||||
|
||||
namespace MOBase::log
|
||||
{
|
||||
|
||||
enum Levels
|
||||
{
|
||||
Debug = 0,
|
||||
Info = 1,
|
||||
Warning = 2,
|
||||
Error = 3
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
namespace MOBase::log::details
|
||||
{
|
||||
|
||||
// T to std::string converters
|
||||
//
|
||||
// those are kept in this namespace so they don't leak all over the place;
|
||||
// they're used directly by doLog() below
|
||||
|
||||
template <class T>
|
||||
struct converter
|
||||
{
|
||||
static const T& convert(const T& t)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct QDLLEXPORT converter<std::wstring>
|
||||
{
|
||||
static std::string convert(const std::wstring& s);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct QDLLEXPORT converter<QString>
|
||||
{
|
||||
static std::string convert(const QString& s);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct QDLLEXPORT converter<QSize>
|
||||
{
|
||||
static std::string convert(const QSize& s);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct QDLLEXPORT converter<QRect>
|
||||
{
|
||||
static std::string convert(const QRect& s);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct QDLLEXPORT converter<QColor>
|
||||
{
|
||||
static std::string convert(const QColor& c);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct QDLLEXPORT converter<QByteArray>
|
||||
{
|
||||
static std::string convert(const QByteArray& v);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct QDLLEXPORT converter<QVariant>
|
||||
{
|
||||
static std::string convert(const QVariant& v);
|
||||
};
|
||||
|
||||
|
||||
void QDLLEXPORT doLogImpl(
|
||||
spdlog::logger& lg, Levels lv, const std::string& s) noexcept;
|
||||
|
||||
template <class F, class... Args>
|
||||
void doLog(
|
||||
spdlog::logger& logger, Levels lv, F&& format, Args&&... args) noexcept
|
||||
{
|
||||
std::string s;
|
||||
|
||||
// format errors are logged without much information to avoid throwing again
|
||||
|
||||
try
|
||||
{
|
||||
s = fmt::format(
|
||||
std::forward<F>(format),
|
||||
converter<std::decay_t<Args>>::convert(std::forward<Args>(args))...);
|
||||
}
|
||||
catch(fmt::format_error&)
|
||||
{
|
||||
s = "format error while logging";
|
||||
lv = Levels::Error;
|
||||
}
|
||||
catch(std::exception&)
|
||||
{
|
||||
s = "exception while formatting for logging";
|
||||
lv = Levels::Error;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
s = "unknown exception while formatting for logging";
|
||||
lv = Levels::Error;
|
||||
}
|
||||
|
||||
doLogImpl(logger, lv, s);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
namespace MOBase::log
|
||||
{
|
||||
|
||||
struct QDLLEXPORT File
|
||||
{
|
||||
public:
|
||||
enum Types
|
||||
{
|
||||
None = 0,
|
||||
Daily,
|
||||
Rotating,
|
||||
Single
|
||||
};
|
||||
|
||||
File();
|
||||
|
||||
static File daily(std::filesystem::path file, int hour, int minute);
|
||||
|
||||
static File rotating(
|
||||
std::filesystem::path file, std::size_t maxSize, std::size_t maxFiles);
|
||||
|
||||
static File single(std::filesystem::path file);
|
||||
|
||||
Types type;
|
||||
std::filesystem::path file;
|
||||
std::size_t maxSize, maxFiles;
|
||||
int dailyHour, dailyMinute;
|
||||
};
|
||||
|
||||
|
||||
struct Entry
|
||||
{
|
||||
std::chrono::system_clock::time_point time;
|
||||
Levels level;
|
||||
std::string message;
|
||||
std::string formattedMessage;
|
||||
};
|
||||
|
||||
using Callback = void (Entry);
|
||||
|
||||
|
||||
struct LoggerConfiguration
|
||||
{
|
||||
std::string name;
|
||||
Levels maxLevel = Levels::Info;
|
||||
std::string pattern;
|
||||
bool utc = false;
|
||||
};
|
||||
|
||||
|
||||
class QDLLEXPORT Logger
|
||||
{
|
||||
public:
|
||||
Logger(LoggerConfiguration conf);
|
||||
~Logger();
|
||||
|
||||
Levels level() const;
|
||||
void setLevel(Levels lv);
|
||||
|
||||
void setPattern(const std::string& pattern);
|
||||
void setFile(const File& f);
|
||||
void setCallback(Callback* f);
|
||||
|
||||
template <class F, class... Args>
|
||||
void debug(F&& format, Args&&... args) noexcept
|
||||
{
|
||||
log(Debug, std::forward<F>(format), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class F, class... Args>
|
||||
void info(F&& format, Args&&... args) noexcept
|
||||
{
|
||||
log(Info, std::forward<F>(format), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class F, class... Args>
|
||||
void warn(F&& format, Args&&... args) noexcept
|
||||
{
|
||||
log(Warning, std::forward<F>(format), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class F, class... Args>
|
||||
void error(F&& format, Args&&... args) noexcept
|
||||
{
|
||||
log(Error, std::forward<F>(format), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class F, class... Args>
|
||||
void log(Levels lv, F&& format, Args&&... args) noexcept
|
||||
{
|
||||
details::doLog(
|
||||
*m_logger, lv, std::forward<F>(format), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
LoggerConfiguration m_conf;
|
||||
std::unique_ptr<spdlog::logger> m_logger;
|
||||
std::shared_ptr<spdlog::sinks::sink> m_sinks;
|
||||
std::shared_ptr<spdlog::sinks::sink> m_console, m_callback, m_file;
|
||||
|
||||
void createLogger(const std::string& name);
|
||||
void addSink(std::shared_ptr<spdlog::sinks::sink> sink);
|
||||
};
|
||||
|
||||
QDLLEXPORT void createDefault(LoggerConfiguration conf);
|
||||
QDLLEXPORT Logger& getDefault();
|
||||
|
||||
|
||||
template <class F, class... Args>
|
||||
void debug(F&& format, Args&&... args) noexcept
|
||||
{
|
||||
getDefault().debug(
|
||||
std::forward<F>(format), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class F, class... Args>
|
||||
void info(F&& format, Args&&... args) noexcept
|
||||
{
|
||||
getDefault().info(
|
||||
std::forward<F>(format), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class F, class... Args>
|
||||
void warn(F&& format, Args&&... args) noexcept
|
||||
{
|
||||
getDefault().warn(
|
||||
std::forward<F>(format), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class F, class... Args>
|
||||
void error(F&& format, Args&&... args) noexcept
|
||||
{
|
||||
getDefault().error(
|
||||
std::forward<F>(format), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class F, class... Args>
|
||||
void log(Levels lv, F&& format, Args&&... args) noexcept
|
||||
{
|
||||
getDefault().log(
|
||||
lv, std::forward<F>(format), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
QDLLEXPORT QString levelToString(Levels level);
|
||||
|
||||
} // namespace
|
||||
@@ -37,7 +37,7 @@ MOBase::ModRepositoryFileInfo MOBase::ModRepositoryFileInfo::createFromJson(cons
|
||||
newInfo.version.parse(result.at(4).toString());
|
||||
newInfo.description = result.at(5).toString();
|
||||
newInfo.categoryID = result.at(6).toInt();
|
||||
newInfo.fileSize = result.at(7).toInt();
|
||||
newInfo.fileSize = result.at(7).toUInt();
|
||||
newInfo.modID = result.at(8).toInt();
|
||||
newInfo.modName = result.at(9).toString();
|
||||
newInfo.newestVersion.parse(result.at(10).toString());
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#include "pch.h"
|
||||
@@ -0,0 +1,140 @@
|
||||
#pragma warning(disable: 4251) // neds to have dll-interface
|
||||
#pragma warning(disable: 4355) // this used in initializer list
|
||||
#pragma warning(disable: 4371) // layout may have changed
|
||||
#pragma warning(disable: 4514) // unreferenced inline function removed
|
||||
#pragma warning(disable: 4571) // catch semantics changed
|
||||
#pragma warning(disable: 4619) // no warning X
|
||||
#pragma warning(disable: 4623) // default constructor deleted
|
||||
#pragma warning(disable: 4625) // copy constructor deleted
|
||||
#pragma warning(disable: 4626) // copy assignment operator deleted
|
||||
#pragma warning(disable: 4710) // function not inlined
|
||||
#pragma warning(disable: 4820) // padding
|
||||
#pragma warning(disable: 4866) // left-to-right evaluation order
|
||||
#pragma warning(disable: 4868) // left-to-right evaluation order
|
||||
#pragma warning(disable: 5026) // move constructor deleted
|
||||
#pragma warning(disable: 5027) // move assignment operator deleted
|
||||
#pragma warning(disable: 5045) // spectre mitigation
|
||||
|
||||
#pragma warning(push, 3)
|
||||
#pragma warning(disable: 4365) // signed/unsigned mismatch
|
||||
#pragma warning(disable: 4774) // bad format string
|
||||
#pragma warning(disable: 4946) // reinterpret_cast used between related classes
|
||||
#pragma warning(disable: 4800) // implicit conversion
|
||||
|
||||
#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING 1
|
||||
|
||||
// std
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <typeindex>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <wchar.h>
|
||||
|
||||
// fmt
|
||||
#include <fmt/format.h>
|
||||
|
||||
// windows
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_MEAN_AND_LEAN
|
||||
#define WIN32_MEAN_AND_LEAN
|
||||
#endif
|
||||
#include <ShlObj.h>
|
||||
#include <shobjidl.h>
|
||||
#include <Windows.h>
|
||||
|
||||
// boost
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <boost/any.hpp>
|
||||
#include <boost/assign.hpp>
|
||||
#include <boost/scoped_array.hpp>
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
// Qt
|
||||
#include <QAbstractButton>
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QBitmap>
|
||||
#include <QBuffer>
|
||||
#include <QCommandLinkButton>
|
||||
#include <QCoreApplication>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDateTime>
|
||||
#include <QDesktopWidget>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDir>
|
||||
#include <QDropEvent>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QFlags>
|
||||
#include <QGraphicsObject>
|
||||
#include <QIcon>
|
||||
#include <QImage>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMainWindow>
|
||||
#include <QMenuBar>
|
||||
#include <QMessageBox>
|
||||
#include <QMetaEnum>
|
||||
#include <QMetaMethod>
|
||||
#include <qmetaobject.h>
|
||||
#include <QMetaObject>
|
||||
#include <QMetaType>
|
||||
#include <QMouseEvent>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QQmlContext>
|
||||
#include <QQmlEngine>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickWidget>
|
||||
#include <QRect>
|
||||
#include <QRegExp>
|
||||
#include <QRegularExpression>
|
||||
#include <QResizeEvent>
|
||||
#include <QSettings>
|
||||
#include <QShortcutEvent>
|
||||
#include <QShowEvent>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QStyle>
|
||||
#include <QTableWidget>
|
||||
#include <QTabWidget>
|
||||
#include <QtDebug>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTextCodec>
|
||||
#include <QTextEdit>
|
||||
#include <QTextStream>
|
||||
#include <QTime>
|
||||
#include <QTimer>
|
||||
#include <QToolBar>
|
||||
#include <QToolButton>
|
||||
#include <QTreeWidget>
|
||||
#include <QtWinExtras/QtWin>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QVariant>
|
||||
#include <QVariantMap>
|
||||
#include <QVersionNumber>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#undef _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
|
||||
#pragma warning(pop)
|
||||
+126
-62
@@ -20,77 +20,72 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
#include "questionboxmemory.h"
|
||||
#include "ui_questionboxmemory.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <QApplication> // for QApplication
|
||||
#include <QIcon> // for QIcon
|
||||
#include <QApplication>
|
||||
#include <QIcon>
|
||||
#include <QPushButton>
|
||||
#include <QMutex> // for QMutex
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
#include <QSettings>
|
||||
#include <QStyle> // for QStyle, etc
|
||||
#include <QStyle>
|
||||
|
||||
#include <stdlib.h> // for atexit
|
||||
namespace MOBase
|
||||
{
|
||||
|
||||
namespace MOBase {
|
||||
static QMutex g_mutex;
|
||||
static QuestionBoxMemory::GetButton g_get;
|
||||
static QuestionBoxMemory::SetWindowButton g_setWindow;
|
||||
static QuestionBoxMemory::SetFileButton g_setFile;
|
||||
|
||||
QSettings *QuestionBoxMemory::s_SettingFile = nullptr;
|
||||
QMutex QuestionBoxMemory::s_SettingsMutex;
|
||||
|
||||
QuestionBoxMemory::QuestionBoxMemory(QWidget *parent, const QString &title, const QString &text, QString const *filename,
|
||||
const QDialogButtonBox::StandardButtons buttons, QDialogButtonBox::StandardButton defaultButton)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::QuestionBoxMemory)
|
||||
, m_Button(QDialogButtonBox::Cancel)
|
||||
QuestionBoxMemory::QuestionBoxMemory(
|
||||
QWidget *parent, const QString &title, const QString &text, QString const *filename,
|
||||
const QDialogButtonBox::StandardButtons buttons, QDialogButtonBox::StandardButton defaultButton)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::QuestionBoxMemory)
|
||||
, m_Button(QDialogButtonBox::Cancel)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
this->setWindowFlag(Qt::WindowType::WindowContextHelpButtonHint, false);
|
||||
this->setWindowTitle(title);
|
||||
setWindowFlag(Qt::WindowType::WindowContextHelpButtonHint, false);
|
||||
setWindowTitle(title);
|
||||
|
||||
QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion);
|
||||
ui->iconLabel->setPixmap(icon.pixmap(128));
|
||||
ui->messageLabel->setText(text);
|
||||
|
||||
if (filename == nullptr) {
|
||||
//delete the 2nd check box
|
||||
QCheckBox *box = ui->rememberForCheckBox;
|
||||
box->parentWidget()->layout()->removeWidget(box);
|
||||
delete box;
|
||||
} else {
|
||||
ui->rememberForCheckBox->setText(ui->rememberForCheckBox->text() + " " + *filename);
|
||||
ui->rememberForCheckBox->setText(
|
||||
ui->rememberForCheckBox->text().arg(*filename));
|
||||
}
|
||||
|
||||
ui->buttonBox->setStandardButtons(buttons);
|
||||
|
||||
if (defaultButton != QDialogButtonBox::NoButton) {
|
||||
ui->buttonBox->button(defaultButton)->setDefault(true);
|
||||
}
|
||||
connect(ui->buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonClicked(QAbstractButton*)));
|
||||
|
||||
connect(
|
||||
ui->buttonBox, SIGNAL(clicked(QAbstractButton*)),
|
||||
this, SLOT(buttonClicked(QAbstractButton*)));
|
||||
}
|
||||
|
||||
QuestionBoxMemory::~QuestionBoxMemory() = default;
|
||||
|
||||
QuestionBoxMemory::~QuestionBoxMemory()
|
||||
void QuestionBoxMemory::setCallbacks(
|
||||
GetButton get, SetWindowButton setWindow, SetFileButton setFile)
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
QMutexLocker locker(&g_mutex);
|
||||
|
||||
|
||||
void QuestionBoxMemory::init(const QString &fileName)
|
||||
{
|
||||
QMutexLocker locker(&s_SettingsMutex);
|
||||
if (s_SettingFile == nullptr) {
|
||||
s_SettingFile = new QSettings(fileName, QSettings::IniFormat);
|
||||
atexit(&QuestionBoxMemory::cleanup);
|
||||
}
|
||||
}
|
||||
|
||||
void QuestionBoxMemory::resetDialogs()
|
||||
{
|
||||
s_SettingFile->remove("DialogChoices");
|
||||
}
|
||||
|
||||
void QuestionBoxMemory::cleanup()
|
||||
{
|
||||
QMutexLocker locker(&s_SettingsMutex);
|
||||
s_SettingFile->sync();
|
||||
delete s_SettingFile;
|
||||
g_get = get;
|
||||
g_setWindow = setWindow;
|
||||
g_setFile = setFile;
|
||||
}
|
||||
|
||||
void QuestionBoxMemory::buttonClicked(QAbstractButton *button)
|
||||
@@ -98,47 +93,116 @@ void QuestionBoxMemory::buttonClicked(QAbstractButton *button)
|
||||
m_Button = ui->buttonBox->standardButton(button);
|
||||
}
|
||||
|
||||
QDialogButtonBox::StandardButton QuestionBoxMemory::query(QWidget *parent, const QString &windowName,
|
||||
const QString &title, const QString &text, QDialogButtonBox::StandardButtons buttons,
|
||||
QDialogButtonBox::StandardButton defaultButton)
|
||||
QDialogButtonBox::StandardButton QuestionBoxMemory::query(
|
||||
QWidget *parent, const QString &windowName,
|
||||
const QString &title, const QString &text, QDialogButtonBox::StandardButtons buttons,
|
||||
QDialogButtonBox::StandardButton defaultButton)
|
||||
{
|
||||
return queryImpl(parent, windowName, nullptr, title, text, buttons, defaultButton);
|
||||
}
|
||||
|
||||
QDialogButtonBox::StandardButton QuestionBoxMemory::query(QWidget *parent, const QString &windowName, const QString &fileName,
|
||||
const QString &title, const QString &text, QDialogButtonBox::StandardButtons buttons,
|
||||
QDialogButtonBox::StandardButton defaultButton)
|
||||
QDialogButtonBox::StandardButton QuestionBoxMemory::query(
|
||||
QWidget *parent, const QString &windowName, const QString &fileName,
|
||||
const QString &title, const QString &text, QDialogButtonBox::StandardButtons buttons,
|
||||
QDialogButtonBox::StandardButton defaultButton)
|
||||
{
|
||||
return queryImpl(parent, windowName, &fileName, title, text, buttons, defaultButton);
|
||||
}
|
||||
|
||||
QDialogButtonBox::StandardButton QuestionBoxMemory::queryImpl(QWidget *parent, const QString &windowName, const QString *fileName,
|
||||
const QString &title, const QString &text, QDialogButtonBox::StandardButtons buttons,
|
||||
QDialogButtonBox::StandardButton defaultButton)
|
||||
QDialogButtonBox::StandardButton QuestionBoxMemory::queryImpl(
|
||||
QWidget *parent, const QString &windowName, const QString *fileName,
|
||||
const QString &title, const QString &text, QDialogButtonBox::StandardButtons buttons,
|
||||
QDialogButtonBox::StandardButton defaultButton)
|
||||
{
|
||||
QMutexLocker locker(&s_SettingsMutex);
|
||||
QString windowSetting("DialogChoices/" + windowName);
|
||||
QString fileSetting;
|
||||
if (fileName != nullptr) {
|
||||
fileSetting = windowSetting + "/" + *fileName;
|
||||
if (s_SettingFile->contains(fileSetting)) {
|
||||
return static_cast<QDialogButtonBox::StandardButton>(s_SettingFile->value(fileSetting).toInt());
|
||||
}
|
||||
}
|
||||
if (s_SettingFile->contains(windowSetting)) {
|
||||
return static_cast<QDialogButtonBox::StandardButton>(s_SettingFile->value(windowSetting).toInt());
|
||||
QMutexLocker locker(&g_mutex);
|
||||
|
||||
const auto button = getMemory(windowName, (fileName ? *fileName : ""));
|
||||
if (button != NoButton) {
|
||||
log::debug(
|
||||
"{}: not asking because user always wants response {}",
|
||||
windowName + (fileName ? QString("/") + fileName : ""),
|
||||
buttonToString(button));
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
QuestionBoxMemory dialog(parent, title, text, fileName, buttons, defaultButton);
|
||||
dialog.exec();
|
||||
|
||||
if (dialog.m_Button != QDialogButtonBox::Cancel) {
|
||||
if (dialog.ui->rememberCheckBox->isChecked()) {
|
||||
s_SettingFile->setValue(windowSetting, dialog.m_Button);
|
||||
setWindowMemory(windowName, dialog.m_Button);
|
||||
}
|
||||
|
||||
if (fileName != nullptr && dialog.ui->rememberForCheckBox->isChecked()) {
|
||||
s_SettingFile->setValue(fileSetting, dialog.m_Button);
|
||||
setFileMemory(windowName, *fileName, dialog.m_Button);
|
||||
}
|
||||
}
|
||||
|
||||
return dialog.m_Button;
|
||||
}
|
||||
|
||||
void QuestionBoxMemory::setWindowMemory(const QString& windowName, Button b)
|
||||
{
|
||||
log::debug(
|
||||
"remembering choice {} for window {}",
|
||||
buttonToString(b), windowName);
|
||||
|
||||
g_setWindow(windowName, b);
|
||||
}
|
||||
|
||||
void QuestionBoxMemory::setFileMemory(
|
||||
const QString& windowName, const QString& filename, Button b)
|
||||
{
|
||||
log::debug(
|
||||
"remembering choice {} for file {}",
|
||||
buttonToString(b), windowName + "/" + filename);
|
||||
|
||||
g_setFile(windowName, filename, b);
|
||||
}
|
||||
|
||||
QuestionBoxMemory::Button QuestionBoxMemory::getMemory(
|
||||
const QString& windowName, const QString& filename)
|
||||
{
|
||||
return g_get(windowName, filename);
|
||||
}
|
||||
|
||||
QString QuestionBoxMemory::buttonToString(Button b)
|
||||
{
|
||||
using BB = QDialogButtonBox;
|
||||
|
||||
static const std::map<Button, QString> map = {
|
||||
{BB::NoButton, "none"},
|
||||
{BB::Ok, "ok"},
|
||||
{BB::Save, "save"},
|
||||
{BB::SaveAll, "saveall"},
|
||||
{BB::Open, "open"},
|
||||
{BB::Yes, "yes"},
|
||||
{BB::YesToAll, "yestoall"},
|
||||
{BB::No, "no"},
|
||||
{BB::NoToAll, "notoall"},
|
||||
{BB::Abort, "abort"},
|
||||
{BB::Retry, "retry"},
|
||||
{BB::Ignore, "ignore"},
|
||||
{BB::Close, "close"},
|
||||
{BB::Cancel, "cancel"},
|
||||
{BB::Discard, "discard"},
|
||||
{BB::Help, "help"},
|
||||
{BB::Apply, "apply"},
|
||||
{BB::Reset, "reset"},
|
||||
{BB::RestoreDefaults, "restoredefaults"}
|
||||
};
|
||||
|
||||
auto itor = map.find(b);
|
||||
|
||||
if (itor == map.end()) {
|
||||
return QString("0x%1")
|
||||
.arg(static_cast<int>(b), 0, 16);
|
||||
} else {
|
||||
return QString("'%1' (0x%2)")
|
||||
.arg(itor->second)
|
||||
.arg(static_cast<int>(b), 0, 16);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
+46
-34
@@ -33,62 +33,74 @@ class QMutex;
|
||||
class QSettings;
|
||||
class QWidget;
|
||||
|
||||
namespace Ui {
|
||||
class QuestionBoxMemory;
|
||||
}
|
||||
|
||||
|
||||
namespace MOBase {
|
||||
namespace Ui { class QuestionBoxMemory; }
|
||||
|
||||
namespace MOBase
|
||||
{
|
||||
|
||||
class QDLLEXPORT QuestionBoxMemory : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using Button = QDialogButtonBox::StandardButton;
|
||||
static const auto NoButton = QDialogButtonBox::NoButton;
|
||||
|
||||
virtual ~QuestionBoxMemory();
|
||||
using GetButton = std::function<Button (const QString&, const QString&)>;
|
||||
using SetWindowButton = std::function<void (const QString&, Button)>;
|
||||
using SetFileButton = std::function<void (const QString&, const QString&, Button)>;
|
||||
|
||||
static void init(const QString &fileName);
|
||||
~QuestionBoxMemory();
|
||||
|
||||
static void resetDialogs();
|
||||
// QuestionBoxMemory needs to access the settings, but they're only in
|
||||
// the modorganizer project; the only way to avoid accessing the ini file
|
||||
// directly is to use callbacks registered in Settings' constructor
|
||||
//
|
||||
static void setCallbacks(
|
||||
GetButton get, SetWindowButton setWindow, SetFileButton setFile);
|
||||
|
||||
static QDialogButtonBox::StandardButton query(QWidget *parent, const QString &windowName,
|
||||
const QString &title, const QString &text,
|
||||
QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes | QDialogButtonBox::No,
|
||||
QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::NoButton);
|
||||
static Button query(
|
||||
QWidget *parent, const QString &windowName,
|
||||
const QString &title, const QString &text,
|
||||
QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes | QDialogButtonBox::No,
|
||||
QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::NoButton);
|
||||
|
||||
static QDialogButtonBox::StandardButton query(QWidget *parent, const QString &windowName, const QString &fileName,
|
||||
const QString &title, const QString &text,
|
||||
QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes | QDialogButtonBox::No,
|
||||
QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::NoButton);
|
||||
static Button query(
|
||||
QWidget *parent, const QString &windowName, const QString &fileName,
|
||||
const QString &title, const QString &text,
|
||||
QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes | QDialogButtonBox::No,
|
||||
QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::NoButton);
|
||||
|
||||
static void setWindowMemory(const QString& windowName, Button b);
|
||||
|
||||
static void setFileMemory(
|
||||
const QString& windowName, const QString& filename, Button b);
|
||||
|
||||
static Button getMemory(
|
||||
const QString& windowName, const QString& filename);
|
||||
|
||||
static QString buttonToString(Button b);
|
||||
|
||||
private slots:
|
||||
|
||||
void buttonClicked(QAbstractButton *button);
|
||||
|
||||
private:
|
||||
|
||||
explicit QuestionBoxMemory(QWidget *parent, const QString &title, const QString &text, const QString *filename, const QDialogButtonBox::StandardButtons buttons,
|
||||
QDialogButtonBox::StandardButton defaultButton);
|
||||
|
||||
static void cleanup();
|
||||
explicit QuestionBoxMemory(
|
||||
QWidget *parent, const QString &title, const QString &text,
|
||||
const QString *filename, const QDialogButtonBox::StandardButtons buttons,
|
||||
QDialogButtonBox::StandardButton defaultButton);
|
||||
|
||||
private:
|
||||
static Button queryImpl(
|
||||
QWidget *parent, const QString &windowName, const QString *fileName,
|
||||
const QString &title, const QString &text,
|
||||
QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes | QDialogButtonBox::No,
|
||||
QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::NoButton);
|
||||
|
||||
static QMutex s_SettingsMutex;
|
||||
static QSettings *s_SettingFile;
|
||||
|
||||
static QDialogButtonBox::StandardButton queryImpl(QWidget *parent, const QString &windowName, const QString *fileName,
|
||||
const QString &title, const QString &text,
|
||||
QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes | QDialogButtonBox::No,
|
||||
QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::NoButton);
|
||||
|
||||
Ui::QuestionBoxMemory *ui;
|
||||
std::unique_ptr<Ui::QuestionBoxMemory> ui;
|
||||
QDialogButtonBox::StandardButton m_Button;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#endif // QUESTIONBOXMEMORY_H
|
||||
|
||||
@@ -17,7 +17,16 @@
|
||||
<property name="spacing">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -77,7 +86,16 @@
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -92,7 +110,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="rememberForCheckBox">
|
||||
<property name="text">
|
||||
<string>Remember selection only for</string>
|
||||
<string>Remember selection only for %1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user