← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/economy-target-profiles into lp:widelands

 

Benedikt Straub has proposed merging lp:~widelands-dev/widelands/economy-target-profiles into lp:widelands.

Commit message:
Users can define and save their own profiles of economy target quantities. 
Redesigned the economy options menu. 
WaresDisplays and will relayout themselves dynamically on fullscreen switch.

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1827696 in widelands: "Allow users to define their own economy default settings"
  https://bugs.launchpad.net/widelands/+bug/1827696

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/economy-target-profiles/+merge/366987

For each tribe, I added two profiles: "Efficiency" is equal to the changes proposed previously, and "Stockpile" is for people who, well, like to stockpile stuff. Additionally there is an unchangeable "Default" pseudo-profile that resets items to the default settings.
To apply a profile, select the items you wish to change and choose the profile from the dropdown. Use "Save" to save your current settings as a profile. The save window also allows you to delete profiles.
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/economy-target-profiles into lp:widelands.
=== added file 'data/images/ui_basic/scrollbar_down_fast.png'
Binary files data/images/ui_basic/scrollbar_down_fast.png	1970-01-01 00:00:00 +0000 and data/images/ui_basic/scrollbar_down_fast.png	2019-05-06 13:53:16 +0000 differ
=== added file 'data/images/ui_basic/scrollbar_up_fast.png'
Binary files data/images/ui_basic/scrollbar_up_fast.png	1970-01-01 00:00:00 +0000 and data/images/ui_basic/scrollbar_up_fast.png	2019-05-06 13:53:16 +0000 differ
=== added directory 'data/tribes/economy_profiles'
=== added file 'data/tribes/economy_profiles/atlanteans'
--- data/tribes/economy_profiles/atlanteans	1970-01-01 00:00:00 +0000
+++ data/tribes/economy_profiles/atlanteans	2019-05-06 13:53:16 +0000
@@ -0,0 +1,89 @@
+# Automatically created by Widelands bzr9094[economy-target-profiles] (Debug)
+
+[Default]
+0=_"Efficiency"
+1=_"Stockpile"
+
+[0]
+blackroot_flour="1"
+atlanteans_bread="20"
+bread_paddle="0"
+buckets="0"
+coal="5"
+cornmeal="3"
+diamond="3"
+fire_tongs="1"
+fishing_net="2"
+gold="1"
+gold_ore="1"
+gold_thread="0"
+granite="10"
+hammer="0"
+hook_pole="0"
+hunting_bow="1"
+iron="5"
+iron_ore="1"
+milking_tongs="0"
+pick="1"
+planks="1"
+quartz="3"
+saw="0"
+scythe="0"
+shield_advanced="0"
+shield_steel="0"
+shovel="0"
+smoked_fish="5"
+smoked_meat="3"
+spidercloth="5"
+spider_silk="5"
+tabard="1"
+tabard_golden="0"
+trident_double="0"
+trident_heavy_double="0"
+trident_light="1"
+trident_long="0"
+trident_steel="0"
+atlanteans_horse="1"
+atlanteans_soldier="10"
+
+[1]
+blackroot_flour="20"
+atlanteans_bread="30"
+bread_paddle="1"
+buckets="2"
+coal="25"
+cornmeal="20"
+diamond="10"
+fire_tongs="1"
+fishing_net="2"
+gold="20"
+gold_ore="15"
+gold_thread="5"
+granite="30"
+hammer="2"
+hook_pole="1"
+hunting_bow="1"
+iron="25"
+iron_ore="20"
+milking_tongs="1"
+pick="3"
+planks="40"
+quartz="10"
+saw="2"
+scythe="1"
+shield_advanced="1"
+shield_steel="1"
+shovel="2"
+smoked_fish="40"
+smoked_meat="25"
+spidercloth="20"
+spider_silk="15"
+tabard="30"
+tabard_golden="1"
+trident_double="1"
+trident_heavy_double="1"
+trident_light="30"
+trident_long="1"
+trident_steel="1"
+atlanteans_horse="20"
+atlanteans_soldier="20"

=== added file 'data/tribes/economy_profiles/barbarians'
--- data/tribes/economy_profiles/barbarians	1970-01-01 00:00:00 +0000
+++ data/tribes/economy_profiles/barbarians	2019-05-06 13:53:16 +0000
@@ -0,0 +1,81 @@
+# Automatically created by Widelands bzr9094[economy-target-profiles] (Debug)
+
+[Default]
+0=_"Efficiency"
+1=_"Stockpile"
+
+[0]
+ax="1"
+ax_battle="0"
+ax_broad="0"
+ax_bronze="0"
+ax_sharp="0"
+ax_warriors="0"
+beer="0"
+beer_strong="1"
+blackwood="40"
+barbarians_bread="5"
+bread_paddle="0"
+cloth="10"
+coal="20"
+felling_ax="0"
+fire_tongs="1"
+fishing_rod="0"
+gold="1"
+gold_ore="1"
+granite="10"
+grout="1"
+hammer="1"
+helmet="0"
+helmet_mask="0"
+helmet_warhelm="0"
+hunting_spear="0"
+iron="5"
+iron_ore="5"
+kitchen_tools="0"
+meal="5"
+pick="1"
+ration="20"
+scythe="0"
+shovel="0"
+snack="0"
+barbarians_ox="1"
+barbarians_soldier="10"
+
+[1]
+ax="30"
+ax_battle="1"
+ax_broad="1"
+ax_bronze="1"
+ax_sharp="1"
+ax_warriors="1"
+beer="15"
+beer_strong="20"
+blackwood="45"
+barbarians_bread="25"
+bread_paddle="1"
+cloth="10"
+coal="25"
+felling_ax="5"
+fire_tongs="1"
+fishing_rod="1"
+gold="20"
+gold_ore="15"
+granite="30"
+grout="20"
+hammer="2"
+helmet="1"
+helmet_mask="1"
+helmet_warhelm="1"
+hunting_spear="1"
+iron="25"
+iron_ore="20"
+kitchen_tools="1"
+meal="15"
+pick="2"
+ration="30"
+scythe="1"
+shovel="1"
+snack="20"
+barbarians_ox="20"
+barbarians_soldier="20"

=== added file 'data/tribes/economy_profiles/empire'
--- data/tribes/economy_profiles/empire	1970-01-01 00:00:00 +0000
+++ data/tribes/economy_profiles/empire	2019-05-06 13:53:16 +0000
@@ -0,0 +1,89 @@
+# Automatically created by Widelands bzr9094[economy-target-profiles] (Debug)
+
+[Default]
+0=_"Efficiency"
+1=_"Stockpile"
+
+[0]
+armor="1"
+armor_chain="1"
+armor_gilded="1"
+armor_helmet="30"
+basket="1"
+beer="1"
+empire_bread="20"
+bread_paddle="0"
+cloth="15"
+coal="5"
+felling_ax="0"
+fire_tongs="1"
+fishing_rod="0"
+flour="20"
+gold="1"
+gold_ore="1"
+granite="10"
+hammer="0"
+hunting_spear="0"
+iron="5"
+iron_ore="3"
+kitchen_tools="0"
+marble="30"
+marble_column="10"
+meal="5"
+meat="20"
+pick="1"
+planks="1"
+ration="20"
+saw="0"
+scythe="0"
+shovel="0"
+spear="1"
+spear_advanced="1"
+spear_heavy="1"
+spear_war="1"
+spear_wooden="30"
+wool="10"
+empire_donkey="1"
+empire_soldier="10"
+
+[1]
+armor="1"
+armor_chain="1"
+armor_gilded="1"
+armor_helmet="30"
+basket="1"
+beer="20"
+empire_bread="30"
+bread_paddle="1"
+cloth="15"
+coal="25"
+felling_ax="3"
+fire_tongs="1"
+fishing_rod="1"
+flour="25"
+gold="20"
+gold_ore="15"
+granite="30"
+hammer="2"
+hunting_spear="1"
+iron="25"
+iron_ore="20"
+kitchen_tools="1"
+marble="35"
+marble_column="15"
+meal="20"
+meat="30"
+pick="2"
+planks="40"
+ration="25"
+saw="1"
+scythe="1"
+shovel="1"
+spear="1"
+spear_advanced="1"
+spear_heavy="1"
+spear_war="1"
+spear_wooden="30"
+wool="15"
+empire_donkey="20"
+empire_soldier="20"

