1 /*
2  * Copyright (C) 2006-2020 by the Widelands Development Team
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  */
19 
20 #ifndef WL_EDITOR_UI_MENUS_CATEGORIZED_ITEM_SELECTION_MENU_H
21 #define WL_EDITOR_UI_MENUS_CATEGORIZED_ITEM_SELECTION_MENU_H
22 
23 #include "boost/format.hpp"
24 
25 #include "base/i18n.h"
26 #include "logic/map_objects/description_maintainer.h"
27 #include "logic/map_objects/world/editor_category.h"
28 #include "ui_basic/box.h"
29 #include "ui_basic/checkbox.h"
30 #include "ui_basic/multilinetextarea.h"
31 #include "ui_basic/panel.h"
32 #include "ui_basic/tabpanel.h"
33 
34 template <typename DescriptionType, typename ToolType>
35 class CategorizedItemSelectionMenu : public UI::Box {
36 public:
37 	// Creates a box with a tab panel for each category in 'categories' and
38 	// populates them with the 'descriptions' ordered by the category by calling
39 	// 'create_checkbox' for each of the descriptions. Calls
40 	// 'select_correct_tool' whenever a selection has been made, also keeps a
41 	// text label updated and updates the 'tool' with current selections. Does
42 	// not take ownership.
43 	CategorizedItemSelectionMenu(
44 	   UI::Panel* parent,
45 	   const Widelands::DescriptionMaintainer<Widelands::EditorCategory>& categories,
46 	   const Widelands::DescriptionMaintainer<DescriptionType>& descriptions,
47 	   std::function<UI::Checkbox*(UI::Panel* parent, const DescriptionType& descr)> create_checkbox,
48 	   const std::function<void()> select_correct_tool,
49 	   ToolType* const tool);
50 
51 private:
52 	// Called when an item was selected.
53 	void selected(int32_t, bool);
54 
55 	// Update the label with the currently selected object names.
56 	void update_label();
57 
58 	const Widelands::DescriptionMaintainer<DescriptionType>& descriptions_;
59 	std::function<void()> select_correct_tool_;
60 	bool protect_against_recursive_select_;
61 	UI::TabPanel tab_panel_;
62 	UI::MultilineTextarea current_selection_names_;
63 	std::map<int, UI::Checkbox*> checkboxes_;
64 	ToolType* const tool_;  // not owned
65 };
66 
67 template <typename DescriptionType, typename ToolType>
CategorizedItemSelectionMenu(UI::Panel * parent,const Widelands::DescriptionMaintainer<Widelands::EditorCategory> & categories,const Widelands::DescriptionMaintainer<DescriptionType> & descriptions,const std::function<UI::Checkbox * (UI::Panel * parent,const DescriptionType & descr)> create_checkbox,const std::function<void ()> select_correct_tool,ToolType * const tool)68 CategorizedItemSelectionMenu<DescriptionType, ToolType>::CategorizedItemSelectionMenu(
69    UI::Panel* parent,
70    const Widelands::DescriptionMaintainer<Widelands::EditorCategory>& categories,
71    const Widelands::DescriptionMaintainer<DescriptionType>& descriptions,
72    const std::function<UI::Checkbox*(UI::Panel* parent, const DescriptionType& descr)>
73       create_checkbox,
74    const std::function<void()> select_correct_tool,
75    ToolType* const tool)
76    : UI::Box(parent, 0, 0, UI::Box::Vertical),
77      descriptions_(descriptions),
78      select_correct_tool_(select_correct_tool),
79      protect_against_recursive_select_(false),
80      tab_panel_(this, UI::TabPanelStyle::kWuiLight),
81      current_selection_names_(this,
82                               0,
83                               0,
84                               20,
85                               20,
86                               UI::PanelStyle::kWui,
87                               "",
88                               UI::Align::kCenter,
89                               UI::MultilineTextarea::ScrollMode::kNoScrolling),
90      tool_(tool) {
91 	add(&tab_panel_);
92 
93 	for (uint32_t category_index = 0; category_index < categories.size(); ++category_index) {
94 		const Widelands::EditorCategory& category = categories.get(category_index);
95 
96 		std::vector<int> item_indices;
97 		for (size_t j = 0; j < descriptions_.size(); ++j) {
98 			if (descriptions_.get(j).editor_category()->name() != category.name()) {
99 				continue;
100 			}
101 			item_indices.push_back(j);
102 		}
103 
104 		UI::Box* vertical = new UI::Box(&tab_panel_, 0, 0, UI::Box::Vertical);
105 		const int kSpacing = 5;
106 		vertical->add_space(kSpacing);
107 
108 		int nitems_handled = 0;
109 		UI::Box* horizontal = nullptr;
110 		for (const int i : item_indices) {
111 			if (nitems_handled % category.items_per_row() == 0) {
112 				horizontal = new UI::Box(vertical, 0, 0, UI::Box::Horizontal);
113 				horizontal->add_space(kSpacing);
114 
115 				vertical->add(horizontal);
116 				vertical->add_space(kSpacing);
117 			}
118 			assert(horizontal != nullptr);
119 
120 			UI::Checkbox* cb = create_checkbox(horizontal, descriptions_.get(i));
121 			cb->set_state(tool_->is_enabled(i));
122 			cb->changedto.connect([this, i](bool b) { selected(i, b); });
123 			checkboxes_[i] = cb;
124 			horizontal->add(cb);
125 			horizontal->add_space(kSpacing);
126 			++nitems_handled;
127 		}
128 		tab_panel_.add(category.name(), category.picture(), vertical, category.descname());
129 	}
130 	add(&current_selection_names_, UI::Box::Resizing::kFullSize);
131 	tab_panel_.sigclicked.connect([this]() { update_label(); });
132 	update_label();
133 }
134 
135 template <typename DescriptionType, typename ToolType>
selected(const int32_t n,const bool t)136 void CategorizedItemSelectionMenu<DescriptionType, ToolType>::selected(const int32_t n,
137                                                                        const bool t) {
138 	if (protect_against_recursive_select_)
139 		return;
140 
141 	//  TODO(unknown): This code is erroneous. It checks the current key state. What it
142 	//  needs is the key state at the time the mouse was clicked. See the
143 	//  usage comment for get_key_state.
144 	const bool multiselect = SDL_GetModState() & KMOD_CTRL;
145 	if (!t && (!multiselect || tool_->get_nr_enabled() == 1))
146 		checkboxes_[n]->set_state(true);
147 	else {
148 		if (!multiselect) {
149 			for (uint32_t i = 0; tool_->get_nr_enabled(); ++i)
150 				tool_->enable(i, false);
151 			//  disable all checkboxes
152 			protect_against_recursive_select_ = true;
153 			const int32_t size = checkboxes_.size();
154 			for (int32_t i = 0; i < size; ++i) {
155 				if (i != n)
156 					checkboxes_[i]->set_state(false);
157 			}
158 			protect_against_recursive_select_ = false;
159 		}
160 
161 		tool_->enable(n, t);
162 		select_correct_tool_();
163 		update_label();
164 	}
165 }
166 
167 template <typename DescriptionType, typename ToolType>
update_label()168 void CategorizedItemSelectionMenu<DescriptionType, ToolType>::update_label() {
169 	current_selection_names_.set_size(tab_panel_.get_inner_w(), 20);
170 	std::string buf;
171 	constexpr int max_string_size = 100;
172 	int j = tool_->get_nr_enabled();
173 	for (int i = 0; j && buf.size() < max_string_size; ++i) {
174 		if (tool_->is_enabled(i)) {
175 			if (j < tool_->get_nr_enabled()) {
176 				buf += " • ";
177 			}
178 			buf += descriptions_.get(i).descname();
179 			--j;
180 		}
181 	}
182 	if (buf.size() > max_string_size) {
183 		/** TRANSLATORS: %s are the currently selected items in an editor tool*/
184 		buf = (boost::format(_("Current: %s …")) % buf).str();
185 	} else if (buf.empty()) {
186 		/** TRANSLATORS: Help text in an editor tool*/
187 		buf = _("Click to select an item. Use the Ctrl key to select multiple items.");
188 	} else {
189 		/** TRANSLATORS: %s are the currently selected items in an editor tool*/
190 		buf = (boost::format(_("Current: %s")) % buf).str();
191 	}
192 	current_selection_names_.set_text(buf);
193 }
194 
195 #endif  // end of include guard: WL_EDITOR_UI_MENUS_CATEGORIZED_ITEM_SELECTION_MENU_H
196