taddon_list: initial prototype for new full-window GUI2 addons manager

This is still behind the --new_widgets guard and is not ready for deployment yet
This commit is contained in:
Charles Dang 2016-03-11 01:54:42 +11:00
parent 7bb10ec2d6
commit c6aa92d028
3 changed files with 1355 additions and 465 deletions

File diff suppressed because it is too large Load diff

View file

@ -16,11 +16,21 @@
#include "gui/dialogs/addon_list.hpp"
#include "addon/info.hpp"
#include "addon/state.hpp"
#include "desktop/clipboard.hpp"
#include "desktop/open.hpp"
#include "gettext.hpp"
#include "gui/auxiliary/filter.hpp"
#include "gui/auxiliary/find_widget.tpp"
#include "gui/dialogs/helper.hpp"
#include "gui/widgets/button.hpp"
#include "gui/widgets/label.hpp"
#include "gui/widgets/stacked_widget.hpp"
#include "gui/widgets/drawing.hpp"
#include "gui/widgets/image.hpp"
#ifdef GUI2_EXPERIMENTAL_LISTBOX
#include "gui/widgets/list.hpp"
#else
@ -33,8 +43,16 @@
#include "gui/widgets/window.hpp"
#include "utils/foreach.tpp"
#include "serialization/string_utils.hpp"
#include "formula_string_utils.hpp"
#include "marked-up_text.hpp"
#include "font.hpp"
#include "preferences.hpp"
#include "strftime.hpp"
#include "config.hpp"
#include <boost/bind.hpp>
#include <sstream>
namespace gui2
{
@ -105,6 +123,17 @@ struct filter_transform
const std::vector<std::string> filtertext_;
};
taddon_list::taddon_list(const config& cfg)
: orders_()
, cfg_(cfg)
, cfg_iterators_(cfg_.child_range("campaign"))
, addons_()
, tracking_info_()
, ids_()
{
read_addons_list(cfg, addons_);
}
void taddon_list::on_filtertext_changed(ttext_* textbox, const std::string& text)
{
tlistbox& listbox = find_widget<tlistbox>(textbox->get_window(), "addons", true);
@ -187,22 +216,6 @@ void taddon_list::register_sort_button_numeric(twindow& window, const std::strin
register_sort_button(window, id, boost::bind(&num_up, &cfg_, prop_id, _1, _2), boost::bind(&num_down, &cfg_, prop_id, _1, _2));
}
void taddon_list::expand(tgrid& grid, twidget& w)
{
tselectable_& selectable = dynamic_cast<tselectable_&>(w);
if(selectable.get_value_bool())
{
find_widget<tlabel>(&grid, "description", false)
.set_visible(twidget::tvisible::visible);
}
else
{
find_widget<tlabel>(&grid, "description", false)
.set_visible(twidget::tvisible::invisible);
}
}
namespace {
bool default_sort(const tpane::titem& i1, const tpane::titem& i2)
{
@ -222,6 +235,147 @@ namespace {
}
}
static inline const addon_info& addon_at(const std::string& id, const addons_list& addons)
{
addons_list::const_iterator it = addons.find(id);
assert(it != addons.end());
return it->second;
}
static std::string describe_addon_status(const addon_tracking_info& info)
{
std::string tc, tx;
switch(info.state) {
case ADDON_NONE:
tc = "#a69275";
tx = info.can_publish ? _("addon_state^Published, not installed") : _("addon_state^Not installed");
break;
case ADDON_INSTALLED:
case ADDON_NOT_TRACKED:
// Consider add-ons without version information as installed
// for the main display. Their Description info should elaborate
// on their status.
tc = "#00ff00";
tx = info.can_publish ? _("addon_state^Published") : _("addon_state^Installed");
break;
case ADDON_INSTALLED_UPGRADABLE:
tc = "#ffff00";
tx = info.can_publish ? _("addon_state^Published, upgradable") : _("addon_state^Installed, upgradable");
break;
case ADDON_INSTALLED_OUTDATED:
tc = "#ff7f00";
tx = info.can_publish ? _("addon_state^Published, outdated on server") : _("addon_state^Installed, outdated on server");
break;
case ADDON_INSTALLED_BROKEN:
tc = "#ff0000";
tx = info.can_publish ? _("addon_state^Published, broken") : _("addon_state^Installed, broken");
break;
default:
tc = "#777777";
tx = _("addon_state^Unknown");
}
return "<span color='" + tc + "'>" + tx + "</span>";
}
static std::string colorify_addon_state_string(const std::string& str,
const addon_tracking_info& state)
{
std::string colorname = "";
switch(state.state) {
case ADDON_NONE:
return str;
case ADDON_INSTALLED:
case ADDON_NOT_TRACKED:
colorname = "#00ff00"; // GOOD_COLOR
break;
case ADDON_INSTALLED_UPGRADABLE:
colorname = "#ffff00"; // YELLOW_COLOR/color_upgradable
break;
case ADDON_INSTALLED_OUTDATED:
colorname = "#ff7f00"; // <255,127,0>/color_outdated
break;
case ADDON_INSTALLED_BROKEN:
colorname = "#ff0000"; // BAD_COLOR
break;
default:
colorname = "#777777"; // GRAY_COLOR
break;
}
return "<span color='" + colorname + "'>" + str + "</span>";
}
static std::string describe_addon_state_info(const addon_tracking_info& state)
{
std::string s;
utils::string_map i18n_symbols;
i18n_symbols["local_version"] = state.installed_version.str();
switch(state.state) {
case ADDON_NONE:
if(!state.can_publish) {
s = _("addon_state^Not installed");
} else {
s = _("addon_state^Published, not installed");
}
break;
case ADDON_INSTALLED:
if(!state.can_publish) {
s = _("addon_state^Installed");
} else {
s = _("addon_state^Published");
}
break;
case ADDON_NOT_TRACKED:
if(!state.can_publish) {
s = _("addon_state^Installed, not tracking local version");
} else {
// Published add-ons often don't have local status information,
// hence untracked. This should be considered normal.
s = _("addon_state^Published, not tracking local version");
}
break;
case ADDON_INSTALLED_UPGRADABLE: {
const std::string vstr
= !state.can_publish
? _("addon_state^Installed ($local_version|), "
"upgradable")
: _("addon_state^Published ($local_version| "
"installed), upgradable");
s = utils::interpolate_variables_into_string(vstr, &i18n_symbols);
} break;
case ADDON_INSTALLED_OUTDATED: {
const std::string vstr
= !state.can_publish
? _("addon_state^Installed ($local_version|), "
"outdated on server")
: _("addon_state^Published ($local_version| "
"installed), outdated on server");
s = utils::interpolate_variables_into_string(vstr, &i18n_symbols);
} break;
case ADDON_INSTALLED_BROKEN:
if(!state.can_publish) {
s = _("addon_state^Installed, broken");
} else {
s = _("addon_state^Published, broken");
}
break;
default:
s = _("addon_state^Unknown");
}
return colorify_addon_state_string(s, state);
}
void taddon_list::pre_show(CVideo& /*video*/, twindow& window)
{
if(new_widgets) {
@ -264,45 +418,42 @@ void taddon_list::pre_show(CVideo& /*video*/, twindow& window)
} else {
tlistbox& list = find_widget<tlistbox>(&window, "addons", false);
/**
* @todo do we really want to keep the length limit for the various
* items?
*/
FOREACH(const AUTO & c, cfg_.child_range("campaign"))
{
ids_.push_back(c["name"]);
const addon_info& info = addon_at(ids_.back(), addons_);
tracking_info_[info.id] = get_addon_tracking_info(info);
std::map<std::string, string_map> data;
string_map item;
item["label"] = c["icon"];
item["label"] = info.display_icon();
data.insert(std::make_pair("icon", item));
utf8::string tmp = c["name"];
item["label"] = utf8::truncate(tmp, 20);
item["label"] = info.display_title();
data.insert(std::make_pair("name", item));
tmp = c["version"].str();
item["label"] = utf8::truncate(tmp, 12);
item["label"] = describe_addon_status(tracking_info_[info.id]);
item["use_markup"] = "true";
data.insert(std::make_pair("installation_status", item));
item["label"] = info.version.str();
data.insert(std::make_pair("version", item));
tmp = c["author"].str();
item["label"] = utf8::truncate(tmp, 16);
// utf8::truncate(tmp, 16)
item["label"] = info.author;
data.insert(std::make_pair("author", item));
item["label"] = c["downloads"];
data.insert(std::make_pair("downloads", item));
item["label"] = c["size"];
item["label"] = size_display_string(info.size);
data.insert(std::make_pair("size", item));
item["label"] = c["description"];
data.insert(std::make_pair("description", item));
item["label"] = lexical_cast<std::string>(info.downloads);
data.insert(std::make_pair("downloads", item));
item["label"] = info.display_type();
data.insert(std::make_pair("type", item));
list.add_row(data);
unsigned index = list.get_item_count() - 1;
ttoggle_button& button = find_widget<ttoggle_button>(list.get_row_grid(list.get_item_count() - 1), "expand", true);
tgrid& lower_grid = find_widget<tgrid>(list.get_row_grid(list.get_item_count() - 1), "description_grid", false);
lower_grid.set_visible(twidget::tvisible::invisible);
button.set_callback_state_change(boost::bind(show_desc_impl, index, boost::ref(list), boost::ref(lower_grid), _1));
}
register_sort_button_alphabetical(window, "sort_name", "name");
register_sort_button_alphabetical(window, "sort_author", "author");
@ -314,6 +465,117 @@ void taddon_list::pre_show(CVideo& /*video*/, twindow& window)
filter_box.set_text_changed_callback(
boost::bind(&taddon_list::on_filtertext_changed, this, _1, _2));
#ifdef GUI2_EXPERIMENTAL_LISTBOX
connect_signal_notify_modified(list,
boost::bind(&taddon_list::on_addon_select,
*this,
boost::ref(window)));
#else
list.set_callback_value_change(
dialog_callback<taddon_list, &taddon_list::on_addon_select>);
#endif
tbutton& url_go_button = find_widget<tbutton>(&window, "url_go", false);
tbutton& url_copy_button = find_widget<tbutton>(&window, "url_copy", false);
ttext_box& url_textbox = find_widget<ttext_box>(&window, "url", false);
url_textbox.set_active(false);
if (!desktop::clipboard::available()) {
url_copy_button.set_active(false);
url_copy_button.set_tooltip(_("Clipboard support not found, contact your packager"));
}
if(!desktop::open_object_is_supported()) {
// No point in displaying the button on platforms that can't do
// open_object().
url_go_button.set_visible(tcontrol::tvisible::invisible);
}
//connect_signal_mouse_left_click(
// url_go_button,
// boost::bind(&taddon_description::browse_url_callback, this));
//connect_signal_mouse_left_click(
// url_copy_button,
// boost::bind(&taddon_description::copy_url_callback, this));
on_addon_select(window);
}
}
static std::string format_addon_time(time_t time)
{
if(time) {
char buf[1024] = { 0 };
struct std::tm* const t = std::localtime(&time);
const char* format = preferences::use_twelve_hour_clock_format()
? "%Y-%m-%d %I:%M %p"
: "%Y-%m-%d %H:%M";
if(util::strftime(buf, sizeof(buf), format, t)) {
return buf;
}
}
return utils::unicode_em_dash;
}
/**
* Retrieves an element from the given associative container or dies in some
* way.
*
* It fails an @a assert() check or throws an exception if the requested element
* does not exist.
*
* @return An element from the container that is guranteed to have existed
* before running this function.
*/
template <typename MapT>
static typename MapT::mapped_type const& const_at(typename MapT::key_type const& key,
MapT const& map)
{
typename MapT::const_iterator it = map.find(key);
if(it == map.end()) {
assert(it != map.end());
throw std::out_of_range(
"const_at()"); // Shouldn't get here without disabling assert()
}
return it->second;
}
void taddon_list::on_addon_select(twindow& window)
{
const int index = find_widget<tlistbox>(&window, "addons", false).get_selected_row();
const addon_info& info = addon_at(ids_[index], addons_);
find_widget<tdrawing>(&window, "image", false).set_label(info.display_icon());
find_widget<tcontrol>(&window, "title", false).set_label(info.display_title());
find_widget<tcontrol>(&window, "description", false).set_label(info.description);
find_widget<tcontrol>(&window, "version", false).set_label(info.version.str());
find_widget<tcontrol>(&window, "author", false).set_label(info.author);
find_widget<tcontrol>(&window, "type", false).set_label(info.display_type());
tcontrol& status = find_widget<tcontrol>(&window, "status", false);
status.set_label(describe_addon_state_info(tracking_info_[info.id]));
status.set_use_markup(true);
find_widget<tcontrol>(&window, "size", false).set_label(size_display_string(info.size));
find_widget<tcontrol>(&window, "downloads", false).set_label(lexical_cast<std::string>(info.downloads));
find_widget<tcontrol>(&window, "created", false).set_label(format_addon_time(info.created));
find_widget<tcontrol>(&window, "updated", false).set_label(format_addon_time(info.updated));
const std::string& feedback_url = info.feedback_url;
if(!feedback_url.empty()) {
find_widget<tstacked_widget>(&window, "feedback_stack", false).select_layer(1);
find_widget<ttext_box>(&window, "url", false).set_value(feedback_url);
} else {
find_widget<tstacked_widget>(&window, "feedback_stack", false).select_layer(0);
}
}
@ -339,13 +601,12 @@ void taddon_list::create_campaign(tpane& pane, const config& campaign)
item["label"] = utf8::truncate(tmp, 16);
data.insert(std::make_pair("author", item));
item["label"] = utils::si_string(campaign["size"], true, _("unit_byte^B"));
data.insert(std::make_pair("size", item));
item["label"] = campaign["downloads"];
data.insert(std::make_pair("downloads", item));
item["label"] = utils::si_string(campaign["size"], true, _("unit_byte^B"));
data.insert(std::make_pair("size", item));
item["label"] = campaign["description"];
data.insert(std::make_pair("description", item));
@ -367,16 +628,6 @@ void taddon_list::create_campaign(tpane& pane, const config& campaign)
tgrid* grid = pane.grid(id);
assert(grid);
ttoggle_button* expand
= find_widget<ttoggle_button>(grid, "expand", false, false);
if(expand) {
expand->set_callback_state_change(
boost::bind(&taddon_list::expand, this, boost::ref(*grid), _1));
find_widget<tlabel>(grid, "description", false)
.set_visible(twidget::tvisible::invisible);
}
}
void taddon_list::load(tpane& pane)

View file

@ -15,8 +15,11 @@
#ifndef GUI_DIALOGS_ADDON_LIST_HPP_INCLUDED
#define GUI_DIALOGS_ADDON_LIST_HPP_INCLUDED
#include "addon/info.hpp"
#include "addon/state.hpp"
#include "gui/dialogs/dialog.hpp"
#include "../widgets/generator.hpp"
#include "gui/widgets/generator.hpp"
#include "gui/widgets/pane.hpp"
#include "config.hpp" // needed for config::const_child_itors
@ -31,10 +34,7 @@ class tselectable_;
class taddon_list : public tdialog
{
public:
explicit taddon_list(const config& cfg)
: orders_(), cfg_(cfg), cfg_iterators_(cfg_.child_range("campaign"))
{
}
explicit taddon_list(const config& cfg);
private:
void register_sort_button(twindow& window, const std::string& id, const tgenerator_::torder_func& up, const tgenerator_::torder_func& down);
@ -52,7 +52,7 @@ private:
* expand.
*/
void expand(tgrid& grid, twidget& w);
void on_addon_select(twindow& window);
/** Inherited from tdialog, implemented by REGISTER_DIALOG. */
virtual const std::string& window_id() const;
@ -76,6 +76,12 @@ private:
*/
config::const_child_itors cfg_iterators_;
addons_list addons_;
addons_tracking_list tracking_info_;
std::vector<std::string> ids_;
/**
* Debug function to load a single campaign.
*