diff --git a/data/en.mse-locale/locale b/data/en.mse-locale/locale index 69611ecf..979dc0fc 100644 --- a/data/en.mse-locale/locale +++ b/data/en.mse-locale/locale @@ -116,7 +116,7 @@ help: export images: Export images for all cards export apprentice: Export the set so it can be played with in Apprentice export mws: Export the set so it can be played with in Magic Workstation - check updates: Open the update window to download new packages, such as games, styles, and locales. + check updates: Install/update packages. print preview: Shows cards as they will be printed print: Print cards from this set reload data: Reload all template files (game and style) as well as the set. @@ -476,6 +476,7 @@ button: # Welcome new set: New set open set: Open set + check updates: Check updates last opened set: Last opened set # Preferences diff --git a/src/gui/set/window.cpp b/src/gui/set/window.cpp index b9734f8d..60e05bdf 100644 --- a/src/gui/set/window.cpp +++ b/src/gui/set/window.cpp @@ -374,7 +374,6 @@ void SetWindow::onUpdateUI(wxUpdateUIEvent& ev) { case ID_FILE_EXPORT_IMAGE: ev.Enable(!!current_panel->selectedCard()); break; case ID_FILE_EXPORT_APPR: ev.Enable(set->game->isMagic()); break; case ID_FILE_EXPORT_MWS: ev.Enable(set->game->isMagic()); break; - case ID_FILE_CHECK_UPDATES:ev.Enable(false); break; // no update checking in 0.3.5 case ID_FILE_EXIT: // update for ID_FILE_RECENT done for a different id, because ID_FILE_RECENT may not be in the menu yet updateRecentSets(); @@ -538,7 +537,9 @@ void SetWindow::onFileExportMWS(wxCommandEvent&) { } void SetWindow::onFileCheckUpdates(wxCommandEvent&) { + if (!askSaveAndContinue()) return; (new UpdatesWindow)->Show(); + Destroy(); } void SetWindow::onFilePrint(wxCommandEvent&) { diff --git a/src/gui/update_checker.cpp b/src/gui/update_checker.cpp index 82424f21..35c93f47 100644 --- a/src/gui/update_checker.cpp +++ b/src/gui/update_checker.cpp @@ -22,6 +22,7 @@ DECLARE_POINTER_TYPE(PackageVersionData); DECLARE_POINTER_TYPE(VersionData); DECLARE_TYPEOF_COLLECTION(PackageVersionDataP); +DECLARE_TYPEOF_COLLECTION(PackageDependencyP); // ----------------------------------------------------------------------------- : Update data @@ -105,7 +106,7 @@ class CheckUpdateThread : public wxThread { InputStreamP is(isP); // Read version data VersionDataP version_data; - Reader reader(is, _("updates")); + Reader reader(is, nullptr, _("updates")); reader.handle(version_data); // has the updates url changed? if (!version_data->new_updates_url.empty()) { @@ -261,8 +262,10 @@ class PackageUpdateList : public wxVListBox { #define SELECT_WHITE(color) (IsSelected(n) ? *wxWHITE : color) + dc.SetClippingRegion(wxRect(rect.x, rect.y, rect.width / 2, rect.height)); dc.SetTextForeground(SELECT_WHITE(packageFront)); dc.DrawText(pack->name, rect.GetLeft() + 1, rect.GetTop()); + dc.DestroyClippingRegion(); dc.SetTextForeground(SELECT_WHITE(status_colors[status])); dc.DrawText(status_texts[status], rect.GetLeft() + 240, rect.GetTop()); @@ -465,13 +468,102 @@ void UpdatesWindow::setDefaultPackageStatus() { } } +/// Select the dependencies for a package +/** + * \param pack The package data to check dependencies for. + * + * This function will select all the dependencies for the package, to ensure + * that the user can't install a package without it's dependencies. + */ void UpdatesWindow::SelectPackageDependencies (PackageVersionDataP pack) { + FOR_EACH(dep, pack->depends) { + if (packages.checkDependency(*dep)) //It's already installed. + continue; + FOR_EACH(p, update_version_data->packages) { + if (p->name == dep->package) { //We have a match. + if (p->version >= dep->version) { //Versions line up + + PackageStatus& status = package_data[p].first; + PackageAction& action = package_data[p].second; + if (status == STATUS_NOT_INSTALLED) + action = ACTION_INSTALL; + else if (status == STATUS_UPGRADEABLE) + action = ACTION_UPGRADE; + else //status == STATUS_INSTALLED + action = ACTION_NOTHING; + break; + } + } + } + // TODO: Decide what to do if a dependency can't be met. + // It shouldn't happen with a decently maintained updates list. + // But it could, and we need to decide what to do in this situation. + } } +/// This finds all packages that depend on the one provided and marks them for removal. void UpdatesWindow::RemovePackageDependencies (PackageVersionDataP pack) { + FOR_EACH(p, update_version_data->packages) { + FOR_EACH(dep, p->depends) { + if (pack->name == dep->package) { + PackageStatus& status = package_data[p].first; + PackageAction& action = package_data[p].second; + + if (status != STATUS_NOT_INSTALLED) + action = ACTION_UNINSTALL; + else // status == STATUS_NOT_INSTALLED + action = p->app_version > file_version ? ACTION_NOTHING : ACTION_NEW_MSE; + break; + } + } + } } +/// This deals with the complexities of a downgrade and its dependencies. void UpdatesWindow::DowngradePackageDependencies (PackageVersionDataP pack) { + PackagedP old_pack = packages.openAny(pack->name, true); + FOR_EACH(dep, old_pack->dependencies) { + // dependencies the old version has, but the new one might not. + if (packages.checkDependency(*dep)) //It's already installed. + continue; + FOR_EACH(p, update_version_data->packages) { + if (p->name == dep->package) { //We have a match. + if (p->version >= dep->version) { //Versions line up + if (p->app_version > file_version) //We can't install this + continue; + + PackageStatus& status = package_data[p].first; + PackageAction& action = package_data[p].second; + if (status == STATUS_NOT_INSTALLED) + action = ACTION_INSTALL; + else if (status == STATUS_UPGRADEABLE) + action = ACTION_UPGRADE; + break; + } + } + } + // TODO: Decide what to do if a dependency can't be met. + // It shouldn't happen with a decently maintained updates list. + // But it could, and we need to decide what to do in this situation. + } + + FOR_EACH(p, update_version_data->packages) { + // dependencies that can no longer be met. + FOR_EACH(dep, p->depends) { + if (pack->name == dep->package) { + if (old_pack->version > dep->version) { + PackageStatus& status = package_data[p].first; + PackageAction& action = package_data[p].second; + + if (status != STATUS_NOT_INSTALLED) + action = ACTION_UNINSTALL; + else // status == STATUS_NOT_INSTALLED + action = p->app_version > file_version ? ACTION_NOTHING : ACTION_NEW_MSE; + break; + } + } + } + } } BEGIN_EVENT_TABLE(UpdatesWindow, Frame) diff --git a/src/gui/update_checker.hpp b/src/gui/update_checker.hpp index 39bc8521..ea2079ad 100644 --- a/src/gui/update_checker.hpp +++ b/src/gui/update_checker.hpp @@ -10,6 +10,7 @@ // ----------------------------------------------------------------------------- : Includes #include +#include // ----------------------------------------------------------------------------- : Update checking @@ -39,6 +40,7 @@ DECLARE_POINTER_TYPE(PackageVersionData); class UpdatesWindow : public Frame { public: UpdatesWindow(); + ~UpdatesWindow() { (new WelcomeWindow)->Show(); } void DrawTitles(wxPaintEvent&); @@ -52,7 +54,7 @@ class UpdatesWindow : public Frame { ACTION_UNINSTALL, ACTION_UPGRADE, ACTION_NOTHING, - ACTION_NEW_MSE + ACTION_NEW_MSE // means that you need a new version of MSE to install/upgrade }; typedef pair PackageData; diff --git a/src/gui/welcome_window.cpp b/src/gui/welcome_window.cpp index 0ba13f61..67414e43 100644 --- a/src/gui/welcome_window.cpp +++ b/src/gui/welcome_window.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -30,11 +31,13 @@ WelcomeWindow::WelcomeWindow() // init controls #ifdef USE_HOVERBUTTON - 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")); + 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")); + wxControl* updates = new HoverButtonExt(this, ID_FILE_CHECK_UPDATES, wxImage(), _BUTTON_("check updates"), _HELP_("check updates")); #else - wxControl* new_set = new wxButton(this, ID_FILE_NEW, _BUTTON_("new set")); - wxControl* open_set = new wxButton(this, ID_FILE_OPEN, _BUTTON_("open set")); + wxControl* new_set = new wxButton(this, ID_FILE_NEW, _BUTTON_("new set")); + wxControl* open_set = new wxButton(this, ID_FILE_OPEN, _BUTTON_("open set")); + wxControl* updates = new wxButton(this, ID_FILE_CHECK_UPDATES, _BUTTON_("check updates")); #endif wxControl* open_last = nullptr; if (!settings.recent_sets.empty()) { @@ -53,6 +56,7 @@ WelcomeWindow::WelcomeWindow() s2->AddSpacer(100); s2->Add(new_set, 0, wxALL, 2); s2->Add(open_set, 0, wxALL, 2); + s2->Add(updates, 0, wxALL, 2); if (open_last) s2->Add(open_last, 0, wxALL, 2); s2->AddStretchSpacer(); s1->Add(s2); @@ -101,6 +105,11 @@ void WelcomeWindow::onOpenLast(wxCommandEvent&) { close( open_package(settings.recent_sets.front()) ); } +void WelcomeWindow::onCheckUpdates(wxCommandEvent&) { + (new UpdatesWindow)->Show(); + Close(); +} + void WelcomeWindow::close(const SetP& set) { if (!set) return; (new SetWindow(nullptr, set))->Show(); @@ -109,11 +118,12 @@ void WelcomeWindow::close(const SetP& set) { BEGIN_EVENT_TABLE(WelcomeWindow, wxFrame) - EVT_BUTTON (ID_FILE_NEW, WelcomeWindow::onNewSet) - EVT_BUTTON (ID_FILE_OPEN, WelcomeWindow::onOpenSet) - EVT_BUTTON (ID_FILE_RECENT, WelcomeWindow::onOpenLast) - EVT_PAINT ( WelcomeWindow::onPaint) -// EVT_IDLE ( WelcomeWindow::onIdle) + EVT_BUTTON (ID_FILE_NEW, WelcomeWindow::onNewSet) + EVT_BUTTON (ID_FILE_OPEN, WelcomeWindow::onOpenSet) + EVT_BUTTON (ID_FILE_RECENT, WelcomeWindow::onOpenLast) + EVT_BUTTON (ID_FILE_CHECK_UPDATES, WelcomeWindow::onCheckUpdates) + EVT_PAINT ( WelcomeWindow::onPaint) +// EVT_IDLE ( WelcomeWindow::onIdle) END_EVENT_TABLE () diff --git a/src/gui/welcome_window.hpp b/src/gui/welcome_window.hpp index 91c46659..f631e78c 100644 --- a/src/gui/welcome_window.hpp +++ b/src/gui/welcome_window.hpp @@ -37,9 +37,10 @@ class WelcomeWindow : public Frame { void onPaint(wxPaintEvent&); void draw(DC& dc); - void onOpenSet (wxCommandEvent&); - void onNewSet (wxCommandEvent&); - void onOpenLast(wxCommandEvent&); + void onOpenSet (wxCommandEvent&); + void onNewSet (wxCommandEvent&); + void onOpenLast (wxCommandEvent&); + void onCheckUpdates(wxCommandEvent&); // void onIdle (wxIdleEvent& ev); /// Close the welcome window, and show the given set diff --git a/src/resource/common/expected_locale_keys b/src/resource/common/expected_locale_keys index ab3f9204..7b8fc482 100644 --- a/src/resource/common/expected_locale_keys +++ b/src/resource/common/expected_locale_keys @@ -42,6 +42,7 @@ button: add item: 0 always: 0 browse: 0 + check updates: 0 check now: 0 close: 0 defaults: 0 diff --git a/src/util/io/package.cpp b/src/util/io/package.cpp index 1ae28e96..b0116a2d 100644 --- a/src/util/io/package.cpp +++ b/src/util/io/package.cpp @@ -65,7 +65,6 @@ const String& Package::absoluteFilename() const { return filename; } - void Package::open(const String& n) { assert(!isOpened()); // not already opened // get absolute path @@ -86,6 +85,17 @@ void Package::open(const String& n) { } } +void Package::openZipStream(wxZipInputStream* input) { + // close old streams + delete fileStream; fileStream = nullptr; + delete zipStream; + + zipStream = input; + if (!zipStream->IsOk()) throw InternalError(_("Error opening package!")); + + loadZipStream(); +} + void Package::save(bool remove_unused) { assert(!needSaveAs()); saveAs(filename, remove_unused); @@ -304,6 +314,15 @@ Package::FileInfo::~FileInfo() { delete zipEntry; } +void Package::loadZipStream() { + while (true) { + wxZipEntry* entry = zipStream->GetNextEntry(); + if (!entry) break; + String name = toStandardName(entry->GetName()); + files[name].zipEntry = entry; + } + zipStream->CloseEntry(); +} void Package::openDirectory() { openSubdir(wxEmptyString); @@ -341,16 +360,9 @@ void Package::openZipfile() { zipStream = new wxZipInputStream(*fileStream); if (!zipStream->IsOk()) throw PackageError(_ERROR_1_("package not found", filename)); // read zip entries - while (true) { - wxZipEntry* entry = zipStream->GetNextEntry(); - if (!entry) break; - String name = toStandardName(entry->GetName()); - files[name].zipEntry = entry; - } - zipStream->CloseEntry(); + loadZipStream(); } - void Package::saveToDirectory(const String& saveAs, bool remove_unused) { // write to a directory FOR_EACH(f, files) { diff --git a/src/util/io/package.hpp b/src/util/io/package.hpp index f28499c2..c4a3e56c 100644 --- a/src/util/io/package.hpp +++ b/src/util/io/package.hpp @@ -71,6 +71,9 @@ class Package : public IntrusivePtrVirtualBase { /// @pre open not called before [TODO] void open(const String& package); + /// Open a package from a zipstream that doesn't necessarily have a filename (i.e. a URL). + void openZipStream(wxZipInputStream* input); + /// Saves the package, by default saves as a zip file, unless /// it was already a directory /** If remove_unused=true all files that were in the file and @@ -168,6 +171,7 @@ class Package : public IntrusivePtrVirtualBase { /// Filestream for reading zip files wxZipInputStream* zipStream; + void loadZipStream(); void openDirectory(); void openSubdir(const String&); void openZipfile(); diff --git a/src/util/io/package_manager.cpp b/src/util/io/package_manager.cpp index 48e61643..e7a503e4 100644 --- a/src/util/io/package_manager.cpp +++ b/src/util/io/package_manager.cpp @@ -128,13 +128,15 @@ bool PackageManager::checkDependency(const PackageDependency& dep, bool report_e // try global package name = global_data_directory + _("/") + dep.package; if (!wxFileExists(name) && !wxDirExists(name)) { - handle_warning(_ERROR_1_("package not found", dep.package),false); + if (report_errors) + handle_warning(_ERROR_1_("package not found", dep.package),false); return false; } } PackagedP package = openAny(dep.package, true); if (package->version < dep.version) { - handle_warning(_ERROR_3_("package out of date", dep.package, package->version.toString(), dep.version.toString()),false); + if (report_errors) + handle_warning(_ERROR_3_("package out of date", dep.package, package->version.toString(), dep.version.toString()),false); return false; } return true;