/* $Id$ */ /* Copyright (C) 2003 - 2009 by David White Part of the Battle for Wesnoth Project http://www.wesnoth.org/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 or at your option any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the COPYING file for more details. */ /** * @file dialogs.cpp * Various dialogs: advance_unit, show_objectives, save+load game, network::connection. */ #include "global.hpp" #include "dialogs.hpp" #include "foreach.hpp" #include "game_events.hpp" #include "game_preferences.hpp" #include "gettext.hpp" #include "help.hpp" #include "language.hpp" #include "log.hpp" #include "map.hpp" #include "map_exception.hpp" #include "marked-up_text.hpp" #include "menu_events.hpp" #include "mouse_handler_base.hpp" #include "minimap.hpp" #include "replay.hpp" #include "savegame.hpp" #include "thread.hpp" #include "wml_separators.hpp" #include "widgets/progressbar.hpp" #include "wml_exception.hpp" #include "formula_string_utils.hpp" #include "gui/dialogs/game_save.hpp" #include "gui/dialogs/transient_message.hpp" //#ifdef _WIN32 //#include "locale.h" //#endif #include static lg::log_domain log_engine("engine"); #define LOG_NG LOG_STREAM(info, log_engine) #define ERR_NG LOG_STREAM(err, log_engine) static lg::log_domain log_display("display"); #define LOG_DP LOG_STREAM(info, log_display) #define ERR_G LOG_STREAM(err, lg::general) static lg::log_domain log_config("config"); #define ERR_CF LOG_STREAM(err, log_config) namespace dialogs { void advance_unit(const gamemap& map, unit_map& units, map_location loc, game_display& gui, bool random_choice, const bool add_replay_event) { unit_map::iterator u = units.find(loc); if(u == units.end() || u->second.advances() == false) return; LOG_DP << "advance_unit: " << u->second.type_id() << "\n"; const std::vector& options = u->second.advances_to(); std::vector lang_options; std::vector sample_units; for(std::vector::const_iterator op = options.begin(); op != options.end(); ++op) { sample_units.push_back(::get_advanced_unit(units,loc,*op)); const unit& type = sample_units.back(); #ifdef LOW_MEM lang_options.push_back(IMAGE_PREFIX + type.absolute_image() + COLUMN_SEPARATOR + type.type_name()); #else lang_options.push_back(IMAGE_PREFIX + type.absolute_image() + u->second.image_mods() + COLUMN_SEPARATOR + type.type_name()); #endif preferences::encountered_units().insert(*op); } bool always_display = false; foreach (const config &mod, u->second.get_modification_advances()) { if (utils::string_bool(mod["always_display"])) always_display = true; sample_units.push_back(::get_advanced_unit(units,loc,u->second.type_id())); sample_units.back().add_modification("advance", mod); const unit& type = sample_units.back(); if (!mod["image"].empty()) { lang_options.push_back(IMAGE_PREFIX + mod["image"].str() + COLUMN_SEPARATOR + mod["description"].str()); } else { #ifdef LOW_MEM lang_options.push_back(IMAGE_PREFIX + type.absolute_image() + COLUMN_SEPARATOR + mod["description"].str()); #else lang_options.push_back(IMAGE_PREFIX + type.absolute_image() + u->second.image_mods() + COLUMN_SEPARATOR + mod["description"].str()); #endif } } LOG_DP << "options: " << options.size() << "\n"; int res = 0; if(lang_options.empty()) { return; } else if(random_choice) { res = rand()%lang_options.size(); } else if(lang_options.size() > 1 || always_display) { units_list_preview_pane unit_preview(gui,&map,sample_units); std::vector preview_panes; preview_panes.push_back(&unit_preview); gui::dialog advances = gui::dialog(gui, _("Advance Unit"), _("What should our victorious unit become?"), gui::OK_ONLY); advances.set_menu(lang_options); advances.set_panes(preview_panes); res = advances.show(); } if(add_replay_event) { recorder.add_advancement(loc); } recorder.choose_option(res); LOG_DP << "animating advancement...\n"; animate_unit_advancement(units,loc,gui,size_t(res)); // In some rare cases the unit can have enough XP to advance again, // so try to do that. // Make sure that we don't enter an infinite level loop. u = units.find(loc); if(u != units.end()) { // Level 10 unit gives 80 XP and the highest mainline is level 5 if(u->second.experience() < 81) { // For all leveling up we have to add advancement to replay here because replay // doesn't handle multi advancemnet advance_unit(map, units, loc, gui, random_choice, true); } else { ERR_CF << "Unit has an too high amount of " << u->second.experience() << " XP left, cascade leveling disabled\n"; } } else { ERR_NG << "Unit advanced no longer exists\n"; } } bool animate_unit_advancement(unit_map& units, map_location loc, game_display& gui, size_t choice) { const events::command_disabler cmd_disabler; unit_map::iterator u = units.find(loc); if(u == units.end() || u->second.advances() == false) { return false; } const std::vector& options = u->second.advances_to(); std::vector mod_options = u->second.get_modification_advances(); if(choice >= options.size() + mod_options.size()) { return false; } // When the unit advances, it fades to white, and then switches // to the new unit, then fades back to the normal colour if(!gui.video().update_locked()) { unit_animator animator; animator.add_animation(&u->second,"levelout",u->first); animator.start_animations(); animator.wait_for_end(); } if(choice < options.size()) { const std::string& chosen_unit = options[choice]; ::advance_unit(units,loc,chosen_unit); } else { unit amla_unit(u->second); const config &mod_option = mod_options[choice - options.size()]; LOG_NG << "firing advance event (AMLA)\n"; game_events::fire("advance",loc); amla_unit.get_experience(-amla_unit.max_experience()); // subtract xp required amla_unit.add_modification("advance",mod_option); units.replace(loc, amla_unit); LOG_NG << "firing post_advance event (AMLA)\n"; game_events::fire("post_advance",loc); } u = units.find(loc); gui.invalidate_unit(); if(u != units.end() && !gui.video().update_locked()) { unit_animator animator; animator.add_animation(&u->second,"levelin",u->first); animator.start_animations(); animator.wait_for_end(); animator.set_all_standing(); gui.invalidate(loc); gui.draw(); events::pump(); } gui.invalidate_all(); gui.draw(); return true; } void show_objectives(game_display& disp, const config& level, const std::string& objectives) { static const std::string no_objectives(_("No objectives available")); const std::string& name = level["name"]; std::string campaign_name = std::string(level["campaign"]); replace_underbar2space(campaign_name); /** @todo We can now allow pango markup for the objectives if wanted. */ gui2::show_transient_message(disp.video(), "", "" + name + "" + (campaign_name.empty() ? "\n" : " - " + campaign_name + "\n") + (objectives.empty() ? no_objectives : objectives), gui2::tcontrol::PANGO_MARKUP); } namespace { /** Class to handle deleting a saved game. */ class delete_save : public gui::dialog_button_action { public: delete_save(display& disp, gui::filter_textbox& filter, std::vector& saves, std::vector& save_summaries) : disp_(disp), saves_(saves), summaries_(save_summaries), filter_(filter) {} private: gui::dialog_button_action::RESULT button_pressed(int menu_selection); display& disp_; std::vector& saves_; std::vector& summaries_; gui::filter_textbox& filter_; }; gui::dialog_button_action::RESULT delete_save::button_pressed(int menu_selection) { const size_t index = size_t(filter_.get_index(menu_selection)); if(index < saves_.size()) { // See if we should ask the user for deletion confirmation if(preferences::ask_delete_saves()) { gui::dialog dmenu(disp_,"", _("Do you really want to delete this game?"), gui::YES_NO); dmenu.add_option(_("Don't ask me again!"), false); const int res = dmenu.show(); // See if the user doesn't want to be asked this again if(dmenu.option_checked()) { preferences::set_ask_delete_saves(false); } if(res != 0) { return gui::CONTINUE_DIALOG; } } // Remove the item from filter_textbox memory filter_.delete_item(menu_selection); // Delete the file savegame_manager::delete_game(saves_[index].name); // Remove it from the list of saves saves_.erase(saves_.begin() + index); if(index < summaries_.size()) { summaries_.erase(summaries_.begin() + index); } return gui::DELETE_ITEM; } else { return gui::CONTINUE_DIALOG; } } static const int save_preview_border = 10; class save_preview_pane : public gui::preview_pane { public: save_preview_pane(CVideo &video, const config& game_config, gamemap* map, const std::vector& info, const std::vector& summaries, const gui::filter_textbox& textbox) : gui::preview_pane(video), game_config_(&game_config), map_(map), info_(&info), summaries_(&summaries), index_(0), map_cache_(), textbox_(textbox) { set_measurements(std::min(200,video.getx()/4), std::min(400,video.gety() * 4/5)); } void draw_contents(); void set_selection(int index) { index_ = textbox_.get_index(index); set_dirty(); } bool left_side() const { return true; } private: const config* game_config_; gamemap* map_; const std::vector* info_; const std::vector* summaries_; int index_; std::map map_cache_; const gui::filter_textbox& textbox_; }; void save_preview_pane::draw_contents() { if (size_t(index_) >= summaries_->size() || info_->size() != summaries_->size()) { return; } std::string dummy; config& summary = *(*summaries_)[index_]; if (summary["label"] == ""){ try { savegame_manager::load_summary((*info_)[index_].name, summary, &dummy); *(*summaries_)[index_] = summary; } catch(game::load_game_failed&) { summary["corrupt"] = "yes"; } } surface const screen = video().getSurface(); SDL_Rect const &loc = location(); const SDL_Rect area = { loc.x + save_preview_border, loc.y + save_preview_border, loc.w - save_preview_border * 2, loc.h - save_preview_border * 2 }; SDL_Rect clip_area = area; const clip_rect_setter clipper(screen,clip_area); int ypos = area.y; const unit_type_data::unit_type_map::const_iterator leader = unit_type_data::types().find_unit_type(summary["leader"]); if(unit_type_data::types().unit_type_exists(summary["leader"])) { #ifdef LOW_MEM const surface image(image::get_image(leader->second.image())); #else const surface image(image::get_image(leader->second.image() + "~RC(" + leader->second.flag_rgb() + ">1)")); #endif if(image != NULL) { SDL_Rect image_rect = {area.x,area.y,image->w,image->h}; ypos += image_rect.h + save_preview_border; SDL_BlitSurface(image,NULL,screen,&image_rect); } } std::string map_data = summary["map_data"]; if(map_data.empty()) { const config &scenario = game_config_->find_child(summary["campaign_type"], "id", summary["scenario"]); if (scenario && !scenario.find_child("side", "shroud", "yes")) { map_data = scenario["map_data"]; if (map_data.empty() && !scenario["map"].empty()) { try { map_data = read_map(scenario["map"]); } catch(io_exception& e) { ERR_G << "could not read map '" << scenario["map"] << "': " << e.what() << '\n'; } } } } surface map_surf(NULL); if(map_data.empty() == false) { const std::map::const_iterator itor = map_cache_.find(map_data); if(itor != map_cache_.end()) { map_surf = itor->second; } else if(map_ != NULL) { try { #ifdef USE_TINY_GUI const int minimap_size = 60; #else const int minimap_size = 100; #endif map_->read(map_data); map_surf = image::getMinimap(minimap_size, minimap_size, *map_); if(map_surf != NULL) { map_cache_.insert(std::pair(map_data,surface(map_surf))); } } catch(incorrect_map_format_exception& e) { ERR_CF << "map could not be loaded: " << e.msg_ << '\n'; } catch(twml_exception& e) { ERR_CF << "map could not be loaded: " << e.dev_message << '\n'; } } } if(map_surf != NULL) { SDL_Rect map_rect = {area.x + area.w - map_surf->w,area.y,map_surf->w,map_surf->h}; ypos = std::max(ypos,map_rect.y + map_rect.h + save_preview_border); SDL_BlitSurface(map_surf,NULL,screen,&map_rect); } char* old_locale = std::setlocale(LC_TIME, get_locale().localename.c_str()); char time_buf[256] = {0}; const save_info& save = (*info_)[index_]; tm* tm_l = localtime(&save.time_modified); if (tm_l) { const size_t res = strftime(time_buf,sizeof(time_buf),_("%a %b %d %H:%M %Y"),tm_l); if(res == 0) { time_buf[0] = 0; } } else { LOG_NG << "localtime() returned null for time " << save.time_modified << ", save " << save.name; } if(old_locale) { std::setlocale(LC_TIME, old_locale); } std::stringstream str; // Escape all special characters in filenames std::string name = (*info_)[index_].name; str << font::BOLD_TEXT << utils::escape(name) << "\n" << time_buf; const std::string& campaign_type = summary["campaign_type"]; if(utils::string_bool(summary["corrupt"], false)) { str << "\n" << _("#(Invalid)"); } else if (!campaign_type.empty()) { str << "\n"; if(campaign_type == "scenario") { const std::string campaign_id = summary["campaign"]; const config *campaign = NULL; if (!campaign_id.empty()) { if (const config &c = game_config_->find_child("campaign", "id", campaign_id)) campaign = &c; } utils::string_map symbols; if (campaign != NULL) { symbols["campaign_name"] = (*campaign)["name"]; } else { // Fallback to nontranslatable campaign id. symbols["campaign_name"] = "(" + campaign_id + ")"; } str << vgettext("Campaign: $campaign_name", symbols); // Display internal id for debug purposes if we didn't above if (game_config::debug && (campaign != NULL)) { str << '\n' << "(" << campaign_id << ")"; } } else if(campaign_type == "multiplayer") { str << _("Multiplayer"); } else if(campaign_type == "tutorial") { str << _("Tutorial"); } else { str << campaign_type; } str << "\n"; if(utils::string_bool(summary["replay"], false) && !utils::string_bool(summary["snapshot"], true)) { str << _("replay"); } else if (!summary["turn"].empty()) { str << _("Turn") << " " << summary["turn"]; } else { str << _("Scenario Start"); } str << "\n" << _("Difficulty: ") << string_table[summary["difficulty"]]; if(!summary["version"].empty()) { str << "\n" << _("Version: ") << summary["version"]; } } font::draw_text(&video(), area, font::SIZE_SMALL, font::NORMAL_COLOUR, str.str(), area.x, ypos, true); } std::string format_time_summary(time_t t) { time_t curtime = time(NULL); const struct tm* timeptr = localtime(&curtime); if(timeptr == NULL) { return ""; } const struct tm current_time = *timeptr; timeptr = localtime(&t); if(timeptr == NULL) { return ""; } const struct tm save_time = *timeptr; const char* format_string = _("%b %d %y"); if(current_time.tm_year == save_time.tm_year) { const int days_apart = current_time.tm_yday - save_time.tm_yday; if(days_apart == 0) { // save is from today format_string = _("%H:%M"); } else if(days_apart > 0 && days_apart <= current_time.tm_wday) { // save is from this week format_string = _("%A, %H:%M"); } else { // save is from current year format_string = _("%b %d"); } } else { // save is from a different year format_string = _("%b %d %y"); } char buf[40]; const size_t res = strftime(buf,sizeof(buf),format_string,&save_time); if(res == 0) { buf[0] = 0; } return buf; } } // end anon namespace std::string load_game_dialog(display& disp, const config& game_config, bool* show_replay, bool* cancel_orders) { std::vector games; { cursor::setter cur(cursor::WAIT); games = savegame_manager::get_saves_list(); } if(games.empty()) { gui2::show_transient_message(disp.video(), _("No Saved Games"), _("There are no saved games to load.\n\n(Games are saved automatically when you complete a scenario)")); return ""; } std::vector summaries; std::vector::const_iterator i; //FIXME: parent_to_child is not used yet std::map parent_to_child; for(i = games.begin(); i != games.end(); ++i) { config& cfg = save_index::save_summary(i->name); parent_to_child[cfg["parent"]] = i->name; summaries.push_back(&cfg); } const events::event_context context; std::vector items; std::ostringstream heading; heading << HEADING_PREFIX << _("Name") << COLUMN_SEPARATOR << _("Date"); items.push_back(heading.str()); for(i = games.begin(); i != games.end(); ++i) { std::string name = i->name; utils::truncate_as_wstring(name, std::min(name.size(), 40)); std::ostringstream str; str << name << COLUMN_SEPARATOR << format_time_summary(i->time_modified); items.push_back(str.str()); } gamemap map_obj(game_config, ""); gui::dialog lmenu(disp, _("Load Game"), _("Choose the game to load"), gui::NULL_DIALOG); lmenu.set_basic_behavior(gui::OK_CANCEL); gui::menu::basic_sorter sorter; sorter.set_alpha_sort(0).set_id_sort(1); lmenu.set_menu(items, &sorter); gui::filter_textbox* filter = new gui::filter_textbox(disp.video(), _("Filter: "), items, items, 1, lmenu); lmenu.set_textbox(filter); save_preview_pane save_preview(disp.video(),game_config,&map_obj,games,summaries,*filter); lmenu.add_pane(&save_preview); // create an option for whether the replay should be shown or not if(show_replay != NULL) { lmenu.add_option(_("Show replay"), false, game_config::small_gui ? gui::dialog::BUTTON_CHECKBOX : gui::dialog::BUTTON_STANDARD); } if(cancel_orders != NULL) { lmenu.add_option(_("Cancel orders"), false, game_config::small_gui ? gui::dialog::BUTTON_STANDARD : gui::dialog::BUTTON_EXTRA); } lmenu.add_button(new gui::standard_dialog_button(disp.video(),_("OK"),0,false), gui::dialog::BUTTON_STANDARD); lmenu.add_button(new gui::standard_dialog_button(disp.video(),_("Cancel"),1,true), gui::dialog::BUTTON_STANDARD); delete_save save_deleter(disp,*filter,games,summaries); gui::dialog_button_info delete_button(&save_deleter,_("Delete Save")); lmenu.add_button(delete_button, game_config::small_gui ? gui::dialog::BUTTON_HELP : gui::dialog::BUTTON_EXTRA); int res = lmenu.show(); save_index::write_save_index(); if(res == -1) return ""; res = filter->get_index(res); int option_index = 0; if(show_replay != NULL) { *show_replay = lmenu.option_checked(option_index++); const config& summary = *summaries[res]; if(utils::string_bool(summary["replay"], false) && !utils::string_bool(summary["snapshot"], true)) { *show_replay = true; } } if (cancel_orders != NULL) { *cancel_orders = lmenu.option_checked(option_index++); } return games[res].name; } namespace { static const int unit_preview_border = 10; } unit_preview_pane::details::details() : image(), name(), type_name(), race(), level(0), alignment(), traits(), abilities(), hitpoints(0), max_hitpoints(0), experience(0), max_experience(0), hp_color(), xp_color(), movement_left(0), total_movement(0), attacks() { } unit_preview_pane::unit_preview_pane(game_display& disp, const gamemap* map, const gui::filter_textbox* filter, TYPE type, bool on_left_side) : gui::preview_pane(disp.video()), disp_(disp), map_(map), index_(0), details_button_(disp.video(), _("Profile"), gui::button::TYPE_PRESS,"lite_small", gui::button::MINIMUM_SPACE), filter_(filter), weapons_(type == SHOW_ALL), left_(on_left_side) { unsigned w = font::relative_size(weapons_ ? 200 : 190); unsigned h = font::relative_size(weapons_ ? 370 : 140); set_measurements(w, h); } handler_vector unit_preview_pane::handler_members() { handler_vector h; h.push_back(&details_button_); return h; } bool unit_preview_pane::show_above() const { return !weapons_; } bool unit_preview_pane::left_side() const { return left_; } void unit_preview_pane::set_selection(int index) { index = std::min(static_cast(size())-1,index); if (filter_) { index = filter_->get_index(index); } if(index != index_) { index_ = index; set_dirty(); if(map_ != NULL && index >= 0) { details_button_.set_dirty(); } } } void unit_preview_pane::draw_contents() { if(index_ < 0 || index_ >= int(size())) { return; } const details det = get_details(); const bool right_align = left_side(); surface const screen = video().getSurface(); SDL_Rect const &loc = location(); const SDL_Rect area = { loc.x + unit_preview_border, loc.y + unit_preview_border, loc.w - unit_preview_border * 2, loc.h - unit_preview_border * 2 }; SDL_Rect clip_area = area; const clip_rect_setter clipper(screen,clip_area); surface unit_image = det.image; if (!left_) unit_image = image::reverse_image(unit_image); SDL_Rect image_rect = {area.x,area.y,0,0}; if(unit_image != NULL) { SDL_Rect rect = {right_align ? area.x : area.x + area.w - unit_image->w,area.y,unit_image->w,unit_image->h}; SDL_BlitSurface(unit_image,NULL,screen,&rect); image_rect = rect; } // Place the 'unit profile' button if(map_ != NULL) { const SDL_Rect button_loc = {right_align ? area.x : area.x + area.w - details_button_.location().w, image_rect.y + image_rect.h, details_button_.location().w,details_button_.location().h}; details_button_.set_location(button_loc); } SDL_Rect description_rect = {image_rect.x,image_rect.y+image_rect.h+details_button_.location().h,0,0}; if(det.name.empty() == false) { std::stringstream desc; desc << font::NORMAL_TEXT << det.name; const std::string description = desc.str(); description_rect = font::text_area(description, font::SIZE_NORMAL); description_rect = font::draw_text(&video(), area, font::SIZE_NORMAL, font::NORMAL_COLOUR, desc.str(), right_align ? image_rect.x : image_rect.x + image_rect.w - description_rect.w, image_rect.y + image_rect.h + details_button_.location().h); } std::stringstream text; text << det.type_name << "\n" << det.race << "\n" << font::BOLD_TEXT << _("level") << " " << det.level << "\n" << det.alignment << "\n" << det.traits << "\n"; for(std::vector::const_iterator a = det.abilities.begin(); a != det.abilities.end(); a++) { if(a != det.abilities.begin()) { text << ", "; } text << (*a); } text << "\n"; // Use same coloring as in generate_report.cpp: text << det.hp_color << _("HP: ") << det.hitpoints << "/" << det.max_hitpoints << "\n"; text << det.xp_color << _("XP: ") << det.experience << "/" << det.max_experience << "\n"; if(weapons_) { text << _("Moves: ") << det.movement_left << "/" << det.total_movement << "\n"; for(std::vector::const_iterator at_it = det.attacks.begin(); at_it != det.attacks.end(); ++at_it) { // specials_context seems not needed here //at_it->set_specials_context(map_location(),u); // see generate_report() in generate_report.cpp text << "<245,230,193>" << at_it->name() << " (" << gettext(at_it->type().c_str()) << ")\n"; std::string special = at_it->weapon_specials(true); if (!special.empty()) { text << "<166,146,117> " << special << "\n"; } std::string accuracy = at_it->accuracy_parry_description(); if(accuracy.empty() == false) { accuracy += " "; } text << "<166,146,117> " << at_it->damage() << "-" << at_it->num_attacks() << " " << accuracy << "-- " << _(at_it->range().c_str()) << "\n"; } } // we don't remove empty lines, so all fields stay at the same place const std::vector lines = utils::split(text.str(), '\n', utils::STRIP_SPACES & !utils::REMOVE_EMPTY); SDL_Rect cur_area = area; if(weapons_) { cur_area.y += image_rect.h + description_rect.h + details_button_.location().h; } for(std::vector::const_iterator line = lines.begin(); line != lines.end(); ++line) { int xpos = cur_area.x; if(right_align && !weapons_) { const SDL_Rect& line_area = font::text_area(*line,font::SIZE_SMALL); xpos = cur_area.x + cur_area.w - line_area.w; } cur_area = font::draw_text(&video(),location(),font::SIZE_SMALL,font::NORMAL_COLOUR,*line,xpos,cur_area.y); cur_area.y += cur_area.h; } } units_list_preview_pane::units_list_preview_pane(game_display& disp, const gamemap* map, const unit& u, TYPE type, bool on_left_side) : unit_preview_pane(disp, map, NULL, type, on_left_side), units_(&unit_store_), unit_store_(1, u) { } units_list_preview_pane::units_list_preview_pane(game_display& disp, const gamemap* map, std::vector& units, const gui::filter_textbox* filter, TYPE type, bool on_left_side) : unit_preview_pane(disp, map, filter, type, on_left_side), units_(&units), unit_store_() { } size_t units_list_preview_pane::size() const { return (units_!=NULL) ? units_->size() : 0; } const unit_preview_pane::details units_list_preview_pane::get_details() const { unit& u = (*units_)[index_]; details det; det.image = u.still_image(); det.name = u.name(); det.type_name = u.type_name(); if(u.race() != NULL) { det.race = u.race()->name(u.gender()); } det.level = u.level(); det.alignment = unit_type::alignment_description(u.alignment(), u.gender()); det.traits = u.traits_description(); //we filter to remove the tooltips (increment by 2) const std::vector &abilities = u.ability_tooltips(true); for(std::vector::const_iterator a = abilities.begin(); a != abilities.end(); a+=2) { det.abilities.push_back(*a); } det.hitpoints = u.hitpoints(); det.max_hitpoints = u.max_hitpoints(); det.hp_color = font::color2markup(u.hp_color()); det.experience = u.experience(); det.max_experience = u.max_experience(); det.xp_color = font::color2markup(u.xp_color()); det.movement_left = u.movement_left(); det.total_movement= u.total_movement(); det.attacks = u.attacks(); return det; } void units_list_preview_pane::process_event() { if(map_ != NULL && details_button_.pressed() && index_ >= 0 && index_ < int(size())) { show_unit_description(disp_, (*units_)[index_]); } } unit_types_preview_pane::unit_types_preview_pane(game_display& disp, const gamemap* map, std::vector& unit_types, const gui::filter_textbox* filter, int side, TYPE type, bool on_left_side) : unit_preview_pane(disp, map, filter, type, on_left_side), unit_types_(&unit_types), side_(side) {} size_t unit_types_preview_pane::size() const { return (unit_types_!=NULL) ? unit_types_->size() : 0; } const unit_types_preview_pane::details unit_types_preview_pane::get_details() const { const unit_type* t = (*unit_types_)[index_]; details det; if (t==NULL) return det; //FIXME: There should be a better way to deal with this unit_type_data::types().find_unit_type(t->id(), unit_type::WITHOUT_ANIMATIONS); std::string mod = "~RC(" + t->flag_rgb() + ">" + team::get_side_colour_index(side_) + ")"; det.image = image::get_image(t->image()+mod); det.name = ""; det.type_name = t->type_name(); det.level = t->level(); det.alignment = unit_type::alignment_description(t->alignment(), t->genders().front()); race_map const& rcm = unit_type_data::types().races(); race_map::const_iterator ri = rcm.find(t->race()); if(ri != rcm.end()) { assert(t->genders().empty() != true); det.race = (*ri).second.name(t->genders().front()); } //FIXME: This probably must be move into a unit_type function foreach (const config &tr, t->possible_traits()) { if (tr["availability"] != "musthave") continue; std::string gender_string = (!t->genders().empty() && t->genders().front()== unit_race::FEMALE) ? "female_name" : "male_name"; t_string name = tr[gender_string]; if (name.empty()) { name = tr["name"]; } if (!name.empty()) { if (!det.traits.empty()) { det.traits += ", "; } det.traits += name; } } det.abilities = t->abilities(); det.hitpoints = t->hitpoints(); det.max_hitpoints = t->hitpoints(); det.hp_color = "<33,225,0>"; // from unit::hp_color() det.experience = 0; det.max_experience = t->experience_needed(); det.xp_color = "<0,160,225>"; // from unit::xp_color() // Check if AMLA color is needed // FIXME: not sure if it's fully accurate (but not very important for unit_type) // xp_color also need a simpler function for doing this foreach (const config &adv, t->modification_advancements()) { if (!utils::string_bool(adv["strict_amla"]) || !t->can_advance()) { det.xp_color = "<170,0,255>"; // from unit::xp_color() break; } } det.movement_left = 0; det.total_movement= t->movement(); det.attacks = t->attacks(); return det; } void unit_types_preview_pane::process_event() { if(map_ != NULL && details_button_.pressed() && index_ >= 0 && index_ < int(size())) { const unit_type* type = (*unit_types_)[index_]; if (type != NULL) show_unit_description(disp_, *type); } } void show_unit_description(game_display &disp, const unit& u) { const unit_type* t = u.type(); if (t != NULL) show_unit_description(disp, *t); else // can't find type, try open the id page to have feedback and unit error page help::show_unit_help(disp, u.type_id()); } void show_unit_description(game_display &disp, const unit_type& t) { help::show_unit_help(disp, t.id(), t.hide_help()); } namespace { static const int campaign_preview_border = font::relative_size(10); } campaign_preview_pane::campaign_preview_pane(CVideo &video,std::vector >* desc) : gui::preview_pane(video),descriptions_(desc),index_(0) { // size of the campaign info window with the campaign description and image in pixel #ifdef USE_TINY_GUI set_measurements(160, 200); #else set_measurements(430, 440); #endif } bool campaign_preview_pane::show_above() const { return false; } bool campaign_preview_pane::left_side() const { return false; } void campaign_preview_pane::set_selection(int index) { index = std::min(descriptions_->size()-1,index); if(index != index_ && index >= 0) { index_ = index; set_dirty(); } } void campaign_preview_pane::draw_contents() { if (size_t(index_) >= descriptions_->size()) { return; } const SDL_Rect area = { location().x+campaign_preview_border, location().y, location().w-campaign_preview_border*2, location().h }; /* background frame */ gui::dialog_frame f(video(), "", gui::dialog_frame::preview_style, false); f.layout(area); f.draw_background(); f.draw_border(); /* description text */ std::string desc_text; try { desc_text = font::word_wrap_text((*descriptions_)[index_].first, font::SIZE_SMALL, area.w - 2 * campaign_preview_border); } catch (utils::invalid_utf8_exception&) { ERR_NG << "Invalid utf-8 found, campaign description is ignored.\n"; } const std::vector lines = utils::split(desc_text, '\n',utils::STRIP_SPACES); SDL_Rect txt_area = { area.x+campaign_preview_border,area.y+campaign_preview_border,0,0 }; for(std::vector::const_iterator line = lines.begin(); line != lines.end(); ++line) { txt_area = font::draw_text(&video(),location(),font::SIZE_SMALL,font::NORMAL_COLOUR,*line,txt_area.x,txt_area.y); txt_area.y += txt_area.h; } /* description image */ surface img(NULL); const std::string desc_img_name = (*descriptions_)[index_].second; if(!desc_img_name.empty()) { img.assign(image::get_image(desc_img_name)); } if (!img.null()) { SDL_Rect src_rect,dst_rect; int max_height = area.h-(txt_area.h+txt_area.y-area.y); src_rect.x = src_rect.y = 0; src_rect.w = std::min(area.w,img->w); src_rect.h = std::min(max_height,img->h); dst_rect.x = area.x+(area.w-src_rect.w)/2; dst_rect.y = txt_area.y+((max_height-src_rect.h)*8)/13; if(dst_rect.y - txt_area.h - txt_area.y >= 120) { //for really tall dialogs, just put it under the text dst_rect.y = txt_area.y + font::get_max_height(font::SIZE_SMALL)*5; } SDL_BlitSurface(img,&src_rect,video().getSurface(),&dst_rect); } } static network::connection network_data_dialog(display& disp, const std::string& msg, config& cfg, network::connection connection_num, network::statistics (*get_stats)(network::connection handle)) { #ifdef USE_TINY_GUI const size_t width = 200; const size_t height = 40; const size_t border = 10; #else const size_t width = 300; const size_t height = 80; const size_t border = 20; #endif const int left = disp.w()/2 - width/2; const int top = disp.h()/2 - height/2; const events::event_context dialog_events_context; gui::button cancel_button(disp.video(),_("Cancel")); std::vector buttons_ptr(1,&cancel_button); gui::dialog_frame frame(disp.video(), msg, gui::dialog_frame::default_style, true, &buttons_ptr); SDL_Rect centered_layout = frame.layout(left,top,width,height).interior; centered_layout.x = disp.w() / 2 - centered_layout.w / 2; centered_layout.y = disp.h() / 2 - centered_layout.h / 2; // HACK: otherwise we get an empty useless space in the dialog below the progressbar centered_layout.h = height; frame.layout(centered_layout); frame.draw(); const SDL_Rect progress_rect = {centered_layout.x+border,centered_layout.y+border,centered_layout.w-border*2,centered_layout.h-border*2}; gui::progress_bar progress(disp.video()); progress.set_location(progress_rect); events::raise_draw_event(); disp.flip(); network::statistics old_stats = get_stats(connection_num); cfg.clear(); for(;;) { const network::connection res = network::receive_data(cfg,connection_num,100); const network::statistics stats = get_stats(connection_num); if(stats.current_max != 0 && stats != old_stats) { old_stats = stats; progress.set_progress_percent((stats.current*100)/stats.current_max); std::ostringstream stream; stream << stats.current/1024 << "/" << stats.current_max/1024 << _("KB"); progress.set_text(stream.str()); } events::raise_draw_event(); disp.flip(); events::pump(); if(res != 0) { return res; } if(cancel_button.pressed()) { return res; } } } network::connection network_send_dialog(display& disp, const std::string& msg, config& cfg, network::connection connection_num) { return network_data_dialog(disp, msg, cfg, connection_num, network::get_send_stats); } network::connection network_receive_dialog(display& disp, const std::string& msg, config& cfg, network::connection connection_num) { return network_data_dialog(disp, msg, cfg, connection_num, network::get_receive_stats); } } // end namespace dialogs namespace { class connect_waiter : public threading::waiter { public: connect_waiter(display& disp, gui::button& button) : disp_(disp), button_(button) {} ACTION process(); private: display& disp_; gui::button& button_; }; connect_waiter::ACTION connect_waiter::process() { events::raise_draw_event(); disp_.flip(); events::pump(); if(button_.pressed()) { return ABORT; } else { return WAIT; } } } namespace dialogs { network::connection network_connect_dialog(display& disp, const std::string& msg, const std::string& hostname, int port) { #ifdef USE_TINY_GUI const size_t width = 200; const size_t height = 20; #else const size_t width = 250; const size_t height = 20; #endif const int left = disp.w()/2 - width/2; const int top = disp.h()/2 - height/2; const events::event_context dialog_events_context; gui::button cancel_button(disp.video(),_("Cancel")); std::vector buttons_ptr(1,&cancel_button); gui::dialog_frame frame(disp.video(), msg, gui::dialog_frame::default_style, true, &buttons_ptr); frame.layout(left,top,width,height); frame.draw(); events::raise_draw_event(); disp.flip(); connect_waiter waiter(disp,cancel_button); return network::connect(hostname,port,waiter); } } // end namespace dialogs