← Back to team overview

widelands-dev team mailing list archive

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

 

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

Commit message:
Implemented a textual dropdown menu. It is used in:
- Options: Screen resolution
- Options: Language. Got rid of the extra Language tab and moved it to the Interface tab.
- Launch Single Player Game: Win Condition

Requested reviews:
  Klaus Halfmann (klaus-halfmann): compile, test
Related bugs:
  Bug #536489 in widelands: "Dropdown button"
  https://bugs.launchpad.net/widelands/+bug/536489

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/bug-536489-dropdown/+merge/306303

My first branch for Build 20 ;)

Implemented a textual dropdown menu. This is already quite a bit of code, so I'll do the win condition for multiplayer in a separate branch. We will also want pictorial dropdowns.
-- 
Your team Widelands Developers is subscribed to branch lp:~widelands-dev/widelands/bug-536489-dropdown.
=== modified file 'src/ui_basic/CMakeLists.txt'
--- src/ui_basic/CMakeLists.txt	2016-04-02 16:45:53 +0000
+++ src/ui_basic/CMakeLists.txt	2016-09-27 08:44:17 +0000
@@ -6,6 +6,8 @@
     button.h
     checkbox.cc
     checkbox.h
+    dropdown.cc
+    dropdown.h
     editbox.cc
     editbox.h
     fileview_panel.cc

=== modified file 'src/ui_basic/button.cc'
--- src/ui_basic/button.cc	2016-08-04 15:49:05 +0000
+++ src/ui_basic/button.cc	2016-09-27 08:44:17 +0000
@@ -68,6 +68,7 @@
 		set_size(w, new_height);
 	}
 	set_thinks(false);
+	set_can_focus(true);
 }
 
 Button::Button  //  for pictorial buttons
