update checker
@@ -29,7 +29,7 @@ endif()
|
||||
|
||||
# You will most likely get a message about being unable to open hunspell-1.7.lib because pkgconf forgets to add the actual path to
|
||||
# HUNSPELL_LIBRARIES. If so, uncomment the below line and point it to the correct vcpkg root folder/library.
|
||||
#set(HUNSPELL_LIBRARIES "C:\\PATH\\TO\\ROOT\\vcpkg\\installed\\${VCPKG_TARGET_TRIPLET}\\lib\\hunspell-1.7.lib")
|
||||
set(HUNSPELL_LIBRARIES "C:\\src\\vcpkg\\installed\\${VCPKG_TARGET_TRIPLET}\\lib\\hunspell-1.7.lib")
|
||||
message("-- Does this have a full path? If not, and it's just a file name, it's broken: Found Hunspell at ${HUNSPELL_LIBRARIES}")
|
||||
|
||||
include_directories("${PROJECT_BINARY_DIR}/src")
|
||||
@@ -109,6 +109,11 @@ if(WIN32)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Magic Set Editor Updater executable
|
||||
|
||||
add_executable(magicseteditor-updater WIN32)
|
||||
target_link_libraries(magicseteditor-updater PRIVATE wxWidgets::wxWidgets)
|
||||
target_sources(magicseteditor-updater PRIVATE src_updater/main.cpp src_updater/win32_res.rc src_updater/updater.ico)
|
||||
|
||||
# warnings
|
||||
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
||||
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 734 B After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 754 B |
|
Before Width: | Height: | Size: 944 B |
|
Before Width: | Height: | Size: 903 B After Width: | Height: | Size: 4.8 KiB |
@@ -11,6 +11,7 @@
|
||||
|
||||
icon/app ICON "icon/app.ico" // has to come first in alphabet!!
|
||||
icon/installer ICON "icon/installer.ico"
|
||||
icon/updater ICON "icon/updater.ico"
|
||||
icon/set ICON "icon/set.ico"
|
||||
icon/symbol ICON "icon/symbol.ico"
|
||||
|
||||
@@ -28,9 +29,7 @@ tool/export_images IMAGE "tool/export_images.png"
|
||||
tool/export_mws IMAGE "tool/export_mws.png"
|
||||
tool/export_apprentice IMAGE "tool/export_apprentice.png"
|
||||
tool/reload_data IMAGE "tool/reload_data.png"
|
||||
tool/dark_reload_data IMAGE "tool/dark_reload_data.png"
|
||||
tool/check_updates IMAGE "tool/check_updates.png"
|
||||
tool/dark_check_updates IMAGE "tool/dark_check_updates.png"
|
||||
tool/print IMAGE "tool/print.png"
|
||||
tool/print_preview IMAGE "tool/print_preview.png"
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <wx/wfstream.h>
|
||||
#include <wx/zipstrm.h>
|
||||
#include <wx/stdpaths.h>
|
||||
#include <unordered_set>
|
||||
|
||||
// Don't do this check for now, because we can't bless packages
|
||||
#define USE_MODIFIED_CHECK 0
|
||||
@@ -36,17 +37,17 @@ IMPLEMENT_REFLECTION(Installer) {
|
||||
|
||||
void Installer::validate(Version file_app_version) {
|
||||
Packaged::validate(file_app_version);
|
||||
// load icons where possible
|
||||
// load icons if it's a disk path
|
||||
FOR_EACH(p,packages) {
|
||||
String url = p->icon_url;
|
||||
String filename = p->icon_url;
|
||||
if (settings.darkMode() && !p->dark_icon_url.empty()) {
|
||||
url = p->dark_icon_url;
|
||||
filename = p->dark_icon_url;
|
||||
}
|
||||
if (!url.empty() && !starts_with(url,_("http:"))) {
|
||||
if (!filename.empty() && !starts_with(filename,_("http"))) {
|
||||
// TODO: support absolute icon names
|
||||
try{
|
||||
String filename = p->name + _("/") + url;
|
||||
auto img_stream = openIn(p->name + _("/") + url);
|
||||
String filepath = p->name + _("/") + filename;
|
||||
auto img_stream = openIn(filepath);
|
||||
image_load_file(p->icon, *img_stream);
|
||||
} catch (...) {
|
||||
// ignore errors, it's just an image
|
||||
@@ -251,7 +252,7 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(PackageDescription) {
|
||||
}
|
||||
|
||||
void PackageDescription::merge(const PackageDescription& p2) {
|
||||
if (!icon.Ok() && !icon_url) icon = p2.icon;
|
||||
if (!icon.Ok()) icon = p2.icon;
|
||||
if (installer_group.empty()) installer_group = p2.installer_group;
|
||||
if (short_name.empty()) short_name = p2.short_name;
|
||||
if (full_name.empty()) full_name = p2.full_name;
|
||||
@@ -505,33 +506,44 @@ void remove_package_dependency(InstallablePackages& packages, Dep dep, PackageAc
|
||||
|
||||
// ----------------------------------------------------------------------------- : Installable package : dependency stuff (OLD)
|
||||
|
||||
bool add_package_dependency(InstallablePackages& packages, const PackageDependency& dep, PackageAction where, bool set) {
|
||||
bool add_package_dependency(InstallablePackages& packages, const InstallablePackageP& package, const PackageDependency& dep, PackageAction where, bool set, unordered_set<String>& already_checked) {
|
||||
FOR_EACH(p, packages) {
|
||||
if (p->description->name == dep.package) {
|
||||
// Some package depends on this package, so install it if needed
|
||||
if (already_checked.find(p->description->name) != already_checked.end()) return true;
|
||||
already_checked.insert(p->description->name);
|
||||
// package depends on p, so install p if needed
|
||||
// Mark the installation as "automatically needed for X packages"
|
||||
// if !set then instead the dependency is no longer needed because we are not installing the package
|
||||
// if !set then instead the dependency is no longer needed because we are no longer installing package
|
||||
if (!p->installed || p->installed->version < dep.version) {
|
||||
bool change = false;
|
||||
if (p->action & PACKAGE_ACT_INSTALL) {
|
||||
// this package is already scheduled for installation
|
||||
// p is already scheduled for installation
|
||||
if (p->automatic) {
|
||||
// we are already automatically depending on this package
|
||||
// we are already automatically depending on p
|
||||
p->automatic += set ? +1 : -1;
|
||||
if (p->automatic == 0) {
|
||||
// no one needs this package anymore
|
||||
// no one needs p anymore
|
||||
p->action = PACKAGE_ACT_NOTHING;
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
// handle circular dependencies
|
||||
if (!set) {
|
||||
FOR_EACH(pdep, p->description->dependencies) {
|
||||
if (package->description->name == pdep->package && (!package->installed || package->installed->version < pdep->version)) {
|
||||
// p depends on package, so we can no longer install p
|
||||
// because it depends on a version of package that we are no longer installing
|
||||
p->automatic = 0;
|
||||
p->action = PACKAGE_ACT_NOTHING;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (set) {
|
||||
p->action = where | PACKAGE_ACT_INSTALL;
|
||||
p->automatic = 1;
|
||||
change = true;
|
||||
}
|
||||
// recursively add/remove dependencies
|
||||
FOR_EACH(dep, p->description->dependencies) {
|
||||
if (!add_package_dependency(packages, *dep, where, set)) {
|
||||
if (!add_package_dependency(packages, p, *dep, where, set, already_checked)) {
|
||||
return false; // failed
|
||||
}
|
||||
}
|
||||
@@ -574,8 +586,9 @@ bool set_package_action_unsafe(InstallablePackages& packages, const InstallableP
|
||||
package->automatic = 0;
|
||||
package->action = action;
|
||||
// check dependencies
|
||||
unordered_set<String> already_checked;
|
||||
FOR_EACH(dep, package->description->dependencies) {
|
||||
if (!add_package_dependency(packages, *dep, where, !(action & PACKAGE_ACT_NOTHING))) return false;
|
||||
if (!add_package_dependency(packages, package, *dep, where, !(action & PACKAGE_ACT_NOTHING), already_checked)) return false;
|
||||
}
|
||||
return true;
|
||||
} else if ((action & PACKAGE_ACT_REMOVE) || ((action & PACKAGE_ACT_NOTHING) && !package->has(PACKAGE_INSTALLED))) {
|
||||
@@ -608,7 +621,7 @@ bool set_package_action(InstallablePackages& packages, const InstallablePackageP
|
||||
|
||||
// ----------------------------------------------------------------------------- : MSE package
|
||||
|
||||
String mse_package = _("magicseteditor.exe");
|
||||
String mse_package = _("Magic Set Editor");
|
||||
|
||||
InstallablePackageP mse_installable_package() {
|
||||
PackageVersionP mse_version(new PackageVersion(
|
||||
@@ -618,9 +631,8 @@ InstallablePackageP mse_installable_package() {
|
||||
mse_version->name = mse_package;
|
||||
mse_version->version = app_version;
|
||||
PackageDescriptionP mse_description(new PackageDescription);
|
||||
mse_description->name = mse_package;
|
||||
mse_description->short_name = mse_description->full_name = mse_description->installer_group
|
||||
= _TITLE_("magic set editor");
|
||||
mse_description->name = mse_description->installer_group = mse_package;
|
||||
mse_description->short_name = mse_description->full_name = _TITLE_("magic set editor");
|
||||
mse_description->position_hint = -100;
|
||||
mse_description->icon = load_resource_image(_("installer_program"));
|
||||
//mse_description->description = _LABEL_("magic set editor package");
|
||||
|
||||
@@ -191,7 +191,7 @@ bool set_package_action(InstallablePackages& packages, const InstallablePackageP
|
||||
|
||||
// ----------------------------------------------------------------------------- : Program package
|
||||
|
||||
/// The "magicseteditor.exe" package is special, it refers to the program
|
||||
/// The "Magic Set Editor" package is special, it refers to the program
|
||||
extern String mse_package;
|
||||
|
||||
InstallablePackageP mse_installable_package();
|
||||
|
||||
@@ -26,11 +26,18 @@
|
||||
// ----------------------------------------------------------------------------- : Extra types
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(CheckUpdates) {
|
||||
VALUE_N("if connected", CHECK_IF_CONNECTED); //default
|
||||
VALUE_N("always", CHECK_ALWAYS);
|
||||
VALUE_N("every 5", CHECK_5); //default
|
||||
VALUE_N("every 10", CHECK_10);
|
||||
VALUE_N("never", CHECK_NEVER);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(CheckUpdatesTargets) {
|
||||
VALUE_N("update app", CHECK_APP);
|
||||
VALUE_N("update games", CHECK_GAMES);
|
||||
VALUE_N("update everything", CHECK_EVERYTHING); //default
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(InstallType) {
|
||||
VALUE_N("default", INSTALL_DEFAULT); //default
|
||||
VALUE_N("local", INSTALL_LOCAL);
|
||||
@@ -203,13 +210,10 @@ Settings::Settings()
|
||||
, dark_mode_type (DARKMODE_SYSTEM)
|
||||
, import_scale_selection (0)
|
||||
, allow_image_download (true)
|
||||
#if USE_OLD_STYLE_UPDATE_CHECKER
|
||||
, updates_url (_("https://magicseteditor.boards.net/page/downloads"))
|
||||
#endif
|
||||
, package_versions_url (_("https://magicseteditor.boards.net/page/downloads"))
|
||||
, installer_list_url (_("https://magicseteditor.boards.net/page/downloads"))
|
||||
, check_updates (CHECK_IF_CONNECTED)
|
||||
, check_updates_all (true)
|
||||
, installer_list_url (_("https://raw.githubusercontent.com/MagicSetEditorPacks/Installer-Pack/refs/heads/main/packages.txt"))
|
||||
, check_updates_what (CHECK_EVERYTHING)
|
||||
, check_updates_when (CHECK_5)
|
||||
, check_updates_counter (0)
|
||||
, website_url (_("https://magicseteditor.boards.net/"))
|
||||
, documentation_url (_("https://mseverse.miraheze.org/wiki/Dev:Documentation#Topics"))
|
||||
, install_type (INSTALL_DEFAULT)
|
||||
@@ -344,15 +348,9 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(Settings) {
|
||||
REFLECT(apprentice_location);
|
||||
REFLECT(import_scale_selection);
|
||||
REFLECT(allow_image_download);
|
||||
#if USE_OLD_STYLE_UPDATE_CHECKER
|
||||
REFLECT(updates_url);
|
||||
#else
|
||||
REFLECT_COMPAT_IGNORE(<306,"updates_url",String);
|
||||
#endif
|
||||
REFLECT(package_versions_url);
|
||||
REFLECT(installer_list_url);
|
||||
REFLECT(check_updates);
|
||||
REFLECT(check_updates_all);
|
||||
REFLECT(check_updates_what);
|
||||
REFLECT(check_updates_when);
|
||||
REFLECT(check_updates_counter);
|
||||
REFLECT(install_type);
|
||||
REFLECT(website_url);
|
||||
REFLECT(documentation_url);
|
||||
|
||||
@@ -24,18 +24,23 @@ DECLARE_POINTER_TYPE(Field);
|
||||
DECLARE_POINTER_TYPE(Value);
|
||||
DECLARE_POINTER_TYPE(AutoReplace);
|
||||
|
||||
// For now, use the old style update checker
|
||||
#define USE_OLD_STYLE_UPDATE_CHECKER 1
|
||||
|
||||
// ----------------------------------------------------------------------------- : Extra data structures
|
||||
|
||||
/// When to check for updates?
|
||||
enum CheckUpdates
|
||||
{ CHECK_ALWAYS
|
||||
, CHECK_IF_CONNECTED
|
||||
, CHECK_5
|
||||
, CHECK_10
|
||||
, CHECK_NEVER
|
||||
};
|
||||
|
||||
/// What to check for updates?
|
||||
enum CheckUpdatesTargets
|
||||
{ CHECK_APP
|
||||
, CHECK_GAMES
|
||||
, CHECK_EVERYTHING
|
||||
};
|
||||
|
||||
/// Where to install to?
|
||||
enum InstallType
|
||||
{ INSTALL_DEFAULT // the platform default.
|
||||
@@ -236,13 +241,13 @@ public:
|
||||
|
||||
// --------------------------------------------------- : Update checking
|
||||
|
||||
#if USE_OLD_STYLE_UPDATE_CHECKER
|
||||
String updates_url;
|
||||
#endif
|
||||
String package_versions_url; ///< latest package versions
|
||||
String installer_list_url; ///< available installers
|
||||
CheckUpdates check_updates;
|
||||
bool check_updates_all; ///< Check updates of all packages, not just the program
|
||||
CheckUpdatesTargets check_updates_what;
|
||||
CheckUpdates check_updates_when;
|
||||
int check_updates_counter;
|
||||
|
||||
// --------------------------------------------------- : Help links
|
||||
|
||||
String website_url;
|
||||
String documentation_url;
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
//+----------------------------------------------------------------------------+
|
||||
//| Description: Magic Set Editor - Program to make card games |
|
||||
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
|
||||
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||
//+----------------------------------------------------------------------------+
|
||||
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <data/updater.hpp>
|
||||
#include <util/io/package_manager.hpp>
|
||||
|
||||
// ----------------------------------------------------------------------------- : Updater
|
||||
|
||||
void Updater::updateApplication(String argv) {
|
||||
String path = absoluteFilename() + wxFileName::GetPathSeparator() + updater_name;
|
||||
path = path + _(".exe");
|
||||
if (wxFileExists(path)) {
|
||||
wxExecute(path + _(" ") + argv, wxEXEC_ASYNC);
|
||||
}
|
||||
else {
|
||||
queue_message(MESSAGE_ERROR, _("Executable file '" + path + "' not found!"));
|
||||
}
|
||||
}
|
||||
|
||||
UpdaterP Updater::byName(const String& name) {
|
||||
return package_manager.open<Updater>(name + _(".mse-updater"));
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(Updater) {
|
||||
REFLECT_BASE(Packaged);
|
||||
REFLECT(updater_name);
|
||||
}
|
||||
|
||||
String Updater::typeNameStatic() { return _("updater"); }
|
||||
String Updater::typeName() const { return _("updater"); }
|
||||
Version Updater::fileVersion() const { return app_version; }
|
||||
|
||||
void Updater::validate(Version file_app_version) {
|
||||
Packaged::validate(file_app_version);
|
||||
}
|
||||
|
||||
// special behaviour of reading/writing UpdaterPs: only read/write the name
|
||||
|
||||
void Reader::handle(UpdaterP& updater) {
|
||||
updater = Updater::byName(getValue());
|
||||
}
|
||||
void Writer::handle(const UpdaterP& updater) {
|
||||
if (updater) handle(updater->name());
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//+----------------------------------------------------------------------------+
|
||||
//| Description: Magic Set Editor - Program to make card games |
|
||||
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
|
||||
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||
//+----------------------------------------------------------------------------+
|
||||
|
||||
#pragma once
|
||||
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <util/reflect.hpp>
|
||||
#include <util/io/package.hpp>
|
||||
|
||||
DECLARE_POINTER_TYPE(Updater);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Updater
|
||||
|
||||
/// A description of an updater for the app (There should really only ever be one updater)
|
||||
class Updater : public Packaged {
|
||||
public:
|
||||
Updater() {};
|
||||
|
||||
String updater_name;
|
||||
|
||||
// Close MSE and run the updater exe
|
||||
void updateApplication(String argv);
|
||||
|
||||
static UpdaterP byName(const String& name);
|
||||
|
||||
static String typeNameStatic();
|
||||
String typeName() const override;
|
||||
Version fileVersion() const override;
|
||||
|
||||
protected:
|
||||
void validate(Version) override;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
inline String type_name(const Updater&) {
|
||||
return _TYPE_("updater");
|
||||
}
|
||||
|
||||
@@ -510,25 +510,8 @@ bool InsertedImage::operator == (const GeneratedImage& that) const {
|
||||
Image CropImage::generate(const Options& opt) {
|
||||
if (width <= 0) throw ScriptError(_ERROR_1_("negative image width", "crop_image"));
|
||||
if (height <= 0) throw ScriptError(_ERROR_1_("negative image height", "crop_image"));
|
||||
UInt size = width * height;
|
||||
Image img = wxImage(width, height, false);
|
||||
img.InitAlpha();
|
||||
Byte* data = img.GetData();
|
||||
Byte* alpha = img.GetAlpha();
|
||||
Byte r = background_color.Red();
|
||||
Byte g = background_color.Green();
|
||||
Byte b = background_color.Blue();
|
||||
Byte a = background_color.Alpha();
|
||||
for (UInt i = 0; i < size; ++i) {
|
||||
data[0] = r;
|
||||
data[1] = g;
|
||||
data[2] = b;
|
||||
data += 3;
|
||||
alpha[0] = a;
|
||||
alpha += 1;
|
||||
}
|
||||
Image base_img = image->generate(opt);
|
||||
img.Paste(base_img, -(int)offset_x, -(int)offset_y, wxIMAGE_ALPHA_BLEND_COMPOSE);
|
||||
Image img = base_img.Size(wxSize((int)width, (int)height), wxPoint(-(int)offset_x, -(int)offset_y)); //Image img = base_img.Size(wxSize((int)width, (int)height), wxPoint(-(int)offset_x, -(int)offset_y), background_color.Red(), background_color.Green(), background_color.Blue());
|
||||
// transfer metadata
|
||||
if (base_img.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
|
||||
String metadata = transformAllEncodedRects(base_img.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), RealRect::translate, -offset_x, -offset_y);
|
||||
|
||||
@@ -138,7 +138,7 @@ TreeList::TreeList(Window* parent, int id, long style)
|
||||
void TreeList::onPaint(wxPaintEvent& ev) {
|
||||
wxBufferedPaintDC dc(this);
|
||||
size_t cols = columnCount();
|
||||
wxRendererNative& rn = wxRendererNative::GetDefault();
|
||||
//wxRendererNative& rn = wxRendererNative::GetDefault();
|
||||
// clear background
|
||||
wxSize cs = GetClientSize();
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
@@ -149,11 +149,13 @@ void TreeList::onPaint(wxPaintEvent& ev) {
|
||||
// draw header
|
||||
dc.SetFont(*wxNORMAL_FONT);
|
||||
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNTEXT));
|
||||
dc.SetPen(lerp(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW),
|
||||
wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),0.4));
|
||||
int x = 0, y = 0;
|
||||
for (size_t j = 0 ; j < cols ; ++j) {
|
||||
int w = columnWidth(j);
|
||||
wxRect rect(x,0,w-1,header_height-1);
|
||||
rn.DrawHeaderButton(this, dc, rect);
|
||||
if (j>0) dc.DrawLine(x-1,2,x-1,header_height-2); //rn.DrawHeaderButton(this, dc, rect);
|
||||
dc.DrawText(columnText(j),x+3,2);
|
||||
x += w;
|
||||
}
|
||||
@@ -184,8 +186,15 @@ void TreeList::onPaint(wxPaintEvent& ev) {
|
||||
}
|
||||
// draw expand button
|
||||
if (hasChildren(i)) {
|
||||
wxRect rect(x - 13, y + (item_height - 9)/2, 9, 9);
|
||||
rn.DrawTreeItemButton(this, dc, rect, item.expanded ? wxCONTROL_EXPANDED : 0);
|
||||
int left = x - 13, top = y + (item_height - 9)/2;
|
||||
wxRect rect(left, top, 9, 9);
|
||||
dc.SetPen(lerp(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW),
|
||||
wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),0.4));
|
||||
dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
|
||||
dc.DrawRectangle(rect);
|
||||
dc.DrawLine(left+2,top+4,left+7,top+4);
|
||||
if (!item.expanded) dc.DrawLine(left+4,top+2,left+4,top+7);
|
||||
//rn.DrawTreeItemButton(this, dc, rect, item.expanded ? wxCONTROL_EXPANDED : 0);
|
||||
}
|
||||
if (selection == i) {
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
|
||||
@@ -68,7 +68,7 @@ protected:
|
||||
virtual int columnWidth(size_t column) const = 0;
|
||||
|
||||
int item_height;
|
||||
static const int header_height = 17;
|
||||
static const int header_height = 21;
|
||||
static const int level_width = 17;
|
||||
|
||||
private:
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
//+----------------------------------------------------------------------------+
|
||||
//| Description: Magic Set Editor - Program to make card games |
|
||||
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
|
||||
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||
//+----------------------------------------------------------------------------+
|
||||
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <data/installer.hpp>
|
||||
#include <util/io/package_manager.hpp>
|
||||
#include <gui/packages_window.hpp>
|
||||
#include <wx/wfstream.h>
|
||||
#include <wx/webrequest.h>
|
||||
|
||||
class DownloadableInstallerList;
|
||||
|
||||
// ----------------------------------------------------------------------------- : DownloadableInstallers
|
||||
|
||||
/// The global installer downloader
|
||||
extern DownloadableInstallerList downloadable_installers;
|
||||
|
||||
/// Handle downloading of installers
|
||||
class DownloadableInstallerList {
|
||||
public:
|
||||
inline DownloadableInstallerList() : download_status(NOT_DOWNLOADED), check_status(NOT_CHECKED), shown_dialog(false) {}
|
||||
|
||||
/// Check for updates if the settings say so
|
||||
inline void check_updates() {
|
||||
settings.check_updates_counter++;
|
||||
if (
|
||||
(settings.check_updates_when == CHECK_ALWAYS)
|
||||
|| (settings.check_updates_when == CHECK_5 && settings.check_updates_counter > 4)
|
||||
|| (settings.check_updates_when == CHECK_10 && settings.check_updates_counter > 9)
|
||||
) {
|
||||
settings.check_updates_counter = 0;
|
||||
check_updates_now();
|
||||
}
|
||||
}
|
||||
/// Check for updates
|
||||
/// If async==true then checking is done in another thread
|
||||
inline void check_updates_now(bool async = true) {
|
||||
if (async) {
|
||||
CheckUpdateThread* thread = new CheckUpdateThread;
|
||||
thread->Create();
|
||||
thread->Run();
|
||||
} else {
|
||||
CheckUpdateThread::Work();
|
||||
}
|
||||
}
|
||||
|
||||
/// Start downloading the list of updates, return true if we are done
|
||||
inline bool download() {
|
||||
if (download_status == DONE) return true;
|
||||
if (download_status == NOT_DOWNLOADED) {
|
||||
download_status = DOWNLOADING;
|
||||
DownloadThread* thread = new DownloadThread();
|
||||
thread->Create();
|
||||
thread->Run();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Show a dialog to inform the user that updates are available (if there are any)
|
||||
/// Call check_updates first. Call this function from an onIdle loop
|
||||
inline void show_update_dialog(Window* parent) {
|
||||
if (shown_dialog || check_status != FOUND) return; // we already have the latest version, or this has already been displayed.
|
||||
shown_dialog = true;
|
||||
wxMessageDialog dial = wxMessageDialog(parent, _LABEL_("updates found"), _TITLE_("updates available"), wxYES_NO);
|
||||
if (dial.ShowModal() == wxID_YES) {
|
||||
(new PackagesWindow(parent))->Show();
|
||||
}
|
||||
}
|
||||
|
||||
// Have we shown the update dialog?
|
||||
bool shown_dialog;
|
||||
|
||||
vector<DownloadableInstallerP> installers;
|
||||
|
||||
enum DownloadStatus { NOT_DOWNLOADED, DOWNLOADING, DONE } download_status;
|
||||
enum CheckStatus { NOT_CHECKED, CHECKING, FAILED, FOUND, NOT_FOUND } check_status;
|
||||
private:
|
||||
wxMutex lock;
|
||||
|
||||
struct DownloadThread : public wxThread {
|
||||
inline ExitCode Entry() override {
|
||||
// fetch list
|
||||
wxWebRequestSync request = wxWebSessionSync::GetDefault().CreateRequest(settings.installer_list_url);
|
||||
auto const result = request.Execute();
|
||||
if (!result) {
|
||||
wxMutexLocker l(downloadable_installers.lock);
|
||||
downloadable_installers.download_status = DONE;
|
||||
downloadable_installers.check_status = FAILED;
|
||||
return 0;
|
||||
}
|
||||
wxInputStream* is = request.GetResponse().GetStream();
|
||||
// Read installer list
|
||||
Reader reader(*is, nullptr, _("installers"), true);
|
||||
vector<DownloadableInstallerP> installers;
|
||||
reader.handle(_("installers"),installers);
|
||||
// done
|
||||
wxMutexLocker l(downloadable_installers.lock);
|
||||
swap(installers, downloadable_installers.installers);
|
||||
downloadable_installers.download_status = DONE;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckUpdateThread : public wxThread {
|
||||
public:
|
||||
inline void* Entry() override {
|
||||
#ifndef __APPLE__
|
||||
Work();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline static void Work() {
|
||||
if (downloadable_installers.check_status > NOT_CHECKED) return; // don't check multiple times simultaneously
|
||||
downloadable_installers.check_status = CHECKING;
|
||||
try {
|
||||
while (!downloadable_installers.download()) {
|
||||
wxMilliSleep(30);
|
||||
}
|
||||
if (downloadable_installers.check_status == FAILED) return;
|
||||
InstallablePackages installable_packages;
|
||||
FOR_EACH(inst, downloadable_installers.installers) {
|
||||
merge(installable_packages, inst);
|
||||
}
|
||||
FOR_EACH(p, installable_packages) {
|
||||
if (p->description->name == mse_package && app_version < p->description->version) {
|
||||
downloadable_installers.check_status = FOUND;
|
||||
return;
|
||||
}
|
||||
Version v;
|
||||
if (package_manager.installedVersion(p->description->name, v) && v < p->description->version) {
|
||||
if (
|
||||
(settings.check_updates_what == CHECK_EVERYTHING)
|
||||
|| (settings.check_updates_what == CHECK_GAMES && ( p->description->name.EndsWith("mse-updater")
|
||||
|| p->description->name.EndsWith("mse-locale")
|
||||
|| p->description->name.EndsWith("mse-game")
|
||||
|| p->description->name.EndsWith("mse-include")
|
||||
|| p->description->name.EndsWith("mse-style")
|
||||
|| p->description->name.EndsWith("mse-symbol-font")))
|
||||
|| (settings.check_updates_what == CHECK_APP && ( p->description->name.EndsWith("mse-updater")
|
||||
|| p->description->name.EndsWith("mse-locale")))
|
||||
)
|
||||
downloadable_installers.check_status = FOUND;
|
||||
return;
|
||||
}
|
||||
}
|
||||
downloadable_installers.check_status = NOT_FOUND;
|
||||
} catch (...) {
|
||||
// ignore all errors, we don't want problems if update checking fails
|
||||
downloadable_installers.check_status = FAILED;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <gui/thumbnail_thread.hpp>
|
||||
#include <gui/util.hpp>
|
||||
#include <gfx/gfx.hpp>
|
||||
#include <wx/url.h>
|
||||
#include <wx/webrequest.h>
|
||||
|
||||
// ----------------------------------------------------------------------------- : PackageUpdateList::TreeItem
|
||||
|
||||
@@ -85,12 +85,14 @@ PackageUpdateList::TreeItem::PackageType PackageUpdateList::TreeItem::package_ty
|
||||
if (desc.name == mse_package) return TYPE_PROG;
|
||||
size_t pos = desc.name.find_last_of(_('.'));
|
||||
if (pos == String::npos) return TYPE_OTHER;
|
||||
if (is_substr(desc.name,pos,_(".mse-locale"))) return TYPE_LOCALE;
|
||||
if (is_substr(desc.name,pos,_(".mse-game"))) return TYPE_GAME;
|
||||
if (is_substr(desc.name,pos,_(".mse-style"))) return TYPE_STYLESHEET;
|
||||
if (is_substr(desc.name,pos,_(".mse-export-template"))) return TYPE_EXPORT_TEMPLATE;
|
||||
if (is_substr(desc.name,pos,_(".mse-symbol-font"))) return TYPE_SYMBOL_FONT;
|
||||
if (is_substr(desc.name,pos,_(".mse-game"))) return TYPE_GAME;
|
||||
if (is_substr(desc.name,pos,_(".mse-locale"))) return TYPE_LOCALE;
|
||||
if (is_substr(desc.name,pos,_(".mse-export-template"))) return TYPE_EXPORT_TEMPLATE;
|
||||
if (is_substr(desc.name,pos,_(".mse-import-template"))) return TYPE_IMPORT_TEMPLATE;
|
||||
if (is_substr(desc.name,pos,_(".mse-include"))) return TYPE_INCLUDE;
|
||||
if (is_substr(desc.name,pos,_(".mse-updater"))) return TYPE_PROG;
|
||||
if (is_substr(desc.name,pos,_(".ttf"))) return TYPE_FONT;
|
||||
return TYPE_OTHER;
|
||||
}
|
||||
@@ -165,12 +167,16 @@ public:
|
||||
{}
|
||||
|
||||
Image generate() override {
|
||||
wxURL url(settings.darkMode() && !ti->package->description->dark_icon_url.empty() ? ti->package->description->dark_icon_url : ti->package->description->icon_url);
|
||||
unique_ptr<wxInputStream> isP(url.GetInputStream());
|
||||
if (!isP) return wxImage();
|
||||
SeekAtStartInputStream is2(*isP);
|
||||
Image result(is2);
|
||||
return result;
|
||||
String url(settings.darkMode() && !ti->package->description->dark_icon_url.empty() ? ti->package->description->dark_icon_url : ti->package->description->icon_url);
|
||||
wxWebRequestSync request = wxWebSessionSync::GetDefault().CreateRequest(url);
|
||||
auto const result = request.Execute();
|
||||
if (!result) return Image();
|
||||
wxInputStream* is(request.GetResponse().GetStream());
|
||||
if (!is) return Image();
|
||||
Image img(*is);
|
||||
if (!img.IsOk()) return Image();
|
||||
ti->setIcon(img);
|
||||
return img;
|
||||
}
|
||||
void store(const Image& image) override {
|
||||
if (!image.Ok()) return;
|
||||
@@ -255,7 +261,7 @@ void PackageUpdateList::drawItem(DC& dc, size_t index, size_t column, int x, int
|
||||
dc.SetTextForeground(lerp(color,Color(255,255,0),0.5));
|
||||
dc.DrawText(_LABEL_("package modified"), x,y);
|
||||
} else if (package.has(PACKAGE_UPDATES)) {
|
||||
dc.SetTextForeground(lerp(color,Color(0,0,255),0.5));
|
||||
dc.SetTextForeground(lerp(color,settings.darkMode() ? Color(80,80,255) : Color(0,0,255),0.5));
|
||||
dc.DrawText(_LABEL_("package updates"), x,y);
|
||||
} else if (package.has(PACKAGE_INSTALLED)) {
|
||||
dc.SetTextForeground(color);
|
||||
@@ -265,15 +271,20 @@ void PackageUpdateList::drawItem(DC& dc, size_t index, size_t column, int x, int
|
||||
dc.SetTextForeground(color);
|
||||
dc.DrawText(_LABEL_("package installable"), x,y);
|
||||
}
|
||||
} else if (column == 1 && !ti.expanded) {
|
||||
if (CheckChildrenForUpdates(ti)) {
|
||||
dc.SetTextForeground(lerp(color, settings.darkMode() ? Color(80, 80, 255) : Color(0, 0, 255), 0.5));
|
||||
dc.DrawText(_LABEL_("package updates"), x, y);
|
||||
}
|
||||
} else if (column == 2 && ti.package) {
|
||||
// Action
|
||||
InstallablePackage& package = *ti.package;
|
||||
if (package.has(PACKAGE_ACT_INSTALL)) {
|
||||
if (package.has(PACKAGE_UPDATES)) {
|
||||
dc.SetTextForeground(lerp(color,Color(0,0,255),0.6));
|
||||
dc.SetTextForeground(lerp(color,settings.darkMode() ? Color(80,80,255) : Color(0,0,255),0.6));
|
||||
dc.DrawText(_LABEL_("upgrade package"), x,y);
|
||||
} else if (package.has(PACKAGE_INSTALLED)) {
|
||||
dc.SetTextForeground(lerp(color,Color(0,0,255),0.2));
|
||||
dc.SetTextForeground(lerp(color,settings.darkMode() ? Color(80,80,255) : Color(0,0,255),0.2));
|
||||
dc.DrawText(_LABEL_("reinstall package"), x,y);
|
||||
} else {
|
||||
dc.SetTextForeground(lerp(color,Color(0,255,0),0.6));
|
||||
@@ -286,6 +297,14 @@ void PackageUpdateList::drawItem(DC& dc, size_t index, size_t column, int x, int
|
||||
}
|
||||
}
|
||||
|
||||
bool PackageUpdateList::CheckChildrenForUpdates(const TreeItem& ti) const {
|
||||
for (auto& c : ti.children) {
|
||||
if (c->package && c->package->has(PACKAGE_UPDATES)) return true;
|
||||
if (CheckChildrenForUpdates(*c)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String PackageUpdateList::columnText(size_t column) const {
|
||||
if (column == 0) return _LABEL_("package name");
|
||||
else if (column == 1) return _LABEL_("package status");
|
||||
@@ -296,9 +315,9 @@ String PackageUpdateList::columnText(size_t column) const {
|
||||
int PackageUpdateList::columnWidth(size_t column) const {
|
||||
if (column == 0) {
|
||||
wxSize cs = GetClientSize();
|
||||
return cs.x - 240;
|
||||
return cs.x - 280;
|
||||
} else {
|
||||
return 120;
|
||||
return 140;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ private:
|
||||
TYPE_GAME,
|
||||
TYPE_STYLESHEET,
|
||||
TYPE_EXPORT_TEMPLATE,
|
||||
TYPE_IMPORT_TEMPLATE,
|
||||
TYPE_SYMBOL_FONT,
|
||||
TYPE_INCLUDE,
|
||||
TYPE_FONT,
|
||||
@@ -74,7 +75,11 @@ private:
|
||||
bool highlight() const;
|
||||
|
||||
static PackageType package_type(const PackageDescription& desc);
|
||||
|
||||
};
|
||||
|
||||
bool CheckChildrenForUpdates(const TreeItem& ti) const;
|
||||
|
||||
friend class PackageIconRequest;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,92 +9,25 @@
|
||||
#include <util/prec.hpp>
|
||||
#include <gui/packages_window.hpp>
|
||||
#include <gui/package_update_list.hpp>
|
||||
#include <gui/downloadable_installers.hpp>
|
||||
#include <gui/util.hpp>
|
||||
#include <util/io/package_manager.hpp>
|
||||
#include <util/window_id.hpp>
|
||||
#include <data/installer.hpp>
|
||||
#include <data/updater.hpp>
|
||||
#include <data/settings.hpp>
|
||||
#include <gfx/gfx.hpp>
|
||||
#include <wx/wfstream.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
#include <wx/dialup.h>
|
||||
#include <wx/url.h>
|
||||
#include <wx/webrequest.h>
|
||||
#include <wx/dcbuffer.h>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/tglbtn.h>
|
||||
#include <wx/stdpaths.h>
|
||||
|
||||
DECLARE_POINTER_TYPE(Installer);
|
||||
|
||||
// ----------------------------------------------------------------------------- : TODO: MOVE
|
||||
|
||||
/*
|
||||
// A HTML control that opens all pages in an actual browser
|
||||
struct HtmlWindowToBrowser : public wxHtmlWindow {
|
||||
HtmlWindowToBrowser(Window* parent, int id, const wxPoint& pos, const wxSize& size, long flags)
|
||||
: wxHtmlWindow(parent, id, pos, size, flags)
|
||||
{}
|
||||
|
||||
virtual void OnLinkClicked(const wxHtmlLinkInfo& info) {
|
||||
wxLaunchDefaultBrowser( info.GetHref() );
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
// ----------------------------------------------------------------------------- : DownloadableInstallers
|
||||
|
||||
/// Handle downloading of installers
|
||||
class DownloadableInstallerList {
|
||||
public:
|
||||
DownloadableInstallerList() : status(NONE) {}
|
||||
|
||||
/// start downloading, return true if we are done
|
||||
bool download();
|
||||
|
||||
vector<DownloadableInstallerP> installers;
|
||||
|
||||
private:
|
||||
enum Status { NONE, DOWNLOADING, DONE } status;
|
||||
wxMutex lock;
|
||||
|
||||
struct Thread : public wxThread {
|
||||
ExitCode Entry() override;
|
||||
};
|
||||
};
|
||||
|
||||
/// The global installer downloader
|
||||
DownloadableInstallerList downloadable_installers;
|
||||
|
||||
bool DownloadableInstallerList::download() {
|
||||
if (status == DONE) return true;
|
||||
if (status == NONE) {
|
||||
status = DOWNLOADING;
|
||||
Thread* thread = new Thread();
|
||||
thread->Create();
|
||||
thread->Run();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
wxThread::ExitCode DownloadableInstallerList::Thread::Entry() {
|
||||
// open url
|
||||
wxURL url(settings.installer_list_url);
|
||||
unique_ptr<wxInputStream> stream(url.GetInputStream());
|
||||
if (!stream) {
|
||||
wxMutexLocker l(downloadable_installers.lock);
|
||||
downloadable_installers.status = DONE;
|
||||
return 0;
|
||||
}
|
||||
// Read installer list
|
||||
Reader reader(*stream, nullptr, _("installers"), true);
|
||||
vector<DownloadableInstallerP> installers;
|
||||
reader.handle(_("installers"),installers);
|
||||
// done
|
||||
wxMutexLocker l(downloadable_installers.lock);
|
||||
swap(installers, downloadable_installers.installers);
|
||||
downloadable_installers.status = DONE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : PackageInfoPanel
|
||||
|
||||
/// Information on a package
|
||||
@@ -238,6 +171,8 @@ void PackagesWindow::init(Window* parent, bool show_only_installable) {
|
||||
package_list = new PackageUpdateList(this, installable_packages, show_only_installable, ID_PACKAGE_LIST);
|
||||
package_info = new PackageInfoPanel(this);
|
||||
|
||||
waiting_info = new wxStaticText(this, wxID_ANY, waiting_for_list ? _LABEL_("awaiting package list") : _(""));
|
||||
|
||||
wxToggleButton* keep_button = new wxToggleButton(this, ID_KEEP, _BUTTON_("keep package"));
|
||||
wxToggleButton* install_button = new wxToggleButton(this, ID_INSTALL, _BUTTON_("install package"));
|
||||
wxToggleButton* remove_button = new wxToggleButton(this, ID_REMOVE, _BUTTON_("remove package"));
|
||||
@@ -262,8 +197,13 @@ void PackagesWindow::init(Window* parent, bool show_only_installable) {
|
||||
v2->Add(remove_button, 0, wxEXPAND | wxBOTTOM, 0);
|
||||
h->Add(v2);
|
||||
v->Add(h, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
|
||||
v->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | (wxALL & ~wxTOP), 8);
|
||||
SetSizer(v);
|
||||
wxBoxSizer* h2 = new wxBoxSizer(wxHORIZONTAL);
|
||||
h2->Add(waiting_info, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
|
||||
h2->AddStretchSpacer();
|
||||
h2->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | (wxALL & ~wxTOP), 8);
|
||||
v->Add(h2, 0, wxEXPAND);
|
||||
v->SetMinSize(800,600);
|
||||
SetSizerAndFit(v);
|
||||
|
||||
wxUpdateUIEvent::SetMode(wxUPDATE_UI_PROCESS_SPECIFIED);
|
||||
UpdateWindowUI(wxUPDATE_UI_RECURSE);
|
||||
@@ -293,13 +233,17 @@ void PackagesWindow::onActionChange(wxCommandEvent& ev) {
|
||||
}
|
||||
|
||||
void PackagesWindow::onOk(wxCommandEvent& ev) {
|
||||
// Do we need a new version of MSE first?
|
||||
// count number of packages to change
|
||||
bool app_change = false;
|
||||
int to_change = 0;
|
||||
int to_download = 0;
|
||||
int to_remove = 0;
|
||||
int with_modifications = 0;
|
||||
FOR_EACH(ip, installable_packages) {
|
||||
if (ip->description->name == mse_package) {
|
||||
if (ip->has(PACKAGE_ACT_INSTALL)) app_change = true;
|
||||
continue;
|
||||
}
|
||||
if (!ip->has(PACKAGE_ACT_NOTHING)) ++to_change;
|
||||
if (ip->has(PACKAGE_ACT_INSTALL) && ip->installer && !ip->installer->installer) ++to_download;
|
||||
if (ip->has(PACKAGE_ACT_REMOVE)) {
|
||||
@@ -326,23 +270,25 @@ void PackagesWindow::onOk(wxCommandEvent& ev) {
|
||||
String::Format(_ERROR_("downloading updates"), 0, to_download),
|
||||
to_change + to_download,
|
||||
this,
|
||||
wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT | wxPD_SMOOTH
|
||||
wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT | wxPD_SMOOTH | wxSTAY_ON_TOP
|
||||
);
|
||||
// Clear package list
|
||||
package_manager.reset();
|
||||
// Download installers
|
||||
int package_pos = 0, step = 0;
|
||||
FOR_EACH(ip, installable_packages) {
|
||||
if (ip->description->name == mse_package) continue;
|
||||
if (ip->has(PACKAGE_ACT_INSTALL) && ip->installer && !ip->installer->installer) {
|
||||
if (!progress.Update(step++, String::Format(_ERROR_("downloading updates"), ++package_pos, to_download))) {
|
||||
return; // aborted
|
||||
}
|
||||
// download installer
|
||||
wxURL url(ip->installer->installer_url);
|
||||
unique_ptr<wxInputStream> is(url.GetInputStream());
|
||||
if (!is) {
|
||||
wxWebRequestSync request = wxWebSessionSync::GetDefault().CreateRequest(ip->installer->installer_url);
|
||||
auto const result = request.Execute();
|
||||
if (!result) {
|
||||
throw Error(_ERROR_2_("can't download installer", ip->description->name, ip->installer->installer_url));
|
||||
}
|
||||
wxInputStream* is(request.GetResponse().GetStream());
|
||||
ip->installer->installer_file = wxFileName::CreateTempFileName(_("mse-installer"));
|
||||
wxFileOutputStream os(ip->installer->installer_file);
|
||||
os.Write(*is);
|
||||
@@ -356,6 +302,7 @@ void PackagesWindow::onOk(wxCommandEvent& ev) {
|
||||
package_pos = 0;
|
||||
int success = 0, install = 0, remove = 0;
|
||||
FOR_EACH(ip, installable_packages) {
|
||||
if (ip->description->name == mse_package) continue; // app, we'll do that last
|
||||
if (ip->has(PACKAGE_ACT_NOTHING)) continue; // package unchanged
|
||||
if (!progress.Update(step++, String::Format(_ERROR_("installing updates"), ++package_pos, to_change))) {
|
||||
// don't allow abort.
|
||||
@@ -367,13 +314,52 @@ void PackagesWindow::onOk(wxCommandEvent& ev) {
|
||||
success += 1;
|
||||
}
|
||||
}
|
||||
// Done
|
||||
// Report on package status
|
||||
progress.Update(step++);
|
||||
wxMessageBox(
|
||||
install == success ? _ERROR_1_("install packages successful",String()<<success):
|
||||
String report_message = install == success ? _ERROR_1_("install packages successful",String()<<success):
|
||||
remove == success ? _ERROR_1_("remove packages successful", String()<<success):
|
||||
_ERROR_1_("change packages successful", String()<<success),
|
||||
_TITLE_("packages window"), wxICON_INFORMATION | wxOK);
|
||||
_ERROR_1_("change packages successful", String()<<success);
|
||||
wxMessageDialog report = wxMessageDialog(this, report_message, _TITLE_("packages window"), wxICON_INFORMATION | wxOK | wxSTAY_ON_TOP);
|
||||
report.ShowModal();
|
||||
// Launch exe updater if necessary
|
||||
if (app_change) {
|
||||
// Hard code the only updater, for now
|
||||
PackagedP updater_package = package_manager.openAny(_("github-zip-extractor.mse-updater"));
|
||||
if (updater_package) {
|
||||
Updater* updater = dynamic_cast<Updater*>(updater_package.get());
|
||||
if (updater) {
|
||||
String darkmode = String("darkmode") << settings.darkMode();
|
||||
String locale = String("locale") << settings.locale;
|
||||
locale.Replace("-", "");
|
||||
updater->updateApplication(darkmode + _(" ") + locale);
|
||||
}
|
||||
else {
|
||||
queue_message(MESSAGE_ERROR, _("Failed to load updater package 'github-zip-extractor.mse-updater'."));
|
||||
}
|
||||
}
|
||||
else {
|
||||
queue_message(MESSAGE_ERROR, _("Failed to load updater package 'github-zip-extractor.mse-updater'."));
|
||||
}
|
||||
|
||||
// Check for any updater, for later (very slow)
|
||||
//vector<InstallablePackageP> installed_packages;
|
||||
//package_manager.findAllInstalledPackages(installed_packages);
|
||||
//FOR_EACH(p, installed_packages) {
|
||||
// if (!p->description->name.EndsWith(".mse-updater")) continue;
|
||||
// PackagedP updater_package = package_manager.openAny(p->description->name);
|
||||
// Updater* updater = dynamic_cast<Updater*>(updater_package.get());
|
||||
// if (updater) {
|
||||
// String darkmode = String("darkmode") << settings.darkMode();
|
||||
// String locale = String("locale") << settings.locale;
|
||||
// locale.Replace("-", "");
|
||||
// updater->updateApplication(darkmode + _(" ") + locale);
|
||||
// break;
|
||||
// }
|
||||
// else {
|
||||
// queue_message(MESSAGE_ERROR, _("Failed to load updater package '" + p->description->name + "'."));
|
||||
// }
|
||||
//}
|
||||
}
|
||||
// Continue event propagation into the dialog window so that it closes.
|
||||
ev.Skip();
|
||||
//%% TODO: will we delete packages?
|
||||
@@ -393,8 +379,8 @@ void PackagesWindow::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
case ID_INSTALL:
|
||||
w->SetValue(package && package->has(PACKAGE_ACT_INSTALL | where));
|
||||
w->Enable (package && package->can(PACKAGE_ACT_INSTALL | where));
|
||||
w->SetLabel( !package || !package->installed ? _BUTTON_("install package")
|
||||
: package->has(PACKAGE_UPDATES) ? _BUTTON_("upgrade package")
|
||||
w->SetLabel(!(package && package->installed) ? _BUTTON_("install package")
|
||||
: (package && package->has(PACKAGE_UPDATES)) ? _BUTTON_("upgrade package")
|
||||
: _BUTTON_("reinstall package"));
|
||||
break;
|
||||
case ID_REMOVE:
|
||||
@@ -408,6 +394,7 @@ void PackagesWindow::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
|
||||
void PackagesWindow::onIdle(wxIdleEvent& ev) {
|
||||
ev.RequestMore(!checkInstallerList());
|
||||
if (waiting_info && !waiting_for_list) waiting_info->SetLabel(_(""));
|
||||
}
|
||||
|
||||
bool PackagesWindow::checkInstallerList(bool refresh) {
|
||||
@@ -429,12 +416,12 @@ bool PackagesWindow::checkInstallerList(bool refresh) {
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(PackagesWindow, wxDialog)
|
||||
EVT_LISTBOX(ID_PACKAGE_LIST, PackagesWindow::onPackageSelect)
|
||||
EVT_LISTBOX (ID_PACKAGE_LIST, PackagesWindow::onPackageSelect)
|
||||
EVT_TOGGLEBUTTON(ID_KEEP, PackagesWindow::onActionChange)
|
||||
EVT_TOGGLEBUTTON(ID_INSTALL, PackagesWindow::onActionChange)
|
||||
EVT_TOGGLEBUTTON(ID_REMOVE, PackagesWindow::onActionChange)
|
||||
EVT_TOGGLEBUTTON(ID_UPGRADE, PackagesWindow::onActionChange)
|
||||
EVT_BUTTON(wxID_OK, PackagesWindow::onOk)
|
||||
EVT_UPDATE_UI(wxID_ANY, PackagesWindow::onUpdateUI)
|
||||
EVT_BUTTON (wxID_OK, PackagesWindow::onOk)
|
||||
EVT_UPDATE_UI (wxID_ANY, PackagesWindow::onUpdateUI)
|
||||
EVT_IDLE ( PackagesWindow::onIdle)
|
||||
END_EVENT_TABLE()
|
||||
|
||||
@@ -28,6 +28,7 @@ public:
|
||||
private:
|
||||
PackageUpdateList* package_list; ///< List of available packages
|
||||
PackageInfoPanel* package_info; ///< Description of the selected package
|
||||
wxStaticText* waiting_info; ///< Did we get the list of installers?
|
||||
|
||||
/// List of the packages shown in this window
|
||||
InstallablePackages installable_packages;
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <gui/preferences_window.hpp>
|
||||
#include <gui/update_checker.hpp>
|
||||
#include <data/settings.hpp>
|
||||
#include <util/window_id.hpp>
|
||||
#include <util/io/package_manager.hpp>
|
||||
#include <gui/packages_window.hpp>
|
||||
#include <gui/downloadable_installers.hpp>
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/notebook.h>
|
||||
@@ -98,7 +99,8 @@ public:
|
||||
private:
|
||||
DECLARE_EVENT_TABLE();
|
||||
|
||||
wxChoice* check_at_startup;
|
||||
wxChoice* check_what;
|
||||
wxChoice* check_when;
|
||||
|
||||
// check for updates
|
||||
void onCheckUpdatesNow(wxCommandEvent&);
|
||||
@@ -401,37 +403,51 @@ UpdatePreferencesPage::UpdatePreferencesPage(Window* parent)
|
||||
: PreferencesPage(parent)
|
||||
{
|
||||
// init controls
|
||||
check_at_startup = new wxChoice(this, wxID_ANY);
|
||||
check_what = new wxChoice(this, wxID_ANY);
|
||||
check_when = new wxChoice(this, wxID_ANY);
|
||||
wxButton* check_now = new wxButton(this, ID_CHECK_UPDATES_NOW, _BUTTON_("check now"));
|
||||
// set values
|
||||
check_at_startup->Append(_BUTTON_("always")); // 0
|
||||
check_at_startup->Append(_BUTTON_("if internet connection exists")); // 1
|
||||
check_at_startup->Append(_BUTTON_("never")); // 2
|
||||
check_at_startup->SetSelection(settings.check_updates);
|
||||
check_when->Append(_BUTTON_("always")); // 0
|
||||
check_when->Append(_BUTTON_("every 5 startups")); // 1
|
||||
check_when->Append(_BUTTON_("every 10 startups")); // 2
|
||||
check_when->Append(_BUTTON_("never")); // 3
|
||||
check_when->SetSelection(settings.check_updates_when);
|
||||
check_what->Append(_BUTTON_("check app")); // 0
|
||||
check_what->Append(_BUTTON_("check games")); // 1
|
||||
check_what->Append(_BUTTON_("check everything")); // 2
|
||||
check_what->SetSelection(settings.check_updates_what);
|
||||
// init sizer
|
||||
wxSizer* s = new wxBoxSizer(wxVERTICAL);
|
||||
s->Add(new wxStaticText(this, wxID_ANY, _LABEL_("check at startup")), 0, wxALL, 8);
|
||||
s->Add(check_at_startup, 0, wxALL & ~wxTOP, 8);
|
||||
s->Add(check_when, 0, wxALL & ~wxTOP, 8);
|
||||
s->Add(check_now, 0, wxALL & ~wxTOP, 8);
|
||||
s->Add(new wxStaticText(this, wxID_ANY, _LABEL_("checking requires internet")), 0, wxALL & ~wxTOP, 8);
|
||||
s->Add(new wxStaticText(this, wxID_ANY, _LABEL_("check what targets")), 0, wxALL, 8);
|
||||
s->Add(check_what, 0, wxALL & ~wxTOP, 8);
|
||||
s->Add(new wxStaticText(this, wxID_ANY, _LABEL_("checking requires internet")), 0, wxALL, 8);
|
||||
SetSizer(s);
|
||||
}
|
||||
|
||||
void UpdatePreferencesPage::store() {
|
||||
int sel = check_at_startup->GetSelection();
|
||||
if (sel == 0) settings.check_updates = CHECK_ALWAYS;
|
||||
else if (sel == 1) settings.check_updates = CHECK_IF_CONNECTED;
|
||||
else settings.check_updates = CHECK_NEVER;
|
||||
int sel1 = check_when->GetSelection();
|
||||
if (sel1 == 0) settings.check_updates_when = CHECK_ALWAYS;
|
||||
else if (sel1 == 1) settings.check_updates_when = CHECK_5;
|
||||
else if (sel1 == 2) settings.check_updates_when = CHECK_10;
|
||||
else settings.check_updates_when = CHECK_NEVER;
|
||||
|
||||
int sel2 = check_what->GetSelection();
|
||||
if (sel2 == 0) settings.check_updates_what = CHECK_APP;
|
||||
else if (sel2 == 1) settings.check_updates_what = CHECK_GAMES;
|
||||
else settings.check_updates_what = CHECK_EVERYTHING;
|
||||
}
|
||||
|
||||
void UpdatePreferencesPage::onCheckUpdatesNow(wxCommandEvent&) {
|
||||
check_updates_now(false);
|
||||
if (!update_data_found()) {
|
||||
downloadable_installers.check_updates_now(false);
|
||||
if (downloadable_installers.check_status == DownloadableInstallerList::CheckStatus::FAILED) {
|
||||
wxMessageBox(_ERROR_("checking updates failed"), _TITLE_("update check"), wxICON_ERROR | wxOK);
|
||||
} else if (!update_available()) {
|
||||
} else if (downloadable_installers.check_status == DownloadableInstallerList::CheckStatus::NOT_FOUND) {
|
||||
wxMessageBox(_ERROR_("no updates"), _TITLE_("update check"), wxICON_INFORMATION | wxOK);
|
||||
} else {
|
||||
show_update_dialog(GetParent());
|
||||
(new PackagesWindow(GetParent()))->Show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <gui/control/text_ctrl.hpp>
|
||||
#include <gui/control/filter_ctrl.hpp>
|
||||
#include <gui/about_window.hpp> // for HoverButton
|
||||
#include <gui/update_checker.hpp>
|
||||
#include <gui/util.hpp>
|
||||
#include <data/set.hpp>
|
||||
#include <data/game.hpp>
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
#include <gui/control/card_viewer.hpp>
|
||||
#include <gui/control/gallery_list.hpp>
|
||||
#include <gui/about_window.hpp>
|
||||
#include <gui/update_checker.hpp>
|
||||
#include <gui/packages_window.hpp>
|
||||
#include <gui/downloadable_installers.hpp>
|
||||
#include <gui/new_window.hpp>
|
||||
#include <gui/preferences_window.hpp>
|
||||
#include <gui/print_window.hpp>
|
||||
@@ -62,13 +62,13 @@ SetWindow::SetWindow(Window* parent, const SetP& set)
|
||||
add_menu_item_tr(menuFile, ID_FILE_SAVE_AS_DIRECTORY, "save", "save_set_as_directory");
|
||||
add_menu_item_tr(menuFile, wxID_ANY, "export", "export", wxITEM_NORMAL, makeExportMenu());
|
||||
menuFile->AppendSeparator();
|
||||
add_menu_item_tr(menuFile, ID_FILE_CHECK_UPDATES, settings.darkModePrefix() + "check_updates", "check_updates");
|
||||
add_menu_item_tr(menuFile, ID_FILE_CHECK_UPDATES, "check_updates", "check_updates");
|
||||
#if USE_SCRIPT_PROFILING
|
||||
add_menu_item_tr(menuFile, ID_FILE_PROFILER, nullptr, "show_profiler");
|
||||
#endif
|
||||
// menuFile->Append(ID_FILE_INSPECT, _("Inspect Internal Data..."), _("Shows a the data in the set using a tree structure"));
|
||||
// menuFile->AppendSeparator();
|
||||
add_menu_item_tr(menuFile, ID_FILE_RELOAD, settings.darkModePrefix() + "reload_data", "reload_data");
|
||||
add_menu_item_tr(menuFile, ID_FILE_RELOAD, "reload_data", "reload_data");
|
||||
menuFile->AppendSeparator();
|
||||
add_menu_item_tr(menuFile, ID_FILE_PRINT_PREVIEW, "print_preview", "print_preview");
|
||||
add_menu_item_tr(menuFile, ID_FILE_PRINT, "print", "print");
|
||||
@@ -854,7 +854,7 @@ void SetWindow::onMenuOpen(wxMenuEvent& ev) {
|
||||
|
||||
void SetWindow::onIdle(wxIdleEvent& ev) {
|
||||
// Stuff that must be done in the main thread
|
||||
show_update_dialog(this);
|
||||
downloadable_installers.show_update_dialog(this);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Event table
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
//+----------------------------------------------------------------------------+
|
||||
//| Description: Magic Set Editor - Program to make card games |
|
||||
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
|
||||
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||
//+----------------------------------------------------------------------------+
|
||||
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <gui/update_checker.hpp>
|
||||
#include <gui/packages_window.hpp>
|
||||
#include <data/settings.hpp>
|
||||
#include <data/installer.hpp>
|
||||
#include <util/io/package_manager.hpp>
|
||||
#include <util/version.hpp>
|
||||
#include <util/window_id.hpp>
|
||||
//#include <script/value.hpp> // for some strange reason the profile build needs this :(
|
||||
//#include <script/to_value.hpp>
|
||||
#include <wx/dialup.h>
|
||||
#include <wx/url.h>
|
||||
|
||||
DECLARE_POINTER_TYPE(VersionData);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Update data
|
||||
|
||||
#if !USE_OLD_STYLE_UPDATE_CHECKER
|
||||
|
||||
/// Information on the latest available versions
|
||||
class VersionData : public IntrusivePtrBase<VersionData> {
|
||||
public:
|
||||
vector<PackageDependencyP> packages; ///< Available packages + versions
|
||||
String new_updates_url; ///< updates url has moved?
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(VersionData) {
|
||||
REFLECT_NO_SCRIPT(packages);
|
||||
REFLECT_NO_SCRIPT(new_updates_url);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/// Information on the latest available version
|
||||
class VersionData : public IntrusivePtrBase<VersionData> {
|
||||
public:
|
||||
Version version; ///< Latest version number of MSE
|
||||
String description; ///< html description of the latest MSE release
|
||||
String new_updates_url; ///< updates url has moved?
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
IMPLEMENT_REFLECTION(VersionData) {
|
||||
REFLECT(version);
|
||||
REFLECT(description);
|
||||
REFLECT(new_updates_url);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// The information for the latest version
|
||||
VersionDataP update_version_data;
|
||||
// Have we shown the update dialog?
|
||||
bool shown_dialog = false;
|
||||
// Is update checking in progress?
|
||||
volatile bool checking_updates = false;
|
||||
|
||||
bool update_data_found() { return !!update_version_data; }
|
||||
bool update_available() {
|
||||
if (!update_version_data) return false;
|
||||
#if !USE_OLD_STYLE_UPDATE_CHECKER
|
||||
// updates to any installed package?
|
||||
FOR_EACH_CONST(p, update_version_data->packages) {
|
||||
if (!settings.check_updates_all && p->package != mse_package) continue;
|
||||
Version v;
|
||||
if (package_manager.installedVersion(p->package, v) && v < p->version) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
return update_version_data->version > app_version;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Update checking
|
||||
|
||||
// Thread to retrieve update information
|
||||
// Checks if the current version is the latest version
|
||||
// If not, displays a message
|
||||
class CheckUpdateThread : public wxThread {
|
||||
public:
|
||||
void* Entry() override {
|
||||
#ifndef __APPLE__
|
||||
Work();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void Work() {
|
||||
if (checking_updates) return; // don't check multiple times simultaniously
|
||||
checking_updates = true;
|
||||
try {
|
||||
#if !USE_OLD_STYLE_UPDATE_CHECKER
|
||||
String& the_url = settings.package_versions_url;
|
||||
#else
|
||||
String& the_url = settings.updates_url;
|
||||
#endif
|
||||
wxURL url(the_url);
|
||||
unique_ptr<wxInputStream> isP(url.GetInputStream());
|
||||
if (!isP) return; // failed to get data
|
||||
// Read version data
|
||||
// ignore errors for forwards compatability
|
||||
VersionDataP version_data;
|
||||
Reader reader(*isP, nullptr, _("updates"), true);
|
||||
reader.handle(version_data);
|
||||
// has the updates url changed?
|
||||
if (!version_data->new_updates_url.empty()) {
|
||||
the_url = version_data->new_updates_url;
|
||||
}
|
||||
// Make available
|
||||
update_version_data = version_data;
|
||||
} catch (...) {
|
||||
// ignore all errors, we don't want problems if update checking fails
|
||||
}
|
||||
checking_updates = false;
|
||||
}
|
||||
};
|
||||
|
||||
void check_updates() {
|
||||
if (settings.check_updates == CHECK_ALWAYS) {
|
||||
check_updates_now();
|
||||
} else if (settings.check_updates == CHECK_IF_CONNECTED) {
|
||||
// only if internet connection exists
|
||||
#if wxUSE_DIALUP_MANAGER
|
||||
wxDialUpManager* dum = wxDialUpManager::Create();
|
||||
if (dum->IsOk() && dum->IsOnline()) {
|
||||
check_updates_now();
|
||||
}
|
||||
delete dum;
|
||||
#else
|
||||
check_updates_now();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void check_updates_now(bool async) {
|
||||
if (async) {
|
||||
CheckUpdateThread* thread = new CheckUpdateThread;
|
||||
thread->Create();
|
||||
thread->Run();
|
||||
} else {
|
||||
CheckUpdateThread::Work();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Dialog
|
||||
#if !USE_OLD_STYLE_UPDATE_CHECKER
|
||||
|
||||
void show_update_dialog(Window* parent) {
|
||||
if (!update_available() || shown_dialog) return; // we already have the latest version, or this has already been displayed.
|
||||
shown_dialog = true;
|
||||
(new PackagesWindow(parent))->Show();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Dialog (old style)
|
||||
#else // !USE_OLD_STYLE_UPDATE_CHECKER
|
||||
|
||||
#include <wx/html/htmlwin.h>
|
||||
|
||||
// A HTML control that opens all pages in an actual browser
|
||||
struct HtmlWindowToBrowser : public wxHtmlWindow {
|
||||
HtmlWindowToBrowser(Window* parent, int id, const wxPoint& pos, const wxSize& size, long flags)
|
||||
: wxHtmlWindow(parent, id, pos, size, flags)
|
||||
{}
|
||||
|
||||
void OnLinkClicked(const wxHtmlLinkInfo& info) override {
|
||||
wxLaunchDefaultBrowser( info.GetHref() );
|
||||
}
|
||||
};
|
||||
|
||||
void show_update_dialog(Window* parent) {
|
||||
if (!update_available() || shown_dialog) return; // we already have the latest version, or this has already been displayed.
|
||||
// Show update dialog
|
||||
wxDialog* dlg = new wxDialog(parent, wxID_ANY, _TITLE_("updates available"), wxDefaultPosition);
|
||||
// controls
|
||||
wxHtmlWindow* html = new HtmlWindowToBrowser(dlg, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO | wxBORDER_THEME);
|
||||
html->SetPage(update_version_data->description);
|
||||
wxButton* close = new wxButton(dlg, wxID_OK, _BUTTON_("close"));
|
||||
close->SetDefault();
|
||||
// layout
|
||||
wxSizer* s = new wxBoxSizer(wxVERTICAL);
|
||||
s->Add(html, 1, wxEXPAND | wxALL, 8);
|
||||
s->Add(close, 0, wxALIGN_RIGHT | (wxALL & ~wxTOP), 8);
|
||||
dlg->SetSizer(s);
|
||||
dlg->SetSize(400,400);
|
||||
dlg->Show();
|
||||
// And never show it again this run
|
||||
shown_dialog = true;
|
||||
}
|
||||
|
||||
#endif // !USE_OLD_STYLE_UPDATE_CHECKER
|
||||
@@ -1,32 +0,0 @@
|
||||
//+----------------------------------------------------------------------------+
|
||||
//| Description: Magic Set Editor - Program to make card games |
|
||||
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
|
||||
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||
//+----------------------------------------------------------------------------+
|
||||
|
||||
#pragma once
|
||||
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
|
||||
// ----------------------------------------------------------------------------- : Update checking
|
||||
|
||||
// Checks for updates if the settings say so
|
||||
void check_updates();
|
||||
|
||||
/// Checks if the current version is the latest version
|
||||
/** If async==true then checking is done in another thread
|
||||
*/
|
||||
void check_updates_now(bool async = true);
|
||||
|
||||
/// Show a dialog to inform the user that updates are available (if there are any)
|
||||
/** Call check_updates first.
|
||||
* Call this function from an onIdle loop */
|
||||
void show_update_dialog(Window* parent);
|
||||
|
||||
/// Was update data found?
|
||||
bool update_data_found();
|
||||
/// Is there an update?
|
||||
bool update_available();
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
#include <gui/util.hpp>
|
||||
#include <gui/new_window.hpp>
|
||||
#include <gui/set/window.hpp>
|
||||
#include <gui/update_checker.hpp>
|
||||
#include <gui/packages_window.hpp>
|
||||
#include <util/window_id.hpp>
|
||||
#include <data/settings.hpp>
|
||||
#include <data/format/formats.hpp>
|
||||
@@ -40,9 +38,6 @@ WelcomeWindow::WelcomeWindow()
|
||||
// init controls
|
||||
wxControl* new_set = new HoverButtonExt(this, ID_FILE_NEW, load_resource_image(_("welcome_new")), _BUTTON_("new set"), _HELP_("new set"));
|
||||
wxControl* open_set = new HoverButtonExt(this, ID_FILE_OPEN, load_resource_image(_("welcome_open")), _BUTTON_("open set"), _HELP_("open set"));
|
||||
#if !USE_OLD_STYLE_UPDATE_CHECKER
|
||||
wxControl* updates = new HoverButtonExt(this, ID_FILE_CHECK_UPDATES, load_resource_image(_("welcome_updates")), _BUTTON_("check updates"), _HELP_("check updates"));
|
||||
#endif
|
||||
wxControl* open_last = nullptr;
|
||||
if (!settings.recent_sets.empty()) {
|
||||
const String& filename = settings.recent_sets.front();
|
||||
@@ -58,9 +53,6 @@ WelcomeWindow::WelcomeWindow()
|
||||
s2->AddSpacer(100);
|
||||
s2->Add(new_set, 0, wxALL, 2);
|
||||
s2->Add(open_set, 0, wxALL, 2);
|
||||
#if !USE_OLD_STYLE_UPDATE_CHECKER
|
||||
s2->Add(updates, 0, wxALL, 2);
|
||||
#endif
|
||||
if (open_last) s2->Add(open_last, 0, wxALL, 2);
|
||||
s2->AddStretchSpacer();
|
||||
|
||||
@@ -154,12 +146,6 @@ void WelcomeWindow::onOpenLast(wxCommandEvent&) {
|
||||
}
|
||||
}
|
||||
|
||||
void WelcomeWindow::onCheckUpdates(wxCommandEvent&) {
|
||||
Show(false); // hide, so the PackagesWindow will not use this window as its parent
|
||||
(new PackagesWindow(nullptr))->Show();
|
||||
Close();
|
||||
}
|
||||
|
||||
void WelcomeWindow::close(const SetP& set) {
|
||||
if (!set) return;
|
||||
(new SetWindow(nullptr, set))->Show();
|
||||
@@ -172,7 +158,6 @@ BEGIN_EVENT_TABLE(WelcomeWindow, wxFrame)
|
||||
EVT_BUTTON (ID_FILE_OPEN, WelcomeWindow::onOpenSet)
|
||||
EVT_BUTTON (ID_FILE_RECENT, WelcomeWindow::onOpenLast)
|
||||
EVT_COMBOBOX (ID_SELECT_LANGUAGE, WelcomeWindow::onSelectLanguage)
|
||||
EVT_BUTTON (ID_FILE_CHECK_UPDATES, WelcomeWindow::onCheckUpdates)
|
||||
EVT_PAINT ( WelcomeWindow::onPaint)
|
||||
// EVT_IDLE ( WelcomeWindow::onIdle)
|
||||
END_EVENT_TABLE ()
|
||||
|
||||
@@ -43,7 +43,6 @@ private:
|
||||
void onOpenSet (wxCommandEvent&);
|
||||
void onNewSet (wxCommandEvent&);
|
||||
void onOpenLast (wxCommandEvent&);
|
||||
void onCheckUpdates(wxCommandEvent&);
|
||||
void onSelectLanguage(wxCommandEvent&);
|
||||
// void onIdle (wxIdleEvent& ev);
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
#include <cli/cli_main.hpp>
|
||||
#include <cli/text_io_handler.hpp>
|
||||
#include <gui/welcome_window.hpp>
|
||||
#include <gui/update_checker.hpp>
|
||||
#include <gui/packages_window.hpp>
|
||||
#include <gui/downloadable_installers.hpp>
|
||||
#include <gui/set/window.hpp>
|
||||
#include <gui/symbol/window.hpp>
|
||||
#include <gui/thumbnail_thread.hpp>
|
||||
@@ -99,6 +98,7 @@ int MSE::OnRun() {
|
||||
SetAppearance((Appearance)settings.dark_mode_type);
|
||||
the_locale = Locale::byName(settings.locale);
|
||||
nag_about_ascii_version();
|
||||
downloadable_installers.check_updates();
|
||||
|
||||
// interpret command line
|
||||
{
|
||||
@@ -287,7 +287,6 @@ int MSE::OnRun() {
|
||||
}
|
||||
|
||||
int MSE::runGUI() {
|
||||
//check_updates(); // FIXME: Disable update checking on startup. Likely want to either replace the Update Checker or remove it entirely.
|
||||
return wxApp::OnRun();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <util/error.hpp>
|
||||
#include <util/file_utils.hpp>
|
||||
#include <data/game.hpp>
|
||||
#include <data/updater.hpp>
|
||||
#include <data/stylesheet.hpp>
|
||||
#include <data/symbol_font.hpp>
|
||||
#include <data/locale.hpp>
|
||||
@@ -61,12 +62,15 @@ PackagedP PackageManager::openAny(const String& name_, bool just_header) {
|
||||
if (!p) {
|
||||
// load with the right type, based on extension
|
||||
wxFileName fn(filename);
|
||||
if (fn.GetExt() == _("mse-game")) p = make_intrusive<Game>();
|
||||
else if (fn.GetExt() == _("mse-style")) p = make_intrusive<StyleSheet>();
|
||||
else if (fn.GetExt() == _("mse-locale")) p = make_intrusive<Locale>();
|
||||
else if (fn.GetExt() == _("mse-include")) p = make_intrusive<IncludePackage>();
|
||||
else if (fn.GetExt() == _("mse-symbol-font")) p = make_intrusive<SymbolFont>();
|
||||
else if (fn.GetExt() == _("mse-export-template")) p = make_intrusive<ExportTemplate>();
|
||||
String ext = fn.GetExt();
|
||||
if (ext == _("mse-style")) p = make_intrusive<StyleSheet>();
|
||||
else if (ext == _("mse-symbol-font")) p = make_intrusive<SymbolFont>();
|
||||
else if (ext == _("mse-include")) p = make_intrusive<IncludePackage>();
|
||||
else if (ext == _("mse-game")) p = make_intrusive<Game>();
|
||||
else if (ext == _("mse-locale")) p = make_intrusive<Locale>();
|
||||
else if (ext == _("mse-export-template")) p = make_intrusive<ExportTemplate>();
|
||||
//else if (ext == _("mse-import-template")) p = make_intrusive<ImportTemplate>();
|
||||
else if (ext == _("mse-updater")) p = make_intrusive<Updater>();
|
||||
else {
|
||||
throw PackageError(_("Unrecognized package type: '") + fn.GetExt() + _("'\nwhile trying to open: ") + name);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
template <typename T> class Defaultable;
|
||||
template <typename T> class Scriptable;
|
||||
DECLARE_POINTER_TYPE(Game);
|
||||
DECLARE_POINTER_TYPE(Updater);
|
||||
DECLARE_POINTER_TYPE(StyleSheet);
|
||||
class Packaged;
|
||||
pair<unique_ptr<wxInputStream>, Packaged*> openFileFromPackage(Packaged* package, const String& name);
|
||||
@@ -109,6 +110,7 @@ public:
|
||||
template <typename T> void handle(Scriptable<T>&);
|
||||
// special behaviour
|
||||
void handle(GameP&);
|
||||
void handle(UpdaterP&);
|
||||
void handle(StyleSheetP&);
|
||||
|
||||
/// Indicate that the last value from getValue() was not handled, allowing it to be handled again
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
template <typename T> class Defaultable;
|
||||
template <typename T> class Scriptable;
|
||||
DECLARE_POINTER_TYPE(Game);
|
||||
DECLARE_POINTER_TYPE(Updater);
|
||||
DECLARE_POINTER_TYPE(StyleSheet);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Writer
|
||||
@@ -72,6 +73,7 @@ public:
|
||||
template <typename T> void handle(const Scriptable<T>&);
|
||||
// special behaviour
|
||||
void handle(const GameP&);
|
||||
void handle(const UpdaterP&);
|
||||
void handle(const StyleSheetP&);
|
||||
|
||||
/// Indentation of the current block
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
//+----------------------------------------------------------------------------+
|
||||
//| Description: Magic Set Editor - Program to make card games |
|
||||
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
|
||||
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||
//+----------------------------------------------------------------------------+
|
||||
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <wx/wxprec.h>
|
||||
#include <wx/webrequest.h>
|
||||
#include <wx/process.h>
|
||||
#include <wx/wfstream.h>
|
||||
#include <wx/zipstrm.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/stdpaths.h>
|
||||
#include <unordered_map>
|
||||
|
||||
// ----------------------------------------------------------------------------- : Payload URL
|
||||
|
||||
static const wxString url = wxString("https://github.com/MagicSetEditorPacks/Installer-Pack/raw/refs/heads/main/magicseteditor.zip");
|
||||
|
||||
// ----------------------------------------------------------------------------- : Localized Text
|
||||
|
||||
const std::unordered_map<wxString, wxString> update_map = [] {
|
||||
std::unordered_map<wxString, wxString> map = {
|
||||
{ _("en"), _("Magic Set Editor will now update itself.") }
|
||||
, { _("chs"), _("Magic Set Editor现在将自动更新。") }
|
||||
, { _("cht"), _("Magic Set Editor現在將自動更新。") }
|
||||
, { _("da"), _("Magic Set Editor opdaterer nu sig selv.") }
|
||||
, { _("de"), _("Magic Set Editor wird nun aktualisiert.") }
|
||||
, { _("es"), _("Magic Set Editor se actualizará automáticamente.") }
|
||||
, { _("fr"), _("Magic Set Editor va maintenant se mettre à jour.") }
|
||||
, { _("it"), _("Magic Set Editor si aggiornerà automaticamente.") }
|
||||
, { _("jp"), _("Magic Set Editorが自動的に更新されます。") }
|
||||
, { _("ko"), _("Magic Set Editor가 이제 자동으로 업데이트됩니다.") }
|
||||
, { _("pl"), _("Magic Set Editor zaktualizuje się teraz samoczynnie.") }
|
||||
, { _("ptbr"), _("Magic Set Editor será atualizado automaticamente.") }
|
||||
, { _("ru"), _("Сейчас произойдет обновление Magic Set Editor ") }
|
||||
};
|
||||
return map;
|
||||
}();
|
||||
|
||||
const std::unordered_map<wxString, wxString> close_map = [] {
|
||||
std::unordered_map<wxString, wxString> map = {
|
||||
{ _("en"), _("Please close all other Magic Set Editor windows, then click OK.") }
|
||||
, { _("chs"), _("请关闭所有其他Magic Set Editor窗口,然后单击“OK”。") }
|
||||
, { _("cht"), _("請關閉所有其他Magic Set Editor窗口,然後按一下“OK”。") }
|
||||
, { _("da"), _("Luk alle andre Magic Set Editor-vinduer, og klik derefter på OK.") }
|
||||
, { _("de"), _("Bitte schließen Sie alle anderen Fenster des Magic Set Editor und klicken Sie anschließend auf OK.") }
|
||||
, { _("es"), _("Cierre todas las demás ventanas de Magic Set Editor y haga clic en OK.") }
|
||||
, { _("fr"), _("Veuillez fermer toutes les autres fenêtres de Magic Set Editor, puis cliquez sur OK.") }
|
||||
, { _("it"), _("Chiudere tutte le altre finestre di Magic Set Editor, quindi fare clic su OK.") }
|
||||
, { _("jp"), _("他のMagic Set Editorウィンドウをすべて閉じて、「OK」をクリックしてください。") }
|
||||
, { _("ko"), _("다른 Magic Set Editor 창을 모두 닫은 다음 OK을 클릭하십시오.") }
|
||||
, { _("pl"), _("Zamknij wszystkie pozostałe okna Magic Set Editor, a następnie kliknij OK.") }
|
||||
, { _("ptbr"), _("Feche todas as outras janelas do Magic Set Editor e clique em OK") }
|
||||
, { _("ru"), _("Пожалуйста, закройте все остальные окна Magic Set Editor, затем нажмите ОК.") }
|
||||
};
|
||||
return map;
|
||||
}();
|
||||
|
||||
// ----------------------------------------------------------------------------- : Events
|
||||
|
||||
wxDEFINE_EVENT(wxEVT_EXTRACTION, wxThreadEvent);
|
||||
wxDEFINE_EVENT(wxEVT_FAIL, wxThreadEvent);
|
||||
wxDEFINE_EVENT(wxEVT_SUCCESS, wxThreadEvent);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Classes
|
||||
|
||||
class MSEUpdater : public wxApp {
|
||||
public:
|
||||
virtual bool OnInit();
|
||||
};
|
||||
|
||||
IMPLEMENT_APP(MSEUpdater)
|
||||
|
||||
class Thread;
|
||||
|
||||
class Frame : public wxFrame {
|
||||
public:
|
||||
Frame(wxString locale);
|
||||
|
||||
void OnExtraction(wxThreadEvent& event);
|
||||
void OnFail (wxThreadEvent& event);
|
||||
void OnSuccess (wxThreadEvent& event);
|
||||
void OnOK (wxCommandEvent& event);
|
||||
|
||||
void DoFail (const wxString& error);
|
||||
|
||||
wxStaticText* status;
|
||||
wxStaticText* address;
|
||||
wxButton* button;
|
||||
Thread* thread;
|
||||
|
||||
bool OK = false;
|
||||
};
|
||||
|
||||
class Thread : public wxThread {
|
||||
public:
|
||||
Thread(Frame* frame);
|
||||
|
||||
void NotifyExtraction(const wxString& file);
|
||||
void NotifyFail (const wxString& error);
|
||||
void NotifySuccess (const wxString& app_folder);
|
||||
|
||||
virtual wxThread::ExitCode Entry();
|
||||
|
||||
Frame* frame;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Methods
|
||||
|
||||
bool MSEUpdater::OnInit() {
|
||||
Appearance darkmode = Appearance::System;
|
||||
wxString locale = wxString("en");
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
wxString arg = wxString(argv[i]);
|
||||
if (arg.StartsWith("darkmode") && arg.size() > 8) {
|
||||
darkmode = arg.GetChar(8) == '1' ? Appearance::Dark : Appearance::Light;
|
||||
}
|
||||
else if (arg.StartsWith("locale") && arg.size() > 6) {
|
||||
locale = arg.substr(6);
|
||||
}
|
||||
}
|
||||
SetAppearance(darkmode);
|
||||
Frame *frame = new Frame(locale);
|
||||
SetTopWindow(frame);
|
||||
frame->Show();
|
||||
return true;
|
||||
}
|
||||
|
||||
Frame::Frame(wxString locale) : wxFrame(nullptr, wxID_ANY, wxString("Magic Set Editor Updater")) {
|
||||
// Set app icon
|
||||
wxLogNull noLog;
|
||||
#if defined(__WXMSW__) && !defined(__GNUC__)
|
||||
SetIcon(wxIcon(_("updater")));
|
||||
#else
|
||||
SetIcon(wxIcon(wxStandardPaths::Get().GetDataDir() + _("/src_updater/updater.ico"), wxBITMAP_TYPE_ICO));
|
||||
#endif
|
||||
// Create controls
|
||||
wxString update_text = update_map.find(locale) != update_map.end() ? update_map.at(locale) : update_map.at("en");
|
||||
wxString close_text = close_map.find(locale) != close_map.end() ? close_map.at(locale) : close_map.at("en");
|
||||
status = new wxStaticText(this, wxID_ANY, update_text, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL | wxST_NO_AUTORESIZE);
|
||||
address = new wxStaticText(this, wxID_ANY, close_text, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL | wxST_NO_AUTORESIZE);
|
||||
button = new wxButton (this, wxID_OK, wxString("OK"));
|
||||
wxFont font = status->GetFont().Scale(1.2f);
|
||||
status ->SetFont(font);
|
||||
address->SetFont(font);
|
||||
button ->SetFont(font);
|
||||
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(status, 1, wxEXPAND | wxALL, 12);
|
||||
sizer->Add(address, 1, wxEXPAND | (wxALL & ~wxTOP), 12);
|
||||
sizer->Add(button, 1, wxEXPAND, 12);
|
||||
sizer->SetMinSize(wxSize(800, -1));
|
||||
SetSizerAndFit(sizer);
|
||||
|
||||
// Bind events
|
||||
Bind(wxEVT_EXTRACTION, &Frame::OnExtraction, this);
|
||||
Bind(wxEVT_SUCCESS, &Frame::OnSuccess, this);
|
||||
Bind(wxEVT_FAIL, &Frame::OnFail, this);
|
||||
Bind(wxEVT_BUTTON, &Frame::OnOK, this, wxID_OK);
|
||||
|
||||
// Run thread
|
||||
thread = new Thread(this);
|
||||
if (thread->Create() == wxTHREAD_NO_ERROR) {
|
||||
thread->Run();
|
||||
}
|
||||
}
|
||||
|
||||
void Frame::OnExtraction(wxThreadEvent& event) {
|
||||
status->SetLabelText(wxString("Extracting File To:"));
|
||||
address->SetLabelText(event.GetString());
|
||||
Update();
|
||||
}
|
||||
|
||||
void Frame::OnFail(wxThreadEvent& event) {
|
||||
DoFail(event.GetString());
|
||||
}
|
||||
void Frame::DoFail(const wxString& error) {
|
||||
thread->Kill();
|
||||
status->SetForegroundColour(wxColor(*wxRED));
|
||||
status->SetLabelText(error);
|
||||
address->SetLabelText(wxString(""));
|
||||
Update();
|
||||
wxMilliSleep(3000);
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void Frame::OnSuccess(wxThreadEvent& event) {
|
||||
wxString path = event.GetString() + wxString("magicseteditor");
|
||||
path = path + wxString(".exe");
|
||||
long processID = wxFileExists(path) ? wxExecute(path, wxEXEC_ASYNC) : 0L;
|
||||
if (processID == 0L) {
|
||||
DoFail(wxString("Could not launch Magic Set Editor."));
|
||||
}
|
||||
else {
|
||||
status->SetLabelText(wxString("Launching Magic Set Editor."));
|
||||
address->SetLabelText(wxString(""));
|
||||
Update();
|
||||
wxMilliSleep(800);
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void Frame::OnOK(wxCommandEvent& event) {
|
||||
if (!OK) {
|
||||
button->Enable(false);
|
||||
button->SetLabel(wxString("Cancel"));
|
||||
status->SetLabelText(wxString("Downloading New Version From:"));
|
||||
address->SetLabelText(url);
|
||||
Update();
|
||||
wxMilliSleep(200);
|
||||
button->Enable(true);
|
||||
wxMilliSleep(100);
|
||||
OK = true;
|
||||
}
|
||||
else {
|
||||
thread->Kill();
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Thread::Thread(Frame* frame) : wxThread(wxTHREAD_DETACHED), frame(frame) {}
|
||||
|
||||
void Thread::NotifyExtraction(const wxString& file) {
|
||||
wxThreadEvent* event = new wxThreadEvent(wxEVT_EXTRACTION);
|
||||
event->SetString(file);
|
||||
wxQueueEvent(frame, event);
|
||||
}
|
||||
|
||||
void Thread::NotifyFail(const wxString& error) {
|
||||
wxThreadEvent* event = new wxThreadEvent(wxEVT_FAIL);
|
||||
event->SetString(error);
|
||||
wxQueueEvent(frame, event);
|
||||
}
|
||||
|
||||
void Thread::NotifySuccess(const wxString& app_folder) {
|
||||
wxThreadEvent* event = new wxThreadEvent(wxEVT_SUCCESS);
|
||||
event->SetString(app_folder);
|
||||
wxQueueEvent(frame, event);
|
||||
}
|
||||
|
||||
wxThread::ExitCode Thread::Entry() {
|
||||
try {
|
||||
// Get app folder
|
||||
wxFileName updater_folder = wxFileName(wxStandardPaths::Get().GetExecutablePath());
|
||||
updater_folder.RemoveLastDir();
|
||||
if (!updater_folder.GetPath().EndsWith(wxString("data"))) {
|
||||
NotifyFail("Updater package is not in the 'data' folder.");
|
||||
return (ExitCode)-1;
|
||||
}
|
||||
updater_folder.RemoveLastDir();
|
||||
wxString app_folder = updater_folder.GetPath() + wxFileName::GetPathSeparator();
|
||||
|
||||
// Fetch zip from url
|
||||
wxWebRequestSync request = wxWebSessionSync::GetDefault().CreateRequest(url);
|
||||
auto const result = request.Execute();
|
||||
if (!result) {
|
||||
NotifyFail("Download Failed.");
|
||||
return (ExitCode)-1;
|
||||
}
|
||||
wxInputStream* is = request.GetResponse().GetStream();
|
||||
if (!is->IsOk()) {
|
||||
NotifyFail("Download Corrupted.");
|
||||
return (ExitCode)-1;
|
||||
}
|
||||
|
||||
// Stall until the user gives the go ahead
|
||||
while (!frame->OK) {
|
||||
wxMilliSleep(50);
|
||||
wxYield();
|
||||
}
|
||||
|
||||
// Extract files from zip
|
||||
wxZipInputStream zis(is);
|
||||
if (!zis.IsOk()) {
|
||||
NotifyFail("Zip Archive Corrupted.");
|
||||
return (ExitCode)-1;
|
||||
}
|
||||
std::unique_ptr<wxZipEntry> entry;
|
||||
while (entry.reset(zis.GetNextEntry()), entry.get() != nullptr) {
|
||||
wxString target_path = app_folder + entry->GetName();
|
||||
int nPermBits = entry->GetMode();
|
||||
if (entry->IsDir()) {
|
||||
if (!wxDirExists(target_path)) wxFileName::Mkdir(target_path, nPermBits, wxPATH_MKDIR_FULL);
|
||||
continue;
|
||||
}
|
||||
|
||||
NotifyExtraction(target_path);
|
||||
wxFileName fn;
|
||||
fn.Assign(target_path);
|
||||
if (!wxDirExists(fn.GetPath())) wxFileName::Mkdir(fn.GetPath(), nPermBits, wxPATH_MKDIR_FULL);
|
||||
if (!zis.CanRead()) {
|
||||
NotifyFail("Extraction Failed.");
|
||||
return (ExitCode)-1;
|
||||
}
|
||||
|
||||
wxFileOutputStream fos(target_path);
|
||||
if (!fos.IsOk()) {
|
||||
NotifyFail("Writing Failed. Magic Set Editor may still be open.");
|
||||
return (ExitCode)-1;
|
||||
}
|
||||
|
||||
zis.Read(fos);
|
||||
fos.Close();
|
||||
zis.CloseEntry();
|
||||
}
|
||||
|
||||
NotifySuccess(app_folder);
|
||||
wxMilliSleep(200);
|
||||
return (ExitCode)0;
|
||||
}
|
||||
catch (...) {
|
||||
NotifyFail("Something Went Wrong.");
|
||||
return (ExitCode)-1;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
@@ -0,0 +1,9 @@
|
||||
//+----------------------------------------------------------------------------+
|
||||
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||
//| Copyright: (C) 2001 - 2017 Twan van Laarhoven and "coppro" |
|
||||
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||
//+----------------------------------------------------------------------------+
|
||||
|
||||
// -------------------------------------------------------- : Icon
|
||||
|
||||
updater ICON "updater.ico"
|
||||