widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #09989
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
GunChleoc has proposed merging lp:~widelands-dev/widelands/savegame-menu into lp:widelands.
Commit message:
Refactored load- and savegame screens
- New common class GameDetails to show information about savegames, analogous to MapDetails
- More informative savegame screen
- Load/Savegame screens now use 100% Box layout
- Fixed drawing of frame for scaled Minimap images
Requested reviews:
Widelands Developers (widelands-dev)
Related bugs:
Bug #1377660 in widelands: "Fullscreen Menu overhaul"
https://bugs.launchpad.net/widelands/+bug/1377660
For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/savegame-menu/+merge/322924
Complete overhaul of Load Game, Watch Replay and Save Game screens
--
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/savegame-menu into lp:widelands.
=== modified file 'src/game_io/game_preload_packet.cc'
--- src/game_io/game_preload_packet.cc 2017-01-25 18:55:59 +0000
+++ src/game_io/game_preload_packet.cc 2017-04-21 07:38:25 +0000
@@ -46,9 +46,11 @@
constexpr uint16_t kCurrentPacketVersion = 6;
constexpr const char* kMinimapFilename = "minimap.png";
+// Win condition localization can come from the 'widelands' or 'win_conditions' textdomain.
std::string GamePreloadPacket::get_localized_win_condition() const {
+ std::string result = _(win_condition_);
i18n::Textdomain td("win_conditions");
- return _(win_condition_);
+ return _(result);
}
void GamePreloadPacket::read(FileSystem& fs, Game&, MapObjectLoader* const) {
=== modified file 'src/ui_basic/box.cc'
--- src/ui_basic/box.cc 2017-03-02 08:43:30 +0000
+++ src/ui_basic/box.cc 2017-04-21 07:38:25 +0000
@@ -88,6 +88,12 @@
inner_spacing_ = size;
}
+void Box::set_max_size(int w, int h) {
+ max_x_ = w;
+ max_y_ = h;
+ set_desired_size(w, h);
+}
+
/**
* Compute the desired size based on our children. This assumes that the
* infinite space is zero, and is later on also re-used to calculate the
=== modified file 'src/ui_basic/box.h'
--- src/ui_basic/box.h 2017-02-25 13:27:40 +0000
+++ src/ui_basic/box.h 2017-04-21 07:38:25 +0000
@@ -66,6 +66,8 @@
void set_min_desired_breadth(uint32_t min);
void set_inner_spacing(uint32_t size);
+ /// Sets the maximum dimensions and calls set_desired_size()
+ void set_max_size(int w, int h);
protected:
void layout() override;
=== modified file 'src/ui_basic/icon.cc'
--- src/ui_basic/icon.cc 2017-01-25 18:55:59 +0000
+++ src/ui_basic/icon.cc 2017-04-21 07:38:25 +0000
@@ -54,16 +54,16 @@
if (pic_) {
const float scale = std::min(1.f, std::min(static_cast<float>(get_w()) / pic_->width(),
static_cast<float>(get_h()) / pic_->height()));
-
- const float width = scale * get_w();
- const float height = scale * get_h();
- const float x = (get_w() - width) / 2.f;
- const float y = (get_h() - height) / 2.f;
+ // We need to be pixel perfect, so we use ints.
+ const int width = scale * get_w();
+ const int height = scale * get_h();
+ const int x = (get_w() - width) / 2;
+ const int y = (get_h() - height) / 2;
dst.blitrect_scale(Rectf(x, y, width, height), pic_,
Recti(0, 0, pic_->width(), pic_->height()), 1., BlendMode::UseAlpha);
- }
- if (draw_frame_) {
- dst.draw_rect(Rectf(0.f, 0.f, get_w(), get_h()), framecolor_);
- }
+ if (draw_frame_) {
+ dst.draw_rect(Rectf(x, y, width, height), framecolor_);
+ }
+ }
}
}
=== modified file 'src/ui_basic/icon.h'
--- src/ui_basic/icon.h 2017-01-25 18:55:59 +0000
+++ src/ui_basic/icon.h 2017-04-21 07:38:25 +0000
@@ -33,6 +33,10 @@
Icon(Panel* parent, int32_t x, int32_t y, int32_t w, int32_t h, const Image* picture_id);
void set_icon(const Image* picture_id);
+ const Image* icon() const {
+ return pic_;
+ }
+
void set_frame(const RGBColor& color);
void set_no_frame();
=== modified file 'src/ui_basic/multilinetextarea.cc'
--- src/ui_basic/multilinetextarea.cc 2017-03-04 18:02:23 +0000
+++ src/ui_basic/multilinetextarea.cc 2017-04-21 07:38:25 +0000
@@ -51,6 +51,7 @@
scrollmode_(scroll_mode),
pic_background_(nullptr) {
assert(scrollmode_ == MultilineTextarea::ScrollMode::kNoScrolling || Scrollbar::kSize <= w);
+ set_scrollmode(scroll_mode);
set_thinks(false);
scrollbar_.moved.connect(boost::bind(&MultilineTextarea::scrollpos_changed, this, _1));
@@ -60,9 +61,6 @@
as_uifont(UI::g_fh1->fontset()->representative_character(), UI_FONT_SIZE_SMALL))
->height());
scrollbar_.set_steps(1);
- scrollbar_.set_force_draw(scrollmode_ == ScrollMode::kScrollNormalForced ||
- scrollmode_ == ScrollMode::kScrollLogForced);
-
layout();
}
@@ -192,6 +190,12 @@
void MultilineTextarea::set_background(const Image* background) {
pic_background_ = background;
}
+void MultilineTextarea::set_scrollmode(MultilineTextarea::ScrollMode scroll_mode) {
+ scrollmode_ = scroll_mode;
+ scrollbar_.set_force_draw(scrollmode_ == ScrollMode::kScrollNormalForced ||
+ scrollmode_ == ScrollMode::kScrollLogForced);
+ layout();
+}
std::string MultilineTextarea::make_richtext() {
std::string temp = richtext_escape(text_);
=== modified file 'src/ui_basic/multilinetextarea.h'
--- src/ui_basic/multilinetextarea.h 2017-02-23 19:38:51 +0000
+++ src/ui_basic/multilinetextarea.h 2017-04-21 07:38:25 +0000
@@ -79,6 +79,7 @@
void scroll_to_top();
void set_background(const Image* background);
+ void set_scrollmode(MultilineTextarea::ScrollMode scroll_mode);
protected:
void layout() override;
=== modified file 'src/ui_fsmenu/load_map_or_game.cc'
--- src/ui_fsmenu/load_map_or_game.cc 2017-01-25 18:55:59 +0000
+++ src/ui_fsmenu/load_map_or_game.cc 2017-04-21 07:38:25 +0000
@@ -38,7 +38,7 @@
padding_(4),
indent_(10),
label_height_(20),
- right_column_margin_(15),
+ right_column_margin_(16),
// Main buttons
back_(this, "back", 0, 0, 0, 0, g_gr->images().get("images/ui_basic/but0.png"), _("Back")),
=== modified file 'src/ui_fsmenu/loadgame.cc'
--- src/ui_fsmenu/loadgame.cc 2017-03-05 17:55:29 +0000
+++ src/ui_fsmenu/loadgame.cc 2017-04-21 07:38:25 +0000
@@ -19,667 +19,117 @@
#include "ui_fsmenu/loadgame.h"
-#include <algorithm>
-#include <cstdio>
-#include <ctime>
-#include <memory>
-
-#include <boost/algorithm/string/predicate.hpp>
-#include <boost/format.hpp>
-
#include "base/i18n.h"
-#include "base/log.h"
-#include "base/time_string.h"
-#include "game_io/game_loader.h"
-#include "game_io/game_preload_packet.h"
-#include "graphic/graphic.h"
-#include "graphic/image_io.h"
-#include "graphic/text_constants.h"
-#include "graphic/texture.h"
-#include "helper.h"
-#include "io/filesystem/layered_filesystem.h"
-#include "logic/game.h"
-#include "logic/game_controller.h"
-#include "logic/game_settings.h"
-#include "logic/replay.h"
-#include "ui_basic/icon.h"
-#include "ui_basic/messagebox.h"
-
-// TODO(GunChleoc): Arabic: line height broken for descriptions for Arabic.
-namespace {
-
-// This function concatenates the filename and localized map name for a savegame/replay.
-// If the filename starts with the map name, the map name is omitted.
-// It also prefixes autosave files with a numbered and localized "Autosave" prefix.
-std::string map_filename(const std::string& filename, const std::string& mapname) {
- std::string result = FileSystem::filename_without_ext(filename.c_str());
- std::string mapname_localized;
- {
- i18n::Textdomain td("maps");
- mapname_localized = _(mapname);
- }
-
- if (boost::starts_with(result, "wl_autosave")) {
- std::vector<std::string> autosave_name;
- boost::split(autosave_name, result, boost::is_any_of("_"));
- if (autosave_name.empty() || autosave_name.size() < 3) {
- /** TRANSLATORS: %1% is a map's name. */
- result = (boost::format(_("Autosave: %1%")) % mapname_localized).str();
- } else {
- /** TRANSLATORS: %1% is a number, %2% a map's name. */
- result = (boost::format(_("Autosave %1%: %2%")) % autosave_name.back() % mapname_localized)
- .str();
- }
- } else if (!(boost::starts_with(result, mapname) ||
- boost::starts_with(result, mapname_localized))) {
- /** TRANSLATORS: %1% is a filename, %2% a map's name. */
- result = (boost::format(_("%1% (%2%)")) % result % mapname_localized).str();
- }
- return result;
-}
-
-} // namespace
+#include "wui/gamedetails.h"
FullscreenMenuLoadGame::FullscreenMenuLoadGame(Widelands::Game& g,
GameSettingsProvider* gsp,
GameController* gc,
bool is_replay)
: FullscreenMenuLoadMapOrGame(),
- table_(this,
- tablex_,
- tabley_,
- tablew_,
- tableh_,
- g_gr->images().get("images/ui_basic/but3.png"),
- UI::TableRows::kMultiDescending),
-
- is_replay_(is_replay),
+
+ main_box_(this, 0, 0, UI::Box::Vertical),
+ info_box_(&main_box_, 0, 0, UI::Box::Horizontal),
+
// Main title
- title_(this,
- get_w() / 2,
- tabley_ / 3,
+ title_(&main_box_,
+ 0,
+ 0,
is_replay_ ? _("Choose a replay") : _("Choose a saved game"),
UI::Align::kCenter),
- // Savegame description
- label_mapname_(this, right_column_x_, tabley_),
- ta_mapname_(this,
- right_column_x_ + indent_,
- get_y_from_preceding(label_mapname_) + padding_,
- get_right_column_w(right_column_x_ + indent_),
- 2 * label_height_ - padding_),
-
- label_gametime_(this, right_column_x_, get_y_from_preceding(ta_mapname_) + 2 * padding_),
- ta_gametime_(this,
- right_column_tab_,
- label_gametime_.get_y(),
- get_right_column_w(right_column_tab_),
- label_height_),
-
- label_players_(this, right_column_x_, get_y_from_preceding(ta_gametime_)),
- ta_players_(this,
- right_column_tab_,
- label_players_.get_y(),
- get_right_column_w(right_column_tab_),
- label_height_),
-
- label_version_(this, right_column_x_, get_y_from_preceding(ta_players_)),
- ta_version_(this, right_column_tab_, label_version_.get_y()),
-
- label_win_condition_(this, right_column_x_, get_y_from_preceding(ta_version_) + 3 * padding_),
- ta_win_condition_(this,
- right_column_x_ + indent_,
- get_y_from_preceding(label_win_condition_) + padding_,
- get_right_column_w(right_column_x_ + indent_),
- label_height_),
-
- delete_(this,
- "delete",
- right_column_x_,
- buty_ - buth_ - 2 * padding_,
- butw_,
- buth_,
- g_gr->images().get("images/ui_basic/but0.png"),
- _("Delete")),
-
- ta_long_generic_message_(this,
- right_column_x_,
- get_y_from_preceding(ta_mapname_) + 2 * padding_,
- get_right_column_w(right_column_x_),
- delete_.get_y() - get_y_from_preceding(ta_mapname_) - 6 * padding_),
-
- minimap_y_(get_y_from_preceding(ta_win_condition_) + 3 * padding_),
- minimap_w_(get_right_column_w(right_column_x_)),
- minimap_h_(delete_.get_y() - get_y_from_preceding(ta_win_condition_) - 6 * padding_),
- minimap_icon_(this,
- right_column_x_,
- get_y_from_preceding(ta_win_condition_) + 3 * padding_,
- minimap_w_,
- minimap_h_,
- nullptr),
-
- // "Data container" for the savegame information
+ load_or_save_(&info_box_,
+ g,
+ is_replay ? LoadOrSaveGame::FileType::kReplay : gsp->settings().multiplayer ?
+ LoadOrSaveGame::FileType::kGameMultiPlayer :
+ LoadOrSaveGame::FileType::kGameSinglePlayer,
+ GameDetails::Style::kFsMenu,
+ true),
+
+ is_replay_(is_replay),
game_(g),
settings_(gsp),
ctrl_(gc) {
- title_.set_fontsize(UI_FONT_SIZE_BIG);
- ta_gametime_.set_tooltip(_("The time that elapsed inside this game"));
- ta_players_.set_tooltip(_("The number of players"));
- ta_version_.set_tooltip(_("The version of Widelands that this game was played under"));
- ta_win_condition_.set_tooltip(_("The win condition that was set for this game"));
+
+ // Make sure that we have some space to work with.
+ main_box_.set_size(get_w(), get_w());
+
+ main_box_.add_space(padding_);
+ main_box_.add_inf_space();
+ main_box_.add(&title_, UI::Box::Resizing::kAlign, UI::Align::kCenter);
+ main_box_.add_inf_space();
+ main_box_.add_inf_space();
+ main_box_.add(&info_box_, UI::Box::Resizing::kExpandBoth);
+ main_box_.add_space(padding_);
+
+ info_box_.add(&load_or_save_.table(), UI::Box::Resizing::kFullSize);
+ info_box_.add_space(right_column_margin_);
+ info_box_.add(load_or_save_.game_details(), UI::Box::Resizing::kFullSize);
+
+ button_spacer_ = new UI::Panel(load_or_save_.game_details()->button_box(), 0, 0, 0, 0);
+ load_or_save_.game_details()->button_box()->add(button_spacer_);
+
+ layout();
+
+ ok_.set_enabled(false);
+ set_thinks(false);
if (is_replay_) {
back_.set_tooltip(_("Return to the main menu"));
ok_.set_tooltip(_("Load this replay"));
- ta_mapname_.set_tooltip(_("The map that this replay is based on"));
- delete_.set_tooltip(_("Delete this replay"));
} else {
- back_.set_tooltip(_("Return to the single player menu"));
+ back_.set_tooltip(gsp->settings().multiplayer ? _("Return to the multiplayer game setup") :
+ _("Return to the single player menu"));
ok_.set_tooltip(_("Load this game"));
- ta_mapname_.set_tooltip(_("The map that this game is based on"));
- delete_.set_tooltip(_("Delete this game"));
}
- set_thinks(false);
- minimap_icon_.set_visible(false);
back_.sigclicked.connect(boost::bind(&FullscreenMenuLoadGame::clicked_back, boost::ref(*this)));
ok_.sigclicked.connect(boost::bind(&FullscreenMenuLoadGame::clicked_ok, boost::ref(*this)));
- delete_.sigclicked.connect(
- boost::bind(&FullscreenMenuLoadGame::clicked_delete, boost::ref(*this)));
- table_.add_column(130, _("Save Date"), _("The date this game was saved"));
- if (is_replay_ || settings_->settings().multiplayer) {
- std::vector<std::string> modes;
- if (is_replay_) {
- /** TRANSLATORS: Tooltip for the "Mode" column when choosing a game/replay to load. */
- /** TRANSLATORS: Make sure that you keep consistency in your translation. */
- modes.push_back(_("SP = Single Player"));
- }
- /** TRANSLATORS: Tooltip for the "Mode" column when choosing a game/replay to load. */
- /** TRANSLATORS: Make sure that you keep consistency in your translation. */
- modes.push_back(_("MP = Multiplayer"));
- /** TRANSLATORS: Tooltip for the "Mode" column when choosing a game/replay to load. */
- /** TRANSLATORS: Make sure that you keep consistency in your translation. */
- modes.push_back(_("H = Multiplayer (Host)"));
- const std::string mode_tooltip_1 =
- /** TRANSLATORS: Tooltip for the "Mode" column when choosing a game/replay to load. */
- /** TRANSLATORS: %s is a list of game modes. */
- ((boost::format(_("Game Mode: %s.")) %
- i18n::localize_list(modes, i18n::ConcatenateWith::COMMA)))
- .str();
- const std::string mode_tooltip_2 = _("Numbers are the number of players.");
-
- table_.add_column(
- 65,
- /** TRANSLATORS: Game Mode table column when choosing a game/replay to load. */
- /** TRANSLATORS: Keep this to 5 letters maximum. */
- /** TRANSLATORS: A tooltip will explain if you need to use an abbreviation. */
- _("Mode"), (boost::format("%s %s") % mode_tooltip_1 % mode_tooltip_2).str());
- }
- table_.add_column(0, _("Description"),
- _("The filename that the game was saved under followed by the map’s name, "
- "or the map’s name followed by the last objective achieved."),
- UI::Align::kLeft, UI::TableColumnType::kFlexible);
- table_.set_column_compare(
- 0, boost::bind(&FullscreenMenuLoadGame::compare_date_descending, this, _1, _2));
- table_.selected.connect(boost::bind(&FullscreenMenuLoadGame::entry_selected, this));
- table_.double_clicked.connect(
+ load_or_save_.table().selected.connect(
+ boost::bind(&FullscreenMenuLoadGame::entry_selected, this));
+ load_or_save_.table().double_clicked.connect(
boost::bind(&FullscreenMenuLoadGame::clicked_ok, boost::ref(*this)));
- table_.set_sort_column(0);
- table_.focus();
+
fill_table();
+ if (!load_or_save_.table().empty()) {
+ load_or_save_.table().select(0);
+ }
}
void FullscreenMenuLoadGame::layout() {
- // TODO(GunChleoc): Implement when we have box layout for the details.
- table_.layout();
-}
-
-void FullscreenMenuLoadGame::think() {
- if (ctrl_) {
- ctrl_->think();
- }
-}
-
-// Reverse default sort order for save date column
-bool FullscreenMenuLoadGame::compare_date_descending(uint32_t rowa, uint32_t rowb) {
- const SavegameData& r1 = games_data_[table_[rowa]];
- const SavegameData& r2 = games_data_[table_[rowb]];
-
- return r1.savetimestamp < r2.savetimestamp;
+ FullscreenMenuLoadMapOrGame::layout();
+ main_box_.set_size(get_w() - 2 * tablex_, tabley_ + tableh_ + padding_);
+ main_box_.set_pos(Vector2i(tablex_, 0));
+ title_.set_fontsize(fs_big());
+ load_or_save_.delete_button()->set_desired_size(butw_, buth_);
+ button_spacer_->set_desired_size(butw_, buth_ + 2 * padding_);
+ load_or_save_.table().set_desired_size(tablew_, tableh_);
+ load_or_save_.game_details()->set_max_size(
+ main_box_.get_w() - tablew_ - right_column_margin_, tableh_);
}
void FullscreenMenuLoadGame::clicked_ok() {
- if (!table_.has_selection()) {
+ if (load_or_save_.table().selections().size() != 1) {
return;
}
- const SavegameData& gamedata = games_data_[table_.get_selected()];
- if (gamedata.errormessage.empty()) {
- filename_ = gamedata.filename;
+
+ const SavegameData* gamedata = load_or_save_.entry_selected();
+ if (gamedata && gamedata->errormessage.empty()) {
+ filename_ = gamedata->filename;
end_modal<FullscreenMenuBase::MenuTarget>(FullscreenMenuBase::MenuTarget::kOk);
}
}
-void FullscreenMenuLoadGame::clicked_delete() {
- if (!table_.has_selection()) {
- return;
- }
- std::set<uint32_t> selections = table_.selections();
- size_t no_selections = selections.size();
- std::string message;
- if (no_selections > 1) {
- if (is_replay_) {
- message = (boost::format(ngettext("Do you really want to delete this %d replay?",
- "Do you really want to delete these %d replays?",
- no_selections)) %
- no_selections)
- .str();
- } else {
- message = (boost::format(ngettext("Do you really want to delete this %d game?",
- "Do you really want to delete these %d games?",
- no_selections)) %
- no_selections)
- .str();
- }
- message = (boost::format("%s\n%s") % message % filename_list_string()).str();
-
- } else {
- const SavegameData& gamedata = games_data_[table_.get_selected()];
-
- message = (boost::format("%s %s\n") % label_mapname_.get_text() % gamedata.mapname).str();
-
- message = (boost::format("%s %s %s\n") % message % label_win_condition_.get_text() %
- gamedata.wincondition)
- .str();
-
- message =
- (boost::format("%s %s %s\n") % message % _("Save Date:") % gamedata.savedatestring).str();
-
- message = (boost::format("%s %s %s\n") % message % label_gametime_.get_text() %
- gametimestring(gamedata.gametime))
- .str();
-
- message =
- (boost::format("%s %s %s\n\n") % message % label_players_.get_text() % gamedata.nrplayers)
- .str();
-
- message = (boost::format("%s %s %s\n") % message % _("Filename:") % gamedata.filename).str();
-
- if (is_replay_) {
- message =
- (boost::format("%s\n\n%s") % _("Do you really want to delete this replay?") % message)
- .str();
- } else {
- message =
- (boost::format("%s\n\n%s") % _("Do you really want to delete this game?") % message)
- .str();
- }
- }
-
- UI::WLMessageBox confirmationBox(
- this, ngettext("Confirm deleting file", "Confirm deleting files", no_selections), message,
- UI::WLMessageBox::MBoxType::kOkCancel);
-
- if (confirmationBox.run<UI::Panel::Returncodes>() == UI::Panel::Returncodes::kOk) {
- for (const uint32_t index : selections) {
- const std::string& deleteme = games_data_[table_.get(table_.get_record(index))].filename;
- g_fs->fs_unlink(deleteme);
- if (is_replay_) {
- g_fs->fs_unlink(deleteme + WLGF_SUFFIX);
- }
- }
- fill_table();
- }
-}
-
-std::string FullscreenMenuLoadGame::filename_list_string() {
- std::set<uint32_t> selections = table_.selections();
- boost::format message;
- int counter = 0;
- for (const uint32_t index : selections) {
- ++counter;
- // TODO(GunChleoc): We can exceed the texture size for the font renderer,
- // so we have to restrict this for now.
- if (counter > 50) {
- message = boost::format("%s\n%s") % message % "...";
- break;
- }
- const SavegameData& gamedata = games_data_[table_.get(table_.get_record(index))];
-
- if (gamedata.errormessage.empty()) {
- message =
- boost::format("%s\n%s") % message %
- /** TRANSLATORS %1% = map name, %2% = save date. */
- (boost::format(_("%1%, saved on %2%")) % gamedata.mapname % gamedata.savedatestring);
- } else {
- message = boost::format("%s\n%s") % message % gamedata.filename;
- }
- }
- return message.str();
-}
-
-bool FullscreenMenuLoadGame::set_has_selection() {
- bool has_selection = table_.selections().size() < 2;
- ok_.set_enabled(has_selection);
- delete_.set_enabled(table_.has_selection());
-
- if (!has_selection) {
- label_mapname_.set_text(std::string());
- label_gametime_.set_text(std::string());
- label_players_.set_text(std::string());
- label_version_.set_text(std::string());
- label_win_condition_.set_text(std::string());
-
- ta_mapname_.set_text(std::string());
- ta_gametime_.set_text(std::string());
- ta_players_.set_text(std::string());
- ta_version_.set_text(std::string());
- ta_win_condition_.set_text(std::string());
- minimap_icon_.set_icon(nullptr);
- minimap_icon_.set_visible(false);
- minimap_icon_.set_no_frame();
- minimap_image_.reset();
- } else {
- label_mapname_.set_text(_("Map Name:"));
- label_gametime_.set_text(_("Gametime:"));
- label_players_.set_text(_("Players:"));
- label_win_condition_.set_text(_("Win Condition:"));
- }
- return has_selection;
-}
-
void FullscreenMenuLoadGame::entry_selected() {
- size_t selections = table_.selections().size();
- if (set_has_selection()) {
-
- const SavegameData& gamedata = games_data_[table_.get_selected()];
- ta_long_generic_message_.set_text(gamedata.errormessage);
-
- if (gamedata.errormessage.empty()) {
- ta_long_generic_message_.set_visible(false);
- ta_mapname_.set_text(gamedata.mapname);
- ta_gametime_.set_text(gametimestring(gamedata.gametime));
-
- uint8_t number_of_players = gamedata.nrplayers;
- if (number_of_players > 0) {
- ta_players_.set_text(
- (boost::format("%u") % static_cast<unsigned int>(number_of_players)).str());
- } else {
- label_players_.set_text("");
- ta_players_.set_text("");
- }
-
- if (gamedata.version.empty()) {
- label_version_.set_text("");
- ta_version_.set_text("");
- } else {
- label_version_.set_text(_("Widelands Version:"));
- ta_version_.set_text(gamedata.version);
- }
-
- {
- i18n::Textdomain td("win_conditions");
- ta_win_condition_.set_text(_(gamedata.wincondition));
- }
-
- std::string minimap_path = gamedata.minimap_path;
- // Delete former image
- minimap_icon_.set_icon(nullptr);
- minimap_icon_.set_visible(false);
- minimap_icon_.set_no_frame();
- minimap_image_.reset();
- // Load the new one
- if (!minimap_path.empty()) {
- try {
- // Load the image
- minimap_image_ = load_image(
- minimap_path,
- std::unique_ptr<FileSystem>(g_fs->make_sub_file_system(gamedata.filename)).get());
-
- // Scale it
- double scale = double(minimap_w_) / minimap_image_->width();
- double scaleY = double(minimap_h_) / minimap_image_->height();
- if (scaleY < scale) {
- scale = scaleY;
- }
- if (scale > 1.0)
- scale = 1.0; // Don't make the image too big; fuzziness will result
- uint16_t w = scale * minimap_image_->width();
- uint16_t h = scale * minimap_image_->height();
-
- // Center the minimap in the available space
- int32_t xpos =
- right_column_x_ + (get_w() - right_column_margin_ - w - right_column_x_) / 2;
- int32_t ypos = minimap_y_;
-
- // Set small minimaps higher up for a more harmonious look
- if (h < minimap_h_ * 2 / 3) {
- ypos += (minimap_h_ - h) / 3;
- } else {
- ypos += (minimap_h_ - h) / 2;
- }
-
- minimap_icon_.set_size(w, h);
- minimap_icon_.set_pos(Vector2i(xpos, ypos));
- minimap_icon_.set_frame(UI_FONT_CLR_FG);
- minimap_icon_.set_visible(true);
- minimap_icon_.set_icon(minimap_image_.get());
- } catch (const std::exception& e) {
- log("Failed to load the minimap image : %s\n", e.what());
- }
- }
- } else {
- label_mapname_.set_text(_("Filename:"));
- ta_mapname_.set_text(gamedata.mapname);
- label_gametime_.set_text("");
- ta_gametime_.set_text("");
- label_players_.set_text("");
- ta_players_.set_text("");
- label_version_.set_text("");
- ta_version_.set_text("");
- label_win_condition_.set_text("");
- ta_win_condition_.set_text("");
-
- minimap_icon_.set_icon(nullptr);
- minimap_icon_.set_visible(false);
- minimap_icon_.set_no_frame();
- minimap_image_.reset();
-
- ta_long_generic_message_.set_visible(true);
- ok_.set_enabled(false);
- }
- } else if (selections > 1) {
- label_mapname_.set_text(
- (boost::format(ngettext("Selected %d file:", "Selected %d files:", selections)) %
- selections)
- .str());
- ta_long_generic_message_.set_visible(true);
- ta_long_generic_message_.set_text(filename_list_string());
+ ok_.set_enabled(load_or_save_.table().selections().size() == 1);
+ load_or_save_.delete_button()->set_enabled(load_or_save_.has_selection());
+ if (load_or_save_.has_selection()) {
+ load_or_save_.entry_selected();
}
}
-/**
- * Fill the file list
- */
void FullscreenMenuLoadGame::fill_table() {
-
- games_data_.clear();
- table_.clear();
-
- FilenameSet gamefiles;
-
- if (is_replay_) {
- gamefiles = filter(g_fs->list_directory(REPLAY_DIR),
- [](const std::string& fn) { return boost::ends_with(fn, REPLAY_SUFFIX); });
- } else {
- gamefiles = g_fs->list_directory("save");
- }
-
- Widelands::GamePreloadPacket gpdp;
-
- for (const std::string& gamefilename : gamefiles) {
- if (gamefilename == "save/campvis" || gamefilename == "save\\campvis") {
- continue;
- }
-
- SavegameData gamedata;
-
- std::string savename = gamefilename;
- if (is_replay_)
- savename += WLGF_SUFFIX;
-
- if (!g_fs->file_exists(savename.c_str())) {
- continue;
- }
-
- gamedata.filename = gamefilename;
-
- try {
- Widelands::GameLoader gl(savename.c_str(), game_);
- gl.preload_game(gpdp);
-
- gamedata.gametype = gpdp.get_gametype();
-
- if (!is_replay_) {
- if (settings_->settings().multiplayer) {
- if (gamedata.gametype == GameController::GameType::SINGLEPLAYER) {
- continue;
- }
- } else if (gamedata.gametype > GameController::GameType::SINGLEPLAYER) {
- continue;
- }
- }
-
- gamedata.mapname = gpdp.get_mapname();
- gamedata.gametime = gpdp.get_gametime();
- gamedata.nrplayers = gpdp.get_number_of_players();
- gamedata.version = gpdp.get_version();
-
- gamedata.savetimestamp = gpdp.get_savetimestamp();
- time_t t;
- time(&t);
- struct tm* currenttime = localtime(&t);
- // We need to put these into variables because of a sideeffect of the localtime function.
- int8_t current_year = currenttime->tm_year;
- int8_t current_month = currenttime->tm_mon;
- int8_t current_day = currenttime->tm_mday;
-
- struct tm* savedate = localtime(&gamedata.savetimestamp);
-
- if (gamedata.savetimestamp > 0) {
- if (savedate->tm_year == current_year && savedate->tm_mon == current_month &&
- savedate->tm_mday == current_day) { // Today
-
- // Adding the 0 padding in a separate statement so translators won't have to deal
- // with it
- const std::string minute = (boost::format("%02u") % savedate->tm_min).str();
-
- /** TRANSLATORS: Display date for choosing a savegame/replay */
- /** TRANSLATORS: hour:minute */
- gamedata.savedatestring =
- (boost::format(_("Today, %1%:%2%")) % savedate->tm_hour % minute).str();
- } else if ((savedate->tm_year == current_year && savedate->tm_mon == current_month &&
- savedate->tm_mday == current_day - 1) ||
- (savedate->tm_year == current_year - 1 && savedate->tm_mon == 11 &&
- current_month == 0 && savedate->tm_mday == 31 &&
- current_day == 1)) { // Yesterday
- // Adding the 0 padding in a separate statement so translators won't have to deal
- // with it
- const std::string minute = (boost::format("%02u") % savedate->tm_min).str();
-
- /** TRANSLATORS: Display date for choosing a savegame/replay */
- /** TRANSLATORS: hour:minute */
- gamedata.savedatestring =
- (boost::format(_("Yesterday, %1%:%2%")) % savedate->tm_hour % minute).str();
- } else { // Older
-
- /** TRANSLATORS: Display date for choosing a savegame/replay */
- /** TRANSLATORS: month day, year */
- gamedata.savedatestring =
- (boost::format(_("%2% %1%, %3%")) % savedate->tm_mday %
- localize_month(savedate->tm_mon) % (1900 + savedate->tm_year))
- .str();
- }
- }
-
- gamedata.wincondition = _(gpdp.get_localized_win_condition());
- gamedata.minimap_path = gpdp.get_minimap_path();
- games_data_.push_back(gamedata);
-
- UI::Table<uintptr_t const>::EntryRecord& te = table_.add(games_data_.size() - 1);
- te.set_string(0, gamedata.savedatestring);
-
- if (is_replay_ || settings_->settings().multiplayer) {
- std::string gametypestring;
- switch (gamedata.gametype) {
- case GameController::GameType::SINGLEPLAYER:
- /** TRANSLATORS: "Single Player" entry in the Game Mode table column. */
- /** TRANSLATORS: "Keep this to 6 letters maximum. */
- /** TRANSLATORS: A tooltip will explain the abbreviation. */
- /** TRANSLATORS: Make sure that this translation is consistent with the tooltip. */
- gametypestring = _("SP");
- break;
- case GameController::GameType::NETHOST:
- /** TRANSLATORS: "Multiplayer Host" entry in the Game Mode table column. */
- /** TRANSLATORS: "Keep this to 2 letters maximum. */
- /** TRANSLATORS: A tooltip will explain the abbreviation. */
- /** TRANSLATORS: Make sure that this translation is consistent with the tooltip. */
- /** TRANSLATORS: %1% is the number of players */
- gametypestring =
- (boost::format(_("H (%1%)")) % static_cast<unsigned int>(gamedata.nrplayers))
- .str();
- break;
- case GameController::GameType::NETCLIENT:
- /** TRANSLATORS: "Multiplayer" entry in the Game Mode table column. */
- /** TRANSLATORS: "Keep this to 2 letters maximum. */
- /** TRANSLATORS: A tooltip will explain the abbreviation. */
- /** TRANSLATORS: Make sure that this translation is consistent with the tooltip. */
- /** TRANSLATORS: %1% is the number of players */
- gametypestring =
- (boost::format(_("MP (%1%)")) % static_cast<unsigned int>(gamedata.nrplayers))
- .str();
- break;
- case GameController::GameType::REPLAY:
- gametypestring = "";
- break;
- }
- te.set_string(1, gametypestring);
- te.set_string(2, map_filename(gamedata.filename, gamedata.mapname));
- } else {
- te.set_string(1, map_filename(gamedata.filename, gamedata.mapname));
- }
- } catch (const WException& e) {
- // we simply skip illegal entries
- gamedata.errormessage =
- ((boost::format("%s\n\n%s\n\n%s"))
- /** TRANSLATORS: Error message introduction for when an old savegame can't be loaded */
- % _("This file has the wrong format and can’t be loaded."
- " Maybe it was created with an older version of Widelands.")
- /** TRANSLATORS: This text is on a separate line with an error message below */
- % _("Error message:") % e.what())
- .str();
-
- const std::string fs_filename =
- FileSystem::filename_without_ext(gamedata.filename.c_str());
- gamedata.mapname = fs_filename;
- games_data_.push_back(gamedata);
-
- UI::Table<uintptr_t const>::EntryRecord& te = table_.add(games_data_.size() - 1);
- te.set_string(0, "");
- if (is_replay_ || settings_->settings().multiplayer) {
- te.set_string(1, "");
- /** TRANSLATORS: Prefix for incompatible files in load game screens */
- te.set_string(2, (boost::format(_("Incompatible: %s")) % fs_filename).str());
- } else {
- te.set_string(1, (boost::format(_("Incompatible: %s")) % fs_filename).str());
- }
- }
- }
- table_.sort();
-
- if (table_.size()) {
- table_.select(0);
- }
- set_has_selection();
+ load_or_save_.fill_table();
}
bool FullscreenMenuLoadGame::handle_key(bool down, SDL_Keysym code) {
@@ -692,7 +142,7 @@
break;
/* no break */
case SDLK_DELETE:
- clicked_delete();
+ load_or_save_.clicked_delete();
return true;
default:
break;
=== modified file 'src/ui_fsmenu/loadgame.h'
--- src/ui_fsmenu/loadgame.h 2017-01-26 09:28:40 +0000
+++ src/ui_fsmenu/loadgame.h 2017-04-21 07:38:25 +0000
@@ -22,52 +22,15 @@
#include "ui_fsmenu/base.h"
-#include <memory>
-
-#include "graphic/image.h"
+#include "logic/game.h"
#include "logic/game_controller.h"
+#include "logic/game_settings.h"
+#include "ui_basic/box.h"
#include "ui_basic/button.h"
-#include "ui_basic/icon.h"
-#include "ui_basic/multilinetextarea.h"
-#include "ui_basic/table.h"
+#include "ui_basic/panel.h"
#include "ui_basic/textarea.h"
#include "ui_fsmenu/load_map_or_game.h"
-
-namespace Widelands {
-class EditorGameBase;
-class Game;
-class Map;
-class MapLoader;
-}
-class Image;
-class RenderTarget;
-class GameController;
-struct GameSettingsProvider;
-
-/**
- * Data about a savegame/replay that we're interested in.
- */
-struct SavegameData {
- std::string filename;
- std::string mapname;
- std::string wincondition;
- std::string minimap_path;
- std::string savedatestring;
- std::string errormessage;
-
- uint32_t gametime;
- uint32_t nrplayers;
- std::string version;
- time_t savetimestamp;
- GameController::GameType gametype;
-
- SavegameData()
- : gametime(0),
- nrplayers(0),
- savetimestamp(0),
- gametype(GameController::GameType::SINGLEPLAYER) {
- }
-};
+#include "wui/load_or_save_game.h"
/// Select a Saved Game in Fullscreen Mode. It's a modal fullscreen menu.
class FullscreenMenuLoadGame : public FullscreenMenuLoadMapOrGame {
@@ -77,55 +40,41 @@
GameController* gc = nullptr,
bool is_replay = false);
+ /// Ths currently selected filename
const std::string& filename() {
return filename_;
}
- void think() override;
-
bool handle_key(bool down, SDL_Keysym code) override;
protected:
+ /// Sets the current selected filename and ends the modal screen with 'Ok' status.
void clicked_ok() override;
+
+ /// Update button status and game details
void entry_selected() override;
+
+ /// Fill load_or_save_'s table
void fill_table() override;
private:
void layout() override;
/// Updates buttons and text labels and returns whether a table entry is selected.
- bool set_has_selection();
bool compare_date_descending(uint32_t, uint32_t);
- void clicked_delete();
- std::string filename_list_string();
-
- UI::Table<uintptr_t const> table_;
-
- bool is_replay_;
-
+
+ UI::Box main_box_;
+ UI::Box info_box_;
UI::Textarea title_;
- UI::Textarea label_mapname_;
- UI::MultilineTextarea ta_mapname_; // Multiline for long names
- UI::Textarea label_gametime_;
- UI::MultilineTextarea ta_gametime_; // Multiline because we want tooltips
- UI::Textarea label_players_;
- UI::MultilineTextarea ta_players_;
- UI::Textarea label_version_;
- UI::Textarea ta_version_;
- UI::Textarea label_win_condition_;
- UI::MultilineTextarea ta_win_condition_;
-
- UI::Button delete_;
-
- UI::MultilineTextarea ta_long_generic_message_;
-
- int32_t const minimap_y_, minimap_w_, minimap_h_;
- UI::Icon minimap_icon_;
- std::unique_ptr<const Image> minimap_image_;
-
- std::vector<SavegameData> games_data_;
+
+ LoadOrSaveGame load_or_save_;
+
+ UI::Button* delete_;
+ // TODO(GunChleoc): Get rid of this hack once everything is 100% box layout
+ UI::Panel* button_spacer_;
std::string filename_;
+ bool is_replay_;
Widelands::Game& game_;
GameSettingsProvider* settings_;
GameController* ctrl_;
=== modified file 'src/wui/CMakeLists.txt'
--- src/wui/CMakeLists.txt 2017-03-04 06:55:30 +0000
+++ src/wui/CMakeLists.txt 2017-04-21 07:38:25 +0000
@@ -71,6 +71,10 @@
wl_library(wui_common
SRCS
+ gamedetails.cc
+ gamedetails.h
+ load_or_save_game.cc
+ load_or_save_game.h
mapdetails.cc
mapdetails.h
mapdata.cc
=== modified file 'src/wui/game_main_menu_save_game.cc'
--- src/wui/game_main_menu_save_game.cc 2017-02-26 12:16:09 +0000
+++ src/wui/game_main_menu_save_game.cc 2017-04-21 07:38:25 +0000
@@ -22,7 +22,6 @@
#include <boost/format.hpp>
#include "base/i18n.h"
-#include "base/time_string.h"
#include "game_io/game_loader.h"
#include "game_io/game_preload_packet.h"
#include "game_io/game_saver.h"
@@ -31,188 +30,132 @@
#include "logic/game.h"
#include "logic/game_controller.h"
#include "logic/playersmanager.h"
+#include "ui_basic/messagebox.h"
#include "wui/interactive_gamebase.h"
-namespace {
-
-#define WINDOW_WIDTH 440
-#define WINDOW_HEIGHT 440
-#define VMARGIN 5
-#define VSPACING 5
-#define HSPACING 5
-#define BUTTON_HEIGHT 20
-#define LIST_WIDTH 280
-#define LIST_HEIGHT (WINDOW_HEIGHT - 2 * VMARGIN - VSPACING)
-#define EDITBOX_Y (WINDOW_HEIGHT - 24 - VMARGIN)
-#define DESCRIPTION_X (VMARGIN + LIST_WIDTH + VSPACING)
-#define DESCRIPTION_WIDTH (WINDOW_WIDTH - DESCRIPTION_X - VMARGIN)
-#define CANCEL_Y (WINDOW_HEIGHT - BUTTON_HEIGHT - VMARGIN)
-#define DELETE_Y (CANCEL_Y - BUTTON_HEIGHT - VSPACING)
-#define OK_Y (DELETE_Y - BUTTON_HEIGHT - VSPACING)
-
-} // namespace
-
InteractiveGameBase& GameMainMenuSaveGame::igbase() {
return dynamic_cast<InteractiveGameBase&>(*get_parent());
}
GameMainMenuSaveGame::GameMainMenuSaveGame(InteractiveGameBase& parent,
UI::UniqueWindow::Registry& registry)
- : UI::UniqueWindow(&parent, "save_game", ®istry, WINDOW_WIDTH, WINDOW_HEIGHT, _("Save Game")),
- editbox_(this,
- HSPACING,
- EDITBOX_Y,
- LIST_WIDTH,
- 0,
- 2,
- g_gr->images().get("images/ui_basic/but1.png")),
- ls_(this,
- HSPACING,
- VSPACING,
- LIST_WIDTH,
- LIST_HEIGHT - editbox_.get_h(),
- g_gr->images().get("images/ui_basic/but1.png")),
- name_label_(this, DESCRIPTION_X, 5, 0, 20, _("Map Name:")),
- mapname_(this, DESCRIPTION_X, 20, 0, 20),
- gametime_label_(this, DESCRIPTION_X, 45, 0, 20, _("Game Time:")),
- gametime_(this, DESCRIPTION_X, 60, 0, 20),
- players_label_(this, DESCRIPTION_X, 85, 0, 20),
- win_condition_label_(this, DESCRIPTION_X, 110, 0, 20, _("Win condition:")),
- win_condition_(this, DESCRIPTION_X, 125, 0, 20),
+ : UI::UniqueWindow(&parent,
+ "save_game",
+ ®istry,
+ parent.get_inner_w() - 40,
+ parent.get_inner_h() - 40,
+ _("Save Game")),
+ // Values for alignment and size
+ padding_(4),
+ butw_(150),
+
+ main_box_(this, 0, 0, UI::Box::Vertical),
+ info_box_(&main_box_, 0, 0, UI::Box::Horizontal),
+ filename_box_(&main_box_, 0, 0, UI::Box::Horizontal),
+ buttons_box_(&main_box_, 0, 0, UI::Box::Horizontal),
+
+ load_or_save_(&info_box_,
+ igbase().game(),
+ LoadOrSaveGame::FileType::kGame,
+ GameDetails::Style::kWui,
+ false),
+
+ editbox_label_(&filename_box_, 0, 0, 0, 0, _("Filename:"), UI::Align::kLeft),
+ editbox_(&filename_box_, 0, 0, 0, 0, 2, g_gr->images().get("images/ui_basic/but1.png")),
+
+ cancel_(&buttons_box_,
+ "cancel",
+ 0,
+ 0,
+ butw_,
+ 0,
+ g_gr->images().get("images/ui_basic/but1.png"),
+ _("Cancel")),
+ ok_(&buttons_box_,
+ "ok",
+ 0,
+ 0,
+ butw_,
+ 0,
+ g_gr->images().get("images/ui_basic/but5.png"),
+ _("OK")),
+
curdir_(SaveHandler::get_base_dir()) {
+
+ layout();
+
+ main_box_.add_space(padding_);
+ main_box_.set_inner_spacing(padding_);
+ main_box_.add(&info_box_, UI::Box::Resizing::kExpandBoth);
+ main_box_.add_space(padding_);
+ main_box_.add(&filename_box_, UI::Box::Resizing::kFullSize);
+ main_box_.add_space(0);
+ main_box_.add(&buttons_box_, UI::Box::Resizing::kFullSize);
+
+ info_box_.set_inner_spacing(padding_);
+ info_box_.add_space(padding_);
+ info_box_.add(&load_or_save_.table(), UI::Box::Resizing::kFullSize);
+ info_box_.add(load_or_save_.game_details(), UI::Box::Resizing::kExpandBoth);
+
+ filename_box_.set_inner_spacing(padding_);
+ filename_box_.add_space(padding_);
+ filename_box_.add(&editbox_label_, UI::Box::Resizing::kAlign, UI::Align::kCenter);
+ filename_box_.add(&editbox_, UI::Box::Resizing::kFillSpace);
+
+ buttons_box_.set_inner_spacing(padding_);
+ buttons_box_.add_space(padding_);
+ buttons_box_.add_inf_space();
+ buttons_box_.add_inf_space();
+ buttons_box_.add(&cancel_, UI::Box::Resizing::kAlign, UI::Align::kCenter);
+ buttons_box_.add_inf_space();
+ buttons_box_.add(&ok_, UI::Box::Resizing::kAlign, UI::Align::kCenter);
+ buttons_box_.add_inf_space();
+ buttons_box_.add_inf_space();
+
+ ok_.set_enabled(false);
+
editbox_.changed.connect(boost::bind(&GameMainMenuSaveGame::edit_box_changed, this));
editbox_.ok.connect(boost::bind(&GameMainMenuSaveGame::ok, this));
- button_ok_ = new UI::Button(this, "ok", DESCRIPTION_X, OK_Y, DESCRIPTION_WIDTH, BUTTON_HEIGHT,
- g_gr->images().get("images/ui_basic/but4.png"), _("OK"));
- button_ok_->sigclicked.connect(boost::bind(&GameMainMenuSaveGame::ok, this));
-
- UI::Button* cancelbtn =
- new UI::Button(this, "cancel", DESCRIPTION_X, CANCEL_Y, DESCRIPTION_WIDTH, BUTTON_HEIGHT,
- g_gr->images().get("images/ui_basic/but4.png"), _("Cancel"));
- cancelbtn->sigclicked.connect(boost::bind(&GameMainMenuSaveGame::die, this));
-
- UI::Button* deletebtn =
- new UI::Button(this, "delete", DESCRIPTION_X, DELETE_Y, DESCRIPTION_WIDTH, BUTTON_HEIGHT,
- g_gr->images().get("images/ui_basic/but4.png"), _("Delete"));
- deletebtn->sigclicked.connect(boost::bind(&GameMainMenuSaveGame::delete_clicked, this));
-
- ls_.selected.connect(boost::bind(&GameMainMenuSaveGame::selected, this, _1));
- ls_.double_clicked.connect(boost::bind(&GameMainMenuSaveGame::double_clicked, this, _1));
-
- fill_list();
+ ok_.sigclicked.connect(boost::bind(&GameMainMenuSaveGame::ok, this));
+ cancel_.sigclicked.connect(boost::bind(&GameMainMenuSaveGame::die, this));
+
+ load_or_save_.table().selected.connect(boost::bind(&GameMainMenuSaveGame::entry_selected, this));
+ load_or_save_.table().double_clicked.connect(
+ boost::bind(&GameMainMenuSaveGame::ok, this));
+
+ load_or_save_.fill_table();
+ load_or_save_.select_by_name(parent.game().save_handler().get_cur_filename());
center_to_parent();
move_to_top();
- std::string cur_filename = parent.game().save_handler().get_cur_filename();
- if (!cur_filename.empty()) {
- select_by_name(cur_filename);
- } else {
- // Display current game infos
- {
- // Try to translate the map name.
- i18n::Textdomain td("maps");
- mapname_.set_text(_(parent.game().get_map()->get_name()));
- }
- uint32_t gametime = parent.game().get_gametime();
- gametime_.set_text(gametimestring(gametime));
-
- int player_nr = parent.game().player_manager()->get_number_of_players();
- players_label_.set_text(
- (boost::format(ngettext("%i player", "%i players", player_nr)) % player_nr).str());
- {
- i18n::Textdomain td("win_conditions");
- win_condition_.set_text(_(parent.game().get_win_condition_displayname()));
- }
- }
-
- editbox_.focus();
pause_game(true);
-}
-
-/**
- * called when a item is selected
- */
-void GameMainMenuSaveGame::selected(uint32_t) {
- const std::string& name = ls_.get_selected();
-
- Widelands::GameLoader gl(name, igbase().game());
- Widelands::GamePreloadPacket gpdp;
- gl.preload_game(gpdp); // This has worked before, no problem
- { editbox_.set_text(FileSystem::filename_without_ext(name.c_str())); }
- edit_box_changed();
-
- // Try to translate the map name.
- {
- i18n::Textdomain td("maps");
- mapname_.set_text(_(gpdp.get_mapname()));
- }
-
- uint32_t gametime = gpdp.get_gametime();
- gametime_.set_text(gametimestring(gametime));
-
- if (gpdp.get_number_of_players() > 0) {
- const std::string text =
- (boost::format(ngettext("%u Player", "%u Players", gpdp.get_number_of_players())) %
- static_cast<unsigned int>(gpdp.get_number_of_players()))
- .str();
- players_label_.set_text(text);
- } else {
- // Keep label empty
- players_label_.set_text("");
- }
- win_condition_.set_text(_(gpdp.get_localized_win_condition()));
-}
-
-/**
- * An Item has been doubleclicked
- */
-void GameMainMenuSaveGame::double_clicked(uint32_t) {
- ok();
-}
-
-/*
- * fill the file list
- */
-void GameMainMenuSaveGame::fill_list() {
- ls_.clear();
- FilenameSet gamefiles;
-
- // Fill it with all files we find.
- gamefiles = g_fs->list_directory(curdir_);
-
- Widelands::GamePreloadPacket gpdp;
-
- for (FilenameSet::iterator pname = gamefiles.begin(); pname != gamefiles.end(); ++pname) {
- char const* const name = pname->c_str();
-
- try {
- Widelands::GameLoader gl(name, igbase().game());
- gl.preload_game(gpdp);
- ls_.add(FileSystem::filename_without_ext(name), name);
- } catch (const WException&) {
- } // we simply skip illegal entries
- }
- edit_box_changed();
-}
-
-void GameMainMenuSaveGame::select_by_name(std::string name) {
- for (uint32_t idx = 0; idx < ls_.size(); idx++) {
- const std::string val = ls_[idx];
- if (name == val) {
- ls_.select(idx);
- return;
- }
- }
-}
-
-/*
- * The editbox was changed. Enable ok button
- */
+ set_thinks(false);
+}
+
+void GameMainMenuSaveGame::layout() {
+ main_box_.set_size(get_inner_w() - 2 * padding_, get_inner_h() - 2 * padding_);
+ load_or_save_.table().set_desired_size(get_inner_w() * 7 / 12, load_or_save_.table().get_h());
+}
+
+void GameMainMenuSaveGame::entry_selected() {
+ // TODO(GunChleoc): When editbox is focused, multiselect is not possible, because it steals the
+ // key presses.
+ ok_.set_enabled(load_or_save_.table().selections().size() == 1);
+ load_or_save_.delete_button()->set_enabled(load_or_save_.has_selection());
+ if (load_or_save_.has_selection()) {
+ const SavegameData& gamedata = *load_or_save_.entry_selected();
+ editbox_.set_text(FileSystem::filename_without_ext(gamedata.filename.c_str()));
+ }
+}
+
void GameMainMenuSaveGame::edit_box_changed() {
// Prevent the user from creating nonsense directory names, like e.g. ".." or "...".
- button_ok_->set_enabled(LayeredFileSystem::is_legal_filename(editbox_.text()));
+ const bool is_legal_filename = LayeredFileSystem::is_legal_filename(editbox_.text());
+ ok_.set_enabled(is_legal_filename);
+ load_or_save_.delete_button()->set_enabled(false);
+ load_or_save_.clear_selections();
}
static void dosave(InteractiveGameBase& igbase, const std::string& complete_filename) {
@@ -258,9 +201,6 @@
std::string const filename_;
};
-/**
- * Called when the Ok button is clicked or the Return key pressed in the edit box.
- */
void GameMainMenuSaveGame::ok() {
if (editbox_.text().empty())
return;
@@ -282,42 +222,6 @@
UI::UniqueWindow::die();
}
-struct DeletionMessageBox : public UI::WLMessageBox {
- DeletionMessageBox(GameMainMenuSaveGame& parent, const std::string& filename)
- : UI::WLMessageBox(&parent,
- _("File deletion"),
- str(boost::format(_("Do you really want to delete the file %s?")) %
- FileSystem::fs_filename(filename.c_str())),
- MBoxType::kOkCancel),
- filename_(filename) {
- }
-
- void clicked_ok() override {
- g_fs->fs_unlink(filename_);
- dynamic_cast<GameMainMenuSaveGame&>(*get_parent()).fill_list();
- die();
- }
-
- void clicked_back() override {
- die();
- }
-
-private:
- std::string const filename_;
-};
-
-/**
- * Called when the delete button has been clicked
- */
-void GameMainMenuSaveGame::delete_clicked() {
- std::string const complete_filename =
- igbase().game().save_handler().create_file_name(curdir_, editbox_.text());
-
- // Check if file exists. If it does, let the user confirm the deletion.
- if (g_fs->file_exists(complete_filename))
- new DeletionMessageBox(*this, complete_filename);
-}
-
void GameMainMenuSaveGame::pause_game(bool paused) {
if (igbase().is_multiplayer()) {
return;
=== modified file 'src/wui/game_main_menu_save_game.h'
--- src/wui/game_main_menu_save_game.h 2017-01-25 18:55:59 +0000
+++ src/wui/game_main_menu_save_game.h 2017-04-21 07:38:25 +0000
@@ -21,43 +21,61 @@
#define WL_WUI_GAME_MAIN_MENU_SAVE_GAME_H
#include "base/i18n.h"
+#include "ui_basic/box.h"
#include "ui_basic/button.h"
#include "ui_basic/editbox.h"
-#include "ui_basic/listselect.h"
-#include "ui_basic/messagebox.h"
#include "ui_basic/textarea.h"
#include "ui_basic/unique_window.h"
+#include "wui/load_or_save_game.h"
class InteractiveGameBase;
+/// Displays a warning if the filename to be saved to already esists
struct SaveWarnMessageBox;
+
+/// A window that lets the user save the current game and delete savegames.
struct GameMainMenuSaveGame : public UI::UniqueWindow {
friend struct SaveWarnMessageBox;
GameMainMenuSaveGame(InteractiveGameBase&, UI::UniqueWindow::Registry& registry);
- void fill_list();
- void select_by_name(std::string name);
-
protected:
void die() override;
private:
+ void layout() override;
InteractiveGameBase& igbase();
- void selected(uint32_t);
- void double_clicked(uint32_t);
+
+ /// Update button status and game details and prefill the edibox.
+ void entry_selected();
+
+ /// Update buttons and table selection state
void edit_box_changed();
+
+ /// Called when the OK button is clicked or the Return key pressed in the edit box.
void ok();
- void delete_clicked();
-
- bool save_game(std::string);
+
+ /// Saves the current game to 'filename'
+ bool save_game(std::string filename);
+
+ /// Pause/unpause the game
void pause_game(bool paused);
+ // UI coordinates and spacers
+ int32_t const padding_; // Common padding between panels
+ int32_t const butw_; // Button dimensions
+
+ UI::Box main_box_;
+ UI::Box info_box_;
+ UI::Box filename_box_;
+ UI::Box buttons_box_;
+
+ LoadOrSaveGame load_or_save_;
+ UI::Button* delete_;
+
+ UI::Textarea editbox_label_;
UI::EditBox editbox_;
- UI::Listselect<std::string> ls_;
- UI::Textarea name_label_, mapname_, gametime_label_, gametime_, players_label_,
- win_condition_label_, win_condition_;
- UI::Button* button_ok_;
+ UI::Button cancel_, ok_;
std::string curdir_;
std::string parentdir_;
std::string filename_;
=== added file 'src/wui/gamedetails.cc'
--- src/wui/gamedetails.cc 1970-01-01 00:00:00 +0000
+++ src/wui/gamedetails.cc 2017-04-21 07:38:25 +0000
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2016 by the Widelands Development Team
+ *
+ * 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; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "wui/gamedetails.h"
+
+#include <memory>
+
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/format.hpp>
+
+#include "base/i18n.h"
+#include "base/log.h"
+#include "base/time_string.h"
+#include "graphic/graphic.h"
+#include "graphic/image_io.h"
+#include "graphic/text_constants.h"
+#include "graphic/texture.h"
+#include "io/filesystem/layered_filesystem.h"
+
+// TODO(GunChleoc): Arabic: line height broken for descriptions for Arabic.
+namespace {
+// 'is_first' omits the vertical gap before the line.
+// 'noescape' is needed for error message formatting and does not call richtext_escape.
+std::string as_header_with_content(const std::string& header,
+ const std::string& content,
+ GameDetails::Style style,
+ bool is_first = false,
+ bool noescape = false) {
+ switch (style) {
+ case GameDetails::Style::kFsMenu:
+ return (boost::format(
+ "<p><font size=%i bold=1 shadow=1>%s%s <font color=D1D1D1>%s</font></font></p>") %
+ UI_FONT_SIZE_SMALL % (is_first ? "" : "<vspace gap=9>") %
+ (noescape ? header : richtext_escape(header)) %
+ (noescape ? content : richtext_escape(content)))
+ .str();
+ case GameDetails::Style::kWui:
+ return (boost::format(
+ "<p><font size=%i>%s<font bold=1 color=D1D1D1>%s</font> %s</font></p>") %
+ UI_FONT_SIZE_SMALL % (is_first ? "" : "<vspace gap=6>") %
+ (noescape ? header : richtext_escape(header)) %
+ (noescape ? content : richtext_escape(content)))
+ .str();
+ default:
+ NEVER_HERE();
+ }
+}
+
+} // namespace
+
+SavegameData::SavegameData()
+ : gametime(""),
+ nrplayers("0"),
+ savetimestamp(0),
+ gametype(GameController::GameType::SINGLEPLAYER) {
+}
+
+void SavegameData::set_gametime(uint32_t input_gametime) {
+ gametime = gametimestring(input_gametime);
+}
+void SavegameData::set_nrplayers(Widelands::PlayerNumber input_nrplayers) {
+ nrplayers = boost::lexical_cast<std::string>(static_cast<unsigned int>(input_nrplayers));
+}
+void SavegameData::set_mapname(const std::string& input_mapname) {
+ i18n::Textdomain td("maps");
+ mapname = _(input_mapname);
+}
+
+GameDetails::GameDetails(Panel* parent, Style style)
+ : UI::Box(parent, 0, 0, UI::Box::Vertical),
+ style_(style),
+ padding_(4),
+ name_label_(
+ this,
+ 0,
+ 0,
+ 0,
+ 0,
+ "",
+ UI::Align::kLeft,
+ g_gr->images().get(style == GameDetails::Style::kFsMenu ? "images/ui_basic/but3.png" :
+ "images/ui_basic/but1.png"),
+ UI::MultilineTextarea::ScrollMode::kNoScrolling),
+ descr_(this,
+ 0,
+ 0,
+ 0,
+ 0,
+ "",
+ UI::Align::kLeft,
+ g_gr->images().get(style == GameDetails::Style::kFsMenu ? "images/ui_basic/but3.png" :
+ "images/ui_basic/but1.png"),
+ UI::MultilineTextarea::ScrollMode::kNoScrolling),
+ minimap_icon_(this, 0, 0, 0, 0, nullptr),
+ button_box_(new UI::Box(this, 0, 0, UI::Box::Vertical)) {
+ name_label_.force_new_renderer();
+ descr_.force_new_renderer();
+
+ add(&name_label_, UI::Box::Resizing::kFullSize);
+ add_space(padding_);
+ add(&descr_, UI::Box::Resizing::kExpandBoth);
+ add_space(padding_);
+ add(&minimap_icon_, UI::Box::Resizing::kAlign, UI::Align::kCenter);
+ add_space(padding_);
+ add(button_box_, UI::Box::Resizing::kFullSize);
+
+ minimap_icon_.set_visible(false);
+ minimap_icon_.set_frame(UI_FONT_CLR_FG);
+}
+
+void GameDetails::clear() {
+ name_label_.set_text("");
+ descr_.set_text("");
+ minimap_icon_.set_icon(nullptr);
+ minimap_icon_.set_visible(false);
+ minimap_icon_.set_size(0, 0);
+ minimap_image_.reset();
+}
+
+void GameDetails::update(const SavegameData& gamedata) {
+ clear();
+
+ if (gamedata.errormessage.empty()) {
+ if (gamedata.filename_list.empty()) {
+ name_label_.set_text(
+ (boost::format("<rt>%s</rt>") %
+ as_header_with_content(_("Map Name:"), gamedata.mapname, style_, true))
+ .str());
+
+ name_label_.set_tooltip(gamedata.gametype == GameController::GameType::REPLAY ?
+ _("The map that this replay is based on") :
+ _("The map that this game is based on"));
+
+ // Show game information
+ std::string description =
+ as_header_with_content(_("Game Time:"), gamedata.gametime, style_);
+
+ description = (boost::format("%s%s") % description %
+ as_header_with_content(_("Players:"), gamedata.nrplayers, style_))
+ .str();
+
+ description = (boost::format("%s%s") % description %
+ as_header_with_content(_("Widelands Version:"), gamedata.version, style_))
+ .str();
+
+ description = (boost::format("%s%s") % description %
+ as_header_with_content(_("Win Condition:"), gamedata.wincondition, style_))
+ .str();
+
+ description = (boost::format("<rt>%s</rt>") % description).str();
+ descr_.set_text(description);
+
+ std::string minimap_path = gamedata.minimap_path;
+ if (!minimap_path.empty()) {
+ try {
+ // Load the image
+ minimap_image_ = load_image(
+ minimap_path,
+ std::unique_ptr<FileSystem>(g_fs->make_sub_file_system(gamedata.filename)).get());
+ minimap_icon_.set_visible(true);
+ minimap_icon_.set_icon(minimap_image_.get());
+ } catch (const std::exception& e) {
+ log("Failed to load the minimap image : %s\n", e.what());
+ }
+ }
+ } else {
+ std::string filename_list = richtext_escape(gamedata.filename_list);
+ boost::replace_all(filename_list, "\n", "<br> • ");
+ name_label_.set_text((boost::format("<rt>%s</rt>") %
+ as_header_with_content(gamedata.mapname, "", style_, true))
+ .str());
+
+ descr_.set_text((boost::format("<rt>%s</rt>") %
+ as_header_with_content("", filename_list, style_, true, true))
+ .str());
+ minimap_icon_.set_visible(false);
+ }
+ } else {
+ name_label_.set_text(
+ (boost::format("<rt>%s</rt>") %
+ as_header_with_content(_("Error:"), gamedata.errormessage, style_, true, true))
+ .str());
+ }
+ layout();
+}
+
+void GameDetails::layout() {
+ if (get_w() == 0 && get_h() == 0) {
+ return;
+ }
+ UI::Box::layout();
+ if (minimap_icon_.icon() == nullptr) {
+ descr_.set_scrollmode(UI::MultilineTextarea::ScrollMode::kScrollNormal);
+ minimap_icon_.set_desired_size(0, 0);
+ } else {
+ descr_.set_scrollmode(UI::MultilineTextarea::ScrollMode::kNoScrolling);
+
+ // Scale the minimap image.
+ const float available_width = get_w() - 4 * padding_;
+ const float available_height =
+ get_h() - name_label_.get_h() - descr_.get_h() - button_box_->get_h() - 4 * padding_;
+
+ // Scale it
+ float scale = available_width / minimap_image_->width();
+ const float scale_y = available_height / minimap_image_->height();
+ if (scale_y < scale) {
+ scale = scale_y;
+ }
+ // Don't make the image too big; fuzziness will result
+ scale = std::min(1.f, scale);
+
+ const int w = scale * minimap_image_->width();
+ const int h = scale * minimap_image_->height();
+
+ // Center the minimap in the available space
+ const int xpos = (get_w() - w) / 2;
+ int ypos = name_label_.get_h() + descr_.get_h() + 2 * padding_;
+
+ // Set small minimaps higher up for a more harmonious look
+ if (h < available_height * 2 / 3) {
+ ypos += (available_height - h) / 3;
+ } else {
+ ypos += (available_height - h) / 2;
+ }
+
+ minimap_icon_.set_desired_size(w, h);
+ minimap_icon_.set_pos(Vector2i(xpos, ypos));
+ }
+}
=== added file 'src/wui/gamedetails.h'
--- src/wui/gamedetails.h 1970-01-01 00:00:00 +0000
+++ src/wui/gamedetails.h 2017-04-21 07:38:25 +0000
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 by the Widelands Development Team
+ *
+ * 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; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef WL_WUI_GAMEDETAILS_H
+#define WL_WUI_GAMEDETAILS_H
+
+#include <memory>
+
+#include "graphic/image.h"
+#include "logic/game_controller.h"
+#include "ui_basic/box.h"
+#include "ui_basic/icon.h"
+#include "ui_basic/multilinetextarea.h"
+
+/**
+ * Data about a savegame/replay that we're interested in.
+ */
+struct SavegameData {
+ /// The filename of the currenty selected file
+ std::string filename;
+ /// List of filenames when lumtiple files have been selected
+ std::string filename_list;
+ /// The name of the map that the game is bases on
+ std::string mapname;
+ /// The win condition that was played
+ std::string wincondition;
+ /// Filename of the minimap or empty if none available
+ std::string minimap_path;
+ /// "saved on ..."
+ std::string savedatestring;
+ /// Verbose date and time
+ std::string savedonstring;
+ /// An error message or empty if no error occurred
+ std::string errormessage;
+
+ /// Compact gametime information
+ std::string gametime;
+ /// Number of players on the map
+ std::string nrplayers;
+ /// The version of Widelands that the game as played with
+ std::string version;
+ /// Gametime as time stamp. For games, it's the time the game ended. For replays, it's the time the game started.
+ time_t savetimestamp;
+ /// Single payer, nethost, netclient or replay
+ GameController::GameType gametype;
+
+ SavegameData();
+
+ /// Converts timestamp to UI string and assigns it to gametime
+ void set_gametime(uint32_t input_gametime);
+ /// Sets the number of players on the map as a string
+ void set_nrplayers(Widelands::PlayerNumber input_nrplayers);
+ /// Sets the mapname as a localized string
+ void set_mapname(const std::string& input_mapname);
+};
+
+/**
+ * Show a Panel with information about a savegame/replay file
+ */
+class GameDetails : public UI::Box {
+public:
+ enum class Style { kFsMenu, kWui };
+
+ GameDetails(Panel* parent, Style style);
+
+ /// Reset the data
+ void clear();
+
+ /// Update the display from the 'gamedata'
+ void update(const SavegameData& gamedata);
+
+ /// Box on the bottom where extra buttons can be placed from the outside, e.g. a delete button.
+ UI::Box* button_box() {
+ return button_box_;
+ }
+
+private:
+ /// Layout the information on screen
+ void layout() override;
+
+ const Style style_;
+ const int padding_;
+
+ UI::MultilineTextarea name_label_;
+ UI::MultilineTextarea descr_;
+ UI::Icon minimap_icon_;
+ std::unique_ptr<const Image> minimap_image_;
+ UI::Box* button_box_;
+};
+
+#endif // end of include guard: WL_WUI_GAMEDETAILS_H
=== added file 'src/wui/load_or_save_game.cc'
--- src/wui/load_or_save_game.cc 1970-01-01 00:00:00 +0000
+++ src/wui/load_or_save_game.cc 2017-04-21 07:38:25 +0000
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2002-2016 by the Widelands Development Team
+ *
+ * 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; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "wui/load_or_save_game.h"
+
+#include <ctime>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/format.hpp>
+
+#include "base/i18n.h"
+#include "base/log.h"
+#include "base/time_string.h"
+#include "game_io/game_loader.h"
+#include "game_io/game_preload_packet.h"
+#include "helper.h"
+#include "io/filesystem/layered_filesystem.h"
+#include "logic/game.h"
+#include "logic/game_controller.h"
+#include "logic/game_settings.h"
+#include "logic/replay.h"
+#include "ui_basic/messagebox.h"
+
+namespace {
+// This function concatenates the filename and localized map name for a savegame/replay.
+// If the filename starts with the map name, the map name is omitted.
+// It also prefixes autosave files with a numbered and localized "Autosave" prefix.
+std::string
+map_filename(const std::string& filename, const std::string& mapname, bool localize_autosave) {
+ std::string result = FileSystem::filename_without_ext(filename.c_str());
+
+ if (localize_autosave && boost::starts_with(result, "wl_autosave")) {
+ std::vector<std::string> autosave_name;
+ boost::split(autosave_name, result, boost::is_any_of("_"));
+ if (autosave_name.empty() || autosave_name.size() < 3) {
+ /** TRANSLATORS: %1% is a map's name. */
+ result = (boost::format(_("Autosave: %1%")) % mapname).str();
+ } else {
+ /** TRANSLATORS: %1% is a number, %2% a map's name. */
+ result = (boost::format(_("Autosave %1%: %2%")) % autosave_name.back() % mapname).str();
+ }
+ } else if (!(boost::starts_with(result, mapname))) {
+ /** TRANSLATORS: %1% is a filename, %2% a map's name. */
+ result = (boost::format(_("%1% (%2%)")) % result % mapname).str();
+ }
+ return result;
+}
+} // namespace
+
+LoadOrSaveGame::LoadOrSaveGame(UI::Panel* parent,
+ Widelands::Game& g,
+ FileType filetype,
+ GameDetails::Style style,
+ bool localize_autosave)
+ : parent_(parent),
+ table_(parent,
+ 0,
+ 0,
+ 0,
+ 0,
+ g_gr->images().get(style == GameDetails::Style::kFsMenu ? "images/ui_basic/but3.png" :
+ "images/ui_basic/but1.png"),
+ UI::TableRows::kMultiDescending),
+ filetype_(filetype),
+ localize_autosave_(localize_autosave),
+ // Savegame description
+ game_details_(parent, style),
+ delete_(new UI::Button(game_details()->button_box(),
+ "delete",
+ 0,
+ 0,
+ 0,
+ 0,
+ g_gr->images().get("images/ui_basic/but0.png"),
+ _("Delete"))),
+ game_(g) {
+ table_.add_column(130, _("Save Date"), _("The date this game was saved"), UI::Align::kLeft);
+ if (filetype_ != FileType::kGameSinglePlayer) {
+ std::vector<std::string> modes;
+ if (filetype_ == FileType::kReplay) {
+ /** TRANSLATORS: Tooltip for the "Mode" column when choosing a game/replay to load. */
+ /** TRANSLATORS: Make sure that you keep consistency in your translation. */
+ modes.push_back(_("SP = Single Player"));
+ }
+ /** TRANSLATORS: Tooltip for the "Mode" column when choosing a game/replay to load. */
+ /** TRANSLATORS: Make sure that you keep consistency in your translation. */
+ modes.push_back(_("MP = Multiplayer"));
+ /** TRANSLATORS: Tooltip for the "Mode" column when choosing a game/replay to load. */
+ /** TRANSLATORS: Make sure that you keep consistency in your translation. */
+ modes.push_back(_("H = Multiplayer (Host)"));
+ const std::string mode_tooltip_1 =
+ /** TRANSLATORS: Tooltip for the "Mode" column when choosing a game/replay to load. */
+ /** TRANSLATORS: %s is a list of game modes. */
+ ((boost::format(_("Game Mode: %s.")) %
+ i18n::localize_list(modes, i18n::ConcatenateWith::COMMA)))
+ .str();
+ const std::string mode_tooltip_2 = _("Numbers are the number of players.");
+
+ table_.add_column(
+ 65,
+ /** TRANSLATORS: Game Mode table column when choosing a game/replay to load. */
+ /** TRANSLATORS: Keep this to 5 letters maximum. */
+ /** TRANSLATORS: A tooltip will explain if you need to use an abbreviation. */
+ _("Mode"), (boost::format("%s %s") % mode_tooltip_1 % mode_tooltip_2).str());
+ }
+ table_.add_column(0, _("Description"),
+ _("The filename that the game was saved under followed by the map’s name, "
+ "or the map’s name followed by the last objective achieved."),
+ UI::Align::kLeft, UI::TableColumnType::kFlexible);
+ table_.set_column_compare(
+ 0, boost::bind(&LoadOrSaveGame::compare_date_descending, this, _1, _2));
+ table_.set_sort_column(0);
+ table_.focus();
+ fill_table();
+
+ game_details_.button_box()->add(delete_, style == GameDetails::Style::kFsMenu ?
+ UI::Box::Resizing::kAlign :
+ UI::Box::Resizing::kFullSize,
+ UI::Align::kLeft);
+ delete_->set_enabled(false);
+ delete_->sigclicked.connect(boost::bind(&LoadOrSaveGame::clicked_delete, boost::ref(*this)));
+}
+
+const std::string LoadOrSaveGame::filename_list_string() const {
+ std::set<uint32_t> selections = table_.selections();
+ boost::format message;
+ int counter = 0;
+ for (const uint32_t index : selections) {
+ ++counter;
+ // TODO(GunChleoc): We can exceed the texture size for the font renderer,
+ // so we have to restrict this for now.
+ if (counter > 50) {
+ message = boost::format("%s\n%s") % message % "...";
+ break;
+ }
+ const SavegameData& gamedata = games_data_[table_.get(table_.get_record(index))];
+
+ if (gamedata.errormessage.empty()) {
+ std::vector<std::string> listme;
+ listme.push_back(richtext_escape(gamedata.mapname));
+ listme.push_back(gamedata.savedonstring);
+ message = (boost::format("%s\n%s") % message %
+ i18n::localize_list(listme, i18n::ConcatenateWith::COMMA));
+ } else {
+ message = boost::format("%s\n%s") % message % richtext_escape(gamedata.filename);
+ }
+ }
+ return message.str();
+}
+
+bool LoadOrSaveGame::compare_date_descending(uint32_t rowa, uint32_t rowb) {
+ const SavegameData& r1 = games_data_[table_[rowa]];
+ const SavegameData& r2 = games_data_[table_[rowb]];
+
+ return r1.savetimestamp < r2.savetimestamp;
+}
+
+const SavegameData* LoadOrSaveGame::entry_selected() {
+ SavegameData* result = new SavegameData();
+ size_t selections = table_.selections().size();
+ if (selections == 1) {
+ delete_->set_tooltip(
+ filetype_ == FileType::kReplay ?
+ /** TRANSLATORS: Tooltip for the delete button. The user has selected 1 file */
+ _("Delete this replay") :
+ /** TRANSLATORS: Tooltip for the delete button. The user has selected 1 file */
+ _("Delete this game"));
+ result = &games_data_[table_.get_selected()];
+ } else if (selections > 1) {
+ delete_->set_tooltip(
+ filetype_ == FileType::kReplay ?
+ /** TRANSLATORS: Tooltip for the delete button. The user has selected multiple files */
+ _("Delete these replays") :
+ /** TRANSLATORS: Tooltip for the delete button. The user has selected multiple files */
+ _("Delete these games"));
+ result->mapname =
+ (boost::format(ngettext("Selected %d file:", "Selected %d files:", selections)) %
+ selections)
+ .str();
+ result->filename_list = filename_list_string();
+ } else {
+ delete_->set_tooltip("");
+ }
+ game_details_.update(*result);
+ return result;
+}
+
+bool LoadOrSaveGame::has_selection() {
+ return table_.has_selection();
+}
+
+void LoadOrSaveGame::clear_selections() {
+ table_.clear_selections();
+ game_details_.clear();
+}
+
+void LoadOrSaveGame::select_by_name(const std::string& name) {
+ table_.clear_selections();
+ for (size_t idx = 0; idx < table_.size(); ++idx) {
+ const SavegameData& gamedata = games_data_[table_[idx]];
+ if (name == gamedata.filename) {
+ table_.select(idx);
+ return;
+ }
+ }
+}
+
+const std::string LoadOrSaveGame::get_filename(int index) const {
+ return games_data_[table_.get(table_.get_record(index))].filename;
+}
+
+void LoadOrSaveGame::clicked_delete() {
+ if (!has_selection()) {
+ return;
+ }
+ std::set<uint32_t> selections = table().selections();
+ const SavegameData& gamedata = *entry_selected();
+ size_t no_selections = selections.size();
+ std::string header = "";
+ if (filetype_ == FileType::kReplay) {
+ header = no_selections == 1 ?
+ _("Do you really want to delete this replay?") :
+ /** TRANSLATORS: Used with multiple replays, 1 replay has a separate string. */
+ (boost::format(ngettext("Do you really want to delete this %d replay?",
+ "Do you really want to delete these %d replays?",
+ no_selections)) %
+ no_selections)
+ .str();
+ } else {
+ header = no_selections == 1 ?
+ _("Do you really want to delete this game?") :
+ /** TRANSLATORS: Used with multiple games, 1 game has a separate string. */
+ (boost::format(ngettext("Do you really want to delete this %d game?",
+ "Do you really want to delete these %d games?",
+ no_selections)) %
+ no_selections)
+ .str();
+ }
+ std::string message = no_selections > 1 ? gamedata.filename_list : gamedata.filename;
+ message = (boost::format("%s\n%s") % header % message).str();
+
+ bool do_delete = SDL_GetModState() & KMOD_CTRL;
+ if (!do_delete) {
+ UI::WLMessageBox confirmationBox(
+ parent_, ngettext("Confirm deleting file", "Confirm deleting files", no_selections), message,
+ UI::WLMessageBox::MBoxType::kOkCancel);
+ do_delete = confirmationBox.run<UI::Panel::Returncodes>() == UI::Panel::Returncodes::kOk;
+ }
+ if (do_delete) {
+ for (const uint32_t index : selections) {
+ const std::string& deleteme = get_filename(index);
+ g_fs->fs_unlink(deleteme);
+ if (filetype_ == FileType::kReplay) {
+ g_fs->fs_unlink(deleteme + WLGF_SUFFIX);
+ }
+ }
+ fill_table();
+ }
+}
+
+void LoadOrSaveGame::fill_table() {
+
+ games_data_.clear();
+ table_.clear();
+
+ FilenameSet gamefiles;
+
+ if (filetype_ == FileType::kReplay) {
+ gamefiles = filter(g_fs->list_directory(REPLAY_DIR),
+ [](const std::string& fn) { return boost::ends_with(fn, REPLAY_SUFFIX); });
+ } else {
+ gamefiles = g_fs->list_directory("save");
+ }
+
+ Widelands::GamePreloadPacket gpdp;
+
+ for (const std::string& gamefilename : gamefiles) {
+ if (gamefilename == "save/campvis" || gamefilename == "save\\campvis") {
+ continue;
+ }
+
+ SavegameData gamedata;
+
+ std::string savename = gamefilename;
+ if (filetype_ == FileType::kReplay)
+ savename += WLGF_SUFFIX;
+
+ if (!g_fs->file_exists(savename.c_str())) {
+ continue;
+ }
+
+ gamedata.filename = gamefilename;
+
+ try {
+ Widelands::GameLoader gl(savename.c_str(), game_);
+ gl.preload_game(gpdp);
+
+ gamedata.gametype = gpdp.get_gametype();
+
+ if (filetype_ != FileType::kReplay) {
+ if (filetype_ == FileType::kGame) {
+ if (gamedata.gametype == GameController::GameType::REPLAY) {
+ continue;
+ }
+ } else if (filetype_ == FileType::kGameMultiPlayer) {
+ if (gamedata.gametype == GameController::GameType::SINGLEPLAYER) {
+ continue;
+ }
+ } else if (gamedata.gametype > GameController::GameType::SINGLEPLAYER) {
+ continue;
+ }
+ }
+
+ gamedata.set_mapname(gpdp.get_mapname());
+ gamedata.set_gametime(gpdp.get_gametime());
+ gamedata.set_nrplayers(gpdp.get_number_of_players());
+ gamedata.version = gpdp.get_version();
+
+ gamedata.savetimestamp = gpdp.get_savetimestamp();
+ time_t t;
+ time(&t);
+ struct tm* currenttime = localtime(&t);
+ // We need to put these into variables because of a sideeffect of the localtime function.
+ int8_t current_year = currenttime->tm_year;
+ int8_t current_month = currenttime->tm_mon;
+ int8_t current_day = currenttime->tm_mday;
+
+ struct tm* savedate = localtime(&gamedata.savetimestamp);
+
+ if (gamedata.savetimestamp > 0) {
+ if (savedate->tm_year == current_year && savedate->tm_mon == current_month &&
+ savedate->tm_mday == current_day) { // Today
+
+ // Adding the 0 padding in a separate statement so translators won't have to deal
+ // with it
+ const std::string minute = (boost::format("%02u") % savedate->tm_min).str();
+
+ gamedata.savedatestring =
+ /** TRANSLATORS: Display date for choosing a savegame/replay. Placeholders are:
+ hour:minute */
+ (boost::format(_("Today, %1%:%2%")) % savedate->tm_hour % minute).str();
+ gamedata.savedonstring =
+ /** TRANSLATORS: Display date for choosing a savegame/replay. Placeholders are:
+ hour:minute. This is part of a list. */
+ (boost::format(_("saved today at %1%:%2%")) % savedate->tm_hour % minute).str();
+ } else if ((savedate->tm_year == current_year && savedate->tm_mon == current_month &&
+ savedate->tm_mday == current_day - 1) ||
+ (savedate->tm_year == current_year - 1 && savedate->tm_mon == 11 &&
+ current_month == 0 && savedate->tm_mday == 31 &&
+ current_day == 1)) { // Yesterday
+ // Adding the 0 padding in a separate statement so translators won't have to deal
+ // with it
+ const std::string minute = (boost::format("%02u") % savedate->tm_min).str();
+
+ gamedata.savedatestring =
+ /** TRANSLATORS: Display date for choosing a savegame/replay. Placeholders are:
+ hour:minute */
+ (boost::format(_("Yesterday, %1%:%2%")) % savedate->tm_hour % minute).str();
+ gamedata.savedonstring =
+ /** TRANSLATORS: Display date for choosing a savegame/replay. Placeholders are:
+ hour:minute. This is part of a list. */
+ (boost::format(_("saved yesterday at %1%:%2%")) % savedate->tm_hour % minute)
+ .str();
+ } else { // Older
+ gamedata.savedatestring =
+ /** TRANSLATORS: Display date for choosing a savegame/replay. Placeholders are:
+ month day, year */
+ (boost::format(_("%2% %1%, %3%")) % savedate->tm_mday %
+ localize_month(savedate->tm_mon) % (1900 + savedate->tm_year))
+ .str();
+ gamedata.savedonstring =
+ /** TRANSLATORS: Display date for choosing a savegame/replay. Placeholders are:
+ month day, year. This is part of a list. */
+ (boost::format(_("saved on %2% %1%, %3%")) % savedate->tm_mday %
+ localize_month(savedate->tm_mon) % (1900 + savedate->tm_year))
+ .str();
+ }
+ }
+
+ gamedata.wincondition = gpdp.get_localized_win_condition();
+ gamedata.minimap_path = gpdp.get_minimap_path();
+ games_data_.push_back(gamedata);
+
+ UI::Table<uintptr_t const>::EntryRecord& te = table_.add(games_data_.size() - 1);
+ te.set_string(0, gamedata.savedatestring);
+
+ if (filetype_ != FileType::kGameSinglePlayer) {
+ std::string gametypestring;
+ switch (gamedata.gametype) {
+ case GameController::GameType::SINGLEPLAYER:
+ /** TRANSLATORS: "Single Player" entry in the Game Mode table column. */
+ /** TRANSLATORS: "Keep this to 6 letters maximum. */
+ /** TRANSLATORS: A tooltip will explain the abbreviation. */
+ /** TRANSLATORS: Make sure that this translation is consistent with the tooltip. */
+ gametypestring = _("SP");
+ break;
+ case GameController::GameType::NETHOST:
+ /** TRANSLATORS: "Multiplayer Host" entry in the Game Mode table column. */
+ /** TRANSLATORS: "Keep this to 2 letters maximum. */
+ /** TRANSLATORS: A tooltip will explain the abbreviation. */
+ /** TRANSLATORS: Make sure that this translation is consistent with the tooltip. */
+ /** TRANSLATORS: %1% is the number of players */
+ gametypestring = (boost::format(_("H (%1%)")) % gamedata.nrplayers).str();
+ break;
+ case GameController::GameType::NETCLIENT:
+ /** TRANSLATORS: "Multiplayer" entry in the Game Mode table column. */
+ /** TRANSLATORS: "Keep this to 2 letters maximum. */
+ /** TRANSLATORS: A tooltip will explain the abbreviation. */
+ /** TRANSLATORS: Make sure that this translation is consistent with the tooltip. */
+ /** TRANSLATORS: %1% is the number of players */
+ gametypestring = (boost::format(_("MP (%1%)")) % gamedata.nrplayers).str();
+ break;
+ case GameController::GameType::REPLAY:
+ gametypestring = "";
+ break;
+ }
+ te.set_string(1, gametypestring);
+ if (filetype_ == FileType::kReplay) {
+ if (UI::g_fh1->fontset()->is_rtl()) {
+ te.set_string(
+ 2, (boost::format("%1% ← %2%") % gamedata.gametime % gamedata.mapname).str());
+ } else {
+ te.set_string(
+ 2, (boost::format("%1% → %2%") % gamedata.gametime % gamedata.mapname).str());
+ }
+ } else {
+ te.set_string(
+ 2, map_filename(gamedata.filename, gamedata.mapname, localize_autosave_));
+ }
+ } else {
+ te.set_string(1, map_filename(gamedata.filename, gamedata.mapname, localize_autosave_));
+ }
+ } catch (const WException& e) {
+ std::string errormessage = e.what();
+ boost::replace_all(errormessage, "\n", "<br>");
+ gamedata.errormessage =
+ ((boost::format("<p>%s</p><p>%s</p><p>%s</p>"))
+ /** TRANSLATORS: Error message introduction for when an old savegame can't be loaded */
+ % _("This file has the wrong format and can’t be loaded."
+ " Maybe it was created with an older version of Widelands.")
+ /** TRANSLATORS: This text is on a separate line with an error message below */
+ % _("Error message:") % errormessage)
+ .str();
+
+ gamedata.mapname = FileSystem::filename_without_ext(gamedata.filename.c_str());
+ games_data_.push_back(gamedata);
+
+ UI::Table<uintptr_t const>::EntryRecord& te = table_.add(games_data_.size() - 1);
+ te.set_string(0, "");
+ if (filetype_ != FileType::kGameSinglePlayer) {
+ te.set_string(1, "");
+ /** TRANSLATORS: Prefix for incompatible files in load game screens */
+ te.set_string(2, (boost::format(_("Incompatible: %s")) % gamedata.mapname).str());
+ } else {
+ te.set_string(1, (boost::format(_("Incompatible: %s")) % gamedata.mapname).str());
+ }
+ }
+ }
+ table_.sort();
+}
=== added file 'src/wui/load_or_save_game.h'
--- src/wui/load_or_save_game.h 1970-01-01 00:00:00 +0000
+++ src/wui/load_or_save_game.h 2017-04-21 07:38:25 +0000
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2002-2016 by the Widelands Development Team
+ *
+ * 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; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef WL_WUI_LOAD_OR_SAVE_GAME_H
+#define WL_WUI_LOAD_OR_SAVE_GAME_H
+
+#include "logic/game.h"
+#include "ui_basic/panel.h"
+#include "ui_basic/table.h"
+#include "wui/gamedetails.h"
+
+/// Common functions for loading or saving a game or replay.
+class LoadOrSaveGame {
+ friend class GameMainMenuSaveGame;
+ friend class FullscreenMenuLoadGame;
+
+protected:
+ /// Choose which type of files to show
+ enum class FileType { kReplay, kGame, kGameMultiPlayer, kGameSinglePlayer };
+
+ /// A table of savegame/replay files and a game details panel.
+ LoadOrSaveGame(UI::Panel* parent,
+ Widelands::Game& g,
+ FileType filetype,
+ GameDetails::Style style,
+ bool localize_autosave);
+
+ //// Update gamedetails and tooltips and return information about the current selection
+ const SavegameData* entry_selected();
+
+ /// Whether the table has a selection
+ bool has_selection();
+
+ /// Clear table selections and game data
+ void clear_selections();
+
+ /// Finds the given filename on the table and selects it
+ void select_by_name(const std::string& name);
+
+ /// Read savegame/replay files and fill the table and games data.
+ void fill_table();
+
+ /// The table panel
+ UI::Table<uintptr_t const>& table() {
+ return table_;
+ }
+
+ /// The game details panel
+ GameDetails* game_details() {
+ return &game_details_;
+ }
+
+ /// Returns the filename for the table entry at 'index'
+ const std::string get_filename(int index) const;
+
+ /// The delete button shown on the bottom of the game details panel
+ UI::Button* delete_button() const {
+ return delete_;
+ }
+ /// Show confirmation window and delete the selected file(s)
+ void clicked_delete();
+
+private:
+ /// Formats the current table selection as a list of filenames with savedate information.
+ const std::string filename_list_string() const;
+
+ /// Reverse default sort order for save date column
+ bool compare_date_descending(uint32_t, uint32_t);
+
+ UI::Panel* parent_;
+ UI::Table<uintptr_t const> table_;
+ FileType filetype_;
+ bool localize_autosave_;
+ std::vector<SavegameData> games_data_;
+ GameDetails game_details_;
+ UI::Button* delete_;
+
+ Widelands::Game& game_;
+};
+
+#endif // end of include guard: WL_WUI_LOAD_OR_SAVE_GAME_H
Follow ups
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: noreply, 2017-11-06
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-11-06
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-11-06
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: bunnybot, 2017-11-06
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-11-06
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: bunnybot, 2017-11-06
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: bunnybot, 2017-11-06
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-11-06
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-11-06
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: Notabilis, 2017-11-06
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: bunnybot, 2017-11-06
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: Notabilis, 2017-11-05
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-11-05
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-11-05
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: Notabilis, 2017-11-05
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: bunnybot, 2017-11-05
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-11-04
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-11-04
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-11-04
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-11-03
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: Jukka Pakarinen, 2017-10-31
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-10-14
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: Notabilis, 2017-10-14
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: bunnybot, 2017-08-12
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: bunnybot, 2017-06-28
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: kaputtnik, 2017-06-17
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: kaputtnik, 2017-06-09
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: bunnybot, 2017-05-20
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: bunnybot, 2017-05-20
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-05-06
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: GunChleoc, 2017-04-23
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: kaputtnik, 2017-04-22
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: bunnybot, 2017-04-21
-
Re: [Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: kaputtnik, 2017-04-21
-
[Merge] lp:~widelands-dev/widelands/savegame-menu into lp:widelands
From: bunnybot, 2017-04-21