=== added file 'data/tribes/economy_profiles/frisians'
--- data/tribes/economy_profiles/frisians	1970-01-01 00:00:00 +0000
+++ data/tribes/economy_profiles/frisians	2019-05-06 13:53:16 +0000
@@ -0,0 +1,93 @@
+# Automatically created by Widelands bzr9093[economy-target-profiles] (Debug)
+
+[Default]
+0=_"Efficiency"
+1=_"Stockpile"
+
+[0]
+clay="30"
+brick="40"
+bread_frisians="20"
+honey_bread="20"
+mead="15"
+fur="10"
+fur_garment="30"
+fur_garment_studded="2"
+fur_garment_golden="2"
+helmet_golden="2"
+sword_short="30"
+sword_long="2"
+sword_broad="2"
+sword_double="2"
+needles="1"
+basket="1"
+beer="1"
+bread_paddle="1"
+cloth="10"
+coal="20"
+felling_ax="0"
+fire_tongs="1"
+fish="20"
+fishing_net="2"
+gold="1"
+gold_ore="1"
+granite="10"
+hammer="1"
+helmet="0"
+hunting_spear="0"
+iron="5"
+iron_ore="3"
+kitchen_tools="0"
+meal="1"
+pick="1"
+ration="20"
+scythe="0"
+shovel="0"
+smoked_fish="20"
+smoked_meat="10"
+frisians_reindeer="1"
+frisians_soldier="10"
+
+[1]
+clay="35"
+brick="50"
+bread_frisians="30"
+honey_bread="30"
+mead="30"
+fur="20"
+fur_garment="30"
+fur_garment_studded="2"
+fur_garment_golden="2"
+helmet_golden="2"
+sword_short="30"
+sword_long="2"
+sword_broad="2"
+sword_double="2"
+needles="1"
+basket="1"
+beer="30"
+bread_paddle="1"
+cloth="10"
+coal="35"
+felling_ax="3"
+fire_tongs="2"
+fish="40"
+fishing_net="2"
+gold="20"
+gold_ore="15"
+granite="35"
+hammer="3"
+helmet="2"
+hunting_spear="1"
+iron="25"
+iron_ore="20"
+kitchen_tools="2"
+meal="10"
+pick="3"
+ration="30"
+scythe="2"
+shovel="5"
+smoked_fish="30"
+smoked_meat="20"
+frisians_reindeer="20"
+frisians_soldier="20"

=== modified file 'src/logic/filesystem_constants.h'
--- src/logic/filesystem_constants.h	2019-04-18 16:50:35 +0000
+++ src/logic/filesystem_constants.h	2019-05-06 13:53:16 +0000
@@ -78,4 +78,6 @@
 /// Filesystem names for config
 const std::string kConfigFile = "config";
 
+const std::string kEconomyProfilesDir = "tribes/economy_profiles";
+
 #endif  // end of include guard: WL_LOGIC_FILESYSTEM_CONSTANTS_H

=== modified file 'src/logic/map_objects/tribes/tribe_descr.cc'
--- src/logic/map_objects/tribes/tribe_descr.cc	2019-05-04 10:47:44 +0000
+++ src/logic/map_objects/tribes/tribe_descr.cc	2019-05-06 13:53:16 +0000
@@ -89,8 +89,6 @@
 		load_roads("busy", &busy_road_paths_);
 
 		items_table = table.get_table("wares_order");
-		wares_order_coords_.resize(tribes_.nrwares());
-		int columnindex = 0;
 		for (const int key : items_table->keys<int>()) {
 			std::vector<DescriptionIndex> column;
 			std::vector<std::string> warenames =
@@ -104,7 +102,6 @@
 					}
 					wares_.insert(wareindex);
 					column.push_back(wareindex);
-					wares_order_coords_[wareindex] = std::make_pair(columnindex, rowindex);
 				} catch (const WException& e) {
 					throw GameDataError(
 					   "Failed adding ware '%s: %s", warenames[rowindex].c_str(), e.what());
@@ -112,13 +109,10 @@
 			}
 			if (!column.empty()) {
 				wares_order_.push_back(column);
-				++columnindex;
 			}
 		}
 
 		items_table = table.get_table("workers_order");
-		workers_order_coords_.resize(tribes_.nrworkers());
-		columnindex = 0;
 		for (const int key : items_table->keys<int>()) {
 			std::vector<DescriptionIndex> column;
 			std::vector<std::string> workernames =
@@ -132,7 +126,6 @@
 					}
 					workers_.insert(workerindex);
 					column.push_back(workerindex);
-					workers_order_coords_[workerindex] = std::make_pair(columnindex, rowindex);
 
 					const WorkerDescr& worker_descr = *tribes_.get_worker_descr(workerindex);
 					if (worker_descr.is_buildable() && worker_descr.buildcost().empty()) {
@@ -145,7 +138,6 @@
 			}
 			if (!column.empty()) {
 				workers_order_.push_back(column);
-				++columnindex;
 			}
 		}
 
@@ -423,38 +415,6 @@
 	return list->second.find(lowest)->second;
 }
 