@@ -97,6 +98,7 @@
      pic_custom_(fg_pic),
      clr_down_(229, 161, 2) {
 	set_thinks(false);
+	set_can_focus(true);
 }
 
 Button::~Button() {
@@ -133,6 +135,8 @@
 	if (enabled_ == on)
 		return;
 
+	set_can_focus(on);
+
 	// disabled buttons should look different...
 	if (on)
 		enabled_ = true;
@@ -306,6 +310,7 @@
 		return false;
 
 	if (enabled_) {
+		focus();
 		grab_mouse(true);
 		pressed_ = true;
 		if (repeating_) {

=== modified file 'src/ui_basic/checkbox.cc'
--- src/ui_basic/checkbox.cc	2016-08-04 15:49:05 +0000
+++ src/ui_basic/checkbox.cc	2016-09-27 08:44:17 +0000
@@ -43,7 +43,7 @@
 	uint16_t h = pic->height();
 	set_desired_size(w, h);
 	set_size(w, h);
-
+	set_can_focus(true);
 	set_flags(Has_Custom_Picture, true);
 	pic_graphics_ = pic;
 }
@@ -79,6 +79,7 @@
  * Args: enabled  true if the checkbox should be enabled, false otherwise
  */
 void Statebox::set_enabled(bool const enabled) {
+	set_can_focus(enabled);
 	if (((flags_ & Is_Enabled) > 1) && enabled)
 		return;
 
@@ -157,6 +158,7 @@
  */
 bool Statebox::handle_mousepress(const uint8_t btn, int32_t, int32_t) {
 	if (btn == SDL_BUTTON_LEFT && (flags_ & Is_Enabled)) {
+		focus();
 		clicked();
 		return true;
 	}

=== added file 'src/ui_basic/dropdown.cc'
--- src/ui_basic/dropdown.cc	1970-01-01 00:00:00 +0000
+++ src/ui_basic/dropdown.cc	2016-09-27 08:44:17 +0000
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2016 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#include "ui_basic/dropdown.h"
+
+#include <algorithm>
+
+#include <boost/format.hpp>
+
+#include "base/i18n.h"
+#include "graphic/align.h"
+#include "graphic/font_handler1.h"
+#include "graphic/graphic.h"
+#include "graphic/image.h"
+
+namespace UI {
+
+BaseDropdown::BaseDropdown(UI::Panel* parent,
+                           int32_t x,
+                           int32_t y,
+                           uint32_t w,
+                           uint32_t h,
+                           const std::string& label,
+                           bool show_tick)
+   : UI::Panel(
+        parent,
+        x,
+        y,
+        w,
+        std::max(24,
+                 UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))
+                       ->height() +
+                    2)),  // Height only to fit the button, so we can use this in Box layout.
+     max_list_height_(h - 2 * get_h()),
+     mouse_tolerance_(50),
+     button_box_(this, 0, 0, UI::Box::Horizontal, w, h),
+     push_button_(&button_box_,
+                  "dropdown_select",
+                  0,
+                  0,
+                  24,
+                  get_h(),
+                  g_gr->images().get("images/ui_basic/but3.png"),
+                  g_gr->images().get("images/ui_basic/scrollbar_down.png"),
+                  pgettext("dropdown", "Select Item")),
+     display_button_(&button_box_,
+                     "dropdown_label",
+                     0,
+                     0,
+                     w - 24,
+                     get_h(),
+                     g_gr->images().get("images/ui_basic/but1.png"),
+                     label,
+                     "",
+                     true,
+                     false),
+     list_(parent,
+           x,
+           y + get_h(),
+           w,
+           0,
+           show_tick),  // Hook into parent so we can drop down outside the panel
+     label_(label) {
+	list_.set_visible(false);
+	list_.set_background(g_gr->images().get("images/ui_basic/but1.png"));
+	display_button_.set_perm_pressed(true);
+	button_box_.add(&display_button_, UI::Align::kLeft);
+	button_box_.add(&push_button_, UI::Align::kLeft);
+	button_box_.set_size(w, get_h());
+
+	display_button_.sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
+	push_button_.sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
+	list_.selected.connect(boost::bind(&BaseDropdown::set_value, this));
+	list_.clicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
+}
+
+void BaseDropdown::add(const std::string& name,
+                       const uint32_t value,
+                       const Image* pic,
+                       const bool select_this,
+                       const std::string& tooltip_text) {
+	list_.set_size(
+	   list_.get_w(), std::min(list_.get_h() + list_.get_lineheight(), max_list_height_));
+	list_.add(name, value, pic, select_this, tooltip_text);
+}
+
+bool BaseDropdown::has_selection() const {
+	return list_.has_selection();
+}
+
+uint32_t BaseDropdown::get_selected() const {
+	return list_.get_selected();
+}
+
+void BaseDropdown::set_label(const std::string& text) {
+	label_ = text;
+	display_button_.set_title(label_);
+}
+
+void BaseDropdown::set_tooltip(const std::string& text) {
+	tooltip_ = text;
+	display_button_.set_tooltip(tooltip_);
+}
+
+void BaseDropdown::set_enabled(bool on) {
+	set_can_focus(on);
+	push_button_.set_enabled(on);
+	push_button_.set_tooltip(on ? pgettext("dropdown", "Select Item") : "");
+	display_button_.set_enabled(on);
+	list_.set_visible(false);
+}
+
+void BaseDropdown::set_pos(Point point) {
+	UI::Panel::set_pos(point);
+	list_.set_pos(Point(point.x, point.y + get_h()));
+}
+
+void BaseDropdown::clear() {
+	list_.clear();
+	list_.set_size(list_.get_w(), 0);
+	set_layout_toplevel(false);
+}
+
+void BaseDropdown::think() {
+	if (list_.is_visible()) {
+		// Autocollapse with a bit of tolerance for the mouse movement to make it less fiddly.
+		if (!has_focus() || (get_mouse_position().x + mouse_tolerance_) < 0 ||
+		    get_mouse_position().x > (get_w() + mouse_tolerance_) ||
+		    (get_mouse_position().y + mouse_tolerance_ / 2) < 0 ||
+		    get_mouse_position().y > (get_h() + list_.get_h() + mouse_tolerance_)) {
+			toggle_list();
+		}
+	}
+}
+
+uint32_t BaseDropdown::size() const {
+	return list_.size();
+}
+
+void BaseDropdown::set_value() {
+	const std::string name = list_.has_selection() ? list_.get_selected_name() :
+	                                                 /** TRANSLATORS: Selection in Dropdown menus. */
+	                            pgettext("dropdown", "Not Selected");
+
+	if (label_.empty()) {
+		display_button_.set_title(name);
+	} else {
+		/** TRANSLATORS: Label: Value. */
+		display_button_.set_title((boost::format(_("%1%: %2%")) % label_ % (name)).str());
+	}
+	display_button_.set_tooltip(list_.has_selection() ? list_.get_selected_tooltip() : tooltip_);
+	selected();
+}
+
+void BaseDropdown::toggle_list() {
+	list_.set_visible(!list_.is_visible());
+	if (list_.is_visible()) {
+		list_.move_to_top();
+		focus();
+	}
+	// Make sure that the list covers and deactivates the elements below it
+	set_layout_toplevel(list_.is_visible());
+}
+
+bool BaseDropdown::handle_key(bool down, SDL_Keysym code) {
+	if (down) {
+		switch (code.sym) {
+		case SDLK_ESCAPE:
+		case SDLK_KP_ENTER:
+		case SDLK_RETURN:
+			if (list_.is_visible()) {
+				toggle_list();
+				return true;
+			}
+			break;
+		case SDLK_DOWN:
+			if (!list_.is_visible()) {
+				toggle_list();
+				return true;
+			}
+			break;
+		default:
+			break;  // not handled
+		}
+	}
+	if (list_.is_visible()) {
+		return list_.handle_key(down, code);
+	}
+	return false;
+}
+
+}  // namespace UI

=== added file 'src/ui_basic/dropdown.h'
--- src/ui_basic/dropdown.h	1970-01-01 00:00:00 +0000
+++ src/ui_basic/dropdown.h	2016-09-27 08:44:17 +0000
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifndef WL_UI_BASIC_DROPDOWN_H
+#define WL_UI_BASIC_DROPDOWN_H
+
+#include <deque>
+
+#include <boost/signals2.hpp>
+
+#include "ui_basic/box.h"
+#include "ui_basic/button.h"
+#include "ui_basic/listselect.h"
+#include "ui_basic/panel.h"
+
+namespace UI {
+
+/// Implementation for a dropdown menu that lets the user select a value.
+class BaseDropdown : public Panel {
+protected:
+	/// \param parent     the parent panel
+	/// \param x          the x-position within 'parent'
+	/// \param y          the y-position within 'parent'
+	/// \param w          the dropdown's width
+	/// \param h          the maximum height for the dropdown list
+	/// \param label      a label to prefix to the selected entry on the display button.
+	/// \param show_tick  if 'true', the dropdown list will show a tick for the currently selected
+	///                   entry.
+	BaseDropdown(Panel* parent,
+	             int32_t x,
+	             int32_t y,
+	             uint32_t w,
+	             uint32_t h,
+	             const std::string& label,
+	             bool show_tick = true);
+	~BaseDropdown() {
+		clear();
+	}
+
+public:
+	boost::signals2::signal<void()> selected;
+
+	/// \return true if an element has been selected from the list
+	bool has_selection() const;
+
+	/// Sets a label that will be prefixed to the currently selected element's name
+	/// and displayed on the display button.
+	void set_label(const std::string& text);
+	/// Sets the tooltip for the display button.
+	void set_tooltip(const std::string& text);
+	/// Enables/disables the dropdown selection.
+	void set_enabled(bool on);
+	/// Move the dropdown. The dropdown's position is relative to the parent.
+	void set_pos(Point point) override;
+
+	/// The number of elements listed in the dropdown.
+	uint32_t size() const;
+
+	/// Handle keypresses
+	bool handle_key(bool down, SDL_Keysym code) override;
+
+protected:
+	/// Add an element to the list
+	/// \param name         the display name of the entry
+	/// \param value        the index of the entry
+	/// \param pic          an image to illustrate the entry
+	/// \param select_this  whether this element should be selected
+	/// \param tooltip_text a tooltip for this entry
+	void add(const std::string& name,
+	         uint32_t value,
+	         const Image* pic = nullptr,
+	         const bool select_this = false,
+	         const std::string& tooltip_text = std::string());
+
+	/// \return the index of the selected element
+	uint32_t get_selected() const;
+
+	/// Removes all elements from the list.
+	void clear();
+
+	/// Automatically collapses the list if the mouse gets too far away from the dropdown, or if it
+	/// loses focus.
+	void think() override;
+
+private:
+	/// Updates the title and tooltip of the display button and triggers a 'selected' signal.
+	void set_value();
+	/// Toggles the dropdown list on and off.
+	void toggle_list();
+
+	uint32_t max_list_height_;
+	const int mouse_tolerance_;  // Allow mouse outside the panel a bit before autocollapse
+	UI::Box button_box_;
+	UI::Button push_button_;
+	UI::Button display_button_;
+	UI::Listselect<uintptr_t> list_;
+	std::string label_;
+	std::string tooltip_;
+};
+
+/// A dropdown menu that lets the user select a value of the datatype 'Entry'.
+template <typename Entry> class Dropdown : public BaseDropdown {
+public:
+	/// \param parent     the parent panel
+	/// \param x          the x-position within 'parent'
+	/// \param y          the y-position within 'parent'
+	/// \param w          the dropdown's width
+	/// \param h          the maximum height for the dropdown list
+	/// \param label      a label to prefix to the selected entry on the display button.
+	/// \param show_tick  if 'true', the dropdown list will show a tick for the currently selected
+	///                   entry.
+	Dropdown(Panel* parent,
+	         int32_t x,
+	         int32_t y,
+	         uint32_t w,
+	         uint32_t h,
+	         const std::string& label,
+	         bool show_tick = true)
+	   : BaseDropdown(parent, x, y, w, h, label, show_tick) {
+	}
+	~Dropdown() {
+		clear();
+	}
+
+	/// Add an element to the list
+	/// \param name         the display name of the entry
+	/// \param value        the value for the entry
+	/// \param pic          an image to illustrate the entry
+	/// \param select_this  whether this element should be selected
+	/// \param tooltip_text a tooltip for this entry
+	void add(const std::string& name,
+	         Entry value,
+	         const Image* pic = nullptr,
+	         const bool select_this = false,
+	         const std::string& tooltip_text = std::string()) {
+		entry_cache_.push_back(new Entry(value));
+		BaseDropdown::add(name, size(), pic, select_this, tooltip_text);
+	}
+
+	/// \return the selected element
+	const Entry& get_selected() const {
+		return *entry_cache_[BaseDropdown::get_selected()];
+	}
+
+	/// Removes all elements from the list.
+	void clear() {
+		BaseDropdown::clear();
+		for (Entry* entry : entry_cache_) {
+			delete entry;
+		}
+		entry_cache_.clear();
+	}
+
+private:
+	// Contains the actual elements. The BaseDropdown registers the indices only.
+	std::deque<Entry*> entry_cache_;
+};
+
+}  // namespace UI
+
+#endif  // end of include guard: WL_UI_BASIC_DROPDOWN_H

=== modified file 'src/ui_basic/listselect.cc'
--- src/ui_basic/listselect.cc	2016-08-04 15:49:05 +0000
+++ src/ui_basic/listselect.cc	2016-09-27 08:44:17 +0000
@@ -60,7 +60,8 @@
      selection_(no_selection_index()),
      last_click_time_(-10000),
      last_selection_(no_selection_index()),
-     show_check_(show_check) {
+     show_check_(show_check),
+     background_(nullptr) {
 	set_thinks(false);
 
 	scrollbar_.moved.connect(boost::bind(&BaseListselect::set_scrollpos, this, _1));
@@ -294,6 +295,28 @@
 	remove(selection_);
 }
 
+/**
+ * \return The name of the currently selected entry.
+ * \throw  NoSelection() if no entry has been selected
+ */
+const std::string& BaseListselect::get_selected_name() const {
+	if (selection_ == no_selection_index())
+		throw NoSelection();
+
+	return entry_records_[selection_]->name;
+}
+
+/**
+ * \return The tooltip for the currently selected entry.
+ * \throw  NoSelection() if no entry has been selected
+ */
+const std::string& BaseListselect::get_selected_tooltip() const {
+	if (selection_ == no_selection_index())
+		throw NoSelection();
+
+	return entry_records_[selection_]->tooltip;
+}
+
 uint32_t BaseListselect::get_lineheight() const {
 	return lineheight_ + kMargin;
 }
@@ -302,6 +325,12 @@
 	return scrollbar_.is_enabled() ? get_w() - scrollbar_.get_w() : get_w();
 }
 
+void BaseListselect::layout() {
+	scrollbar_.set_size(scrollbar_.get_w(), get_h());
+	scrollbar_.set_pagesize(get_h() - 2 * get_lineheight());
+	scrollbar_.set_steps(entry_records_.size() * get_lineheight() - get_h());
+}
+
 /**
 Redraw the listselect box
 */
@@ -311,6 +340,10 @@
 	uint32_t idx = scrollpos_ / lineheight;
 	int32_t y = 1 + idx * lineheight - scrollpos_;
 
+	if (background_ != nullptr) {
+		dst.tile(Rect(Point(0, 0), get_w(), get_h()), background_, Point(0, 0));
+	}
+
 	dst.brighten_rect(Rect(Point(0, 0), get_w(), get_h()), ms_darken_value);
 
 	while (idx < entry_records_.size()) {

=== modified file 'src/ui_basic/listselect.h'
--- src/ui_basic/listselect.h	2016-08-04 15:49:05 +0000
+++ src/ui_basic/listselect.h	2016-09-27 08:44:17 +0000
@@ -92,11 +92,20 @@
 	uint32_t get_selected() const;
 	void remove_selected();
 
+	const std::string& get_selected_name() const;
+	const std::string& get_selected_tooltip() const;
+
+	void set_background(const Image* background) {
+		background_ = background;
+	}
+
 	///  Return the total height (text + spacing) occupied by a single line.
 	uint32_t get_lineheight() const;
 
 	uint32_t get_eff_w() const;
 
+	void layout() override;
+
 	// Drawing and event handling
 	void draw(RenderTarget&) override;
 	bool handle_mousepress(uint8_t btn, int32_t x, int32_t y) override;
@@ -134,6 +143,7 @@
 	uint32_t last_selection_;  // for double clicks
 	bool show_check_;          //  show a green arrow left of selected element
 	const Image* check_pic_;
+	const Image* background_;
 	std::string current_tooltip_;
 };
 
@@ -167,6 +177,10 @@
 		return entry_cache_[BaseListselect::get_selected()];
 	}
 
+	void set_background(const Image* background) {
+		BaseListselect::set_background(background);
+	}
+
 private:
 	std::deque<Entry> entry_cache_;
 };
