← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/bug-1399621 into lp:widelands

 

GunChleoc has proposed merging lp:~widelands-dev/widelands/bug-1399621 into lp:widelands.

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #849705 in widelands: "Add a column for "stopped" buildings in building statistics"
  https://bugs.launchpad.net/widelands/+bug/849705
  Bug #1399621 in widelands: "Building Statistics Window Redesign"
  https://bugs.launchpad.net/widelands/+bug/1399621

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/bug-1399621/+merge/258843

The Building Statistics window now uses a grid layout with tabs, analogous to flagaction.

Added "b" hotkey to toggle the window.
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/bug-1399621 into lp:widelands.
=== modified file 'src/graphic/color.cc'
--- src/graphic/color.cc	2015-03-01 09:21:20 +0000
+++ src/graphic/color.cc	2015-06-03 10:36:31 +0000
@@ -19,12 +19,18 @@
 
 #include "graphic/color.h"
 
+#include <boost/format.hpp>
+
 RGBColor::RGBColor() {
 }
 
 RGBColor::RGBColor(uint8_t const R, uint8_t const G, uint8_t const B) :
 	r(R), g(G), b(B) {}
 
+std::string RGBColor::hex_value() const {
+	return (boost::format ("%02x%02x%02x") % int(r) % int(g) % int(b)).str();
+}
+
 uint32_t RGBColor::map(const SDL_PixelFormat& fmt) const {
 	return SDL_MapRGB(&const_cast<SDL_PixelFormat&>(fmt), r, g, b);
 }
@@ -53,6 +59,10 @@
 	a = 255;
 }
 
+std::string RGBAColor::hex_value() const {
+	return (boost::format ("%02x%02x%02x%02x>") % int(r) % int(g) % int(b) % int(a)).str();
+}
+
 uint32_t RGBAColor::map(const SDL_PixelFormat& fmt) const {
 	return SDL_MapRGBA(&const_cast<SDL_PixelFormat&>(fmt), r, g, b, a);
 }

=== modified file 'src/graphic/color.h'
--- src/graphic/color.h	2014-07-14 10:45:44 +0000
+++ src/graphic/color.h	2015-06-03 10:36:31 +0000
@@ -20,6 +20,8 @@
 #ifndef WL_GRAPHIC_COLOR_H
 #define WL_GRAPHIC_COLOR_H
 
+#include <string>
+
 #include <SDL.h>
 
 struct RGBColor {
@@ -28,6 +30,9 @@
 	// Initializes the color to black.
 	RGBColor();
 
+	// Returns this color in hex format.
+	std::string hex_value() const;
+
 	// Map this color to the given 'fmt'
 	uint32_t map(const SDL_PixelFormat& fmt) const;
 
@@ -49,6 +54,9 @@
 	// Initializes to opaque color.
 	RGBAColor(const RGBColor& c);
 
+	// Returns this color in hex format.
+	std::string hex_value() const;
+
 	// Map this color to the given 'fmt'
 	uint32_t map(const SDL_PixelFormat& fmt) const;
 

=== modified file 'src/graphic/text_constants.h'
--- src/graphic/text_constants.h	2015-03-01 09:21:20 +0000
+++ src/graphic/text_constants.h	2015-06-03 10:36:31 +0000
@@ -42,10 +42,10 @@
 #define UI_FONT_TOOLTIP_CLR  RGBColor(255, 255,   0)
 
 /// Colors for good/ok/bad
-#define UI_FONT_CLR_BRIGHT_HEX    "fffaaa"
-#define UI_FONT_CLR_DARK_HEX      "a39013"
-#define UI_FONT_CLR_BAD_HEX       "bb0000"
-#define UI_FONT_CLR_OK_HEX        "ffe11e"
-#define UI_FONT_CLR_GOOD_HEX      "00bb00"
+#define UI_FONT_CLR_BRIGHT   RGBColor(255, 250, 170)
+#define UI_FONT_CLR_DARK     RGBColor(163, 144,  19)
+#define UI_FONT_CLR_BAD      RGBColor(187,   0,   0)
+#define UI_FONT_CLR_OK       RGBColor(255, 225,  30)
+#define UI_FONT_CLR_GOOD     RGBColor(  0, 187,   0)
 
 #endif  // end of include guard: WL_GRAPHIC_TEXT_CONSTANTS_H

=== modified file 'src/graphic/text_layout.cc'
--- src/graphic/text_layout.cc	2014-12-11 12:38:10 +0000
+++ src/graphic/text_layout.cc	2015-06-03 10:36:31 +0000
@@ -39,36 +39,36 @@
 }
 
 std::string as_window_title(const std::string& txt) {
-	static boost::format f("<rt><p><font face=serif size=13 bold=1 color=%02x%02x%02x>%s</font></p></rt>");
+	static boost::format f("<rt><p><font face=serif size=13 bold=1 color=%s>%s</font></p></rt>");
 
-	f % int(UI_FONT_CLR_FG.r) % int(UI_FONT_CLR_FG.g) % int(UI_FONT_CLR_FG.b);
+	f % UI_FONT_CLR_FG.hex_value();
 	f % txt;
 	return f.str();
 }
 std::string as_uifont(const std::string & txt, int size, const RGBColor& clr) {
 	// UI Text is always bold due to historic reasons
 	static boost::format
-			f("<rt><p><font face=serif size=%i bold=1 shadow=1 color=%02x%02x%02x>%s</font></p></rt>");
+			f("<rt><p><font face=serif size=%i bold=1 shadow=1 color=%s>%s</font></p></rt>");
 
 	f % size;
-	f % int(clr.r) % int(clr.g) % int(clr.b);
+	f % clr.hex_value();
 	f % txt;
 	return f.str();
 }
 
 std::string as_tooltip(const std::string & txt) {
-	static boost::format f("<rt><p><font face=serif size=%i bold=1 color=%02x%02x%02x>%s</font></p></rt>");
+	static boost::format f("<rt><p><font face=serif size=%i bold=1 color=%s>%s</font></p></rt>");
 
 	f % UI_FONT_SIZE_SMALL;
-	f % int(UI_FONT_TOOLTIP_CLR.r) % int(UI_FONT_TOOLTIP_CLR.g) % int(UI_FONT_TOOLTIP_CLR.b);
+	f % UI_FONT_TOOLTIP_CLR.hex_value();
 	f % txt;
 	return f.str();
 }
 
 std::string as_waresinfo(const std::string & txt) {
 	static boost::format f
-		("<rt><p><font face=condensed size=10 bold=0 color=%02x%02x%02x>%s</font></p></rt>");
-	f % int(UI_FONT_TOOLTIP_CLR.r) % int(UI_FONT_TOOLTIP_CLR.g) % int(UI_FONT_TOOLTIP_CLR.b);
+		("<rt><p><font face=condensed size=10 bold=0 color=%s>%s</font></p></rt>");
+	f % UI_FONT_TOOLTIP_CLR.hex_value();
 	f % txt;
 	return f.str();
 }