-void TribeDescr::resize_ware_orders(size_t maxLength) {
-	bool need_resize = false;
-
-	// Check if we actually need to resize.
-	for (WaresOrder::iterator it = wares_order_.begin(); it != wares_order_.end(); ++it) {
-		if (it->size() > maxLength) {
-			need_resize = true;
-		}
-	}
-
-	// Build new smaller wares_order.
-	if (need_resize) {
-		WaresOrder new_wares_order;
-		for (WaresOrder::iterator it = wares_order_.begin(); it != wares_order_.end(); ++it) {
-			new_wares_order.push_back(std::vector<Widelands::DescriptionIndex>());
-			for (std::vector<Widelands::DescriptionIndex>::iterator it2 = it->begin();
-			     it2 != it->end(); ++it2) {
-				if (new_wares_order.rbegin()->size() >= maxLength) {
-					new_wares_order.push_back(std::vector<Widelands::DescriptionIndex>());
-				}
-				new_wares_order.rbegin()->push_back(*it2);
-				wares_order_coords_[*it2].first = new_wares_order.size() - 1;
-				wares_order_coords_[*it2].second = new_wares_order.rbegin()->size() - 1;
-			}
-		}
-
-		// Remove old array.
-		wares_order_.clear();
-		wares_order_ = new_wares_order;
-	}
-}
-
 void TribeDescr::add_building(const std::string& buildingname) {
 	try {
 		DescriptionIndex index = tribes_.safe_building_index(buildingname);

=== modified file 'src/logic/map_objects/tribes/tribe_descr.h'
--- src/logic/map_objects/tribes/tribe_descr.h	2019-03-01 04:19:53 +0000
+++ src/logic/map_objects/tribes/tribe_descr.h	2019-05-06 13:53:16 +0000
@@ -149,22 +149,13 @@
 	}
 
 	using WaresOrder = std::vector<std::vector<Widelands::DescriptionIndex>>;
-	using WaresOrderCoords = std::vector<std::pair<uint32_t, uint32_t>>;
 	const WaresOrder& wares_order() const {
 		return wares_order_;
 	}
-	const WaresOrderCoords& wares_order_coords() const {
-		return wares_order_coords_;
-	}
 
 	const WaresOrder& workers_order() const {
 		return workers_order_;
 	}
-	const WaresOrderCoords& workers_order_coords() const {
-		return workers_order_coords_;
-	}
-
-	void resize_ware_orders(size_t maxLength);
 
 	const std::vector<std::string>& get_ship_names() const {
 		return ship_names_;
@@ -215,9 +206,7 @@
 	std::vector<DescriptionIndex> trainingsites_;
 	// Order and positioning of wares in the warehouse display
 	WaresOrder wares_order_;
-	WaresOrderCoords wares_order_coords_;
 	WaresOrder workers_order_;
-	WaresOrderCoords workers_order_coords_;
 
 	std::vector<Widelands::TribeBasicInfo::Initialization> initializations_;
 

=== modified file 'src/logic/map_objects/tribes/tribes.cc'
--- src/logic/map_objects/tribes/tribes.cc	2019-03-01 04:19:53 +0000
+++ src/logic/map_objects/tribes/tribes.cc	2019-05-06 13:53:16 +0000
@@ -340,14 +340,9 @@
 	// Calculate the trainingsites proportions.
 	postload_calculate_trainingsites_proportions();
 
-	// Resize the configuration of our wares if they won't fit in the current window (12 = info label
-	// size).
-	// Also, do some final checks on the gamedata
-	int number = (g_gr->get_yres() - 290) / (WARE_MENU_PIC_HEIGHT + WARE_MENU_PIC_PAD_Y + 12);
+	// Some final checks on the gamedata
 	for (DescriptionIndex i = 0; i < tribes_->size(); ++i) {
 		TribeDescr* tribe_descr = tribes_->get_mutable(i);
-		tribe_descr->resize_ware_orders(number);
-
 		// Verify that the preciousness has been set for all of the tribe's wares
 		for (const DescriptionIndex wi : tribe_descr->wares()) {
 			if (tribe_descr->get_ware_descr(wi)->preciousness(tribe_descr->name()) == kInvalidWare) {

=== modified file 'src/logic/map_objects/tribes/ware_descr.h'
--- src/logic/map_objects/tribes/ware_descr.h	2019-02-27 19:00:36 +0000
+++ src/logic/map_objects/tribes/ware_descr.h	2019-05-06 13:53:16 +0000
@@ -33,10 +33,8 @@
 class Image;
 class LuaTable;
 
-#define WARE_MENU_PIC_WIDTH 24   //!< Default width for ware's menu icons
-#define WARE_MENU_PIC_HEIGHT 24  //!< Default height for ware's menu icons
-#define WARE_MENU_PIC_PAD_X 3    //!< Default padding between menu icons
-#define WARE_MENU_PIC_PAD_Y 4    //!< Default padding between menu icons
+constexpr int kWareMenuPicWidth = 24;   //!< Default width for ware's menu icons
+constexpr int kWareMenuPicHeight = 24;  //!< Default height for ware's menu icons
 
 namespace Widelands {
 

=== modified file 'src/logic/playercommand.h'
--- src/logic/playercommand.h	2019-02-23 11:00:49 +0000
+++ src/logic/playercommand.h	2019-05-06 13:53:16 +0000
@@ -581,6 +581,7 @@
 	uint32_t permanent_;
 };
 
+// TODO(Nordfriese): CmdResetWareTargetQuantity can be removed when we next break savegame compatibility
 struct CmdResetWareTargetQuantity : public CmdChangeTargetQuantity {
 	CmdResetWareTargetQuantity() : CmdChangeTargetQuantity() {
 	}
@@ -629,6 +630,7 @@
 	uint32_t permanent_;
 };
 
+// TODO(Nordfriese): CmdResetWorkerTargetQuantity can be removed when we next break savegame compatibility
 struct CmdResetWorkerTargetQuantity : public CmdChangeTargetQuantity {
 	CmdResetWorkerTargetQuantity() : CmdChangeTargetQuantity() {
 	}

=== modified file 'src/wui/CMakeLists.txt'
--- src/wui/CMakeLists.txt	2019-05-04 15:37:33 +0000
+++ src/wui/CMakeLists.txt	2019-05-06 13:53:16 +0000
@@ -44,8 +44,10 @@
     graphic
     logic
     logic_commands
+    logic_filesystem_constants
     logic_map_objects
     notifications
+    profile
     ui_basic
     wui_waresdisplay
 )

=== modified file 'src/wui/economy_options_window.cc'
--- src/wui/economy_options_window.cc	2019-02-23 11:00:49 +0000
+++ src/wui/economy_options_window.cc	2019-05-06 13:53:16 +0000
@@ -19,36 +19,98 @@
 
 #include "wui/economy_options_window.h"
 
+#include <memory>
+
 #include <boost/lexical_cast.hpp>
 
 #include "graphic/graphic.h"
 #include "logic/editor_game_base.h"
+#include "logic/filesystem_constants.h"
 #include "logic/map_objects/tribes/ware_descr.h"
 #include "logic/map_objects/tribes/worker_descr.h"
 #include "logic/player.h"
 #include "logic/playercommand.h"
+#include "profile/profile.h"
 #include "ui_basic/button.h"
+#include "ui_basic/editbox.h"
+#include "ui_basic/messagebox.h"
+#include "ui_basic/table.h"
 
 static const char pic_tab_wares[] = "images/wui/buildings/menu_tab_wares.png";
 static const char pic_tab_workers[] = "images/wui/buildings/menu_tab_workers.png";
 
+constexpr int kDesiredWidth = 216;
+
+static inline int32_t calc_hgap(int32_t columns, int32_t total_w) {
+	return (total_w - columns * kWareMenuPicWidth) / (columns - 1);
+}
+
 EconomyOptionsWindow::EconomyOptionsWindow(UI::Panel* parent,
                                            Widelands::Economy* economy,
                                            bool can_act)
    : UI::Window(parent, "economy_options", 0, 0, 0, 0, _("Economy options")),
+     main_box_(this, 0, 0, UI::Box::Vertical),
      serial_(economy->serial()),
      player_(&economy->owner()),
      tabpanel_(this, UI::TabPanelStyle::kWuiDark),
-     ware_panel_(new EconomyOptionsPanel(&tabpanel_, serial_, player_, can_act, Widelands::wwWARE)),
+     ware_panel_(new EconomyOptionsPanel(&tabpanel_, this, serial_, player_, can_act, Widelands::wwWARE, kDesiredWidth)),
      worker_panel_(
-        new EconomyOptionsPanel(&tabpanel_, serial_, player_, can_act, Widelands::wwWORKER)) {
-	set_center_panel(&tabpanel_);
+        new EconomyOptionsPanel(&tabpanel_, this, serial_, player_, can_act, Widelands::wwWORKER, kDesiredWidth)),
+     dropdown_box_(this, 0, 0, UI::Box::Horizontal),
+     dropdown_(&dropdown_box_, 0, 0, 174, 200, 34, "", UI::DropdownType::kTextual, UI::PanelStyle::kWui) {
+	set_center_panel(&main_box_);
 
 	tabpanel_.add("wares", g_gr->images().get(pic_tab_wares), ware_panel_, _("Wares"));
 	tabpanel_.add("workers", g_gr->images().get(pic_tab_workers), worker_panel_, _("Workers"));
+
+	UI::Box* buttons = new UI::Box(this, 0, 0, UI::Box::Horizontal);
+	UI::Button* b = new UI::Button(buttons, "decrease_target_fast", 0, 0, 44, 28, UI::ButtonStyle::kWuiSecondary,
+			g_gr->images().get("images/ui_basic/scrollbar_down_fast.png"), _("Decrease target by 10"));
+	b->sigclicked.connect([this] { change_target(-10); });
+	buttons->add(b);
+	b->set_repeating(true);
+	buttons->add_space(8);
+	b = new UI::Button(buttons, "decrease_target", 0, 0, 44, 28, UI::ButtonStyle::kWuiSecondary,
+			g_gr->images().get("images/ui_basic/scrollbar_down.png"), _("Decrease target"));
+	b->sigclicked.connect([this] { change_target(-1); });
+	buttons->add(b);
+	b->set_repeating(true);
+	buttons->add_space(24);
+
+	b = new UI::Button(buttons, "increase_target", 0, 0, 44, 28, UI::ButtonStyle::kWuiSecondary,
+			g_gr->images().get("images/ui_basic/scrollbar_up.png"), _("Increase target"));
+	b->sigclicked.connect([this] { change_target(1); });
+	buttons->add(b);
+	b->set_repeating(true);
+	buttons->add_space(8);
+	b = new UI::Button(buttons, "increase_target_fast", 0, 0, 44, 28, UI::ButtonStyle::kWuiSecondary,
+	                   g_gr->images().get("images/ui_basic/scrollbar_up_fast.png"), _("Increase target by 10"));
+	b->sigclicked.connect([this] { change_target(10); });
+	buttons->add(b);
+	b->set_repeating(true);
+
+	dropdown_.set_tooltip(_("Profile to apply to the selected items"));
+	dropdown_box_.set_size(40, 20);  // Prevent assert failures
+	dropdown_box_.add(&dropdown_, UI::Box::Resizing::kFullSize);
+	dropdown_.selected.connect([this] { reset_target(); });
+
+	b = new UI::Button(&dropdown_box_, "save_targets", 0, 0, 34, 34, UI::ButtonStyle::kWuiMenu,
+			g_gr->images().get("images/wui/menus/menu_save_game.png"), _("Save target settings"));
+	b->sigclicked.connect([this] { create_target(); });
+	dropdown_box_.add_space(8);
+	dropdown_box_.add(b);
+
+	main_box_.add(&tabpanel_, UI::Box::Resizing::kAlign, UI::Align::kCenter);
+	main_box_.add_space(8);
+	main_box_.add(buttons, UI::Box::Resizing::kAlign, UI::Align::kCenter);
+	main_box_.add_space(8);
+	main_box_.add(&dropdown_box_, UI::Box::Resizing::kAlign, UI::Align::kCenter);
+
 	economy->set_has_window(true);
 	economynotes_subscriber_ = Notifications::subscribe<Widelands::NoteEconomy>(
 	   [this](const Widelands::NoteEconomy& note) { on_economy_note(note); });
+
+	read_targets();
 }
 
 EconomyOptionsWindow::~EconomyOptionsWindow() {
@@ -81,6 +143,20 @@
 	}
 }
 
+void EconomyOptionsWindow::layout() {
+	int w, h;
+	tabpanel_.get_desired_size(&w, &h);
+	main_box_.set_desired_size(w, h + 78);
+	update_desired_size();
+	UI::Window::layout();
+}
+
+void EconomyOptionsWindow::EconomyOptionsPanel::update_desired_size() {
+	display_.set_hgap(std::max(3, calc_hgap(display_.get_extent().w, kDesiredWidth)));
+	Box::update_desired_size();
+	get_parent()->layout();
+}
+
 EconomyOptionsWindow::TargetWaresDisplay::TargetWaresDisplay(UI::Panel* const parent,
                                                              int32_t const x,
                                                              int32_t const y,
@@ -129,42 +205,26 @@
  * Wraps the wares/workers display together with some buttons
  */
 EconomyOptionsWindow::EconomyOptionsPanel::EconomyOptionsPanel(UI::Panel* parent,
+		                    								   EconomyOptionsWindow* eco_window,
                                                                Widelands::Serial serial,
                                                                Widelands::Player* player,
                                                                bool can_act,
-                                                               Widelands::WareWorker type)
+                                                               Widelands::WareWorker type,
+                                                               int32_t min_w)
    : UI::Box(parent, 0, 0, UI::Box::Vertical),
      serial_(serial),
      player_(player),
      type_(type),
      can_act_(can_act),
-     display_(this, 0, 0, serial_, player_, type_, can_act_) {
+     display_(this, 0, 0, serial_, player_, type_, can_act_),
+     economy_options_window_(eco_window) {
 	add(&display_, UI::Box::Resizing::kFullSize);
 
+	display_.set_hgap(std::max(3, calc_hgap(display_.get_extent().w, min_w)));
+
 	if (!can_act_) {
 		return;
 	}
-	UI::Box* buttons = new UI::Box(this, 0, 0, UI::Box::Horizontal);
-	add(buttons);
-
-	UI::Button* b = new UI::Button(buttons, "decrease_target", 0, 0, 34, 34,
-	                               UI::ButtonStyle::kWuiMenu, "-", _("Decrease target"));
-	b->sigclicked.connect(boost::bind(&EconomyOptionsPanel::change_target, this, -1));
-	buttons->add(b);
-	b->set_repeating(true);
-	buttons->add_space(8);
-
-	b = new UI::Button(buttons, "increase_target", 0, 0, 34, 34, UI::ButtonStyle::kWuiMenu, "+",
-	                   _("Increase target"));
-	b->sigclicked.connect(boost::bind(&EconomyOptionsPanel::change_target, this, 1));
-	buttons->add(b);
-	b->set_repeating(true);
-	buttons->add_space(8);
-
-	b = new UI::Button(
-	   buttons, "reset_target", 0, 0, 34, 34, UI::ButtonStyle::kWuiMenu, "R", _("Reset to default"));
-	b->sigclicked.connect(boost::bind(&EconomyOptionsPanel::reset_target, this));
-	buttons->add(b);
 }
 
 void EconomyOptionsWindow::EconomyOptionsPanel::set_economy(Widelands::Serial serial) {
@@ -172,7 +232,26 @@
 	display_.set_economy(serial);
 }
 
-void EconomyOptionsWindow::EconomyOptionsPanel::change_target(int amount) {
+void EconomyOptionsWindow::change_target(int amount) {
+	if (tabpanel_.active() == 0) {
+		ware_panel_->change_target(amount);
+	} else {
+		worker_panel_->change_target(amount);
+	}
+}
+
+void EconomyOptionsWindow::reset_target() {
+	if (tabpanel_.active() == 0) {
+		ware_panel_->reset_target();
+	} else {
+		worker_panel_->reset_target();
+	}
+}
+
+void EconomyOptionsWindow::EconomyOptionsPanel::change_target(int delta) {
+	if (delta == 0) {
+		return;
+	}
 	Widelands::Economy* economy = player_->get_economy(serial_);
 	if (economy == nullptr) {
 		die();
@@ -186,35 +265,286 @@
 			const Widelands::Economy::TargetQuantity& tq = is_wares ?
 			                                                  economy->ware_target_quantity(index) :
 			                                                  economy->worker_target_quantity(index);
-			// Don't allow negative new amount.
-			if (amount >= 0 || -amount <= static_cast<int>(tq.permanent)) {
-				if (is_wares) {
-					game.send_player_command(*new Widelands::CmdSetWareTargetQuantity(
-					   game.get_gametime(), player_->player_number(), serial_, index,
-					   tq.permanent + amount));
+			// Don't allow negative new amount
+			const int old_amount = static_cast<int>(tq.permanent);
+			const int new_amount = std::max(0, old_amount + delta);
+			if (new_amount == old_amount) {
+				continue;
+			}
+			if (is_wares) {
+				game.send_player_command(*new Widelands::CmdSetWareTargetQuantity(
+				   game.get_gametime(), player_->player_number(), serial_, index, new_amount));
+			} else {
+				game.send_player_command(*new Widelands::CmdSetWorkerTargetQuantity(
+				   game.get_gametime(), player_->player_number(), serial_, index, new_amount));
+			}
+		}
+	}
+}
+
+void EconomyOptionsWindow::EconomyOptionsPanel::reset_target() {
+	Widelands::Game& game = dynamic_cast<Widelands::Game&>(player_->egbase());
+	const bool is_wares = type_ == Widelands::wwWARE;
+	const auto& items = is_wares ? player_->tribe().wares() : player_->tribe().workers();
+	const PredefinedTargets settings = economy_options_window_->get_selected_target();
+	for (const Widelands::DescriptionIndex& index : items) {
+		if (display_.ware_selected(index)) {
+			if (is_wares) {
+				game.send_player_command(*new Widelands::CmdSetWareTargetQuantity(
+						game.get_gametime(), player_->player_number(), serial_, index, settings.wares.at(index)));
+			} else {
+				game.send_player_command(*new Widelands::CmdSetWorkerTargetQuantity(
+						game.get_gametime(), player_->player_number(), serial_, index, settings.workers.at(index)));
+			}
+		}
+	}
+}
+
+void EconomyOptionsWindow::update_profiles(const std::string& select) {
+	dropdown_.clear();
+	for (const auto& pair : predefined_targets_) {
+		dropdown_.add(_(pair.first), pair.first, nullptr, pair.first == select);
+	}
+}
+
+struct SaveProfileWindow : public UI::Window {
+	void update_save_enabled() {
+		const std::string& text = profile_name_.text();
+		if (text.empty() || text == kDefaultEconomyProfile) {
+			save_.set_enabled(false);
+			save_.set_tooltip(text.empty() ? _("The profile name cannot be empty") :
+					_("The default profile cannot be overwritten"));
+		} else {
+			save_.set_enabled(true);
+			save_.set_tooltip(_("Save the profile under this name"));
+		}
+	}
+
+	void table_selection_changed() {
+		if (!table_.has_selection()) {
+			delete_.set_enabled(false);
+			delete_.set_tooltip("");
+			return;
+		}
+		const std::string& sel = table_[table_.selection_index()];
+		if (sel == kDefaultEconomyProfile) {
+			delete_.set_tooltip(_("The default profile cannot be deleted"));
+			delete_.set_enabled(false);
+		} else {
+			delete_.set_tooltip(_("Delete the selected profiles"));
+			delete_.set_enabled(true);
+		}
+		profile_name_.set_text(sel);
+		update_save_enabled();
+	}
+
+	void close(bool ok) {
+		end_modal(ok ? UI::Panel::Returncodes::kOk : UI::Panel::Returncodes::kBack);
+		die();
+	}
+
+	void update_table() {
+		table_.clear();
+		for (const auto& pair : economy_options_->get_predefined_targets()) {
+			table_.add(pair.first).set_string(0, _(pair.first));
+		}
+		layout();
+	}
+
+	void save() {
+		const std::string name = profile_name_.text();
+		assert(!name.empty());
+		assert(name != kDefaultEconomyProfile);
+		for (const auto& pair : economy_options_->get_predefined_targets()) {
+			if (pair.first == name) {
+				UI::WLMessageBox m(this, _("Overwrite?"),
+						_("A profile with this name already exists.\nDo you wish to replace it?"),
+						UI::WLMessageBox::MBoxType::kOkCancel);
+				if (m.run<UI::Panel::Returncodes>() != UI::Panel::Returncodes::kOk) {
+					return;
+				}
+				break;
+			}
+		}
+		economy_options_->do_create_target(name);
+		close(true);
+	}
+
+	void delete_selected() {
+		assert(table_.has_selection());
+		auto& map = economy_options_->get_predefined_targets();
+		const std::string& name = table_[table_.selection_index()];
+		assert(name != kDefaultEconomyProfile);
+		for (auto it = map.begin(); it != map.end(); ++it) {
+			if (it->first == name) {
+				map.erase(it);
+				break;
+			}
+		}
+		economy_options_->save_targets();
+		update_table();
+	}
+
+	explicit SaveProfileWindow(UI::Panel* parent, EconomyOptionsWindow* eco)
+	   : UI::Window(parent, "save_economy_options_profile", 0, 0, 0, 0, _("Save Profile")),
+	     economy_options_(eco),
+	     main_box_(this, 0, 0, UI::Box::Vertical),
+	     table_box_(&main_box_, 0, 0, UI::Box::Vertical),
+	     table_(&table_box_, 0, 0, 460, 120, UI::PanelStyle::kWui),
+	     buttons_box_(&main_box_, 0, 0, UI::Box::Horizontal),
+	     profile_name_(&buttons_box_, 0, 0, 240, 0, 0, UI::PanelStyle::kWui),
+	     save_(&buttons_box_, "save", 0, 0, 80, 34, UI::ButtonStyle::kWuiPrimary, _("Save")),
+	     cancel_(&buttons_box_, "cancel", 0, 0, 80, 34, UI::ButtonStyle::kWuiSecondary, _("Cancel")),
+	     delete_(&buttons_box_, "delete", 0, 0, 80, 34, UI::ButtonStyle::kWuiSecondary, _("Delete")) {
+		table_.add_column(200, _("Existing Profiles"));
+		update_table();
+
+		table_.selected.connect([this](uint32_t) { table_selection_changed(); });
+		profile_name_.changed.connect([this] { update_save_enabled(); });
+		profile_name_.ok.connect([this] { save(); });
+		profile_name_.cancel.connect([this] { close(false); });
+		save_.sigclicked.connect([this] { save(); });
+		cancel_.sigclicked.connect([this] { close(false); });
+		delete_.sigclicked.connect([this] { delete_selected(); });
+
+		table_box_.add(&table_, UI::Box::Resizing::kFullSize);
+		buttons_box_.add(&profile_name_, UI::Box::Resizing::kFullSize);
+		buttons_box_.add(&save_);
+		buttons_box_.add(&cancel_);
+		buttons_box_.add(&delete_);
+		main_box_.add(&table_box_, UI::Box::Resizing::kFullSize);
+		main_box_.add(&buttons_box_, UI::Box::Resizing::kFullSize);
+		set_center_panel(&main_box_);
+
+		table_selection_changed();
+		update_save_enabled();
+	}
+	~SaveProfileWindow() {
+	}
+
+private:
+	EconomyOptionsWindow* economy_options_;
+	UI::Box main_box_;
+	UI::Box table_box_;
+	UI::Table<const std::string&> table_;
+	UI::Box buttons_box_;
+	UI::EditBox profile_name_;
+	UI::Button save_;
+	UI::Button cancel_;
+	UI::Button delete_;
+};
+
+void EconomyOptionsWindow::create_target() {
+	std::unique_ptr<SaveProfileWindow> s (new SaveProfileWindow(get_parent(), this));
+	s->run<UI::Panel::Returncodes>();
+}
+
+void EconomyOptionsWindow::do_create_target(const std::string& name) {
+	assert(!name.empty());
+	assert(name != kDefaultEconomyProfile);
+	const Widelands::Tribes& tribes = player_->egbase().tribes();
+	const Widelands::TribeDescr& tribe = player_->tribe();
+	Widelands::Economy* economy = player_->get_economy(serial_);
+	PredefinedTargets t;
+	for (Widelands::DescriptionIndex di : tribe.wares()) {
+		if (tribes.get_ware_descr(di)->has_demand_check(tribe.name())) {
+			t.wares[di] = economy->ware_target_quantity(di).permanent;
+		}
+	}
+	for (Widelands::DescriptionIndex di : tribe.workers()) {
+		if (tribes.get_worker_descr(di)->has_demand_check()) {
+			t.workers[di] = economy->worker_target_quantity(di).permanent;
+		}
+	}
+	predefined_targets_[name] = t;
+
+	save_targets();
+	update_profiles(name);
+}
+
+void EconomyOptionsWindow::save_targets() {
+	const Widelands::Tribes& tribes = player_->egbase().tribes();
+	Profile profile;
+
+	std::map<std::string, uint32_t> serials;
+	for (const auto& pair : predefined_targets_) {
+		if (pair.first != kDefaultEconomyProfile) {
+			serials.emplace(pair.first, serials.size());
+		}
+	}
+	Section& global_section = profile.create_section(kDefaultEconomyProfile.c_str());
+	for (const auto& pair : serials) {
+		global_section.set_string(std::to_string(pair.second).c_str(), pair.first);
+	}
+
+	for (const auto& pair : predefined_targets_) {
+		if (pair.first == kDefaultEconomyProfile) {
+			continue;
+		}
+		Section& section = profile.create_section(std::to_string(serials.at(pair.first)).c_str());
+		for (const auto& setting : pair.second.wares) {
+			section.set_natural(tribes.get_ware_descr(setting.first)->name().c_str(), setting.second);
+		}
+		for (const auto& setting : pair.second.workers) {
+			section.set_natural(tribes.get_worker_descr(setting.first)->name().c_str(), setting.second);
+		}
+	}
+
+	g_fs->ensure_directory_exists(kEconomyProfilesDir);
+	std::string complete_filename = kEconomyProfilesDir + g_fs->file_separator() + player_->tribe().name();
+	profile.write(complete_filename.c_str(), false);
+}
+
+void EconomyOptionsWindow::read_targets(const std::string& select) {
+	predefined_targets_.clear();
+	const Widelands::Tribes& tribes = player_->egbase().tribes();
+	const Widelands::TribeDescr& tribe = player_->tribe();
+
+	{
+		PredefinedTargets t;
+		for (Widelands::DescriptionIndex di : tribe.wares()) {
+			const Widelands::WareDescr* descr = tribes.get_ware_descr(di);
+			if (descr->has_demand_check(tribe.name())) {
+				t.wares.emplace(di, descr->default_target_quantity(tribe.name()));
+			}
+		}
+		for (Widelands::DescriptionIndex di : tribe.workers()) {
+			const Widelands::WorkerDescr* descr = tribes.get_worker_descr(di);
+			if (descr->has_demand_check()) {
+				t.workers.emplace(di, descr->default_target_quantity());
+			}
+		}
+		predefined_targets_.emplace(kDefaultEconomyProfile, t);
+	}
+
+	std::string complete_filename = kEconomyProfilesDir + g_fs->file_separator() + player_->tribe().name();
+	Profile profile;
+	profile.read(complete_filename.c_str());
+
+	Section* global_section = profile.get_section(kDefaultEconomyProfile);
+	if (global_section) {
+		std::map<std::string, std::string> serials;
+		while (Section::Value* v = global_section->get_next_val()) {
+			serials.emplace(std::string(v->get_name()), v->get_string());
+		}
+
+		for (const auto& pair : serials) {
+			Section* section = profile.get_section(pair.first);
+			PredefinedTargets t;
+			while (Section::Value* v = section->get_next_val()) {
+				const std::string name = std::string(v->get_name());
+				Widelands::DescriptionIndex di = tribes.ware_index(name);
+				if (di == Widelands::INVALID_INDEX) {
+					di = tribes.worker_index(name);
+					assert(di != Widelands::INVALID_INDEX);
+					t.workers.emplace(di, v->get_natural());
 				} else {
-					game.send_player_command(*new Widelands::CmdSetWorkerTargetQuantity(
-					   game.get_gametime(), player_->player_number(), serial_, index,
-					   tq.permanent + amount));
+					t.wares.emplace(di, v->get_natural());
 				}
 			}
+			predefined_targets_.emplace(pair.second, t);
 		}
 	}
-}
 
-void EconomyOptionsWindow::EconomyOptionsPanel::reset_target() {
-	Widelands::Game& game = dynamic_cast<Widelands::Game&>(player_->egbase());
-	const bool is_wares = type_ == Widelands::wwWARE;
-	const auto& items = is_wares ? player_->tribe().wares() : player_->tribe().workers();
-	for (const Widelands::DescriptionIndex& index : items) {
-		if (display_.ware_selected(index)) {
-			if (is_wares) {
-				game.send_player_command(*new Widelands::CmdResetWareTargetQuantity(
-				   game.get_gametime(), player_->player_number(), serial_, index));
-			} else {
-				game.send_player_command(*new Widelands::CmdResetWorkerTargetQuantity(
-				   game.get_gametime(), player_->player_number(), serial_, index));
-			}
-		}
-	}
+	update_profiles(select);
 }

=== modified file 'src/wui/economy_options_window.h'
--- src/wui/economy_options_window.h	2019-02-23 11:00:49 +0000
+++ src/wui/economy_options_window.h	2019-05-06 13:53:16 +0000
@@ -20,20 +20,48 @@
 #ifndef WL_WUI_ECONOMY_OPTIONS_WINDOW_H
 #define WL_WUI_ECONOMY_OPTIONS_WINDOW_H
 
+#include <map>
 #include <memory>
+#include <string>
 
 #include "economy/economy.h"
 #include "logic/map_objects/tribes/tribe_descr.h"
 #include "notifications/notifications.h"
 #include "ui_basic/box.h"
+#include "ui_basic/dropdown.h"
 #include "ui_basic/tabpanel.h"
 #include "ui_basic/window.h"
 #include "wui/waresdisplay.h"
 
+const std::string kDefaultEconomyProfile = "Default";
+
 struct EconomyOptionsWindow : public UI::Window {
 	EconomyOptionsWindow(UI::Panel* parent, Widelands::Economy* economy, bool can_act);
 	~EconomyOptionsWindow();
 
+	struct PredefinedTargets {
+		using Targets = std::map<Widelands::DescriptionIndex, uint32_t>;
+		Targets wares;
+		Targets workers;
+	};
+
+	void create_target();
+	void do_create_target(const std::string&);
+	void save_targets();
+	void read_targets(const std::string& = kDefaultEconomyProfile);
+	void update_profiles(const std::string&);
+	std::map<std::string, PredefinedTargets>& get_predefined_targets() {
+		return predefined_targets_;
+	}
+	const PredefinedTargets& get_selected_target() const {
+		return predefined_targets_.at(dropdown_.get_selected());
+	}
+
+	void change_target(int amount);
+	void reset_target();
+
+	void layout() override;
+
 private:
 	struct TargetWaresDisplay : public AbstractWaresDisplay {
 		TargetWaresDisplay(UI::Panel* const parent,
@@ -59,14 +87,17 @@
 	 */
 	struct EconomyOptionsPanel : UI::Box {
 		EconomyOptionsPanel(UI::Panel* parent,
+		                    EconomyOptionsWindow* eco_window,
 		                    Widelands::Serial serial,
 		                    Widelands::Player* player,
 		                    bool can_act,
-		                    Widelands::WareWorker type);
+		                    Widelands::WareWorker type,
+		                    int32_t min_w);
 
 		void set_economy(Widelands::Serial serial);
 		void change_target(int amount);
 		void reset_target();
+		void update_desired_size() override;
 
 	private:
 		Widelands::Serial serial_;
@@ -74,17 +105,23 @@
 		Widelands::WareWorker type_;
 		bool can_act_;
 		TargetWaresDisplay display_;
+		EconomyOptionsWindow* economy_options_window_;
 	};
 
 	/// Actions performed when a NoteEconomyWindow is received.
 	void on_economy_note(const Widelands::NoteEconomy& note);
 
+	UI::Box main_box_;
 	Widelands::Serial serial_;
 	Widelands::Player* player_;
 	UI::TabPanel tabpanel_;
 	EconomyOptionsPanel* ware_panel_;
 	EconomyOptionsPanel* worker_panel_;
 	std::unique_ptr<Notifications::Subscriber<Widelands::NoteEconomy>> economynotes_subscriber_;
+
+	std::map<std::string, PredefinedTargets> predefined_targets_;
+	UI::Box dropdown_box_;
+	UI::Dropdown<std::string> dropdown_;
 };
 
 #endif  // end of include guard: WL_WUI_ECONOMY_OPTIONS_WINDOW_H

=== modified file 'src/wui/inputqueuedisplay.cc'
--- src/wui/inputqueuedisplay.cc	2019-04-25 06:40:24 +0000
+++ src/wui/inputqueuedisplay.cc	2019-05-06 13:53:16 +0000
@@ -71,7 +71,7 @@
 
 	uint32_t priority_button_height = show_only ? 0 : 3 * PriorityButtonSize;
 	uint32_t image_height =
-	   show_only ? WARE_MENU_PIC_HEIGHT : std::max<int32_t>(WARE_MENU_PIC_HEIGHT, ph);
+	   show_only ? kWareMenuPicHeight : std::max<int32_t>(kWareMenuPicHeight, ph);
 
 	total_height_ = std::max(priority_button_height, image_height) + 2 * Border;
 
@@ -91,7 +91,7 @@
  */
 void InputQueueDisplay::max_size_changed() {
 	uint32_t pbs = show_only_ ? 0 : PriorityButtonSize;
-	uint32_t ctrl_b_size = show_only_ ? 0 : 2 * WARE_MENU_PIC_WIDTH;
+	uint32_t ctrl_b_size = show_only_ ? 0 : 2 * kWareMenuPicWidth;
 
 	cache_size_ = queue_.get_max_size();
 
@@ -140,7 +140,7 @@
 
 	Vector2i point = Vector2i::zero();
 	point.x = Border + (show_only_ ? 0 : CellWidth + CellSpacing);
-	point.y = Border + (total_height_ - 2 * Border - WARE_MENU_PIC_HEIGHT) / 2;
+	point.y = Border + (total_height_ - 2 * Border - kWareMenuPicHeight) / 2;
 
 	for (; nr_inputs_to_draw; --nr_inputs_to_draw, point.x += CellWidth + CellSpacing) {
 		dst.blitrect(Vector2i(point.x, point.y), icon_, Recti(0, 0, icon_->width(), icon_->height()),
@@ -240,12 +240,12 @@
 		return;
 
 	uint32_t x = Border;
-	uint32_t y = Border + (total_height_ - 2 * Border - WARE_MENU_PIC_WIDTH) / 2;
+	uint32_t y = Border + (total_height_ - 2 * Border - kWareMenuPicWidth) / 2;
 
 	boost::format tooltip_format("%s<br><p><font size=%d bold=0>%s<br>%s</font></p>");
 
 	decrease_max_fill_ = new UI::Button(
-	   this, "decrease_max_fill", x, y, WARE_MENU_PIC_WIDTH, WARE_MENU_PIC_HEIGHT,
+	   this, "decrease_max_fill", x, y, kWareMenuPicWidth, kWareMenuPicHeight,
 	   UI::ButtonStyle::kWuiMenu, g_gr->images().get("images/ui_basic/scrollbar_left.png"),
 	   (tooltip_format
 	    /** TRANSLATORS: Button tooltip in in a building's wares input queue */
@@ -262,7 +262,7 @@
 	x = Border + (cache_size_ + 1) * (CellWidth + CellSpacing);
 
 	increase_max_fill_ = new UI::Button(
-	   this, "increase_max_fill", x, y, WARE_MENU_PIC_WIDTH, WARE_MENU_PIC_HEIGHT,
+	   this, "increase_max_fill", x, y, kWareMenuPicWidth, kWareMenuPicHeight,
 	   UI::ButtonStyle::kWuiMenu, g_gr->images().get("images/ui_basic/scrollbar_right.png"),
 	   (tooltip_format
 	    /** TRANSLATORS: Button tooltip in a building's wares input queue */

=== modified file 'src/wui/inputqueuedisplay.h'
--- src/wui/inputqueuedisplay.h	2019-04-23 14:53:35 +0000
+++ src/wui/inputqueuedisplay.h	2019-05-06 13:53:16 +0000
@@ -49,7 +49,7 @@
  */
 class InputQueueDisplay : public UI::Panel {
 public:
-	enum { CellWidth = WARE_MENU_PIC_WIDTH, CellSpacing = 2, Border = 4, PriorityButtonSize = 10 };
+	enum { CellWidth = kWareMenuPicWidth, CellSpacing = 2, Border = 4, PriorityButtonSize = 10 };
 
 	InputQueueDisplay(UI::Panel* parent,
 	                  int32_t x,

=== modified file 'src/wui/waresdisplay.cc'
--- src/wui/waresdisplay.cc	2019-02-23 11:00:49 +0000
+++ src/wui/waresdisplay.cc	2019-05-06 13:53:16 +0000
@@ -35,8 +35,9 @@
 #include "logic/map_objects/tribes/ware_descr.h"
 #include "logic/map_objects/tribes/worker.h"
 #include "logic/player.h"
+#include "ui_basic/window.h"
 
-const int WARE_MENU_INFO_SIZE = 12;
+constexpr int kWareMenuInfoSize = 12;
 
 AbstractWaresDisplay::AbstractWaresDisplay(
    UI::Panel* const parent,
@@ -46,7 +47,9 @@
    Widelands::WareWorker type,
    bool selectable,
    boost::function<void(Widelands::DescriptionIndex, bool)> callback_function,
-   bool horizontal)
+   bool horizontal,
+   int32_t hgap,
+   int32_t vgap)
    :  // Size is set when add_warelist is called, as it depends on the type_.
      UI::Panel(parent, x, y, 0, 0),
      tribe_(tribe),
@@ -57,6 +60,8 @@
 
      selectable_(selectable),
      horizontal_(horizontal),
+     hgap_(hgap),
+     vgap_(vgap),
      selection_anchor_(Widelands::INVALID_INDEX),
      callback_function_(callback_function) {
 	for (const Widelands::DescriptionIndex& index : indices_) {
@@ -67,22 +72,65 @@
 
 	curware_.set_text(_("Stock"));
 
-	// Find out geometry from icons_order
-	unsigned int columns = icons_order().size();
-	unsigned int rows = 0;
-	for (unsigned int i = 0; i < icons_order().size(); i++)
-		if (icons_order()[i].size() > rows)
-			rows = icons_order()[i].size();
+	graphic_resolution_changed_subscriber_ = Notifications::subscribe<GraphicResolutionChanged>(
+		[this](const GraphicResolutionChanged&) {
+			recalc_desired_size(true);
+		});
+
+	recalc_desired_size(false);
+}
+
+Widelands::Extent AbstractWaresDisplay::get_extent() const {
+	int16_t columns = 0;
+	int16_t rows = 0;
+	for (const auto& pair : icons_order_coords()) {
+		columns = std::max(columns, pair.second.x);
+		rows = std::max(rows, pair.second.y);
+	}
+	// We cound from 0 up
+	++columns;
+	++rows;
+
 	if (horizontal_) {
-		unsigned int s = columns;
+		const int16_t s = columns;
 		columns = rows;
 		rows = s;
 	}
+	return Widelands::Extent(columns, rows);
+}
+
+void AbstractWaresDisplay::set_hgap(int32_t gap) {
+	hgap_ = gap;
+	recalc_desired_size(true);
+}
+
+void AbstractWaresDisplay::set_vgap(int32_t gap) {
+	vgap_ = gap;
+	recalc_desired_size(true);
+}
+
+void AbstractWaresDisplay::recalc_desired_size(bool relayout) {
+	relayout_icons_order_coords();
+
+	// Find out geometry from icons_order
+	const Widelands::Extent size = get_extent();
 
 	// 25 is height of curware_ text
 	set_desired_size(
-	   columns * (WARE_MENU_PIC_WIDTH + WARE_MENU_PIC_PAD_X) + 1,
-	   rows * (WARE_MENU_PIC_HEIGHT + WARE_MENU_INFO_SIZE + WARE_MENU_PIC_PAD_Y) + 1 + 25);
+	   size.w * (kWareMenuPicWidth + hgap_) + 1,
+	   size.h * (kWareMenuPicHeight + kWareMenuInfoSize + vgap_) + 1 + 25);
+
+	if (relayout) {
+		// Since we are usually stacked deep within other panels, we need to tell our highest parent window to relayout
+		UI::Panel* p = this;
+		while (p->get_parent()) {
+			p = p->get_parent();
+			if (dynamic_cast<UI::Window*>(p)) {
+				p->layout();
+				return;
+			}
+		}
+	}
 }
 
 bool AbstractWaresDisplay::handle_mousemove(uint8_t state, int32_t x, int32_t y, int32_t, int32_t) {
@@ -162,21 +210,33 @@
  * DescriptionIndex::null() if the given point is outside the range.
  */
 Widelands::DescriptionIndex AbstractWaresDisplay::ware_at_point(int32_t x, int32_t y) const {
-	if (x < 0 || y < 0)
-		return Widelands::INVALID_INDEX;
-
-	unsigned int i = x / (WARE_MENU_PIC_WIDTH + WARE_MENU_PIC_PAD_X);
-	unsigned int j = y / (WARE_MENU_PIC_HEIGHT + WARE_MENU_INFO_SIZE + WARE_MENU_PIC_PAD_Y);
+	// Graphical offset
+	x -= 2;
+	y -= 2;
+
+	if (x < 0 || y < 0) {
+		return Widelands::INVALID_INDEX;
+	}
+
+	int i = x / (kWareMenuPicWidth + hgap_);
+	int j = y / (kWareMenuPicHeight + kWareMenuInfoSize + vgap_);
+	if (kWareMenuPicWidth * (i + 1) + hgap_ * i < x ||
+			(kWareMenuPicHeight + kWareMenuInfoSize) * (j + 1) + vgap_ * j < y) {
+		// Not on the ware, but on the space between
+		return Widelands::INVALID_INDEX;
+	}
 	if (horizontal_) {
-		unsigned int s = i;
+		int s = i;
 		i = j;
 		j = s;
 	}
-	if (i < icons_order().size() && j < icons_order()[i].size()) {
-		const Widelands::DescriptionIndex& ware = icons_order()[i][j];
-		assert(hidden_.count(ware) == 1);
-		if (!(hidden_.find(ware)->second)) {
-			return ware;
+	for (const auto& pair : icons_order_coords()) {
+		if (pair.second.x == i && pair.second.y == j) {
+			assert(hidden_.count(pair.first) == 1);
+			if (!(hidden_.find(pair.first)->second)) {
+				return pair.first;
+			}
+			break;
 		}
 	}
 
@@ -199,15 +259,13 @@
 	Vector2i anchor_pos = ware_position(selection_anchor_);
 	// Add an offset to make sure the anchor line and column will be
 	// selected when selecting in topleft direction
-	int32_t anchor_x = anchor_pos.x + WARE_MENU_PIC_WIDTH / 2;
-	int32_t anchor_y = anchor_pos.y + WARE_MENU_PIC_HEIGHT / 2;
+	int32_t anchor_x = anchor_pos.x + kWareMenuPicWidth / 2;
+	int32_t anchor_y = anchor_pos.y + kWareMenuPicHeight / 2;
 
-	unsigned int left_ware_idx = anchor_x / (WARE_MENU_PIC_WIDTH + WARE_MENU_PIC_PAD_X);
-	unsigned int top_ware_idx =
-	   anchor_y / (WARE_MENU_PIC_HEIGHT + WARE_MENU_INFO_SIZE + WARE_MENU_PIC_PAD_Y);
-	unsigned int right_ware_idx = x / (WARE_MENU_PIC_WIDTH + WARE_MENU_PIC_PAD_X);
-	unsigned int bottoware_idx_ =
-	   y / (WARE_MENU_PIC_HEIGHT + WARE_MENU_INFO_SIZE + WARE_MENU_PIC_PAD_Y);
+	unsigned int left_ware_idx = anchor_x / (kWareMenuPicWidth + hgap_);
+	unsigned int top_ware_idx = anchor_y / (kWareMenuPicHeight + kWareMenuInfoSize + vgap_);
+	unsigned int right_ware_idx = x / (kWareMenuPicWidth + hgap_);
+	unsigned int bottoware_idx_ = y / (kWareMenuPicHeight + kWareMenuInfoSize + vgap_);
 	unsigned int tmp;
 
 	// Reverse col/row and anchor/endpoint if needed
@@ -271,26 +329,43 @@
 	NEVER_HERE();
 }
 
-const Widelands::TribeDescr::WaresOrderCoords& AbstractWaresDisplay::icons_order_coords() const {
-	switch (type_) {
-	case Widelands::wwWARE:
-		return tribe_.wares_order_coords();
-	case Widelands::wwWORKER:
-		return tribe_.workers_order_coords();
+const WaresOrderCoords& AbstractWaresDisplay::icons_order_coords() const {
+	assert(!order_coords_.empty());
+	return order_coords_;
+}
+
+void AbstractWaresDisplay::relayout_icons_order_coords() {
+	order_coords_.clear();
+	const int column_number = icons_order().size();
+	const int column_max_size = (g_gr->get_yres() - 290) / (kWareMenuPicHeight + vgap_ + kWareMenuInfoSize);
+
+	int16_t column_index_to_apply = 0;
+	for (int16_t column_index = 0; column_index < column_number; ++column_index) {
+		const std::vector<Widelands::DescriptionIndex>& column = icons_order().at(column_index);
+		const int row_number = column.size();
+		int16_t row_index_to_apply = 0;
+		for (int16_t row_index = 0; row_index < row_number; ++row_index) {
+			order_coords_.emplace(column.at(row_index), Widelands::Coords(column_index_to_apply, row_index_to_apply));
+			++row_index_to_apply;
+			if (row_index_to_apply > column_max_size) {
+				row_index_to_apply = 0;
+				++column_index_to_apply;
+			}
+		}
+		if (row_index_to_apply > 0) {
+			++column_index_to_apply;
+		}
 	}
-	NEVER_HERE();
 }
 
 Vector2i AbstractWaresDisplay::ware_position(Widelands::DescriptionIndex id) const {
 	Vector2i p(2, 2);
 	if (horizontal_) {
-		p.x += icons_order_coords()[id].second * (WARE_MENU_PIC_WIDTH + WARE_MENU_PIC_PAD_X);
-		p.y += icons_order_coords()[id].first *
-		       (WARE_MENU_PIC_HEIGHT + WARE_MENU_PIC_PAD_Y + WARE_MENU_INFO_SIZE);
+		p.x += icons_order_coords().at(id).y * (kWareMenuPicWidth + hgap_);
+		p.y += icons_order_coords().at(id).x * (kWareMenuPicHeight + vgap_ + kWareMenuInfoSize);
 	} else {
-		p.x += icons_order_coords()[id].first * (WARE_MENU_PIC_WIDTH + WARE_MENU_PIC_PAD_X);
-		p.y += icons_order_coords()[id].second *
-		       (WARE_MENU_PIC_HEIGHT + WARE_MENU_PIC_PAD_Y + WARE_MENU_INFO_SIZE);
+		p.x += icons_order_coords().at(id).x * (kWareMenuPicWidth + hgap_);
+		p.y += icons_order_coords().at(id).y * (kWareMenuPicHeight + vgap_ + kWareMenuInfoSize);
 	}
 	return p;
 }
@@ -326,15 +401,15 @@
 	const Image* icon = type_ == Widelands::wwWORKER ? tribe_.get_worker_descr(id)->icon() :
 	                                                   tribe_.get_ware_descr(id)->icon();
 
-	dst.blit(p + Vector2i((w - WARE_MENU_PIC_WIDTH) / 2, 1), icon);
+	dst.blit(p + Vector2i((w - kWareMenuPicWidth) / 2, 1), icon);
 
-	dst.fill_rect(Recti(p + Vector2i(0, WARE_MENU_PIC_HEIGHT), w, WARE_MENU_INFO_SIZE),
+	dst.fill_rect(Recti(p + Vector2i(0, kWareMenuPicHeight), w, kWareMenuInfoSize),
 	              info_color_for_ware(id));
 
 	std::shared_ptr<const UI::RenderedText> rendered_text =
 	   UI::g_fh->render(as_waresinfo(info_for_ware(id)));
 	rendered_text->draw(dst, Vector2i(p.x + w - rendered_text->width() - 1,
-	                                  p.y + WARE_MENU_PIC_HEIGHT + WARE_MENU_INFO_SIZE + 1 -
+	                                  p.y + kWareMenuPicHeight + kWareMenuInfoSize + 1 -
 	                                     rendered_text->height()));
 }
 

=== modified file 'src/wui/waresdisplay.h'
--- src/wui/waresdisplay.h	2019-02-23 11:00:49 +0000
+++ src/wui/waresdisplay.h	2019-05-06 13:53:16 +0000
@@ -20,6 +20,7 @@
 #ifndef WL_WUI_WARESDISPLAY_H
 #define WL_WUI_WARESDISPLAY_H
 
+#include <memory>
 #include <vector>
 
 #include "logic/map_objects/tribes/tribe_descr.h"
@@ -36,6 +37,8 @@
 struct WareList;
 }  // namespace Widelands
 
+using WaresOrderCoords = std::map<Widelands::DescriptionIndex, Widelands::Coords>;
+
 /**
  * Display wares or workers together with some string (typically a number)
  * in the style of the @ref WarehouseWindow.
@@ -54,7 +57,9 @@
 	   CLANG_DIAG_OFF("-Wunknown-pragmas") CLANG_DIAG_OFF("-Wzero-as-null-pointer-constant")
 	      boost::function<void(Widelands::DescriptionIndex, bool)> callback_function = 0,
 	   CLANG_DIAG_ON("-Wzero-as-null-pointer-constant")
-	      CLANG_DIAG_ON("-Wunknown-pragmas") bool horizontal = false);
+	      CLANG_DIAG_ON("-Wunknown-pragmas") bool horizontal = false,
+	   int32_t hgap = 3,
+	   int32_t vgap = 4);
 
 	bool
 	handle_mousemove(uint8_t state, int32_t x, int32_t y, int32_t xdiff, int32_t ydiff) override;
@@ -74,6 +79,19 @@
 		return type_;
 	}
 
+	int32_t get_hgap() {
+		return hgap_;
+	}
+	int32_t get_vgap() {
+		return vgap_;
+	}
+	void set_hgap(int32_t);
+	void set_vgap(int32_t);
+
+	Widelands::Extent get_extent() const;
+
+	const WaresOrderCoords& icons_order_coords() const;
+
 protected:
 	void layout() override;
 
@@ -82,7 +100,6 @@
 	virtual RGBColor info_color_for_ware(Widelands::DescriptionIndex);
 
 	const Widelands::TribeDescr::WaresOrder& icons_order() const;
-	const Widelands::TribeDescr::WaresOrderCoords& icons_order_coords() const;
 	virtual Vector2i ware_position(Widelands::DescriptionIndex) const;
 	void draw(RenderTarget&) override;
 	virtual void draw_ware(RenderTarget&, Widelands::DescriptionIndex);
@@ -110,6 +127,13 @@
 	WareListSelectionType in_selection_;  // Wares in temporary anchored selection
 	bool selectable_;
 	bool horizontal_;
+	int32_t hgap_;
+	int32_t vgap_;
+
+	WaresOrderCoords order_coords_;
+
+	void relayout_icons_order_coords();
+	void recalc_desired_size(bool);
 
 	/**
 	 * The ware on which the mouse press has been performed.
@@ -117,6 +141,8 @@
 	 */
 	Widelands::DescriptionIndex selection_anchor_;
 	boost::function<void(Widelands::DescriptionIndex, bool)> callback_function_;
+
+	std::unique_ptr<Notifications::Subscriber<GraphicResolutionChanged>> graphic_resolution_changed_subscriber_;
 };
 
 /*


Follow ups