@@ -207,6 +221,10 @@
 	Entry& get_selected() const {
 		return *Base::get_selected();
 	}
+
+	void set_background(const Image* background) {
+		*Base::set_background(background);
+	}
 };
 }
 

=== modified file 'src/ui_basic/panel.h'
--- src/ui_basic/panel.h	2016-08-04 15:59:26 +0000
+++ src/ui_basic/panel.h	2016-09-27 08:44:17 +0000
@@ -113,7 +113,7 @@
 	// Geometry
 	void set_size(int nw, int nh);
 	void set_desired_size(int w, int h);
-	void set_pos(Point);
+	virtual void set_pos(Point);
 	virtual void move_inside_parent();
 	virtual void layout();
 

=== modified file 'src/ui_basic/slider.cc'
--- src/ui_basic/slider.cc	2016-08-04 15:49:05 +0000
+++ src/ui_basic/slider.cc	2016-09-27 08:44:17 +0000
@@ -75,6 +75,7 @@
      bar_size_(bar_size),
      cursor_size_(cursor_size) {
 	set_thinks(false);
+	set_can_focus(true);
 	calculate_cursor_position();
 }
 
@@ -200,6 +201,7 @@
 	if (enabled_ == enabled)
 		return;
 
+	set_can_focus(enabled);
 	enabled_ = enabled;
 	if (!enabled) {
 		pressed_ = false;
@@ -400,6 +402,7 @@
 	if (btn != SDL_BUTTON_LEFT)
 		return false;
 
+	focus();
 	if (x >= cursor_pos_ && x <= cursor_pos_ + cursor_size_) {
 		//  click on cursor
 		cursor_pressed(x);
@@ -470,6 +473,7 @@
 	if (btn != SDL_BUTTON_LEFT)
 		return false;
 
+	focus();
 	if (y >= cursor_pos_ && y <= cursor_pos_ + cursor_size_) {
 		//  click on cursor
 		cursor_pressed(y);

=== modified file 'src/ui_fsmenu/launch_spg.cc'
--- src/ui_fsmenu/launch_spg.cc	2016-08-27 10:15:32 +0000
+++ src/ui_fsmenu/launch_spg.cc	2016-09-27 08:44:17 +0000
@@ -77,17 +77,13 @@
                  std::string(),
                  false,
                  false),
-     wincondition_(this,
-                   "win_condition",
-                   get_w() * 7 / 10,
-                   get_h() * 4 / 10 + buth_,
-                   butw_,
-                   buth_,
-                   g_gr->images().get("images/ui_basic/but1.png"),
-                   "",
-                   std::string(),
-                   false,
-                   false),
+     win_condition_dropdown_(this,
+                             get_w() * 7 / 10,
+                             get_h() * 4 / 10 + buth_,
+                             butw_,
+                             get_h() - get_h() * 4 / 10 - buth_,
+                             ""),
+     last_win_condition_(""),
      back_(this,
            "back",
            get_w() * 7 / 10,
@@ -152,15 +148,12 @@
      is_scenario_(false) {
 	select_map_.sigclicked.connect(
 	   boost::bind(&FullscreenMenuLaunchSPG::select_map, boost::ref(*this)));
-	wincondition_.sigclicked.connect(
-	   boost::bind(&FullscreenMenuLaunchSPG::win_condition_clicked, boost::ref(*this)));
+	win_condition_dropdown_.selected.connect(
+	   boost::bind(&FullscreenMenuLaunchSPG::win_condition_selected, this));
 	back_.sigclicked.connect(boost::bind(&FullscreenMenuLaunchSPG::clicked_back, boost::ref(*this)));
 	ok_.sigclicked.connect(boost::bind(&FullscreenMenuLaunchSPG::clicked_ok, boost::ref(*this)));
 
 	lua_ = new LuaInterface();
-	win_condition_scripts_ = settings_->settings().win_condition_scripts;
-	cur_wincondition_ = -1;
-	win_condition_clicked();
 
 	title_.set_fontsize(UI_FONT_SIZE_BIG);
 
@@ -226,68 +219,112 @@
 }
 
 /**
- * WinCondition button has been pressed
- */
-void FullscreenMenuLaunchSPG::win_condition_clicked() {
-	if (settings_->can_change_map()) {
-		cur_wincondition_++;
-		cur_wincondition_ %= win_condition_scripts_.size();
-		settings_->set_win_condition_script(win_condition_scripts_[cur_wincondition_]);
-	}
-
-	win_condition_update();
-}
-
-/**
- * update win conditions information
- */
-void FullscreenMenuLaunchSPG::win_condition_update() {
+ * Fill the dropdown with the available win conditions.
+ */
+void FullscreenMenuLaunchSPG::load_win_conditions() {
+	win_condition_dropdown_.clear();
 	if (settings_->settings().scenario) {
-		wincondition_.set_title(_("Scenario"));
-		wincondition_.set_tooltip(_("Win condition is set through the scenario"));
+		win_condition_dropdown_.set_label(_("Scenario"));
+		win_condition_dropdown_.set_tooltip(_("Win condition is set through the scenario"));
+		win_condition_dropdown_.set_enabled(false);
 	} else {
-		win_condition_load();
+		win_condition_dropdown_.set_label("");
+		win_condition_dropdown_.set_tooltip("");
+		Widelands::Map map;
+		std::unique_ptr<Widelands::MapLoader> ml =
+		   map.get_correct_loader(settings_->settings().mapfilename);
+		if (ml != nullptr) {
+			try {
+				ml->preload_map(true);
+				std::set<std::string> tags = map.get_tags();
+				// Make sure that the last win condition is still valid. If not, pick the first one
+				// available.
+				if (last_win_condition_.empty()) {
+					last_win_condition_ = settings_->settings().win_condition_scripts.front();
+				}
+				std::unique_ptr<LuaTable> t = win_condition_if_valid(last_win_condition_, tags);
+				for (const std::string& win_condition_script :
+				     settings_->settings().win_condition_scripts) {
+					if (t) {
+						break;
+					} else {
+						last_win_condition_ = win_condition_script;
+						t = win_condition_if_valid(last_win_condition_, tags);
+					}
+				}
+
+				// Now fill the dropdown.
+				for (const std::string& win_condition_script :
+				     settings_->settings().win_condition_scripts) {
+					try {
+						t = win_condition_if_valid(win_condition_script, tags);
+						if (t) {
+							i18n::Textdomain td("win_conditions");
+							win_condition_dropdown_.add(
+							   _(t->get_string("name")), win_condition_script, nullptr,
+							   win_condition_script == last_win_condition_, t->get_string("description"));
+						}
+					} catch (LuaTableKeyError& e) {
+						log("LaunchSPG: Error loading win condition: %s %s\n",
+						    win_condition_script.c_str(), e.what());
+					}
+				}
+			} catch (const std::exception& e) {
+				const std::string error_message =
+				   (boost::format(_("Unable to determine valid win conditions because the map '%s' "
+				                    "could not be loaded.")) %
+				    settings_->settings().mapfilename)
+				      .str();
+				win_condition_dropdown_.set_label(_("Error"));
+				win_condition_dropdown_.set_tooltip(error_message);
+				log("LaunchSPG: Exception: %s %s\n", error_message.c_str(), e.what());
+			}
+
+		} else {
+			const std::string error_message =
+			   (boost::format(_("Unable to determine valid win conditions because the map '%s' could "
+			                    "not be loaded.")) %
+			    settings_->settings().mapfilename)
+			      .str();
+			win_condition_dropdown_.set_label(_("Error"));
+			win_condition_dropdown_.set_tooltip(error_message);
+			log("LaunchSPG: No map loader: %s\n", error_message.c_str());
+		}
+		win_condition_dropdown_.set_enabled(true);
 	}
 }
 
-/**
- * Loads the current win condition script from the settings provider.
- * Calls win_condition_clicked() if the current map can't handle the win condition.
- */
-void FullscreenMenuLaunchSPG::win_condition_load() {
+void FullscreenMenuLaunchSPG::win_condition_selected() {
+	last_win_condition_ = win_condition_dropdown_.get_selected();
+}
+
+std::unique_ptr<LuaTable>
+FullscreenMenuLaunchSPG::win_condition_if_valid(const std::string& win_condition_script,
+                                                std::set<std::string> tags) const {
 	bool is_usable = true;
+	std::unique_ptr<LuaTable> t;
 	try {
-		std::unique_ptr<LuaTable> t = lua_->run_script(settings_->get_win_condition_script());
+		t = lua_->run_script(win_condition_script);
 		t->do_not_warn_about_unaccessed_keys();
 
 		// Skip this win condition if the map doesn't have all the required tags
-		if (t->has_key("map_tags") && !settings_->settings().mapfilename.empty()) {
-			Widelands::Map map;
-			std::unique_ptr<Widelands::MapLoader> ml =
-			   map.get_correct_loader(settings_->settings().mapfilename);
-			ml->preload_map(true);
+		if (t->has_key("map_tags")) {
+
 			for (const std::string& map_tag : t->get_table("map_tags")->array_entries<std::string>()) {
-				if (!map.has_tag(map_tag)) {
+				if (!tags.count(map_tag)) {
 					is_usable = false;
 					break;
 				}
 			}
 		}
-
-		const std::string name = t->get_string("name");
-		const std::string descr = t->get_string("description");
-		{
-			i18n::Textdomain td("win_conditions");
-			wincondition_.set_title(_(name));
-		}
-		wincondition_.set_tooltip(descr.c_str());
-	} catch (LuaTableKeyError&) {
-		// might be that this is not a win condition after all.
-		is_usable = false;
+	} catch (LuaTableKeyError& e) {
+		log(
+		   "LaunchSPG: Error loading win condition: %s %s\n", win_condition_script.c_str(), e.what());
 	}
 	if (!is_usable) {
-		win_condition_clicked();
+		t.reset(nullptr);
 	}
+	return t;
 }
 
 /**
@@ -308,6 +345,7 @@
 		if (is_scenario_) {
 			end_modal<FullscreenMenuBase::MenuTarget>(FullscreenMenuBase::MenuTarget::kScenarioGame);
 		} else {
+			settings_->set_win_condition_script(win_condition_dropdown_.get_selected());
 			end_modal<FullscreenMenuBase::MenuTarget>(FullscreenMenuBase::MenuTarget::kNormalGame);
 		}
 	}
@@ -333,7 +371,6 @@
 
 	select_map_.set_visible(settings_->can_change_map());
 	select_map_.set_enabled(settings_->can_change_map());
-	wincondition_.set_enabled(settings_->can_change_map() && !settings.scenario);
 
 	if (settings.scenario) {
 		set_scenario_values();
@@ -352,8 +389,6 @@
 	// update the player description groups
 	for (uint32_t i = 0; i < MAX_PLAYERS; ++i)
 		players_[i]->refresh();
-
-	win_condition_update();
 }
 
 /**
@@ -380,6 +415,7 @@
 
 	safe_place_for_host(nr_players_);
 	settings_->set_map(mapdata.name, mapdata.filename, nr_players_);
+	load_win_conditions();
 }
 
 /**

=== modified file 'src/ui_fsmenu/launch_spg.h'
--- src/ui_fsmenu/launch_spg.h	2016-08-04 15:49:05 +0000
+++ src/ui_fsmenu/launch_spg.h	2016-09-27 08:44:17 +0000
@@ -20,10 +20,12 @@
 #ifndef WL_UI_FSMENU_LAUNCH_SPG_H
 #define WL_UI_FSMENU_LAUNCH_SPG_H
 
+#include <memory>
 #include <string>
 
 #include "logic/constants.h"
 #include "ui_basic/button.h"
+#include "ui_basic/dropdown.h"
 #include "ui_basic/multilinetextarea.h"
 #include "ui_basic/textarea.h"
 #include "ui_fsmenu/base.h"
@@ -64,9 +66,16 @@
 	LuaInterface* lua_;
 
 	void select_map();
-	void win_condition_clicked();
-	void win_condition_update();
-	void win_condition_load();
+	/// Loads all win conditions that can be played with the map into the selection dropdown.
+	/// Disables the dropdown if the map is a scenario.
+	void load_win_conditions();
+	/// Remembers the win condition that is currently selected in the dropdown.
+	void win_condition_selected();
+	/// If the win condition in 'win_condition_script' can be played with the map tags,
+	/// parses the win condition and returns it as a std::unique_ptr<LuaTable>.
+	/// If this win condition can't be played with the map tags, returns a unique_ptr to nullptr.
+	std::unique_ptr<LuaTable> win_condition_if_valid(const std::string& win_condition_script,
+	                                                 std::set<std::string> tags) const;
 	void set_scenario_values();
 	void switch_to_position(uint8_t);
 	void safe_place_for_host(uint8_t);
@@ -74,7 +83,10 @@
 	uint32_t butw_;
 	uint32_t buth_;
 
-	UI::Button select_map_, wincondition_, back_, ok_;
+	UI::Button select_map_;
+	UI::Dropdown<std::string> win_condition_dropdown_;
+	std::string last_win_condition_;
+	UI::Button back_, ok_;
 	UI::Button* pos_[MAX_PLAYERS];
 	UI::Textarea title_, mapname_;
 	UI::Textarea name_, type_, team_, tribe_, init_, wincondition_type_;
@@ -87,8 +99,6 @@
 	std::string player_save_tribe_[MAX_PLAYERS];
 	int8_t nr_players_;
 	bool is_scenario_;
-	std::vector<std::string> win_condition_scripts_;
-	uint8_t cur_wincondition_;
 };
 
 #endif  // end of include guard: WL_UI_FSMENU_LAUNCH_SPG_H

=== modified file 'src/ui_fsmenu/options.cc'
--- src/ui_fsmenu/options.cc	2016-08-04 15:49:05 +0000
+++ src/ui_fsmenu/options.cc	2016-09-27 08:44:17 +0000
@@ -153,11 +153,20 @@
      box_sound_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_),
      box_saving_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_),
      box_game_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_),
-     box_language_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_),
 
      // Interface options
-     label_resolution_(&box_interface_, _("In-game resolution"), UI::Align::kLeft),
-     resolution_list_(&box_interface_, 0, 0, column_width_ / 2, 80, true),
+     language_dropdown_(&box_interface_,
+                        0,
+                        0,
+                        column_width_ / 2,
+                        get_inner_h() - tab_panel_y_ - buth_ - hmargin_ - 4 * padding_,
+                        _("Language")),
+     resolution_dropdown_(&box_interface_,
+                          0,
+                          0,
+                          column_width_ / 2,
+                          get_inner_h() - tab_panel_y_ - 2 * buth_ - hmargin_ - 5 * padding_,
+                          _("In-game resolution")),
 
      fullscreen_(&box_interface_, Point(0, 0), _("Fullscreen"), "", column_width_),
      inputgrab_(&box_interface_, Point(0, 0), _("Grab Input"), "", column_width_),
@@ -257,15 +266,6 @@
      /** TRANSLATORS: and it also lets you jump to it on the map. */
      single_watchwin_(&box_game_, Point(0, 0), _("Use single watchwindow mode")),
 
-     // Language options
-     label_language_(&box_language_, _("Language"), UI::Align::kLeft),
-     language_list_(&box_language_,
-                    0,
-                    0,
-                    column_width_ / 2,
-                    get_inner_h() - tab_panel_y_ - 2 * buth_ - hmargin_ - 5 * padding_,
-                    true),
-
      os_(opt) {
 	// Set up UI Elements
 	title_.set_fontsize(UI_FONT_SIZE_BIG);
@@ -275,7 +275,6 @@
 	tabs_.add("options_sound", _("Sound"), &box_sound_, "");
 	tabs_.add("options_saving", _("Saving"), &box_saving_, "");
 	tabs_.add("options_game", _("Game"), &box_game_, "");
-	tabs_.add("options_language", _("Language"), &box_language_, "");
 
 	// We want the last active tab when "Apply" was clicked.
 	if (os_.active_tab < tabs_.tabs().size()) {
@@ -289,11 +288,10 @@
 	box_sound_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h());
 	box_saving_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h());
 	box_game_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h());
-	box_language_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h());
 
 	// Interface
-	box_interface_.add(&label_resolution_, UI::Align::kLeft);
-	box_interface_.add(&resolution_list_, UI::Align::kLeft);
+	box_interface_.add(&language_dropdown_, UI::Align::kLeft);
+	box_interface_.add(&resolution_dropdown_, UI::Align::kLeft);
 	box_interface_.add(&fullscreen_, UI::Align::kLeft);
 	box_interface_.add(&inputgrab_, UI::Align::kLeft);
 	box_interface_.add(&sb_maxfps_, UI::Align::kLeft);
@@ -321,10 +319,6 @@
 	box_game_.add(&transparent_chat_, UI::Align::kLeft);
 	box_game_.add(&single_watchwin_, UI::Align::kLeft);
 
-	// Language
-	box_language_.add(&label_language_, UI::Align::kLeft);
-	box_language_.add(&language_list_, UI::Align::kLeft);
-
 	// Bind actions
 	cancel_.sigclicked.connect(boost::bind(&FullscreenMenuOptions::clicked_back, this));
 	apply_.sigclicked.connect(boost::bind(&FullscreenMenuOptions::clicked_apply, this));
@@ -355,18 +349,18 @@
 	for (uint32_t i = 0; i < resolutions_.size(); ++i) {
 		const bool selected = resolutions_[i].xres == opt.xres && resolutions_[i].yres == opt.yres;
 		did_select_a_res |= selected;
-		/** TRANSLATORS: Screen resolution, e.g. 800 x 600*/
-		resolution_list_.add(
-		   (boost::format(_("%1% x %2%")) % resolutions_[i].xres % resolutions_[i].yres).str(),
-		   nullptr, nullptr, selected);
+		resolution_dropdown_.add(
+		   /** TRANSLATORS: Screen resolution, e.g. 800 x 600*/
+		   (boost::format(_("%1% x %2%")) % resolutions_[i].xres % resolutions_[i].yres).str(), i,
+		   nullptr, selected);
 	}
 	if (!did_select_a_res) {
-		resolution_list_.add(
-		   (boost::format(_("%1% x %2%")) % opt.xres % opt.yres).str(), nullptr, nullptr, true);
 		uint32_t entry = resolutions_.size();
 		resolutions_.resize(entry + 1);
 		resolutions_[entry].xres = opt.xres;
 		resolutions_[entry].yres = opt.yres;
+		resolution_dropdown_.add(
+		   (boost::format(_("%1% x %2%")) % opt.xres % opt.yres).str(), entry, nullptr, true);
 	}
 
 	fullscreen_.set_state(opt.fullscreen);
@@ -395,14 +389,13 @@
 
 	// Language options
 	add_languages_to_list(opt.language);
-	language_list_.focus();
 }
 
 void FullscreenMenuOptions::add_languages_to_list(const std::string& current_locale) {
 
 	// We want these two entries on top - the most likely user's choice and the default.
-	language_list_.add(_("Try system language"), "", nullptr, current_locale == "");
-	language_list_.add("English", "en", nullptr, current_locale == "en");
+	language_dropdown_.add(_("Try system language"), "", nullptr, current_locale == "");
+	language_dropdown_.add("English", "en", nullptr, current_locale == "en");
 
 	// Add translation directories to the list
 	std::vector<LanguageEntry> entries;
@@ -451,8 +444,8 @@
 	find_selected_locale(&selected_locale, current_locale);
 	std::sort(entries.begin(), entries.end());
 	for (const LanguageEntry& entry : entries) {
-		language_list_.add(entry.descname.c_str(), entry.localename, nullptr,
-		                   entry.localename == selected_locale, "");
+		language_dropdown_.add(entry.descname.c_str(), entry.localename, nullptr,
+		                       entry.localename == selected_locale, "");
 	}
 }
 
@@ -463,9 +456,14 @@
 OptionsCtrl::OptionsStruct FullscreenMenuOptions::get_values() {
 	// Write all data from UI elements
 	// Interface options
-	const uint32_t res_index = resolution_list_.selection_index();
-	os_.xres = resolutions_[res_index].xres;
-	os_.yres = resolutions_[res_index].yres;
+	if (language_dropdown_.has_selection()) {
+		os_.language = language_dropdown_.get_selected();
+	}
+	if (resolution_dropdown_.has_selection()) {
+		const uint32_t res_index = resolution_dropdown_.get_selected();
+		os_.xres = resolutions_[res_index].xres;
+		os_.yres = resolutions_[res_index].yres;
+	}
 	os_.fullscreen = fullscreen_.get_state();
 	os_.inputgrab = inputgrab_.get_state();
 	os_.maxfps = sb_maxfps_.get_value();
@@ -493,11 +491,6 @@
 	os_.transparent_chat = transparent_chat_.get_state();
 	os_.single_watchwin = single_watchwin_.get_state();
 
-	// Language options
-	if (language_list_.has_selection()) {
-		os_.language = language_list_.get_selected();
-	}
-
 	// Last tab for reloading the options menu
 	os_.active_tab = tabs_.active();
 	return os_;

=== modified file 'src/ui_fsmenu/options.h'
--- src/ui_fsmenu/options.h	2016-08-04 15:49:05 +0000
+++ src/ui_fsmenu/options.h	2016-09-27 08:44:17 +0000
@@ -27,7 +27,7 @@
 
 #include "ui_basic/button.h"
 #include "ui_basic/checkbox.h"
-#include "ui_basic/listselect.h"
+#include "ui_basic/dropdown.h"
 #include "ui_basic/multilinetextarea.h"
 #include "ui_basic/spinbox.h"
 #include "ui_basic/tabpanel.h"
@@ -122,11 +122,10 @@
 	UI::Box box_sound_;
 	UI::Box box_saving_;
 	UI::Box box_game_;
-	UI::Box box_language_;
 
 	// Interface options
-	UI::Textarea label_resolution_;
-	UI::Listselect<void*> resolution_list_;
+	UI::Dropdown<std::string> language_dropdown_;
+	UI::Dropdown<uintptr_t> resolution_dropdown_;
 	UI::Checkbox fullscreen_;
 	UI::Checkbox inputgrab_;
 	UI::SpinBox sb_maxfps_;
@@ -154,10 +153,6 @@
 	UI::Checkbox transparent_chat_;
 	UI::Checkbox single_watchwin_;
 
-	// Language options
-	UI::Textarea label_language_;
-	UI::Listselect<std::string> language_list_;
-
 	OptionsCtrl::OptionsStruct os_;
 
 	class ScreenResolution {


Follow ups