=== modified file 'src/logic/constructionsite.cc'
--- src/logic/constructionsite.cc	2014-12-06 12:22:35 +0000
+++ src/logic/constructionsite.cc	2015-06-03 10:36:31 +0000
@@ -84,7 +84,7 @@
 void ConstructionSite::update_statistics_string(std::string* s)
 {
 	unsigned int percent = (get_built_per64k() * 100) >> 16;
-	*s = (boost::format("<font color=%s>%s</font>") % UI_FONT_CLR_DARK_HEX %
+	*s = (boost::format("<font color=%s>%s</font>") % UI_FONT_CLR_DARK.hex_value() %
 	      (boost::format(_("%i%% built")) % percent).str()).str();
 }
 

=== modified file 'src/logic/immovable.cc'
--- src/logic/immovable.cc	2015-04-22 19:21:02 +0000
+++ src/logic/immovable.cc	2015-06-03 10:36:31 +0000
@@ -570,7 +570,7 @@
 		unsigned int percent = (100 * done / total);
 		m_construct_string =
 			(boost::format("<font color=%s>%s</font>")
-			 % UI_FONT_CLR_DARK_HEX % (boost::format(_("%i%% built")) % percent).str())
+			 % UI_FONT_CLR_DARK.hex_value() % (boost::format(_("%i%% built")) % percent).str())
 			 .str();
 		m_construct_string = as_uifont(m_construct_string);
 		dst.blit(pos - Point(0, 48),

=== modified file 'src/logic/productionsite.cc'
--- src/logic/productionsite.cc	2015-05-04 19:06:37 +0000
+++ src/logic/productionsite.cc	2015-06-03 10:36:31 +0000
@@ -228,19 +228,19 @@
 		nr_workers += m_working_positions[--i].worker ? 1 : 0;
 
 	if (nr_workers == 0) {
-		*s = (boost::format("<font color=%s>%s</font>") % UI_FONT_CLR_BAD_HEX % _("(not occupied)"))
+		*s = (boost::format("<font color=%s>%s</font>") % UI_FONT_CLR_BAD.hex_value() % _("(not occupied)"))
 		        .str();
 		return;
 	}
 
 	if (uint32_t const nr_requests = nr_working_positions - nr_workers) {
-		*s = (boost::format("<font color=%s>%s</font>") % UI_FONT_CLR_BAD_HEX %
+		*s = (boost::format("<font color=%s>%s</font>") % UI_FONT_CLR_BAD.hex_value() %
 		      ngettext("Worker missing", "Workers missing", nr_requests)).str();
 		return;
 	}
 
 	if (m_is_stopped) {
-		*s = (boost::format("<font color=%s>%s</font>") % UI_FONT_CLR_BRIGHT_HEX % _("(stopped)"))
+		*s = (boost::format("<font color=%s>%s</font>") % UI_FONT_CLR_BRIGHT.hex_value() % _("(stopped)"))
 		        .str();
 		return;
 	}
@@ -312,23 +312,23 @@
 
 	std::string color;
 	if (percOk < 33)
-		color = UI_FONT_CLR_BAD_HEX;
+		color = UI_FONT_CLR_BAD.hex_value();
 	else if (percOk < 66)
-		color = UI_FONT_CLR_OK_HEX;
+		color = UI_FONT_CLR_OK.hex_value();
 	else
-		color = UI_FONT_CLR_GOOD_HEX;
+		color = UI_FONT_CLR_GOOD.hex_value();
 	const std::string perc_str =
 		(boost::format("<font color=%s>%i%%</font>") % color % percOk).str();
 
 	std::string trend;
 	if (lastPercOk > percOk) {
-		color = UI_FONT_CLR_GOOD_HEX;
+		color = UI_FONT_CLR_GOOD.hex_value();
 		trend = "+";
 	} else if (lastPercOk < percOk) {
-		color = UI_FONT_CLR_BAD_HEX;
+		color = UI_FONT_CLR_BAD.hex_value();
 		trend = "-";
 	} else {
-		color = UI_FONT_CLR_BRIGHT_HEX;
+		color = UI_FONT_CLR_BRIGHT.hex_value();
 		trend = "=";
 	}
 	const std::string trend_str =

=== modified file 'src/wui/building_statistics_menu.cc'
--- src/wui/building_statistics_menu.cc	2014-11-30 18:49:38 +0000
+++ src/wui/building_statistics_menu.cc	2015-06-03 10:36:31 +0000
@@ -19,494 +19,767 @@
 
 #include "wui/building_statistics_menu.h"
 
-#include <vector>
+#include <cmath>
 
 #include <boost/bind.hpp>
 #include <boost/format.hpp>
 
 #include "base/i18n.h"
-#include "base/macros.h"
-#include "graphic/graphic.h"
-#include "graphic/rendertarget.h"
-#include "logic/building.h"
+#include "graphic/font_handler1.h"
 #include "logic/player.h"
+#include "logic/militarysite.h"
 #include "logic/productionsite.h"
 #include "logic/tribe.h"
-#include "ui_basic/button.h"
-#include "wui/interactive_player.h"
-#include "wui/mapviewpixelconstants.h"
-#include "wui/plot_area.h"
-
-#define WINDOW_WIDTH         625
-#define WINDOW_HEIGHT        440
-#define VMARGIN                5
-#define HMARGIN                5
-#define VSPACING               5
-#define HSPACING               5
-#define BUILDING_LIST_HEIGHT 285
-#define BUILDING_LIST_WIDTH  (WINDOW_WIDTH - HMARGIN - HMARGIN)
-#define LABEL_X              200
-#define LABEL_WIDTH          150
-#define VALUE_X              (LABEL_X + LABEL_WIDTH)
-#define JUMP_PREV_BUTTON_X   (WINDOW_WIDTH - HMARGIN - 24 - HSPACING - 24)
-#define JUMP_NEXT_BUTTON_X   (WINDOW_WIDTH - HMARGIN - 24)
-#define TOTAL_PRODUCTIVITY_Y (VMARGIN + BUILDING_LIST_HEIGHT + VSPACING + 22)
-#define PROGRESS_BAR_Y       (TOTAL_PRODUCTIVITY_Y + 24)
-#define OWNED_Y              (PROGRESS_BAR_Y       + 24)
-#define IN_BUILD_Y           (OWNED_Y              + 24)
-#define UNPRODUCTIVE_Y       (IN_BUILD_Y           + 24)
-#define FLAG_POINT           Point(125, WINDOW_HEIGHT - 8)
-
-#define LOW_PROD 33
-
-#define UPDATE_TIME 1000  //  1 second, gametime
-
-
-namespace Columns {enum {Name, Size, Prod, Owned, Build};}
-
-inline InteractivePlayer & BuildingStatisticsMenu::iplayer() const {
+
+constexpr int kBuildGridCellSize = 50;
+constexpr int kMargin = 5;
+constexpr int kColumns = 5;
+constexpr int kButtonHeight = 20;
+constexpr int kButtonRowHeight = kButtonHeight + kMargin;
+constexpr int kLabelHeight = 18;
+constexpr int kLabelFontSize = 12;
+constexpr int kTabHeight = 35 + 5 * (kBuildGridCellSize + kLabelHeight + kLabelHeight);
+constexpr int32_t kWindowWidth = kColumns * kBuildGridCellSize;
+constexpr int32_t kWindowHeight = kTabHeight + kMargin + 4 * kButtonRowHeight;
+
+constexpr int32_t kUpdateTime = 1000;  //  1 second, gametime
+
+namespace {
+void set_label_font(UI::Textarea* label) {
+	label->set_font(UI::g_fh1->fontset().serif(), kLabelFontSize, UI_FONT_CLR_FG);
+}
+void set_editbox_font(UI::EditBox* editbox) {
+	editbox->set_font(UI::g_fh1->fontset().serif(), kLabelFontSize, UI_FONT_CLR_FG);
+}
+
+}  // namespace
+
+inline InteractivePlayer& BuildingStatisticsMenu::iplayer() const {
 	return dynamic_cast<InteractivePlayer&>(*get_parent());
 }
 
-BuildingStatisticsMenu::BuildingStatisticsMenu
-	(InteractivePlayer & parent, UI::UniqueWindow::Registry & registry)
-:
-	UI::UniqueWindow
-		(&parent, "building_statistics",
-		 &registry,
-		 WINDOW_WIDTH, WINDOW_HEIGHT,
-		 _("Building Statistics")),
-	m_table
-		(this, HMARGIN, VMARGIN, BUILDING_LIST_WIDTH, BUILDING_LIST_HEIGHT),
-	m_progbar
-		(this,
-		 LABEL_X, PROGRESS_BAR_Y, WINDOW_WIDTH - LABEL_X - HMARGIN, 20,
-		 UI::ProgressBar::Horizontal),
-	m_total_productivity_label
-		(this,
-		 LABEL_X, TOTAL_PRODUCTIVITY_Y, LABEL_WIDTH, 24,
-		 _("Total Productivity:"), UI::Align_CenterLeft),
-	m_owned_label
-		(this,
-		 LABEL_X, OWNED_Y, LABEL_WIDTH, 24,
-		 _("Owned:"), UI::Align_CenterLeft),
-	m_owned
-		(this, VALUE_X, OWNED_Y, 100, 24, UI::Align_CenterLeft),
-	m_in_build_label
-		(this,
-		 LABEL_X, IN_BUILD_Y, LABEL_WIDTH, 24,
-		 _("Being built:"), UI::Align_CenterLeft),
-	m_in_build
-		(this, VALUE_X, IN_BUILD_Y, 100, 24, UI::Align_CenterLeft),
-	m_unproductive_label
-		(this,
-		 LABEL_X, UNPRODUCTIVE_Y, LABEL_WIDTH, 24,
-		 _("Jump to unproductive"), UI::Align_CenterLeft),
-	m_anim               (0),
-	m_lastupdate         (0),
-	m_last_building_index(0),
-	m_last_table_index   (0)
-{
-	//  building list
-	m_table.add_column(310, _("Name"));
-	m_table.add_column (70, _("Type"), "",     UI::Align_HCenter);
-	m_table.add_column (70, _("Prod"), "",     UI::Align_Right);
-	m_table.add_column (70, _("Owned"), "",    UI::Align_Right);
-	m_table.add_column (70, _("Build"), "",    UI::Align_Right);
-	m_table.selected.connect(boost::bind(&BuildingStatisticsMenu::table_changed, this, _1));
-	m_table.set_column_compare
-		(Columns::Size,
-		 boost::bind
-		 	(&BuildingStatisticsMenu::compare_building_size, this, _1, _2));
-	m_table.focus();
-
-	//  toggle when to run button
-	m_progbar.set_total(100);
-
-	m_btn[PrevOwned] =
-		new UI::Button
-			(this, "previous_owned",
-			 JUMP_PREV_BUTTON_X, OWNED_Y, 24, 24,
-			 g_gr->images().get("pics/but4.png"),
-			 g_gr->images().get("pics/scrollbar_left.png"),
-			 _("Show previous"),
-			 false);
-	m_btn[PrevOwned]->sigclicked.connect
-		(boost::bind(&BuildingStatisticsMenu::clicked_jump, boost::ref(*this), PrevOwned));
-
-	m_btn[NextOwned] =
-		new UI::Button
-			(this, "next_owned",
-			 JUMP_NEXT_BUTTON_X, OWNED_Y, 24, 24,
-			 g_gr->images().get("pics/but4.png"),
-			 g_gr->images().get("pics/scrollbar_right.png"),
-			 _("Show next"),
-			 false);
-	m_btn[NextOwned]->sigclicked.connect
-		(boost::bind(&BuildingStatisticsMenu::clicked_jump, boost::ref(*this), NextOwned));
-
-	m_btn[PrevConstruction] =
-		new UI::Button
-			(this, "previous_constructed",
-			 JUMP_PREV_BUTTON_X, IN_BUILD_Y, 24, 24,
-			 g_gr->images().get("pics/but4.png"),
-			 g_gr->images().get("pics/scrollbar_left.png"),
-			 _("Show previous"),
-			 false);
-	m_btn[PrevConstruction]->sigclicked.connect
-		(boost::bind(&BuildingStatisticsMenu::clicked_jump, boost::ref(*this), PrevConstruction));
-
-	m_btn[NextConstruction] =
-		new UI::Button
-			(this, "next_constructed",
-			 JUMP_NEXT_BUTTON_X, IN_BUILD_Y, 24, 24,
-			 g_gr->images().get("pics/but4.png"),
-			 g_gr->images().get("pics/scrollbar_right.png"),
-			 _("Show next"),
-			 false);
-	m_btn[NextConstruction]->sigclicked.connect
-		(boost::bind(&BuildingStatisticsMenu::clicked_jump, boost::ref(*this), NextConstruction));
-
-	m_btn[PrevUnproductive] =
-		new UI::Button
-			(this, "previous_unproductive",
-			 JUMP_PREV_BUTTON_X, UNPRODUCTIVE_Y, 24, 24,
-			 g_gr->images().get("pics/but4.png"),
-			 g_gr->images().get("pics/scrollbar_left.png"),
-			 _("Show previous"),
-			 false);
-	m_btn[PrevUnproductive]->sigclicked.connect
-		(boost::bind(&BuildingStatisticsMenu::clicked_jump, boost::ref(*this), PrevUnproductive));
-
-	m_btn[NextUnproductive] =
-		new UI::Button
-			(this, "next_unproductive",
-			 JUMP_NEXT_BUTTON_X, UNPRODUCTIVE_Y, 24, 24,
-			 g_gr->images().get("pics/but4.png"),
-			 g_gr->images().get("pics/scrollbar_right.png"),
-			 _("Show next"),
-			 false);
-	m_btn[NextUnproductive]->sigclicked.connect
-		(boost::bind(&BuildingStatisticsMenu::clicked_jump, boost::ref(*this), NextUnproductive));
-}
-
+BuildingStatisticsMenu::BuildingStatisticsMenu(InteractivePlayer& parent,
+                                               UI::UniqueWindow::Registry& registry)
+   : UI::UniqueWindow(&parent,
+                      "building_statistics",
+                      &registry,
+                      kWindowWidth,
+                      kWindowHeight,
+                      _("Building Statistics")),
+     tab_panel_(this, 0, 0, g_gr->images().get("pics/but1.png")),
+     navigation_panel_(this, 0, 0, kWindowWidth, 4 * kButtonRowHeight),
+     building_name_(
+        &navigation_panel_, get_inner_w() / 2, 0, 0, kButtonHeight, "", UI::Align_Center),
+     owned_label_(&navigation_panel_,
+                  kMargin,
+                  kButtonRowHeight,
+                  0,
+                  kButtonHeight,
+                  _("Owned:"),
+                  UI::Align_CenterLeft),
+     construction_label_(&navigation_panel_,
+                         kMargin,
+                         2 * kButtonRowHeight,
+                         0,
+                         kButtonHeight,
+                         _("Under Construction:"),
+                         UI::Align_CenterLeft),
+     unproductive_box_(&navigation_panel_, kMargin, 3 * kButtonRowHeight + 3, UI::Box::Horizontal),
+     unproductive_label_(
+        &unproductive_box_,
+        /** TRANSLATORS: This is the first part of productivity with input field */
+        /** TRANSLATORS: Building statistics window -  'Low Production: <input>%' */
+        _("Low Production: "),
+        UI::Align_BottomLeft),
+     unproductive_percent_(
+        &unproductive_box_, 0, 0, 35, kLabelHeight, g_gr->images().get("pics/but1.png")),
+     unproductive_label2_(
+        &unproductive_box_,
+        /** TRANSLATORS: This is the second part of productivity with input field */
+        /** TRANSLATORS: Building statistics window -  'Low Production: <input>%' */
+        _("%"),
+        UI::Align_BottomLeft),
+     no_owned_label_(&navigation_panel_,
+                     get_inner_w() - 2 * kButtonRowHeight - kMargin,
+                     kButtonRowHeight,
+                     0,
+                     kButtonHeight,
+                     "",
+                     UI::Align_CenterRight),
+     no_construction_label_(&navigation_panel_,
+                            get_inner_w() - 2 * kButtonRowHeight - kMargin,
+                            2 * kButtonRowHeight,
+                            0,
+                            kButtonHeight,
+                            "",
+                            UI::Align_CenterRight),
+     no_unproductive_label_(&navigation_panel_,
+                            get_inner_w() - 2 * kButtonRowHeight - kMargin,
+                            3 * kButtonRowHeight,
+                            0,
+                            kButtonHeight,
+                            "",
+                            UI::Align_CenterRight),
+     low_production_(33),
+     has_selection_(false) {
+
+	for (int i = 0; i < kNoOfBuildingTabs; ++i) {
+		row_counters_[i] = 0;
+		tabs_[i] = new UI::Box(&tab_panel_, 0, 0, UI::Box::Vertical);
+	}
+
+	tab_panel_.add("building_stats_small",
+	               g_gr->images().get("pics/menu_tab_buildsmall.png"),
+	               tabs_[BuildingTab::Small],
+	               _("Small Buildings"));
+	tab_panel_.add("building_stats_medium",
+	               g_gr->images().get("pics/menu_tab_buildmedium.png"),
+	               tabs_[BuildingTab::Medium],
+	               _("Medium Buildings"));
+	tab_panel_.add("building_stats_big",
+	               g_gr->images().get("pics/menu_tab_buildbig.png"),
+	               tabs_[BuildingTab::Big],
+	               _("Big Buildings"));
+	tab_panel_.add("building_stats_mines",
+	               g_gr->images().get("pics/menu_tab_buildmine.png"),
+	               tabs_[BuildingTab::Mines],
+	               _("Mines"));
+
+	// Hide the ports tab for non-seafaring maps
+	if (iplayer().game().map().get_port_spaces().size() > 1) {
+		tab_panel_.add("building_stats_ports",
+		               g_gr->images().get("pics/menu_tab_buildport.png"),
+		               tabs_[BuildingTab::Ports],
+		               _("Ports"));
+	}
+
+	const TribeDescr& tribe = iplayer().player().tribe();
+
+	const BuildingIndex nr_buildings = tribe.get_nrbuildings();
+	building_buttons_ = std::vector<UI::Button*>(nr_buildings);
+	owned_labels_ = std::vector<UI::MultilineTextarea*>(nr_buildings);
+	productivity_labels_ = std::vector<UI::MultilineTextarea*>(nr_buildings);
+
+	// Column counters
+	int columns[kNoOfBuildingTabs] = {0, 0, 0, 0, 0};
+
+	// Row containers
+	UI::Box* rows[kNoOfBuildingTabs];
+	for (int i = 0; i < kNoOfBuildingTabs; ++i) {
+		rows[i] = new UI::Box(tabs_[i], 0, 0, UI::Box::Horizontal);
+	}
+
+	for (BuildingIndex id = 0; id < nr_buildings; ++id) {
+		const BuildingDescr& descr = *tribe.get_building_descr(id);
+
+		if (descr.type() != MapObjectType::CONSTRUCTIONSITE &&
+		    descr.type() != MapObjectType::DISMANTLESITE) {
+			if (descr.get_ismine()) {
+				if (add_button(id,
+				               descr,
+				               BuildingTab::Mines,
+				               *rows[BuildingTab::Mines],
+				               &columns[BuildingTab::Mines])) {
+					rows[BuildingTab::Mines] =
+					   new UI::Box(tabs_[BuildingTab::Mines], 0, 0, UI::Box::Horizontal);
+				}
+			} else if (descr.get_isport()) {
+				if (add_button(id,
+				               descr,
+				               BuildingTab::Ports,
+				               *rows[BuildingTab::Ports],
+				               &columns[BuildingTab::Ports])) {
+					rows[BuildingTab::Ports] =
+					   new UI::Box(tabs_[BuildingTab::Ports], 0, 0, UI::Box::Horizontal);
+				}
+			} else {
+				switch (descr.get_size()) {
+				case BaseImmovable::SMALL:
+					if (add_button(id,
+					               descr,
+					               BuildingTab::Small,
+					               *rows[BuildingTab::Small],
+					               &columns[BuildingTab::Small])) {
+						rows[BuildingTab::Small] =
+						   new UI::Box(tabs_[BuildingTab::Small], 0, 0, UI::Box::Horizontal);
+					}
+					break;
+				case BaseImmovable::MEDIUM:
+					if (add_button(id,
+					               descr,
+					               BuildingTab::Medium,
+					               *rows[BuildingTab::Medium],
+					               &columns[BuildingTab::Medium])) {
+						rows[BuildingTab::Medium] =
+						   new UI::Box(tabs_[BuildingTab::Medium], 0, 0, UI::Box::Horizontal);
+					}
+					break;
+				case BaseImmovable::BIG:
+					if (add_button(id,
+					               descr,
+					               BuildingTab::Big,
+					               *rows[BuildingTab::Big],
+					               &columns[BuildingTab::Big])) {
+						rows[BuildingTab::Big] =
+						   new UI::Box(tabs_[BuildingTab::Big], 0, 0, UI::Box::Horizontal);
+					}
+					break;
+				default:
+					throw wexception(
+					   "Building statictics: Found building without a size: %s", descr.name().c_str());
+				}
+			}
+		}
+	}
+
+	for (int i = 0; i < kNoOfBuildingTabs; ++i) {
+		tabs_[i]->add(rows[i], UI::Align_Left);
+	}
+
+	set_label_font(&owned_label_);
+	set_label_font(&construction_label_);
+	set_label_font(&unproductive_label_);
+	set_editbox_font(&unproductive_percent_);
+	set_label_font(&unproductive_label2_);
+	set_label_font(&no_owned_label_);
+	set_label_font(&no_construction_label_);
+	set_label_font(&no_unproductive_label_);
+
+	unproductive_label_.set_size(unproductive_label_.get_w(), kButtonRowHeight);
+	unproductive_percent_.set_text(std::to_string(low_production_));
+	unproductive_percent_.set_max_length(4);
+	unproductive_label2_.set_size(unproductive_label2_.get_w(), kButtonRowHeight);
+	unproductive_box_.add(&unproductive_label_, UI::Align_Left);
+	unproductive_box_.add(&unproductive_percent_, UI::Align_Left);
+	unproductive_box_.add(&unproductive_label2_, UI::Align_Left);
+	unproductive_box_.set_size(
+	   unproductive_label_.get_w() + unproductive_percent_.get_w() + unproductive_label2_.get_w(),
+	   kButtonRowHeight);
+
+	navigation_buttons_[NavigationButton::PrevOwned] =
+	   new UI::Button(&navigation_panel_,
+	                  "previous_owned",
+	                  get_inner_w() - 2 * kButtonRowHeight,
+	                  kButtonRowHeight,
+	                  kButtonHeight,
+	                  kButtonHeight,
+	                  g_gr->images().get("pics/but4.png"),
+	                  g_gr->images().get("pics/scrollbar_left.png"),
+	                  _("Show previous building"),
+	                  false);
+
+	navigation_buttons_[NavigationButton::NextOwned] =
+	   new UI::Button(&navigation_panel_,
+	                  "next_owned",
+	                  get_inner_w() - kButtonRowHeight,
+	                  kButtonRowHeight,
+	                  kButtonHeight,
+	                  kButtonHeight,
+	                  g_gr->images().get("pics/but4.png"),
+	                  g_gr->images().get("pics/scrollbar_right.png"),
+	                  _("Show next building"),
+	                  false);
+
+	navigation_buttons_[NavigationButton::PrevConstruction] =
+	   new UI::Button(&navigation_panel_,
+	                  "previous_constructed",
+	                  get_inner_w() - 2 * kButtonRowHeight,
+	                  2 * kButtonRowHeight,
+	                  kButtonHeight,
+	                  kButtonHeight,
+	                  g_gr->images().get("pics/but4.png"),
+	                  g_gr->images().get("pics/scrollbar_left.png"),
+	                  _("Show previous building"),
+	                  false);
+
+	navigation_buttons_[NavigationButton::NextConstruction] =
+	   new UI::Button(&navigation_panel_,
+	                  "next_constructed",
+	                  get_inner_w() - kButtonRowHeight,
+	                  2 * kButtonRowHeight,
+	                  kButtonHeight,
+	                  kButtonHeight,
+	                  g_gr->images().get("pics/but4.png"),
+	                  g_gr->images().get("pics/scrollbar_right.png"),
+	                  _("Show next building"),
+	                  false);
+
+	navigation_buttons_[NavigationButton::PrevUnproductive] =
+	   new UI::Button(&navigation_panel_,
+	                  "previous_unproductive",
+	                  get_inner_w() - 2 * kButtonRowHeight,
+	                  3 * kButtonRowHeight,
+	                  kButtonHeight,
+	                  kButtonHeight,
+	                  g_gr->images().get("pics/but4.png"),
+	                  g_gr->images().get("pics/scrollbar_left.png"),
+	                  _("Show previous building"),
+	                  false);
+
+	navigation_buttons_[NavigationButton::NextUnproductive] =
+	   new UI::Button(&navigation_panel_,
+	                  "next_unproductive",
+	                  get_inner_w() - kButtonRowHeight,
+	                  3 * kButtonRowHeight,
+	                  kButtonHeight,
+	                  kButtonHeight,
+	                  g_gr->images().get("pics/but4.png"),
+	                  g_gr->images().get("pics/scrollbar_right.png"),
+	                  _("Show next building"),
+	                  false);
+
+	navigation_buttons_[NavigationButton::PrevOwned]->sigclicked.connect(boost::bind(
+	   &BuildingStatisticsMenu::jump_building, boost::ref(*this), JumpTarget::kOwned, true));
+	navigation_buttons_[NavigationButton::NextOwned]->sigclicked.connect(boost::bind(
+	   &BuildingStatisticsMenu::jump_building, boost::ref(*this), JumpTarget::kOwned, false));
+	navigation_buttons_[NavigationButton::PrevConstruction]->sigclicked.connect(boost::bind(
+	   &BuildingStatisticsMenu::jump_building, boost::ref(*this), JumpTarget::kConstruction, true));
+	navigation_buttons_[NavigationButton::NextConstruction]->sigclicked.connect(boost::bind(
+	   &BuildingStatisticsMenu::jump_building, boost::ref(*this), JumpTarget::kConstruction, false));
+	navigation_buttons_[NavigationButton::PrevUnproductive]->sigclicked.connect(boost::bind(
+	   &BuildingStatisticsMenu::jump_building, boost::ref(*this), JumpTarget::kUnproductive, true));
+	navigation_buttons_[NavigationButton::NextUnproductive]->sigclicked.connect(boost::bind(
+	   &BuildingStatisticsMenu::jump_building, boost::ref(*this), JumpTarget::kUnproductive, false));
+
+	unproductive_percent_.changed.connect(
+	   boost::bind(&BuildingStatisticsMenu::low_production_changed, boost::ref(*this)));
+	unproductive_percent_.ok.connect(
+	   boost::bind(&BuildingStatisticsMenu::low_production_reset_focus, boost::ref(*this)));
+	unproductive_percent_.cancel.connect(
+	   boost::bind(&BuildingStatisticsMenu::low_production_reset_focus, boost::ref(*this)));
+
+	update();
+}
+
+BuildingStatisticsMenu::~BuildingStatisticsMenu() {
+	building_buttons_.clear();
+	owned_labels_.clear();
+	productivity_labels_.clear();
+}
+
+// Adds 3 buttons per building type:
+// - Building image, steps through all buildings of the type
+// - Buildings owned, steps through constructionsites
+// - Productivity, steps though buildings with low productivity and stopped buildings
+bool BuildingStatisticsMenu::add_button(
+   BuildingIndex id, const BuildingDescr& descr, int tab_index, UI::Box& row, int* column) {
+	// Only add headquarter types that are owned by player.
+	if (!(descr.is_buildable() || descr.is_enhanced() || descr.global()) &&
+	    iplayer().get_player()->get_building_statistics(id).empty()) {
+		return false;
+	}
+
+	UI::Box* button_box = new UI::Box(&row, 0, 0, UI::Box::Vertical);
+	building_buttons_[id] = new UI::Button(button_box,
+	                                       (boost::format("building_button%s") % id).str(),
+	                                       0,
+	                                       0,
+	                                       kBuildGridCellSize,
+	                                       kBuildGridCellSize,
+	                                       g_gr->images().get("pics/but1.png"),
+	                                       &g_gr->animations()
+	                                           .get_animation(descr.get_animation("idle"))
+	                                           .representative_image_from_disk(),
+	                                       "",
+	                                       false,
+	                                       true);
+	button_box->add(building_buttons_[id], UI::Align_Left);
+
+	owned_labels_[id] =
+	   new UI::MultilineTextarea(button_box, 0, 0, kBuildGridCellSize, kLabelHeight);
+	button_box->add(owned_labels_[id], UI::Align_Left);
+
+	productivity_labels_[id] =
+	   new UI::MultilineTextarea(button_box, 0, 0, kBuildGridCellSize, kLabelHeight);
+	button_box->add(productivity_labels_[id], UI::Align_Left);
+
+	row.add(button_box, UI::Align_Left);
+
+	building_buttons_[id]->sigclicked.connect(
+	   boost::bind(&BuildingStatisticsMenu::set_current_building_type, boost::ref(*this), id));
+
+	// For dynamic window height
+	if (*column == 0) {
+		++row_counters_[tab_index];
+	}
+
+	// Check if the row is full
+	++*column;
+	if (*column == kColumns) {
+		tabs_[tab_index]->add(&row, UI::Align_Left);
+		*column = 0;
+		return true;
+	}
+	return false;
+}
+
+void BuildingStatisticsMenu::jump_building(JumpTarget target, bool reverse) {
+	bool found = true;
+	if (last_building_type_ != current_building_type_) {
+		last_building_index_ = 0;
+	}
+	last_building_type_ = current_building_type_;
+
+	const std::vector<Player::BuildingStats>& stats_vector =
+	   iplayer().get_player()->get_building_statistics(current_building_type_);
+
+	switch (target) {
+	case JumpTarget::kOwned: {
+		int32_t const curindex = last_building_index_;
+		if (reverse) {
+			while (validate_pointer(&(--last_building_index_), stats_vector.size()) != curindex) {
+				if (!stats_vector[last_building_index_].is_constructionsite) {
+					break;
+				}
+			}
+		} else {
+			while (validate_pointer(&(++last_building_index_), stats_vector.size()) != curindex) {
+				if (!stats_vector[last_building_index_].is_constructionsite) {
+					break;
+				}
+			}
+		}
+		break;
+	}
+	case JumpTarget::kConstruction: {
+		int32_t const curindex = last_building_index_;
+		if (reverse) {
+			while (validate_pointer(&(--last_building_index_), stats_vector.size()) != curindex) {
+				if (stats_vector[last_building_index_].is_constructionsite) {
+					break;
+				}
+			}
+		} else {
+			while (validate_pointer(&(++last_building_index_), stats_vector.size()) != curindex) {
+				if (stats_vector[last_building_index_].is_constructionsite) {
+					break;
+				}
+			}
+		}
+		break;
+	}
+	case JumpTarget::kUnproductive: {
+		const Map& map = iplayer().egbase().map();
+		int32_t const curindex = last_building_index_;
+		found = false;
+		if (reverse) {
+			while (validate_pointer(&(--last_building_index_), stats_vector.size()) != curindex) {
+				if (!stats_vector[last_building_index_].is_constructionsite) {
+					if (upcast(MilitarySite,
+					           militarysite,
+					           map[stats_vector[last_building_index_].pos].get_immovable())) {
+						if (militarysite->stationed_soldiers().size() <
+						    militarysite->soldier_capacity()) {
+							found = true;
+							break;
+						}
+					} else if (upcast(ProductionSite,
+					                  productionsite,
+					                  map[stats_vector[last_building_index_].pos].get_immovable())) {
+						if (productionsite->is_stopped() ||
+						    productionsite->get_statistics_percent() < low_production_) {
+							found = true;
+							break;
+						}
+					}
+				}
+			}
+		} else {
+			while (validate_pointer(&(++last_building_index_), stats_vector.size()) != curindex) {
+				if (!stats_vector[last_building_index_].is_constructionsite) {
+					if (upcast(MilitarySite,
+					           militarysite,
+					           map[stats_vector[last_building_index_].pos].get_immovable())) {
+						if (militarysite->stationed_soldiers().size() <
+						    militarysite->soldier_capacity()) {
+							found = true;
+							break;
+						}
+					} else if (upcast(ProductionSite,
+					                  productionsite,
+					                  map[stats_vector[last_building_index_].pos].get_immovable())) {
+						if (productionsite->is_stopped() ||
+						    productionsite->get_statistics_percent() < low_production_) {
+							found = true;
+							break;
+						}
+					}
+				}
+			}
+		}
+		if (!found) {  // Now look at the old
+			if (upcast(MilitarySite,
+			           militarysite,
+			           map[stats_vector[last_building_index_].pos].get_immovable())) {
+				if (militarysite->stationed_soldiers().size() < militarysite->soldier_capacity()) {
+					found = true;
+				}
+			} else if (upcast(ProductionSite,
+			                  productionsite,
+			                  map[stats_vector[last_building_index_].pos].get_immovable())) {
+				if (productionsite->is_stopped() ||
+				    productionsite->get_statistics_percent() < low_production_) {
+					found = true;
+				}
+			}
+		}
+		break;
+	}
+	default:
+		assert(false);
+		break;
+	}
+
+	if (found) {
+		validate_pointer(&last_building_index_, stats_vector.size());
+		iplayer().move_view_to(stats_vector[last_building_index_].pos);
+	}
+	low_production_reset_focus();
+	update();
+}
 
 /*
  * Update this statistic
  */
 void BuildingStatisticsMenu::think() {
-	const Widelands::Game & game = iplayer().game();
+	// Adjust height to current tab
+	int tab_height =
+	   35 + row_counters_[tab_panel_.active()] * (kBuildGridCellSize + kLabelHeight + kLabelHeight);
+	tab_panel_.set_size(kWindowWidth, tab_height);
+	set_size(get_w(), tab_height + kMargin + 4 * kButtonRowHeight + get_tborder() + get_bborder());
+	navigation_panel_.set_pos(Point(0, tab_height + kMargin));
+
+	// Update statistics
+	const Game& game = iplayer().game();
 	int32_t const gametime = game.get_gametime();
 
-	if ((gametime - m_lastupdate) > UPDATE_TIME) {
+	if ((gametime - lastupdate_) > kUpdateTime) {
 		update();
-		m_lastupdate = gametime;
+		lastupdate_ = gametime;
 	}
 }
 
 /*
- * draw()
- *
- * Draw this window
- */
-void BuildingStatisticsMenu::draw(RenderTarget & dst) {
-	UI::Window::draw(dst);
-
-	const Widelands::Player & player = iplayer().player();
-	if (m_anim)
-		dst.drawanim
-			(FLAG_POINT - Point(TRIANGLE_WIDTH / 2, TRIANGLE_HEIGHT),
-			 m_anim, 0, &player);
-}
-
-/*
  * validate if this pointer is ok
  */
-int32_t BuildingStatisticsMenu::validate_pointer
-	(int32_t * const id, int32_t const size)
-{
-	if (*id < 0)
+int32_t BuildingStatisticsMenu::validate_pointer(int32_t* const id, int32_t const size) {
+	if (*id < 0) {
 		*id = size - 1;
-	if (size <= *id)
+	}
+	if (size <= *id) {
 		*id = 0;
+	}
 
 	return *id;
 }
 
-
-void BuildingStatisticsMenu::clicked_jump(JumpTargets const id) {
-	assert(m_table.has_selection());
-	if (m_last_table_index != m_table.selection_index())
-		m_last_building_index = 0;
-	m_last_table_index = m_table.selection_index();
-	const std::vector<Widelands::Player::BuildingStats> & vec =
-		iplayer().get_player()->get_building_statistics
-			(Widelands::BuildingIndex
-				(static_cast<size_t>(m_table.get_selected())));
-	const Widelands::Map & map = iplayer().egbase().map();
-
-	bool found = true; //  we think, we always find a proper building
-
-	switch (id) {
-	case PrevOwned:
-		--m_last_building_index;
-		break;
-	case NextOwned:
-		++m_last_building_index;
-		break;
-	case PrevConstruction: {
-		int32_t const curindex = m_last_building_index;
-		while
-			(validate_pointer(&(--m_last_building_index), vec.size()) != curindex)
-			if (vec[m_last_building_index].is_constructionsite)
-				break;
-		break;
-	}
-	case NextConstruction: {
-		int32_t const curindex = m_last_building_index;
-		while
-			(validate_pointer(&(++m_last_building_index), vec.size()) != curindex)
-			if (vec[m_last_building_index].is_constructionsite)
-				break;
-		break;
-	}
-	case PrevUnproductive: {
-		int32_t const curindex = m_last_building_index;
-		found = false;
-		while (validate_pointer(&(--m_last_building_index), vec.size()) != curindex)
-			if (!vec[m_last_building_index].is_constructionsite) {
-				if
-					(upcast
-					 	(Widelands::ProductionSite,
-					 	 productionsite,
-					 	 map[vec[m_last_building_index].pos].get_immovable()))
-					if (productionsite->get_statistics_percent() < LOW_PROD) {
-						found = true;
-						break;
-					}
-			}
-		if (!found) // Now look at the old
-			if
-				(upcast
-				 	(Widelands::ProductionSite,
-				 	 productionsite,
-				 	 map[vec[m_last_building_index].pos].get_immovable()))
-				if (productionsite->get_statistics_percent() < LOW_PROD)
-					found = true;
-		break;
-	}
-	case NextUnproductive: {
-		int32_t const curindex = m_last_building_index;
-		found = false;
-		while
-			(validate_pointer(&(++m_last_building_index), vec.size()) != curindex)
-			if (!vec[m_last_building_index].is_constructionsite) {
-				if
-					(upcast
-					 	(Widelands::ProductionSite,
-					 	 productionsite,
-					 	 map[vec[m_last_building_index].pos].get_immovable()))
-					if (productionsite->get_statistics_percent() < LOW_PROD) {
-						found = true;
-						break;
-					}
-			}
-		if (!found) // Now look at the old
-			if
-				(upcast
-				 	(Widelands::ProductionSite,
-				 	 productionsite,
-				 	 map[vec[m_last_building_index].pos].get_immovable()))
-				if (productionsite->get_statistics_percent() < LOW_PROD)
-					found = true;
-		break;
-	}
-	default:
-		assert(false);
-		break;
-	}
-
-	validate_pointer(&m_last_building_index, vec.size());
-
-	if (found)
-		iplayer().move_view_to(vec[m_last_building_index].pos);
-}
-
-/*
- * The table has been selected
- */
-void BuildingStatisticsMenu::table_changed(uint32_t) {update();}
-
-/**
- * Callback to sort table based on building size.
- */
-bool BuildingStatisticsMenu::compare_building_size
-	(uint32_t const rowa, uint32_t const rowb)
-{
-	const Widelands::TribeDescr & tribe = iplayer().player().tribe();
-	Widelands::BuildingIndex a = Widelands::BuildingIndex(m_table[rowa]);
-	Widelands::BuildingIndex b = Widelands::BuildingIndex(m_table[rowb]);
-	const Widelands::BuildingDescr * descra = tribe.get_building_descr(a);
-	const Widelands::BuildingDescr * descrb = tribe.get_building_descr(b);
-
-	if (!descra || !descrb)
-		return false; // shouldn't happen, but be defensive
-
-	// mines come last
-	if (descrb->get_ismine())
-		return !descra->get_ismine();
-	else if (descra->get_ismine())
-		return false;
-
-	// smallest first
-	return descra->get_size() < descrb->get_size();
-}
-
-
-/*
- * Update table
+/*
+ * Update Buttons
  */
 void BuildingStatisticsMenu::update() {
-	m_owned   .set_text("");
-	m_in_build.set_text("");
-	m_progbar .set_state(0);
-
-	const Widelands::Player      & player = iplayer().player();
-	const Widelands::TribeDescr & tribe  = player.tribe();
-	const Widelands::Map         & map   = iplayer().game().map();
-	Widelands::BuildingIndex      const nr_buildings = tribe.get_nrbuildings();
-	for
-		(Widelands::BuildingIndex i = 0;
-		 i < nr_buildings;
-		 ++i)
-	{
-		const Widelands::BuildingDescr & building =
-			*tribe.get_building_descr(i);
-		if
-			(!(building.is_buildable()
-			 || building.is_enhanced()
-			 || building.global()))
+	const Player& player = iplayer().player();
+	const TribeDescr& tribe = player.tribe();
+	const Map& map = iplayer().game().map();
+	const BuildingIndex nr_buildings = tribe.get_nrbuildings();
+
+	owned_label_.set_visible(false);
+	no_owned_label_.set_visible(false);
+	navigation_buttons_[NavigationButton::NextOwned]->set_visible(false);
+	navigation_buttons_[NavigationButton::PrevOwned]->set_visible(false);
+	construction_label_.set_visible(false);
+	no_construction_label_.set_visible(false);
+	navigation_buttons_[NavigationButton::NextConstruction]->set_visible(false);
+	navigation_buttons_[NavigationButton::PrevConstruction]->set_visible(false);
+	unproductive_box_.set_visible(false);
+	unproductive_label_.set_visible(false);
+	unproductive_percent_.set_visible(false);
+	unproductive_label2_.set_visible(false);
+	navigation_buttons_[NavigationButton::NextUnproductive]->set_visible(false);
+	navigation_buttons_[NavigationButton::PrevUnproductive]->set_visible(false);
+
+	for (BuildingIndex id = 0; id < nr_buildings; ++id) {
+		const BuildingDescr& building = *tribe.get_building_descr(id);
+		if (building_buttons_[id] == nullptr) {
 			continue;
-
-		const std::vector<Widelands::Player::BuildingStats> & vec =
-			player.get_building_statistics(i);
-
-		//  walk all entries, add new ones if needed
-		UI::Table<uintptr_t const>::EntryRecord * te = nullptr;
-		const uint32_t table_size = m_table.size();
-		for (uint32_t l = 0; l < table_size; ++l) {
-			UI::Table<uintptr_t const>::EntryRecord & er = m_table.get_record(l);
-			if (UI::Table<uintptr_t const>::get(er) == i) {
-				te = &er;
-				break;
-			}
-		}
-
-		//  If not in list, add new one, as long as this building is enabled.
-		if (!te) {
-			if (! iplayer().player().is_building_type_allowed(i))
-				continue;
-			te = &m_table.add(i);
-		}
-
-		uint32_t nr_owned   = 0;
-		uint32_t nr_build   = 0;
+		}
+		assert(productivity_labels_[id] != nullptr);
+		assert(owned_labels_[id] != nullptr);
+
+		const std::vector<Player::BuildingStats>& stats_vector = player.get_building_statistics(id);
+
+		uint32_t nr_owned = 0;
+		uint32_t nr_build = 0;
 		uint32_t total_prod = 0;
-		upcast(Widelands::ProductionSiteDescr const, productionsite, &building);
-		for (uint32_t l = 0; l < vec.size(); ++l) {
-			if (vec[l].is_constructionsite)
+		uint32_t total_soldier_capacity = 0;
+		uint32_t total_stationed_soldiers = 0;
+		uint32_t nr_unproductive = 0;
+
+		for (uint32_t l = 0; l < stats_vector.size(); ++l) {
+			if (stats_vector[l].is_constructionsite)
 				++nr_build;
 			else {
 				++nr_owned;
-				if (productionsite)
-					total_prod +=
-						dynamic_cast<Widelands::ProductionSite&>
-							(*map[vec[l].pos].get_immovable())
-						.get_statistics_percent();
-			}
-		}
-
-		const bool is_selected = //  Is this entry selected?
-			m_table.has_selection() && m_table.get_selected() == i;
-
-		if (is_selected) {
-			m_anim = building.get_ui_anim();
-			m_btn[PrevOwned]       ->set_enabled(nr_owned);
-			m_btn[NextOwned]       ->set_enabled(nr_owned);
-			m_btn[PrevConstruction]->set_enabled(nr_build);
-			m_btn[NextConstruction]->set_enabled(nr_build);
-		}
-
-		//  add new Table Entry
-		te->set_picture
-			(Columns::Name, building.get_icon(), building.descname());
-
-		{
-			char const * pic = "pics/novalue.png";
-			if (building.get_ismine()) {
-				pic = "pics/menu_tab_buildmine.png";
-			} else if (building.get_isport()) {
-				pic = "pics/menu_tab_buildport.png";
-			}
-			else switch (building.get_size()) {
-			case Widelands::BaseImmovable::SMALL:
-				pic = "pics/menu_tab_buildsmall.png";
-				break;
-			case Widelands::BaseImmovable::MEDIUM:
-				pic = "pics/menu_tab_buildmedium.png";
-				break;
-			case Widelands::BaseImmovable::BIG:
-				pic = "pics/menu_tab_buildbig.png";
-				break;
-			default:
-				assert(false);
-				break;
-			}
-			te->set_picture(Columns::Size, g_gr->images().get(pic));
-		}
-
-		if (productionsite && nr_owned) {
-			uint32_t const percent =
-				static_cast<uint32_t>
-					(static_cast<float>(total_prod) / static_cast<float>(nr_owned));
-			te->set_string(Columns::Prod, (boost::format("%3u") % percent).str()); //  space-pad for sort
-			if (is_selected)  {
-				m_progbar.set_state(percent);
-				m_btn[PrevUnproductive]->set_enabled(true);
-				m_btn[NextUnproductive]->set_enabled(true);
-			}
-		} else {
-			te->set_string(Columns::Prod,  " ");
-			if (is_selected) {
-				m_btn[PrevUnproductive]->set_enabled(false);
-				m_btn[NextUnproductive]->set_enabled(false);
-			}
-		}
-
-		//  number of these buildings
-		const std::string owned_string =
-		   (boost::format("%3u") % nr_owned).str();  //  space-pad for sort
-		te->set_string(Columns::Owned, owned_string);
-		if (is_selected) {
-			m_owned.set_text(owned_string);
-		}
-
-		//  number of these buildings currently being built
-		const std::string build_string =
-		   (boost::format("%3u") % nr_build).str();  //  space-pad for sort
-		te->set_string(Columns::Build, build_string);
-		if (is_selected) {
-			m_in_build.set_text(build_string);
-		}
-	}
-
-	//  disable all buttons, if nothing to select
-	if (!m_table.has_selection()) {
-		m_btn[PrevOwned]       ->set_enabled(false);
-		m_btn[NextOwned]       ->set_enabled(false);
-		m_btn[PrevConstruction]->set_enabled(false);
-		m_btn[NextConstruction]->set_enabled(false);
-		m_btn[PrevUnproductive]->set_enabled(false);
-		m_btn[NextUnproductive]->set_enabled(false);
-	}
+				BaseImmovable& immovable = *map[stats_vector[l].pos].get_immovable();
+				if (building.type() == MapObjectType::PRODUCTIONSITE ||
+				    building.type() == MapObjectType::TRAININGSITE) {
+					ProductionSite& productionsite = dynamic_cast<ProductionSite&>(immovable);
+					int percent = productionsite.get_statistics_percent();
+					total_prod += percent;
+
+					if (percent < low_production_ || productionsite.is_stopped()) {
+						++nr_unproductive;
+					}
+				} else if (building.type() == MapObjectType::MILITARYSITE) {
+					MilitarySite& militarysite = dynamic_cast<MilitarySite&>(immovable);
+					total_soldier_capacity += militarysite.soldier_capacity();
+					total_stationed_soldiers += militarysite.stationed_soldiers().size();
+					if (total_stationed_soldiers < total_soldier_capacity) {
+						++nr_unproductive;
+					}
+				}
+			}
+		}
+
+		if (building.type() == MapObjectType::PRODUCTIONSITE ||
+		    building.type() == MapObjectType::TRAININGSITE) {
+			if (nr_owned) {
+				int const percent =
+				   static_cast<int>(static_cast<float>(total_prod) / static_cast<float>(nr_owned));
+
+				RGBColor color;
+				if (percent < low_production_) {
+					color = UI_FONT_CLR_BAD;
+				} else if (percent < ((low_production_ < 50) ?
+				                         2 * low_production_ :
+				                         low_production_ + ((100 - low_production_) / 2))) {
+					color = UI_FONT_CLR_OK;
+				} else {
+					color = UI_FONT_CLR_GOOD;
+				}
+				const std::string perc_str = (boost::format("%i%%") % percent).str();
+				set_labeltext_autosize(productivity_labels_[id], perc_str, color);
+			}
+			if (has_selection_ && id == current_building_type_) {
+				no_unproductive_label_.set_text(nr_unproductive > 0 ? std::to_string(nr_unproductive) :
+				                                                      "");
+				navigation_buttons_[NavigationButton::NextUnproductive]->set_enabled(nr_unproductive >
+				                                                                     0);
+				navigation_buttons_[NavigationButton::PrevUnproductive]->set_enabled(nr_unproductive >
+				                                                                     0);
+				navigation_buttons_[NavigationButton::NextUnproductive]->set_visible(true);
+				navigation_buttons_[NavigationButton::PrevUnproductive]->set_visible(true);
+				unproductive_label_.set_text(_("Low Production: "));
+				unproductive_box_.set_visible(true);
+				unproductive_label_.set_visible(true);
+				unproductive_percent_.set_visible(true);
+				unproductive_label2_.set_visible(true);
+			}
+		} else if (building.type() == MapObjectType::MILITARYSITE) {
+			if (nr_owned) {
+				RGBColor color;
+				if (total_stationed_soldiers < total_soldier_capacity / 2) {
+					color = UI_FONT_CLR_BAD;
+				} else if (total_stationed_soldiers < total_soldier_capacity) {
+					color = UI_FONT_CLR_OK;
+				} else {
+					color = UI_FONT_CLR_GOOD;
+				}
+				const std::string perc_str = (boost::format(_("%1%/%2%")) % total_stationed_soldiers %
+				                              total_soldier_capacity).str();
+				set_labeltext_autosize(productivity_labels_[id], perc_str, color);
+			}
+			if (has_selection_ && id == current_building_type_) {
+				no_unproductive_label_.set_text(nr_unproductive > 0 ? std::to_string(nr_unproductive) :
+				                                                      "");
+				navigation_buttons_[NavigationButton::NextUnproductive]->set_enabled(
+				   total_soldier_capacity > total_stationed_soldiers);
+				navigation_buttons_[NavigationButton::PrevUnproductive]->set_enabled(
+				   total_soldier_capacity > total_stationed_soldiers);
+				navigation_buttons_[NavigationButton::NextUnproductive]->set_visible(true);
+				navigation_buttons_[NavigationButton::PrevUnproductive]->set_visible(true);
+				/** TRANSLATORS Label for number of buildings that are waiting for soldiers */
+				unproductive_label_.set_text(_("Lacking Soldiers:"));
+				unproductive_box_.set_visible(true);
+				unproductive_label_.set_visible(true);
+			}
+		} else {
+			productivity_labels_[id]->set_text(" ");
+			productivity_labels_[id]->set_visible(false);
+		}
+
+		std::string owned_text = "";
+		if (!building.global() && (building.is_buildable() || building.is_enhanced())) {
+			/** TRANSLATORS Buildings: owned / under construction */
+			owned_text = (boost::format(_("%1%/%2%")) % nr_owned % nr_build).str();
+		} else {
+			owned_text = (boost::format(_("%1%/%2%")) % nr_owned % "–").str();
+		}
+		set_labeltext_autosize(owned_labels_[id], owned_text, UI_FONT_CLR_FG);
+		owned_labels_[id]->set_visible((nr_owned + nr_build) > 0);
+
+		building_buttons_[id]->set_enabled((nr_owned + nr_build) > 0);
+		if (has_selection_ && id == current_building_type_) {
+			no_owned_label_.set_text(nr_owned > 0 ? std::to_string(nr_owned) : "");
+			navigation_buttons_[NavigationButton::NextOwned]->set_enabled(nr_owned > 0);
+			navigation_buttons_[NavigationButton::PrevOwned]->set_enabled(nr_owned > 0);
+			owned_label_.set_visible(true);
+			no_owned_label_.set_visible(true);
+			navigation_buttons_[NavigationButton::NextOwned]->set_visible(true);
+			navigation_buttons_[NavigationButton::PrevOwned]->set_visible(true);
+			if (!building.global() && building.is_buildable()) {
+				no_construction_label_.set_text(nr_build > 0 ? std::to_string(nr_build) : "");
+				navigation_buttons_[NavigationButton::NextConstruction]->set_enabled(nr_build > 0);
+				navigation_buttons_[NavigationButton::PrevConstruction]->set_enabled(nr_build > 0);
+				construction_label_.set_visible(true);
+				no_construction_label_.set_visible(true);
+				navigation_buttons_[NavigationButton::NextConstruction]->set_visible(true);
+				navigation_buttons_[NavigationButton::PrevConstruction]->set_visible(true);
+			}
+		}
+		building_buttons_[id]->set_tooltip(building.descname());
+	}
+}
+
+void BuildingStatisticsMenu::set_labeltext_autosize(UI::MultilineTextarea* textarea,
+                                                    const std::string& text,
+                                                    const RGBColor& color) {
+	const std::string formatted_str =
+	   (boost::format("<rt><p font-face=condensed font-weight=bold "
+	                  "font-size=%i font-color=%s>%s</p></rt>") %
+	    (text.length() > 5 ? kLabelFontSize - floor(text.length() / 2) : kLabelFontSize) %
+	    color.hex_value() % text).str();
+	textarea->set_text(formatted_str);
+	textarea->set_visible(true);
+}
+
+void BuildingStatisticsMenu::set_current_building_type(BuildingIndex id) {
+	assert(building_buttons_[id] != nullptr);
+	current_building_type_ = id;
+	for (BuildingIndex i = 0; i < iplayer().player().tribe().get_nrbuildings(); ++i) {
+		if (building_buttons_[i] != nullptr) {
+			building_buttons_[i]->set_flat(true);
+		}
+	}
+	building_buttons_[current_building_type_]->set_flat(false);
+	building_buttons_[current_building_type_]->set_perm_pressed(true);
+	building_name_.set_text(iplayer().player().tribe().get_building_descr(id)->descname());
+	low_production_reset_focus();
+	has_selection_ = true;
+	update();
+}
+
+void BuildingStatisticsMenu::low_production_changed() {
+	const std::string cutoff = unproductive_percent_.text();
+	int number = std::atoi(cutoff.c_str());
+
+	// Make sure that the user specified a correct number
+	if (std::to_string(number) == cutoff && 0 <= number && number <= 100) {
+		low_production_ = number;
+		update();
+	}
+}
+
+void BuildingStatisticsMenu::low_production_reset_focus() {
+	unproductive_percent_.set_can_focus(false);
+	unproductive_percent_.set_can_focus(true);
 }

=== modified file 'src/wui/building_statistics_menu.h'
--- src/wui/building_statistics_menu.h	2014-09-10 14:48:40 +0000
+++ src/wui/building_statistics_menu.h	2015-06-03 10:36:31 +0000
@@ -20,56 +20,122 @@
 #ifndef WL_WUI_BUILDING_STATISTICS_MENU_H
 #define WL_WUI_BUILDING_STATISTICS_MENU_H
 
-#include "ui_basic/progressbar.h"
-#include "ui_basic/table.h"
+#include <vector>
+
+#include "graphic/color.h"
+#include "logic/building.h"
+#include "logic/widelands.h"
+#include "ui_basic/box.h"
+#include "ui_basic/button.h"
+#include "ui_basic/editbox.h"
+#include "ui_basic/tabpanel.h"
+#include "ui_basic/multilinetextarea.h"
 #include "ui_basic/textarea.h"
 #include "ui_basic/unique_window.h"
-
-namespace Widelands {struct BuildingDescr;}
-class InteractivePlayer;
-namespace UI {
-struct Button;
-struct ProgressBar;
-struct Textarea;
-}
-
+#include "wui/interactive_player.h"
+
+using namespace Widelands;
+
+namespace {
+
+constexpr int kNoOfBuildingTabs = 5;
+
+}  // namespace
+
+/// This window shows statistics for all the buildings that the player owns.
+/// It also allows to jump through buildings on the map.
 struct BuildingStatisticsMenu : public UI::UniqueWindow {
-	BuildingStatisticsMenu
-		(InteractivePlayer &, UI::UniqueWindow::Registry &);
+	BuildingStatisticsMenu(InteractivePlayer&, UI::UniqueWindow::Registry&);
+	~BuildingStatisticsMenu();
 
 	void think() override;
-	void draw(RenderTarget &) override;
 	void update();
 
 private:
-	bool compare_building_size(uint32_t rowa, uint32_t rowb);
-
-	InteractivePlayer & iplayer() const;
-	enum JumpTargets {
-		PrevOwned,        NextOwned,
-		PrevConstruction, NextConstruction,
-		PrevUnproductive, NextUnproductive
+	/// Array indices for the tabs
+	enum BuildingTab {Small, Medium, Big, Mines, Ports};
+
+	/// Which building state to jump through
+	enum class JumpTarget {kOwned, kConstruction, kUnproductive};
+
+	/// Array indices for the navigation buttons
+	enum NavigationButton {
+		PrevOwned,
+		NextOwned,
+		PrevConstruction,
+		NextConstruction,
+		PrevUnproductive,
+		NextUnproductive
 	};
 
-	UI::Table<uintptr_t const> m_table;
-	UI::ProgressBar          m_progbar;
-	UI::Textarea              m_total_productivity_label;
-	UI::Textarea              m_owned_label;
-	UI::Textarea              m_owned;
-	UI::Textarea              m_in_build_label;
-	UI::Textarea              m_in_build;
-	UI::Textarea              m_unproductive_label;
-	uint32_t                  m_anim;
-	uint32_t                  m_lastupdate;
-	uint32_t                  m_end_of_table_y;
-	UI::Button * m_btn[6];
-	int32_t                   m_last_building_index;
-	uint32_t                  m_last_table_index;
-
-	void clicked_help();
-	void clicked_jump(JumpTargets);
-	void table_changed(uint32_t);
-	int32_t validate_pointer(int32_t *, int32_t);
+	/// Adds a button for the building type belonging to the id and descr to the tab.
+	/// Returns true when a new row needs to be created.
+	bool add_button(
+	   BuildingIndex id, const BuildingDescr& descr, int tab_index, UI::Box& row, int* column);
+
+	/// Jumps to the next / previous appropriate building
+	void jump_building(JumpTarget target, bool reverse);
+
+	/// Sets the label for id type to text in the chosen color with dynamic font size
+	void set_labeltext_autosize(UI::MultilineTextarea* textarea,
+	                            const std::string& text,
+	                            const RGBColor& color);
+
+	/// Sets the current building type for the bottom navigation
+	void set_current_building_type(BuildingIndex id);
+
+	/// Change the percentage where buildings are deemed unproductive
+	void low_production_changed();
+	/// Unfocuses the editbox to free the keyboard input
+	void low_production_reset_focus();
+
+	/// Helper function for jump_building to go round robin
+	int32_t validate_pointer(int32_t*, int32_t);
+
+	InteractivePlayer& iplayer() const;
+
+	/// UI tabs
+	UI::TabPanel tab_panel_;
+	UI::Box* tabs_[kNoOfBuildingTabs];
+	int row_counters_[kNoOfBuildingTabs];
+
+	/// Button with building icon
+	std::vector<UI::Button*> building_buttons_;
+	/// Labels with owned / under construction buildings
+	std::vector<UI::MultilineTextarea*> owned_labels_;
+	/// Labels with buildings' productivity
+	// TODO(GunChleoc): These need to be multiline, so we can give them a color.
+	// Turn into normal textareas in fh1 branch.
+	std::vector<UI::MultilineTextarea*> productivity_labels_;
+
+	/// The buttons for stepping through buildings
+	UI::Panel navigation_panel_;
+	UI::Button* navigation_buttons_[6];
+	UI::Textarea building_name_;
+	UI::Textarea owned_label_;
+	UI::Textarea construction_label_;
+	UI::Box unproductive_box_;
+	UI::Textarea unproductive_label_;
+	UI::EditBox unproductive_percent_;
+	UI::Textarea unproductive_label2_;
+	UI::Textarea no_owned_label_;
+	UI::Textarea no_construction_label_;
+	UI::Textarea no_unproductive_label_;
+
+	/// The building type we are currently navigating
+	BuildingIndex current_building_type_;
+	/// The last building that was jumped to
+	int32_t last_building_index_;
+	/// The type of last building that was jumped to
+	BuildingIndex last_building_type_;
+	/// The last time the information in this Panel got updated
+	uint32_t lastupdate_;
+
+	/// At which percent to deem buildings as unproductive
+	int low_production_;
+
+	/// Whether a building has been selected
+	bool has_selection_;
 };
 
 #endif  // end of include guard: WL_WUI_BUILDING_STATISTICS_MENU_H

=== modified file 'src/wui/interactive_player.cc'
--- src/wui/interactive_player.cc	2015-05-10 10:59:28 +0000
+++ src/wui/interactive_player.cc	2015-06-03 10:36:31 +0000
@@ -336,6 +336,14 @@
 			set_display_flag(dfShowCensus, !get_display_flag(dfShowCensus));
 			return true;
 
+		case SDLK_b:
+			if (m_mainm_windows.building_stats.window == nullptr) {
+				new BuildingStatisticsMenu(*this, m_mainm_windows.building_stats);
+			} else {
+				m_mainm_windows.building_stats.toggle();
+			}
+			return true;
+
 		case SDLK_s:
 			if (code.mod & (KMOD_LCTRL | KMOD_RCTRL))
 				new GameMainMenuSaveGame(*this, m_mainm_windows.savegame);

=== modified file 'txts/README.lua'
--- txts/README.lua	2014-12-28 16:45:37 +0000
+++ txts/README.lua	2015-06-03 10:36:31 +0000
@@ -67,6 +67,7 @@
 .. _"S: toggles statistics" .. "<br>"
 .. _"I: toggles stock inventory" .. "<br>"
 .. _"O: toggles objectives" .. "<br>"
+.. _"B: toggles building statistics" .. "<br>"
 .. _"F: toggles fullscreen (if supported by the OS)" .. "<br>"
 .. _"Home: centers main mapview on starting location" .. "<br>"
 .. _"(CTRL+) 0-9: Remember and go to previously remembered locations" .. "<br>"


Follow ups