1 /*
2  * Copyright (C) 2008-2020 by the Widelands Development Team
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  *
18  */
19 
20 #include "wui/economy_options_window.h"
21 
22 #include <memory>
23 
24 #include "graphic/graphic.h"
25 #include "io/profile.h"
26 #include "logic/editor_game_base.h"
27 #include "logic/filesystem_constants.h"
28 #include "logic/map_objects/tribes/ware_descr.h"
29 #include "logic/map_objects/tribes/worker_descr.h"
30 #include "logic/player.h"
31 #include "logic/playercommand.h"
32 #include "ui_basic/messagebox.h"
33 
34 static const char pic_tab_wares[] = "images/wui/buildings/menu_tab_wares.png";
35 static const char pic_tab_workers[] = "images/wui/buildings/menu_tab_workers.png";
36 
37 constexpr int kDesiredWidth = 216;
38 
EconomyOptionsWindow(UI::Panel * parent,Widelands::Economy * ware_economy,Widelands::Economy * worker_economy,bool can_act)39 EconomyOptionsWindow::EconomyOptionsWindow(UI::Panel* parent,
40                                            Widelands::Economy* ware_economy,
41                                            Widelands::Economy* worker_economy,
42                                            bool can_act)
43    : UI::Window(parent, "economy_options", 0, 0, 0, 0, _("Economy options")),
44      main_box_(this, 0, 0, UI::Box::Vertical),
45      ware_serial_(ware_economy->serial()),
46      worker_serial_(worker_economy->serial()),
47      player_(&ware_economy->owner()),
48      tabpanel_(this, UI::TabPanelStyle::kWuiDark),
49      ware_panel_(new EconomyOptionsPanel(
50         &tabpanel_, this, ware_serial_, player_, can_act, Widelands::wwWARE, kDesiredWidth)),
51      worker_panel_(new EconomyOptionsPanel(
52         &tabpanel_, this, worker_serial_, player_, can_act, Widelands::wwWORKER, kDesiredWidth)),
53      dropdown_box_(this, 0, 0, UI::Box::Horizontal),
54      dropdown_(&dropdown_box_,
55                "economy_profiles",
56                0,
57                0,
58                174,
59                10,
60                34,
61                "",
62                UI::DropdownType::kTextual,
63                UI::PanelStyle::kWui,
64                UI::ButtonStyle::kWuiSecondary),
65      time_last_thought_(0),
66      save_profile_dialog_(nullptr) {
67 	set_center_panel(&main_box_);
68 
69 	tabpanel_.add("wares", g_gr->images().get(pic_tab_wares), ware_panel_, _("Wares"));
70 	tabpanel_.add("workers", g_gr->images().get(pic_tab_workers), worker_panel_, _("Workers"));
71 
72 	UI::Box* buttons = new UI::Box(this, 0, 0, UI::Box::Horizontal);
73 	UI::Button* b = new UI::Button(
74 	   buttons, "decrease_target_fast", 0, 0, 40, 28, UI::ButtonStyle::kWuiSecondary,
75 	   g_gr->images().get("images/ui_basic/scrollbar_down_fast.png"), _("Decrease target by 10"));
76 	b->sigclicked.connect([this] { change_target(-10); });
77 	buttons->add(b);
78 	b->set_repeating(true);
79 	b->set_enabled(can_act);
80 	buttons->add_space(6);
81 	b = new UI::Button(buttons, "decrease_target", 0, 0, 40, 28, UI::ButtonStyle::kWuiSecondary,
82 	                   g_gr->images().get("images/ui_basic/scrollbar_down.png"),
83 	                   _("Decrease target"));
84 	b->sigclicked.connect([this] { change_target(-1); });
85 	buttons->add(b);
86 	b->set_repeating(true);
87 	b->set_enabled(can_act);
88 	buttons->add_space(6);
89 
90 	b = new UI::Button(buttons, "toggle_infinite", 0, 0, 32, 28, UI::ButtonStyle::kWuiSecondary,
91 	                   g_gr->images().get("images/wui/menus/infinity.png"),
92 	                   _("Toggle infinite target"));
93 	b->sigclicked.connect([this] { toggle_infinite(); });
94 	buttons->add(b);
95 	b->set_repeating(false);
96 	b->set_enabled(can_act);
97 	buttons->add_space(6);
98 
99 	b = new UI::Button(buttons, "increase_target", 0, 0, 40, 28, UI::ButtonStyle::kWuiSecondary,
100 	                   g_gr->images().get("images/ui_basic/scrollbar_up.png"), _("Increase target"));
101 	b->sigclicked.connect([this] { change_target(1); });
102 	buttons->add(b);
103 	b->set_repeating(true);
104 	b->set_enabled(can_act);
105 	buttons->add_space(6);
106 	b = new UI::Button(buttons, "increase_target_fast", 0, 0, 40, 28, UI::ButtonStyle::kWuiSecondary,
107 	                   g_gr->images().get("images/ui_basic/scrollbar_up_fast.png"),
108 	                   _("Increase target by 10"));
109 	b->sigclicked.connect([this] { change_target(10); });
110 	buttons->add(b);
111 	b->set_repeating(true);
112 	b->set_enabled(can_act);
113 
114 	dropdown_.set_tooltip(_("Profile to apply to the selected items"));
115 	dropdown_box_.set_size(40, 20);  // Prevent assert failures
116 	dropdown_box_.add(&dropdown_, UI::Box::Resizing::kFullSize);
117 	if (can_act) {
118 		dropdown_.selected.connect([this] { reset_target(); });
119 	} else {
120 		dropdown_.set_enabled(false);
121 	}
122 
123 	b = new UI::Button(&dropdown_box_, "save_targets", 0, 0, 34, 34, UI::ButtonStyle::kWuiMenu,
124 	                   g_gr->images().get("images/wui/menus/save_game.png"),
125 	                   _("Save target settings"));
126 	b->sigclicked.connect([this] { create_target(); });
127 	dropdown_box_.add_space(8);
128 	dropdown_box_.add(b);
129 
130 	main_box_.add(&tabpanel_, UI::Box::Resizing::kAlign, UI::Align::kCenter);
131 	main_box_.add_space(8);
132 	main_box_.add(buttons, UI::Box::Resizing::kAlign, UI::Align::kCenter);
133 	main_box_.add_space(8);
134 	main_box_.add(&dropdown_box_, UI::Box::Resizing::kAlign, UI::Align::kCenter);
135 
136 	ware_economy->set_options_window(static_cast<void*>(this));
137 	worker_economy->set_options_window(static_cast<void*>(this));
138 	economynotes_subscriber_ = Notifications::subscribe<Widelands::NoteEconomy>(
139 	   [this](const Widelands::NoteEconomy& note) { on_economy_note(note); });
140 	profilenotes_subscriber_ =
141 	   Notifications::subscribe<NoteEconomyProfile>([this](const NoteEconomyProfile& n) {
142 		   if (n.ware_serial == ware_serial_ && n.worker_serial == worker_serial_) {
143 			   // We already updated ourself before we changed something
144 			   return;
145 		   }
146 		   read_targets();
147 		   if (save_profile_dialog_) {
148 			   save_profile_dialog_->update_table();
149 		   }
150 		});
151 
152 	read_targets();
153 }
154 
~EconomyOptionsWindow()155 EconomyOptionsWindow::~EconomyOptionsWindow() {
156 	if (Widelands::Economy* e_wa = player_->get_economy(ware_serial_)) {
157 		e_wa->set_options_window(nullptr);
158 	}
159 	if (Widelands::Economy* e_wo = player_->get_economy(worker_serial_)) {
160 		e_wo->set_options_window(nullptr);
161 	}
162 	if (save_profile_dialog_) {
163 		save_profile_dialog_->unset_parent();
164 	}
165 }
166 
localize_profile_name(const std::string & name)167 std::string EconomyOptionsWindow::localize_profile_name(const std::string& name) {
168 	// Translation for the default profile is sourced from the widelands textdomain, and for the
169 	// other profiles from the tribes.
170 	std::string localized_name = _(name);
171 	{
172 		i18n::Textdomain td("tribes");
173 		localized_name = _(localized_name);
174 	}
175 	return localized_name;
176 }
177 
on_economy_note(const Widelands::NoteEconomy & note)178 void EconomyOptionsWindow::on_economy_note(const Widelands::NoteEconomy& note) {
179 	Widelands::Serial* serial = note.old_economy == ware_serial_ ?
180 	                               &ware_serial_ :
181 	                               note.old_economy == worker_serial_ ? &worker_serial_ : nullptr;
182 	if (serial) {
183 		switch (note.action) {
184 		case Widelands::NoteEconomy::Action::kMerged: {
185 			*serial = note.new_economy;
186 			Widelands::Economy* economy = player_->get_economy(*serial);
187 			if (economy == nullptr) {
188 				die();
189 				return;
190 			}
191 			economy->set_options_window(static_cast<void*>(this));
192 			(*serial == ware_serial_ ? ware_panel_ : worker_panel_)->set_economy(note.new_economy);
193 			move_to_top();
194 		} break;
195 		case Widelands::NoteEconomy::Action::kDeleted:
196 			// Make sure that the panels stop thinking first.
197 			die();
198 			break;
199 		}
200 	}
201 }
202 
layout()203 void EconomyOptionsWindow::layout() {
204 	int w, h;
205 	tabpanel_.get_desired_size(&w, &h);
206 	main_box_.set_desired_size(w, h + 78);
207 	update_desired_size();
208 	UI::Window::layout();
209 }
210 
update_desired_size()211 void EconomyOptionsWindow::EconomyOptionsPanel::update_desired_size() {
212 	display_.set_hgap(AbstractWaresDisplay::calc_hgap(display_.get_extent().w, kDesiredWidth));
213 	Box::update_desired_size();
214 	get_parent()->layout();
215 }
216 
TargetWaresDisplay(UI::Panel * const parent,int32_t const x,int32_t const y,Widelands::Serial serial,Widelands::Player * player,Widelands::WareWorker type,bool selectable)217 EconomyOptionsWindow::TargetWaresDisplay::TargetWaresDisplay(UI::Panel* const parent,
218                                                              int32_t const x,
219                                                              int32_t const y,
220                                                              Widelands::Serial serial,
221                                                              Widelands::Player* player,
222                                                              Widelands::WareWorker type,
223                                                              bool selectable)
224    : AbstractWaresDisplay(parent, x, y, player->tribe(), type, selectable),
225      serial_(serial),
226      player_(player) {
227 	const Widelands::TribeDescr& owner_tribe = player->tribe();
228 	if (type == Widelands::wwWORKER) {
229 		for (const Widelands::DescriptionIndex& worker_index : owner_tribe.workers()) {
230 			const Widelands::WorkerDescr* worker_descr = owner_tribe.get_worker_descr(worker_index);
231 			if (!worker_descr->has_demand_check()) {
232 				hide_ware(worker_index);
233 			}
234 		}
235 	} else {
236 		for (const Widelands::DescriptionIndex& ware_index : owner_tribe.wares()) {
237 			const Widelands::WareDescr* ware_descr = owner_tribe.get_ware_descr(ware_index);
238 			if (!ware_descr->has_demand_check(owner_tribe.name())) {
239 				hide_ware(ware_index);
240 			}
241 		}
242 	}
243 }
244 
set_economy(Widelands::Serial serial)245 void EconomyOptionsWindow::TargetWaresDisplay::set_economy(Widelands::Serial serial) {
246 	serial_ = serial;
247 }
248 
249 std::string
info_for_ware(Widelands::DescriptionIndex const ware)250 EconomyOptionsWindow::TargetWaresDisplay::info_for_ware(Widelands::DescriptionIndex const ware) {
251 	Widelands::Economy* economy = player_->get_economy(serial_);
252 	if (economy == nullptr) {
253 		die();
254 		return *(new std::string());
255 	}
256 	const Widelands::Quantity amount = economy->target_quantity(ware).permanent;
257 	if (amount == Widelands::kEconomyTargetInfinity) {
258 		/** TRANSLATORS: Infinite number of wares or workers */
259 		return g_gr->styles().font_style(UI::FontStyle::kLabel).as_font_tag(_("∞"));
260 	}
261 	return boost::lexical_cast<std::string>(amount);
262 }
263 
264 /**
265  * Wraps the wares/workers display together with some buttons
266  */
EconomyOptionsPanel(UI::Panel * parent,EconomyOptionsWindow * eco_window,Widelands::Serial serial,Widelands::Player * player,bool can_act,Widelands::WareWorker type,int32_t min_w)267 EconomyOptionsWindow::EconomyOptionsPanel::EconomyOptionsPanel(UI::Panel* parent,
268                                                                EconomyOptionsWindow* eco_window,
269                                                                Widelands::Serial serial,
270                                                                Widelands::Player* player,
271                                                                bool can_act,
272                                                                Widelands::WareWorker type,
273                                                                int32_t min_w)
274    : UI::Box(parent, 0, 0, UI::Box::Vertical),
275      serial_(serial),
276      player_(player),
277      type_(type),
278      display_(this, 0, 0, serial_, player_, type, can_act),
279      economy_options_window_(eco_window) {
280 	add(&display_, UI::Box::Resizing::kFullSize);
281 
282 	display_.set_hgap(AbstractWaresDisplay::calc_hgap(display_.get_extent().w, min_w));
283 }
284 
set_economy(Widelands::Serial serial)285 void EconomyOptionsWindow::EconomyOptionsPanel::set_economy(Widelands::Serial serial) {
286 	serial_ = serial;
287 	display_.set_economy(serial);
288 }
289 
toggle_infinite()290 void EconomyOptionsWindow::toggle_infinite() {
291 	if (tabpanel_.active() == 0) {
292 		ware_panel_->toggle_infinite();
293 	} else {
294 		worker_panel_->toggle_infinite();
295 	}
296 }
297 
change_target(int amount)298 void EconomyOptionsWindow::change_target(int amount) {
299 	if (tabpanel_.active() == 0) {
300 		ware_panel_->change_target(amount);
301 	} else {
302 		worker_panel_->change_target(amount);
303 	}
304 }
305 
reset_target()306 void EconomyOptionsWindow::reset_target() {
307 	if (dropdown_.get_selected().empty()) {
308 		return;
309 	}
310 	if (tabpanel_.active() == 0) {
311 		ware_panel_->reset_target();
312 	} else {
313 		worker_panel_->reset_target();
314 	}
315 }
316 
toggle_infinite()317 void EconomyOptionsWindow::EconomyOptionsPanel::toggle_infinite() {
318 	Widelands::Economy* economy = player_->get_economy(serial_);
319 	if (!economy) {
320 		return die();
321 	}
322 	assert(economy->type() == type_);
323 	Widelands::Game& game = dynamic_cast<Widelands::Game&>(player_->egbase());
324 	const bool is_wares = type_ == Widelands::wwWARE;
325 	const auto& items = is_wares ? player_->tribe().wares() : player_->tribe().workers();
326 	for (const Widelands::DescriptionIndex& index : items) {
327 		if (display_.ware_selected(index)) {
328 			const Widelands::Economy::TargetQuantity& tq = economy->target_quantity(index);
329 			Widelands::Quantity new_quantity;
330 			if (tq.permanent == Widelands::kEconomyTargetInfinity) {
331 				auto it = infinity_substitutes_.find(index);
332 				if (it == infinity_substitutes_.end()) {
333 					// The window was opended with the target set to infinite,
334 					// so we just use the default value
335 					new_quantity = is_wares ?
336 					                  economy_options_window_->get_predefined_targets()
337 					                     .at(kDefaultEconomyProfile)
338 					                     .wares.at(index) :
339 					                  economy_options_window_->get_predefined_targets()
340 					                     .at(kDefaultEconomyProfile)
341 					                     .workers.at(index);
342 				} else {
343 					// Restore saved old value
344 					new_quantity = it->second;
345 					infinity_substitutes_.erase(it);
346 				}
347 			} else {
348 				new_quantity = Widelands::kEconomyTargetInfinity;
349 				// Save old target for when the infinity option is disabled again
350 				infinity_substitutes_[index] = tq.permanent;
351 			}
352 			if (is_wares) {
353 				game.send_player_command(new Widelands::CmdSetWareTargetQuantity(
354 				   game.get_gametime(), player_->player_number(), serial_, index, new_quantity));
355 			} else {
356 				game.send_player_command(new Widelands::CmdSetWorkerTargetQuantity(
357 				   game.get_gametime(), player_->player_number(), serial_, index, new_quantity));
358 			}
359 		}
360 	}
361 }
362 
change_target(int delta)363 void EconomyOptionsWindow::EconomyOptionsPanel::change_target(int delta) {
364 	if (delta == 0) {
365 		return;
366 	}
367 	Widelands::Economy* economy = player_->get_economy(serial_);
368 	if (!economy) {
369 		return die();
370 	}
371 	assert(economy->type() == type_);
372 	Widelands::Game& game = dynamic_cast<Widelands::Game&>(player_->egbase());
373 	const bool is_wares = type_ == Widelands::wwWARE;
374 	const auto& items = is_wares ? player_->tribe().wares() : player_->tribe().workers();
375 	for (const Widelands::DescriptionIndex& index : items) {
376 		if (display_.ware_selected(index)) {
377 			const Widelands::Economy::TargetQuantity& tq = economy->target_quantity(index);
378 			if (tq.permanent == Widelands::kEconomyTargetInfinity) {
379 				// Infinity ± finite value = infinity
380 				continue;
381 			}
382 			// Don't allow negative new amount
383 			const int old_amount = static_cast<int>(tq.permanent);
384 			const int new_amount = std::max(0, old_amount + delta);
385 			assert(old_amount >= 0);
386 			assert(new_amount >= 0);
387 			if (new_amount == old_amount) {
388 				continue;
389 			}
390 			if (is_wares) {
391 				game.send_player_command(new Widelands::CmdSetWareTargetQuantity(
392 				   game.get_gametime(), player_->player_number(), serial_, index, new_amount));
393 			} else {
394 				game.send_player_command(new Widelands::CmdSetWorkerTargetQuantity(
395 				   game.get_gametime(), player_->player_number(), serial_, index, new_amount));
396 			}
397 		}
398 	}
399 }
400 
reset_target()401 void EconomyOptionsWindow::EconomyOptionsPanel::reset_target() {
402 	Widelands::Game& game = dynamic_cast<Widelands::Game&>(player_->egbase());
403 	const bool is_wares = type_ == Widelands::wwWARE;
404 	const auto& items = is_wares ? player_->tribe().wares() : player_->tribe().workers();
405 	const PredefinedTargets settings = economy_options_window_->get_selected_target();
406 
407 	bool anything_selected = false;
408 	for (const Widelands::DescriptionIndex& index : items) {
409 		if (display_.ware_selected(index)) {
410 			anything_selected = true;
411 			break;
412 		}
413 	}
414 	for (const Widelands::DescriptionIndex& index : items) {
415 		if (display_.ware_selected(index) ||
416 		    (!anything_selected && !display_.is_ware_hidden(index))) {
417 			if (is_wares) {
418 				game.send_player_command(new Widelands::CmdSetWareTargetQuantity(
419 				   game.get_gametime(), player_->player_number(), serial_, index,
420 				   settings.wares.at(index)));
421 			} else {
422 				game.send_player_command(new Widelands::CmdSetWorkerTargetQuantity(
423 				   game.get_gametime(), player_->player_number(), serial_, index,
424 				   settings.workers.at(index)));
425 			}
426 		}
427 	}
428 }
429 
430 constexpr unsigned kThinkInterval = 200;
431 
think()432 void EconomyOptionsWindow::think() {
433 	const uint32_t time = player_->egbase().get_gametime();
434 	if (time - time_last_thought_ < kThinkInterval || !player_->get_economy(ware_serial_) ||
435 	    !player_->get_economy(worker_serial_)) {
436 		// If our economy has been deleted, die() was already called, no need to do anything
437 		return;
438 	}
439 	time_last_thought_ = time;
440 	update_profiles();
441 }
442 
applicable_target()443 std::string EconomyOptionsWindow::applicable_target() {
444 	const Widelands::Economy* eco_ware = player_->get_economy(ware_serial_);
445 	const Widelands::Economy* eco_worker = player_->get_economy(worker_serial_);
446 	for (const auto& pair : predefined_targets_) {
447 		bool matches = true;
448 		if (tabpanel_.active() == 0) {
449 			for (const Widelands::DescriptionIndex& index : player_->tribe().wares()) {
450 				const auto it = pair.second.wares.find(index);
451 				if (it != pair.second.wares.end() &&
452 				    eco_ware->target_quantity(index).permanent != it->second) {
453 					matches = false;
454 					break;
455 				}
456 			}
457 		} else {
458 			for (const Widelands::DescriptionIndex& index : player_->tribe().workers()) {
459 				const auto it = pair.second.workers.find(index);
460 				if (it != pair.second.workers.end() &&
461 				    eco_worker->target_quantity(index).permanent != it->second) {
462 					matches = false;
463 					break;
464 				}
465 			}
466 		}
467 		if (matches) {
468 			return pair.first;
469 		}
470 	}
471 	return "";
472 }
473 
update_profiles()474 void EconomyOptionsWindow::update_profiles() {
475 	const std::string current_profile = applicable_target();
476 
477 	for (const auto& pair : predefined_targets_) {
478 		if (last_added_to_dropdown_.count(pair.first) == 0) {
479 			return update_profiles_needed(current_profile);
480 		}
481 	}
482 	for (const auto& string : last_added_to_dropdown_) {
483 		if (!string.empty() && predefined_targets_.find(string) == predefined_targets_.end()) {
484 			return update_profiles_needed(current_profile);
485 		}
486 	}
487 	if (last_added_to_dropdown_.count("") == (current_profile.empty() ? 0 : 1)) {
488 		return update_profiles_needed(current_profile);
489 	}
490 
491 	update_profiles_select(current_profile);
492 }
493 
update_profiles_needed(const std::string & current_profile)494 void EconomyOptionsWindow::update_profiles_needed(const std::string& current_profile) {
495 	dropdown_.clear();
496 	last_added_to_dropdown_.clear();
497 	for (const auto& pair : predefined_targets_) {
498 		dropdown_.add(EconomyOptionsWindow::localize_profile_name(pair.first), pair.first);
499 		last_added_to_dropdown_.insert(pair.first);
500 	}
501 	if (current_profile.empty()) {
502 		// Nothing selected
503 		dropdown_.add("–", "");
504 		last_added_to_dropdown_.insert("");
505 	}
506 	update_profiles_select(current_profile);
507 }
508 
update_profiles_select(const std::string & current_profile)509 void EconomyOptionsWindow::update_profiles_select(const std::string& current_profile) {
510 	if (dropdown_.is_expanded()) {
511 		return;
512 	}
513 	if (!dropdown_.has_selection() || dropdown_.get_selected() != current_profile) {
514 		dropdown_.select(current_profile);
515 	}
516 	assert(dropdown_.has_selection());
517 }
518 
update_save_enabled()519 void EconomyOptionsWindow::SaveProfileWindow::update_save_enabled() {
520 	const std::string& text = profile_name_.text();
521 	if (text.empty() || text == kDefaultEconomyProfile) {
522 		save_.set_enabled(false);
523 		save_.set_tooltip(text.empty() ? _("The profile name cannot be empty") :
524 		                                 _("The default profile cannot be overwritten"));
525 	} else {
526 		save_.set_enabled(true);
527 		save_.set_tooltip(_("Save the profile under this name"));
528 	}
529 }
530 
table_selection_changed()531 void EconomyOptionsWindow::SaveProfileWindow::table_selection_changed() {
532 	if (!table_.has_selection()) {
533 		delete_.set_enabled(false);
534 		delete_.set_tooltip("");
535 		return;
536 	}
537 	const std::string& sel = table_[table_.selection_index()];
538 	if (economy_options_->get_predefined_targets().at(sel).undeletable) {
539 		delete_.set_tooltip(_("The predefined profiles cannot be deleted"));
540 		delete_.set_enabled(false);
541 	} else {
542 		delete_.set_tooltip(_("Delete the selected profiles"));
543 		delete_.set_enabled(true);
544 	}
545 	profile_name_.set_text(sel);
546 	update_save_enabled();
547 }
548 
update_table()549 void EconomyOptionsWindow::SaveProfileWindow::update_table() {
550 	table_.clear();
551 	for (const auto& pair : economy_options_->get_predefined_targets()) {
552 		table_.add(pair.first).set_string(0, EconomyOptionsWindow::localize_profile_name(pair.first));
553 	}
554 	layout();
555 }
556 
save()557 void EconomyOptionsWindow::SaveProfileWindow::save() {
558 	const std::string name = profile_name_.text();
559 	assert(!name.empty());
560 	assert(name != kDefaultEconomyProfile);
561 	for (const auto& pair : economy_options_->get_predefined_targets()) {
562 		if (pair.first == name) {
563 			UI::WLMessageBox m(
564 			   this, _("Overwrite?"),
565 			   _("A profile with this name already exists. Do you wish to replace it?"),
566 			   UI::WLMessageBox::MBoxType::kOkCancel);
567 			if (m.run<UI::Panel::Returncodes>() != UI::Panel::Returncodes::kOk) {
568 				return;
569 			}
570 			break;
571 		}
572 	}
573 	economy_options_->do_create_target(name);
574 	die();
575 }
576 
delete_selected()577 void EconomyOptionsWindow::SaveProfileWindow::delete_selected() {
578 	assert(table_.has_selection());
579 
580 	auto& map = economy_options_->get_predefined_targets();
581 	const std::string& name = table_[table_.selection_index()];
582 
583 	assert(name != kDefaultEconomyProfile);
584 	assert(!map.at(name).undeletable);
585 	auto it = map.find(name);
586 	assert(it != map.end());
587 	map.erase(it);
588 
589 	economy_options_->save_targets();
590 	economy_options_->update_profiles();
591 	update_table();
592 }
593 
unset_parent()594 void EconomyOptionsWindow::SaveProfileWindow::unset_parent() {
595 	economy_options_ = nullptr;
596 	die();
597 }
598 
think()599 void EconomyOptionsWindow::SaveProfileWindow::think() {
600 	if (!economy_options_) {
601 		die();
602 	}
603 	UI::Window::think();
604 }
605 
SaveProfileWindow(UI::Panel * parent,EconomyOptionsWindow * eco)606 EconomyOptionsWindow::SaveProfileWindow::SaveProfileWindow(UI::Panel* parent,
607                                                            EconomyOptionsWindow* eco)
608    : UI::Window(parent, "save_economy_options_profile", 0, 0, 0, 0, _("Save Profile")),
609      economy_options_(eco),
610      main_box_(this, 0, 0, UI::Box::Vertical),
611      table_box_(&main_box_, 0, 0, UI::Box::Vertical),
612      table_(&table_box_, 0, 0, 460, 120, UI::PanelStyle::kWui),
613      buttons_box_(&main_box_, 0, 0, UI::Box::Horizontal),
614      profile_name_(&buttons_box_, 0, 0, 240, UI::PanelStyle::kWui),
615      save_(&buttons_box_, "save", 0, 0, 80, 34, UI::ButtonStyle::kWuiPrimary, _("Save")),
616      cancel_(&buttons_box_, "cancel", 0, 0, 80, 34, UI::ButtonStyle::kWuiSecondary, _("Cancel")),
617      delete_(&buttons_box_, "delete", 0, 0, 80, 34, UI::ButtonStyle::kWuiSecondary, _("Delete")) {
618 	table_.add_column(200, _("Existing Profiles"));
619 	update_table();
620 
621 	table_.selected.connect([this](uint32_t) { table_selection_changed(); });
622 	profile_name_.changed.connect([this] { update_save_enabled(); });
623 	profile_name_.ok.connect([this] { save(); });
624 	profile_name_.cancel.connect([this] { die(); });
625 	save_.sigclicked.connect([this] { save(); });
626 	cancel_.sigclicked.connect([this] { die(); });
627 	delete_.sigclicked.connect([this] { delete_selected(); });
628 
629 	table_box_.add(&table_, UI::Box::Resizing::kFullSize);
630 	buttons_box_.add(&profile_name_, UI::Box::Resizing::kFullSize);
631 	buttons_box_.add(&save_);
632 	buttons_box_.add(&cancel_);
633 	buttons_box_.add(&delete_);
634 	main_box_.add(&table_box_, UI::Box::Resizing::kFullSize);
635 	main_box_.add(&buttons_box_, UI::Box::Resizing::kFullSize);
636 	set_center_panel(&main_box_);
637 
638 	table_selection_changed();
639 	update_save_enabled();
640 }
641 
~SaveProfileWindow()642 EconomyOptionsWindow::SaveProfileWindow::~SaveProfileWindow() {
643 	if (economy_options_) {
644 		economy_options_->close_save_profile_window();
645 	}
646 }
647 
create_target()648 void EconomyOptionsWindow::create_target() {
649 	if (save_profile_dialog_) {
650 		// Already open
651 		return;
652 	}
653 	save_profile_dialog_ = new SaveProfileWindow(get_parent(), this);
654 }
655 
close_save_profile_window()656 void EconomyOptionsWindow::close_save_profile_window() {
657 	assert(save_profile_dialog_);
658 	save_profile_dialog_ = nullptr;
659 }
660 
do_create_target(const std::string & name)661 void EconomyOptionsWindow::do_create_target(const std::string& name) {
662 	assert(!name.empty());
663 	assert(name != kDefaultEconomyProfile);
664 	const Widelands::Tribes& tribes = player_->egbase().tribes();
665 	const Widelands::TribeDescr& tribe = player_->tribe();
666 	Widelands::Economy* ware_economy = player_->get_economy(ware_serial_);
667 	Widelands::Economy* worker_economy = player_->get_economy(worker_serial_);
668 	PredefinedTargets t;
669 	for (Widelands::DescriptionIndex di : tribe.wares()) {
670 		if (tribes.get_ware_descr(di)->has_demand_check(tribe.name())) {
671 			t.wares[di] = ware_economy->target_quantity(di).permanent;
672 		}
673 	}
674 	for (Widelands::DescriptionIndex di : tribe.workers()) {
675 		if (tribes.get_worker_descr(di)->has_demand_check()) {
676 			t.workers[di] = worker_economy->target_quantity(di).permanent;
677 		}
678 	}
679 	predefined_targets_[name] = t;
680 
681 	save_targets();
682 	update_profiles();
683 }
684 
save_targets()685 void EconomyOptionsWindow::save_targets() {
686 	const Widelands::Tribes& tribes = player_->egbase().tribes();
687 	Profile profile;
688 
689 	std::map<std::string, uint32_t> serials;
690 	for (const auto& pair : predefined_targets_) {
691 		if (pair.first != kDefaultEconomyProfile) {
692 			serials.insert(std::make_pair(pair.first, serials.size()));
693 		}
694 	}
695 	Section& global_section = profile.create_section(kDefaultEconomyProfile.c_str());
696 	for (const auto& pair : serials) {
697 		global_section.set_string(std::to_string(pair.second).c_str(), pair.first);
698 	}
699 
700 	for (const auto& pair : predefined_targets_) {
701 		if (pair.first == kDefaultEconomyProfile) {
702 			continue;
703 		}
704 		Section& section = profile.create_section(std::to_string(serials.at(pair.first)).c_str());
705 		for (const auto& setting : pair.second.wares) {
706 			section.set_natural(tribes.get_ware_descr(setting.first)->name().c_str(), setting.second);
707 		}
708 		for (const auto& setting : pair.second.workers) {
709 			section.set_natural(
710 			   tribes.get_worker_descr(setting.first)->name().c_str(), setting.second);
711 		}
712 	}
713 
714 	Section& section = profile.create_section("undeletable");
715 	for (const auto& pair : predefined_targets_) {
716 		if (pair.first != kDefaultEconomyProfile) {
717 			section.set_bool(std::to_string(serials.at(pair.first)).c_str(), pair.second.undeletable);
718 		}
719 	}
720 
721 	g_fs->ensure_directory_exists(kEconomyProfilesDir);
722 	std::string complete_filename =
723 	   kEconomyProfilesDir + g_fs->file_separator() + player_->tribe().name();
724 	profile.write(complete_filename.c_str(), false);
725 
726 	// Inform the windows of other economies of new and deleted profiles
727 	Notifications::publish(NoteEconomyProfile(ware_serial_, worker_serial_));
728 }
729 
read_targets()730 void EconomyOptionsWindow::read_targets() {
731 	predefined_targets_.clear();
732 	const Widelands::Tribes& tribes = player_->egbase().tribes();
733 	const Widelands::TribeDescr& tribe = player_->tribe();
734 
735 	{
736 		PredefinedTargets t;
737 		t.undeletable = true;
738 		for (Widelands::DescriptionIndex di : tribe.wares()) {
739 			const Widelands::WareDescr* descr = tribes.get_ware_descr(di);
740 			if (descr->has_demand_check(tribe.name())) {
741 				t.wares.insert(std::make_pair(di, descr->default_target_quantity(tribe.name())));
742 			}
743 		}
744 		for (Widelands::DescriptionIndex di : tribe.workers()) {
745 			const Widelands::WorkerDescr* descr = tribes.get_worker_descr(di);
746 			if (descr->has_demand_check()) {
747 				t.workers.insert(std::make_pair(di, descr->default_target_quantity()));
748 			}
749 		}
750 		predefined_targets_.insert(std::make_pair(kDefaultEconomyProfile, t));
751 	}
752 
753 	std::string complete_filename =
754 	   kEconomyProfilesDir + g_fs->file_separator() + player_->tribe().name();
755 	Profile profile;
756 	profile.read(complete_filename.c_str());
757 
758 	Section* global_section = profile.get_section(kDefaultEconomyProfile);
759 	if (global_section) {
760 		std::map<std::string, std::string> serials;
761 		while (Section::Value* v = global_section->get_next_val()) {
762 			serials.insert(std::make_pair(v->get_name(), v->get_string()));
763 		}
764 
765 		for (const auto& pair : serials) {
766 			Section* section = profile.get_section(pair.first);
767 			PredefinedTargets t;
768 			while (Section::Value* v = section->get_next_val()) {
769 				const std::string name(v->get_name());
770 				Widelands::DescriptionIndex di = tribes.ware_index(name);
771 				if (di == Widelands::INVALID_INDEX) {
772 					di = tribes.worker_index(name);
773 					assert(di != Widelands::INVALID_INDEX);
774 					t.workers.insert(std::make_pair(di, v->get_natural()));
775 				} else {
776 					t.wares.insert(std::make_pair(di, v->get_natural()));
777 				}
778 			}
779 			predefined_targets_.insert(std::make_pair(pair.second, t));
780 		}
781 
782 		if (Section* section = profile.get_section("undeletable")) {
783 			while (Section::Value* v = section->get_next_val()) {
784 				predefined_targets_.at(serials.at(std::string(v->get_name()))).undeletable =
785 				   v->get_bool();
786 			}
787 		}
788 	}
789 
790 	update_profiles();
791 }
792