addon/client: Add modeless dialog for displaying add-on install progress
This implements an add-on extraction progress dialog that does not actually run its own event loop, allowing the caller to take ownership of it (display() static method) and update its state using a callback function object. The object in question is passed into the add-ons management API and used to update the dialog status each time an add-on file is written to disk as part of the pack extraction process. In order to avoid stalling the extraction process in UI code, the callback is invoked for every single file, but the dialog's progress update method places a time restriction on GUI2 API calls of 120 milliseconds -- this is a good throttle interval that allows add-ons to be extracted in about the same amount of time as before while still updating the progress bar smoothly enough for add-ons that take longer than that. (This is not the most trivial code to test, so it is suggested to add a sleep/delay API call in unarchive_file() in src/addon/manager.cpp to introduce artificial delays.) One issue with this code, however, is that because modeless_dialog doesn't execute its own event loop, the only way to get the dialog to be updated is to force a draw event in ourselves via the new gui2::dialogs::modeless_dialog::force_redraw() method. This is really a side-effect of my design choice here to run the dialog in the middle of a blocking operation instead of somewhere where events are being processed normally. I'm not entirely sure if the draw events would be pushed even in that case, however. Closes #1101.
This commit is contained in:
parent
ad8f6a6f4c
commit
532ec4e06f
9 changed files with 282 additions and 7 deletions
105
data/gui/window/file_progress.cfg
Normal file
105
data/gui/window/file_progress.cfg
Normal file
|
@ -0,0 +1,105 @@
|
|||
#textdomain wesnoth-lib
|
||||
###
|
||||
### Modeless dialog that tracks progress of a file operation
|
||||
###
|
||||
### NOTE: The dialog layout is intended to match network_transmission's since
|
||||
### they are both used during the add-on download/install flow.
|
||||
###
|
||||
|
||||
[window]
|
||||
id = "file_progress"
|
||||
description = "Modeless dialog that tracks progress of a file operation"
|
||||
|
||||
[resolution]
|
||||
definition = "default"
|
||||
|
||||
maximum_width = 800
|
||||
|
||||
[tooltip]
|
||||
id = "tooltip"
|
||||
[/tooltip]
|
||||
|
||||
[helptip]
|
||||
id = "tooltip"
|
||||
[/helptip]
|
||||
|
||||
[grid]
|
||||
|
||||
[row]
|
||||
|
||||
[column]
|
||||
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "left"
|
||||
|
||||
[label]
|
||||
id = "title"
|
||||
definition = "title"
|
||||
[/label]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
|
||||
[column]
|
||||
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "left"
|
||||
|
||||
[label]
|
||||
id = "message"
|
||||
definition = "default"
|
||||
[/label]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
|
||||
[column]
|
||||
|
||||
grow_factor = 1
|
||||
horizontal_grow = true
|
||||
border = "all"
|
||||
border_size = 5
|
||||
|
||||
[progress_bar]
|
||||
id = "progress"
|
||||
definition = "default"
|
||||
[/progress_bar]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
|
||||
[column]
|
||||
|
||||
horizontal_alignment = "right"
|
||||
grow_factor = 0
|
||||
border = "all"
|
||||
border_size = 5
|
||||
|
||||
[button]
|
||||
# This button is only used for decoration and doesn't
|
||||
# actually do anything, hence the nonstandard id.
|
||||
id = "placeholder"
|
||||
definition = "default"
|
||||
label = _ "Cancel"
|
||||
[/button]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[/grid]
|
||||
|
||||
[/resolution]
|
||||
|
||||
[/window]
|
|
@ -2,6 +2,9 @@
|
|||
###
|
||||
### Dialog that tracks progress of a network transmission
|
||||
###
|
||||
### NOTE: The dialog layout is intended to match file_progress' since they are
|
||||
### both used during the add-on download/install flow.
|
||||
###
|
||||
|
||||
[window]
|
||||
id = "network_transmission"
|
||||
|
|
|
@ -323,6 +323,8 @@
|
|||
46F92DBA2174F6A300602C1C /* help_browser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46F92C692174F6A300602C1C /* help_browser.cpp */; };
|
||||
46F92DBB2174F6A300602C1C /* file_dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46F92C6B2174F6A300602C1C /* file_dialog.cpp */; };
|
||||
46F92DBC2174F6A300602C1C /* file_dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46F92C6B2174F6A300602C1C /* file_dialog.cpp */; };
|
||||
1234567890ABCDEF12345678 /* file_progress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1234567890ABCDEF12345680 /* file_progress.cpp */; };
|
||||
1234567890ABCDEF12345679 /* file_progress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1234567890ABCDEF12345680 /* file_progress.cpp */; };
|
||||
46F92DBD2174F6A300602C1C /* folder_create.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46F92C6E2174F6A300602C1C /* folder_create.cpp */; };
|
||||
46F92DBE2174F6A300602C1C /* folder_create.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46F92C6E2174F6A300602C1C /* folder_create.cpp */; };
|
||||
46F92DBF2174F6A300602C1C /* language_selection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46F92C6F2174F6A300602C1C /* language_selection.cpp */; };
|
||||
|
@ -1731,6 +1733,7 @@
|
|||
46F92C692174F6A300602C1C /* help_browser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = help_browser.cpp; sourceTree = "<group>"; };
|
||||
46F92C6A2174F6A300602C1C /* title_screen.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = title_screen.hpp; sourceTree = "<group>"; };
|
||||
46F92C6B2174F6A300602C1C /* file_dialog.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = file_dialog.cpp; sourceTree = "<group>"; };
|
||||
1234567890ABCDEF12345680 /* file_progress.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = file_progress.cpp; sourceTree = "<group>"; };
|
||||
46F92C6C2174F6A300602C1C /* attack_predictions.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = attack_predictions.hpp; sourceTree = "<group>"; };
|
||||
46F92C6D2174F6A300602C1C /* tooltip.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = tooltip.hpp; sourceTree = "<group>"; };
|
||||
46F92C6E2174F6A300602C1C /* folder_create.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = folder_create.cpp; sourceTree = "<group>"; };
|
||||
|
@ -1756,6 +1759,7 @@
|
|||
46F92C822174F6A300602C1C /* unit_recruit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = unit_recruit.cpp; sourceTree = "<group>"; };
|
||||
46F92C832174F6A300602C1C /* edit_text.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = edit_text.cpp; sourceTree = "<group>"; };
|
||||
46F92C842174F6A300602C1C /* file_dialog.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = file_dialog.hpp; sourceTree = "<group>"; };
|
||||
1234567890ABCDEF12345681 /* file_progress.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = file_progress.hpp; sourceTree = "<group>"; };
|
||||
46F92C852174F6A300602C1C /* attack_predictions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = attack_predictions.cpp; sourceTree = "<group>"; };
|
||||
46F92C862174F6A300602C1C /* tooltip.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tooltip.cpp; sourceTree = "<group>"; };
|
||||
46F92C872174F6A300602C1C /* title_screen.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = title_screen.cpp; sourceTree = "<group>"; };
|
||||
|
@ -3623,6 +3627,8 @@
|
|||
46F92C8D2174F6A300602C1C /* end_credits.hpp */,
|
||||
46F92C6B2174F6A300602C1C /* file_dialog.cpp */,
|
||||
46F92C842174F6A300602C1C /* file_dialog.hpp */,
|
||||
1234567890ABCDEF12345680 /* file_progress.cpp */,
|
||||
1234567890ABCDEF12345681 /* file_progress.hpp */,
|
||||
46F92C6E2174F6A300602C1C /* folder_create.cpp */,
|
||||
46F92C8A2174F6A300602C1C /* folder_create.hpp */,
|
||||
46F92CBB2174F6A300602C1C /* formula_debugger.cpp */,
|
||||
|
@ -5497,6 +5503,7 @@
|
|||
B52EE8D6121359A600CFBDAB /* persist_context.cpp in Sources */,
|
||||
B52EE8D7121359A600CFBDAB /* persist_manager.cpp in Sources */,
|
||||
46F92DBB2174F6A300602C1C /* file_dialog.cpp in Sources */,
|
||||
1234567890ABCDEF12345678 /* file_progress.cpp in Sources */,
|
||||
46F92F0F2174FEC000602C1C /* standard_colors.cpp in Sources */,
|
||||
46F92E852174F6A400602C1C /* debug.cpp in Sources */,
|
||||
B52EE8D8121359A600CFBDAB /* persist_var.cpp in Sources */,
|
||||
|
@ -6116,6 +6123,7 @@
|
|||
46F92E142174F6A400602C1C /* player_list_helper.cpp in Sources */,
|
||||
91E3570A1CACC9B200774252 /* playcampaign.cpp in Sources */,
|
||||
46F92DBC2174F6A300602C1C /* file_dialog.cpp in Sources */,
|
||||
1234567890ABCDEF12345679 /* file_progress.cpp in Sources */,
|
||||
91E3570B1CACC9B200774252 /* singleplayer.cpp in Sources */,
|
||||
4649B88B20288EEF00827CFB /* surface.cpp in Sources */,
|
||||
46F92DB02174F6A300602C1C /* campaign_selection.cpp in Sources */,
|
||||
|
|
|
@ -193,6 +193,7 @@ gui/dialogs/editor/new_map.cpp
|
|||
gui/dialogs/editor/resize_map.cpp
|
||||
gui/dialogs/end_credits.cpp
|
||||
gui/dialogs/file_dialog.cpp
|
||||
gui/dialogs/file_progress.cpp
|
||||
gui/dialogs/folder_create.cpp
|
||||
gui/dialogs/formula_debugger.cpp
|
||||
gui/dialogs/game_cache_options.cpp
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "gettext.hpp"
|
||||
#include "gui/dialogs/addon/addon_auth.hpp"
|
||||
#include "gui/dialogs/addon/install_dependencies.hpp"
|
||||
#include "gui/dialogs/file_progress.hpp"
|
||||
#include "gui/dialogs/message.hpp"
|
||||
#include "gui/widgets/retval.hpp"
|
||||
#include "log.hpp"
|
||||
|
@ -344,6 +345,11 @@ bool addons_client::install_addon(config& archive_cfg, const addon_info& info)
|
|||
utils::string_map i18n_symbols;
|
||||
i18n_symbols["addon_title"] = font::escape_text(info.title);
|
||||
|
||||
auto progress_dlg = gui2::dialogs::file_progress::display(_("Add-ons Manager"), VGETTEXT("Installing add-on <i>$addon_title</i>...", i18n_symbols));
|
||||
auto progress_cb = [&progress_dlg](unsigned value) {
|
||||
progress_dlg->update_progress(value);
|
||||
};
|
||||
|
||||
if(archive_cfg.has_child("removelist") || archive_cfg.has_child("addlist")) {
|
||||
LOG_ADDONS << "Received an updatepack for the addon '" << info.id << "'";
|
||||
|
||||
|
@ -366,7 +372,7 @@ bool addons_client::install_addon(config& archive_cfg, const addon_info& info)
|
|||
if(entry.key == "removelist") {
|
||||
purge_addon(entry.cfg);
|
||||
} else if(entry.key == "addlist") {
|
||||
unarchive_addon(entry.cfg);
|
||||
unarchive_addon(entry.cfg, progress_cb);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -393,7 +399,7 @@ bool addons_client::install_addon(config& archive_cfg, const addon_info& info)
|
|||
WRN_ADDONS << "failed to uninstall previous version of " << info.id << "; the add-on may not work properly!";
|
||||
}
|
||||
|
||||
unarchive_addon(archive_cfg);
|
||||
unarchive_addon(archive_cfg, progress_cb);
|
||||
LOG_ADDONS << "unpacking finished";
|
||||
}
|
||||
|
||||
|
|
|
@ -293,7 +293,7 @@ static void unarchive_file(const std::string& path, const config& cfg)
|
|||
filesystem::write_file(path + '/' + cfg["name"].str(), unencode_binary(cfg["contents"]));
|
||||
}
|
||||
|
||||
static void unarchive_dir(const std::string& path, const config& cfg)
|
||||
static void unarchive_dir(const std::string& path, const config& cfg, std::function<void()> file_callback = {})
|
||||
{
|
||||
std::string dir;
|
||||
if (cfg["name"].empty())
|
||||
|
@ -304,18 +304,36 @@ static void unarchive_dir(const std::string& path, const config& cfg)
|
|||
filesystem::make_directory(dir);
|
||||
|
||||
for(const config &d : cfg.child_range("dir")) {
|
||||
unarchive_dir(dir, d);
|
||||
unarchive_dir(dir, d, file_callback);
|
||||
}
|
||||
|
||||
for(const config &f : cfg.child_range("file")) {
|
||||
unarchive_file(dir, f);
|
||||
if(file_callback) {
|
||||
file_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unarchive_addon(const config& cfg)
|
||||
static unsigned count_pack_files(const config& cfg)
|
||||
{
|
||||
unsigned count = 0;
|
||||
|
||||
for(const config& d : cfg.child_range("dir")) {
|
||||
count += count_pack_files(d);
|
||||
}
|
||||
|
||||
return count + cfg.child_count("file");
|
||||
}
|
||||
|
||||
void unarchive_addon(const config& cfg, std::function<void(unsigned)> progress_callback)
|
||||
{
|
||||
const std::string parentd = filesystem::get_addons_dir();
|
||||
unarchive_dir(parentd, cfg);
|
||||
unsigned file_count = progress_callback ? count_pack_files(cfg) : 0, done = 0;
|
||||
auto file_callback = progress_callback
|
||||
? [&]() { progress_callback(++done * 100.0 / file_count); }
|
||||
: std::function<void()>{};
|
||||
unarchive_dir(parentd, cfg, file_callback);
|
||||
}
|
||||
|
||||
static void purge_dir(const std::string& path, const config& removelist)
|
||||
|
|
|
@ -33,6 +33,7 @@ class version_info;
|
|||
|
||||
#include "addon/validation.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
@ -153,7 +154,7 @@ bool is_addon_installed(const std::string& addon_name);
|
|||
void archive_addon(const std::string& addon_name, class config& cfg);
|
||||
|
||||
/** Unarchives an add-on from campaignd's retrieved config object. */
|
||||
void unarchive_addon(const class config& cfg);
|
||||
void unarchive_addon(const class config& cfg, std::function<void(unsigned)> progress_callback = {});
|
||||
|
||||
/** Removes the listed files from the addon. */
|
||||
void purge_addon(const config& removelist);
|
||||
|
|
80
src/gui/dialogs/file_progress.cpp
Normal file
80
src/gui/dialogs/file_progress.cpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
Copyright (C) 2021 by Iris Morelle <shadowm@wesnoth.org>
|
||||
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#define GETTEXT_DOMAIN "wesnoth-lib"
|
||||
|
||||
#include "gui/dialogs/file_progress.hpp"
|
||||
|
||||
#include "gui/auxiliary/find_widget.hpp"
|
||||
#include "gui/widgets/button.hpp"
|
||||
#include "gui/dialogs/modal_dialog.hpp"
|
||||
#include "gui/widgets/label.hpp"
|
||||
#include "gui/widgets/progress_bar.hpp"
|
||||
#include "gui/widgets/settings.hpp"
|
||||
#include "gui/widgets/window.hpp"
|
||||
|
||||
namespace gui2::dialogs {
|
||||
|
||||
REGISTER_WINDOW(file_progress)
|
||||
|
||||
const std::string& file_progress::window_id() const
|
||||
{
|
||||
static std::string wid = "file_progress";
|
||||
return wid;
|
||||
}
|
||||
|
||||
file_progress::file_progress(const std::string& title, const std::string& message)
|
||||
: title_(title)
|
||||
, message_(message)
|
||||
, update_time_()
|
||||
{
|
||||
}
|
||||
|
||||
void file_progress::pre_show(window& window)
|
||||
{
|
||||
find_widget<label>(&window, "title", false).set_label(title_);
|
||||
auto& message = find_widget<label>(&window, "message", false);
|
||||
|
||||
message.set_use_markup(true);
|
||||
message.set_label(message_);
|
||||
|
||||
find_widget<button>(&window, "placeholder", false).set_active(false);
|
||||
|
||||
update_time_ = clock::now();
|
||||
}
|
||||
|
||||
void file_progress::update_progress(unsigned value)
|
||||
{
|
||||
auto* window = get_window();
|
||||
|
||||
if(!window) {
|
||||
return;
|
||||
}
|
||||
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::milliseconds;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto now = clock::now();
|
||||
|
||||
if(duration_cast<milliseconds>(now - update_time_) < 120ms) {
|
||||
return;
|
||||
}
|
||||
|
||||
find_widget<progress_bar>(window, "progress", false).set_percentage(value);
|
||||
force_redraw();
|
||||
update_time_ = now;
|
||||
}
|
||||
|
||||
}
|
53
src/gui/dialogs/file_progress.hpp
Normal file
53
src/gui/dialogs/file_progress.hpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Copyright (C) 2021 by Iris Morelle <shadowm@wesnoth.org>
|
||||
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gui/dialogs/modeless_dialog.hpp"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace gui2::dialogs {
|
||||
|
||||
class file_progress : public modeless_dialog
|
||||
{
|
||||
public:
|
||||
file_progress(const std::string& title, const std::string& message);
|
||||
|
||||
template<typename... T>
|
||||
static auto display(T&&... args)
|
||||
{
|
||||
auto instance = std::make_unique<file_progress>(std::forward<T>(args)...);
|
||||
instance->show(true);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void update_progress(unsigned value);
|
||||
|
||||
private:
|
||||
/** Inherited from modeless_dialog. */
|
||||
virtual const std::string& window_id() const override;
|
||||
|
||||
/** Inherited from modeless_dialog. */
|
||||
virtual void pre_show(window& window) override;
|
||||
|
||||
std::string title_;
|
||||
std::string message_;
|
||||
|
||||
using clock = std::chrono::steady_clock;
|
||||
|
||||
std::chrono::time_point<clock> update_time_;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue