Merge pull request #36 from ModOrganizer2/Develop

Stage for release 2.2.2
This commit is contained in:
Chris Bessent
2020-01-06 05:11:08 -07:00
committed by GitHub
34 changed files with 5090 additions and 283 deletions
+10 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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")
+2 -1
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -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
+2 -2
View File
@@ -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)
{
}
+93
View File
@@ -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
+62
View File
@@ -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
View File
@@ -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 {
+29
View File
@@ -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);
}
}
}
+38
View File
@@ -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
View File
@@ -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
+272
View File
@@ -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
+1 -1
View File
@@ -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());
+1
View File
@@ -0,0 +1 @@
#include "pch.h"
+140
View File
@@ -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
View File
@@ -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
View File
@@ -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
+21 -3
View File
@@ -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