Merge lp:~widelands-dev/widelands/table_multiselect into lp:widelands

Proposed by GunChleoc on 2016-12-07
Status: Merged
Merged at revision: 8305
Proposed branch: lp:~widelands-dev/widelands/table_multiselect
Merge into: lp:widelands
Diff against target: 1243 lines (+391/-230)
13 files modified
data/campaigns/tutorial01_basic_control.wmf/scripting/texts.lua (+3/-1)
data/tribes/scripting/help/controls.lua (+11/-0)
src/editor/ui_menus/main_menu_load_or_save_map.cc (+1/-1)
src/ui_basic/table.cc (+141/-88)
src/ui_basic/table.h (+38/-30)
src/ui_fsmenu/loadgame.cc (+103/-43)
src/ui_fsmenu/loadgame.h (+2/-1)
src/ui_fsmenu/mapselect.cc (+1/-1)
src/wlapplication.cc (+1/-1)
src/wui/game_message_menu.cc (+84/-59)
src/wui/game_message_menu.h (+3/-0)
src/wui/maptable.cc (+2/-4)
src/wui/maptable.h (+1/-1)
To merge this branch: bzr merge lp:~widelands-dev/widelands/table_multiselect
Reviewer Review Type Date Requested Status
Klaus Halfmann code review, compile Approve on 2017-02-26
kaputtnik (community) testing 2016-12-07 Approve on 2017-02-06
GunChleoc Resubmit on 2017-01-26
Review via email: mp+312747@code.launchpad.net

Commit message

Let the user select multiple entries in a table using Ctrl/Shift + Click and Ctrl+A. The new table mode is used in the messages window and in game/replay deleting.

This replaces the currently unused checkbox_column function.

To post a comment you must log in.
8148. By GunChleoc on 2016-12-07

Undo accidental string change.

bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 1730. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/182100284.
Appveyor build 1570. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_table_multiselect-1570.

8149. By GunChleoc on 2016-12-08

Renamed some variables to fix compiler errors.

8150. By GunChleoc on 2016-12-08

Merged trunk.

8151. By GunChleoc on 2016-12-10

Renamed a variable.

bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 1756. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/182873235.
Appveyor build 1596. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_table_multiselect-1596.

bunnybot (widelandsofficial) wrote :

Bunnybot encountered an error while working on this merge proposal:

('The read operation timed out',)

bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 1756. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/182873235.
Appveyor build 1596. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_table_multiselect-1596.

8152. By GunChleoc on 2017-01-16

Merged trunk.

kaputtnik (franku) wrote :

For the message menu and the 'load save game' menu it works fine :-)

The only thing is that the highlighted background of an marked element does not distinguish very well.

I can't test for the "Watch replay" menu, because of bug 1653460. See there also for a screenshot in comment 8: https://bugs.launchpad.net/widelands/+bug/1653460/comments/8 May we need to fix the other bug first?

kaputtnik (franku) wrote :

Found another:

If you mark an entry and press SHIFT + UP-ARROW the previous list item is marked. Pressing then SHIFT + DOWN-ARROW the previous item is not unmarked again. This is unusual in comparison with e.g. a file manager.

review: Needs Fixing
kaputtnik (franku) wrote :

Ahh... using the arrow keys do always mark an additional entry, one doesn't have to use additionally the SHIFT key.

1. Open menu Load Game
2. Use the down arrow key

The next entry is marked then. If one want's to load the game of next entry in the list, he must use the mouse.

review: Needs Fixing
8153. By GunChleoc on 2017-01-22

Merged trunk.

8154. By GunChleoc on 2017-01-26

Fixed keyboard navigation.

8155. By GunChleoc on 2017-01-26

Merged trunk.

GunChleoc (gunchleoc) wrote :

Implemented the keyboard navigation now.

review: Resubmit
kaputtnik (franku) wrote :

Works fine now :-)

One issue: If multiple rows are selected and you click on a column title to have a different ordering, then only the last selected entry is marked anymore. But for me this is ok :-)

review: Approve (testing)
8156. By GunChleoc on 2017-01-27

Keep selection while sorting.

8157. By GunChleoc on 2017-01-27

Formatting.

GunChleoc (gunchleoc) wrote :

I figured out how to keep the selection while sorting :)

8158. By GunChleoc on 2017-01-27

Removed unneeded conditional statement.

bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 1881. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/195775760.
Appveyor build 1716. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_table_multiselect-1716.

8159. By GunChleoc on 2017-01-28

Renamed variables and updated a comment.

bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 1884. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/196097438.
Appveyor build 1719. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_table_multiselect-1719.

kaputtnik (franku) wrote :

Still works :-)

review: Approve (testing)
8160. By GunChleoc on 2017-02-10

Merged trunk.

8161. By GunChleoc on 2017-02-15

Merged trunk.

8162. By GunChleoc on 2017-02-24

Merged trunk.

Klaus Halfmann (klaus-halfmann) wrote :

Couls no find any major flaw in this code,
lets get this in.

I will now test this a bit, though

review: Approve (code review, compile)
Klaus Halfmann (klaus-halfmann) wrote :

In any "Load Game Dialog" the description does not update when I use the shift-key to extend the selection, basic functionallity is not affected however.

Shall I open a followup Bug?

8163. By GunChleoc on 2017-02-26

Merged trunk.

GunChleoc (gunchleoc) wrote :

@bunnybot merge

bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 1999. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/205505693.
Appveyor build 1835. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_table_multiselect-1835.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/campaigns/tutorial01_basic_control.wmf/scripting/texts.lua'
2--- data/campaigns/tutorial01_basic_control.wmf/scripting/texts.lua 2016-09-20 17:01:35 +0000
3+++ data/campaigns/tutorial01_basic_control.wmf/scripting/texts.lua 2017-02-26 12:43:34 +0000
4@@ -405,6 +405,7 @@
5 rt(
6 p(_[[Once you have archived a message, another message will be selected automatically from the list.]]) ..
7 paragraphdivider() ..
8+ listitem_arrow(_[[You can also hold down the Ctrl or Shift key to select multiple messages, or press Ctrl + A to select them all.]]) ..
9 listitem_bullet(_[[Archive all messages that you currently have in your inbox, including this one.]])
10 ),
11 obj_name = "archive_all_messages",
12@@ -414,7 +415,8 @@
13 p(_[[The message window is central to fully controlling your tribe’s fortune. However, you will get a lot of messages in a real game. To keep your head straight, you should try to keep the inbox empty.]]) ..
14 paragraphdivider() ..
15 listitem_bullet(_[[Archive all your messages in your inbox now.]]) ..
16- listitem_arrow(_[[To do so, open the message window by pressing ‘n’ or clicking the second button from the right at the very bottom of the screen. The newest message will be marked for you automatically. Keep clicking the ‘Archive selected message’ button until all messages have been archived and the list is empty.]])
17+ listitem_arrow(_[[To do so, open the message window by pressing ‘n’ or clicking the second button from the right at the very bottom of the screen. The newest message will be marked for you automatically. Keep clicking the ‘Archive selected message’ button until all messages have been archived and the list is empty.]]) ..
18+ listitem_arrow(_[[You can also hold down the Ctrl or Shift key to select multiple messages, or press Ctrl + A to select them all.]])
19 )
20 }
21
22
23=== modified file 'data/tribes/scripting/help/controls.lua'
24--- data/tribes/scripting/help/controls.lua 2017-02-13 11:56:07 +0000
25+++ data/tribes/scripting/help/controls.lua 2017-02-26 12:43:34 +0000
26@@ -73,6 +73,17 @@
27 dl(help_format_hotkey(pgettext("hotkey", "F6")), _"Show the debug console (only in debug-builds)")
28 ) ..
29
30+ h2(_"Table Control") ..
31+ h3(_"In tables that allow the selection of multiple entries, the following key combinations are available:") ..
32+ p(
33+ -- TRANSLATORS: This is an access key combination. Localize, but do not change the key.
34+ dl(help_format_hotkey(pgettext("hotkey", "Ctrl + Click")), pgettext("table_control", "Select multiple entries")) ..
35+ -- TRANSLATORS: This is an access key combination. Localize, but do not change the key.
36+ dl(help_format_hotkey(pgettext("hotkey", "Shift + Click")), pgettext("table_control", "Select a range of entries")) ..
37+ -- TRANSLATORS: This is an access key combination. Localize, but do not change the key.
38+ dl(help_format_hotkey(pgettext("hotkey", "Ctrl + A")), pgettext("table_control", "Select all entries"))) ..
39+
40+ h2(_"Message Window") ..
41 h3(_"In the message window, the following additional shortcuts are available:") ..
42 p(
43 -- TRANSLATORS: This is the helptext for an access key combination.
44
45=== modified file 'src/editor/ui_menus/main_menu_load_or_save_map.cc'
46--- src/editor/ui_menus/main_menu_load_or_save_map.cc 2017-02-25 13:27:40 +0000
47+++ src/editor/ui_menus/main_menu_load_or_save_map.cc 2017-02-26 12:43:34 +0000
48@@ -49,7 +49,7 @@
49 right_column_x_(tablew_ + 2 * padding_),
50 butw_((get_inner_w() - right_column_x_ - 2 * padding_) / 2),
51
52- table_(this, tablex_, tabley_, tablew_, tableh_, false),
53+ table_(this, tablex_, tabley_, tablew_, tableh_),
54 map_details_(this,
55 right_column_x_,
56 tabley_,
57
58=== modified file 'src/ui_basic/table.cc'
59--- src/ui_basic/table.cc 2017-02-23 19:38:51 +0000
60+++ src/ui_basic/table.cc 2017-02-26 12:43:34 +0000
61@@ -50,7 +50,7 @@
62 uint32_t w,
63 uint32_t h,
64 const Image* button_background,
65- const bool descending)
66+ TableRows rowtype)
67 : Panel(parent, x, y, w, h),
68 total_width_(0),
69 headerheight_(
70@@ -64,11 +64,16 @@
71 new Button(this, "", 0, 0, Scrollbar::kSize, headerheight_, button_background, "")),
72 scrollpos_(0),
73 selection_(no_selection_index()),
74+ last_multiselect_(no_selection_index()),
75 last_click_time_(-10000),
76 last_selection_(no_selection_index()),
77 sort_column_(0),
78- sort_descending_(descending),
79- flexible_column_(std::numeric_limits<size_t>::max()) {
80+ sort_descending_(rowtype == TableRows::kSingleDescending ||
81+ rowtype == TableRows::kMultiDescending),
82+ flexible_column_(std::numeric_limits<size_t>::max()),
83+ is_multiselect_(rowtype == TableRows::kMulti || rowtype == TableRows::kMultiDescending),
84+ ctrl_down_(false),
85+ shift_down_(false) {
86 set_thinks(false);
87 set_can_focus(true);
88 scrollbar_filler_button_->set_visible(false);
89@@ -91,6 +96,7 @@
90 for (Column& column : columns_) {
91 delete column.btn;
92 }
93+ multiselect_.clear();
94 }
95
96 /// Add a new column to this table.
97@@ -98,8 +104,7 @@
98 const std::string& title,
99 const std::string& tooltip_string,
100 Align const alignment,
101- TableColumnType column_type,
102- bool const is_checkbox_column) {
103+ TableColumnType column_type) {
104 // If there would be existing entries, they would not get the new column.
105 assert(size() == 0);
106
107@@ -121,16 +126,7 @@
108 boost::bind(&Table::header_button_clicked, boost::ref(*this), columns_.size()));
109 c.width = width;
110 c.alignment = alignment;
111- c.is_checkbox_column = is_checkbox_column;
112-
113- if (is_checkbox_column) {
114- c.compare =
115- boost::bind(&Table<void*>::default_compare_checkbox, this, columns_.size(), _1, _2);
116- } else {
117- c.compare =
118- boost::bind(&Table<void*>::default_compare_string, this, columns_.size(), _1, _2);
119- }
120-
121+ c.compare = boost::bind(&Table<void*>::default_compare_string, this, columns_.size(), _1, _2);
122 columns_.push_back(c);
123 if (column_type == TableColumnType::kFlexible) {
124 assert(flexible_column_ == std::numeric_limits<size_t>::max());
125@@ -156,24 +152,6 @@
126 column.compare = fn;
127 }
128
129-void Table<void*>::EntryRecord::set_checked(uint8_t const col, bool const checked) {
130- Data& cell = data_.at(col);
131-
132- cell.d_checked = checked;
133- cell.d_picture = g_gr->images().get(checked ? "images/ui_basic/checkbox_checked.png" :
134- "images/ui_basic/checkbox_empty.png");
135-}
136-
137-void Table<void*>::EntryRecord::toggle(uint8_t const col) {
138- set_checked(col, !is_checked(col));
139-}
140-
141-bool Table<void*>::EntryRecord::is_checked(uint8_t const col) const {
142- const Data& cell = data_.at(col);
143-
144- return cell.d_checked;
145-}
146-
147 Table<void*>::EntryRecord* Table<void*>::find(const void* const entry) const
148
149 {
150@@ -213,8 +191,13 @@
151 if (scrollbar_)
152 scrollbar_->set_steps(1);
153 scrollpos_ = 0;
154+ last_click_time_ = -10000;
155+ clear_selections();
156+}
157+
158+void Table<void*>::clear_selections() {
159+ multiselect_.clear();
160 selection_ = no_selection_index();
161- last_click_time_ = -10000;
162 last_selection_ = no_selection_index();
163 }
164
165@@ -250,7 +233,7 @@
166
167 const EntryRecord& er = *entry_records_[idx];
168
169- if (idx == selection_) {
170+ if (idx == selection_ || multiselect_.count(idx)) {
171 assert(2 <= get_eff_w());
172 dst.brighten_rect(Rectf(1.f, y, get_eff_w() - 2, lineheight_), -ms_darken_value);
173 }
174@@ -369,8 +352,33 @@
175 * handle key presses
176 */
177 bool Table<void*>::handle_key(bool down, SDL_Keysym code) {
178+ if (is_multiselect_) {
179+ switch (code.sym) {
180+ case SDLK_LSHIFT:
181+ case SDLK_RSHIFT:
182+ shift_down_ = down;
183+ break;
184+ case SDLK_LCTRL:
185+ case SDLK_RCTRL:
186+ ctrl_down_ = down;
187+ break;
188+ default:
189+ break;
190+ }
191+ }
192 if (down) {
193 switch (code.sym) {
194+ case SDLK_a:
195+ if (is_multiselect_ && ctrl_down_ && !empty()) {
196+ multiselect_.clear();
197+ for (uint32_t i = 0; i < size(); ++i) {
198+ toggle_entry(i);
199+ }
200+ selection_ = 0;
201+ selected(0);
202+ return true;
203+ }
204+ break;
205 case SDLK_UP:
206 case SDLK_KP_8:
207 move_selection(-1);
208@@ -396,7 +404,7 @@
209 /**
210 * Handle mouse presses: select the appropriate entry
211 */
212-bool Table<void*>::handle_mousepress(uint8_t const btn, int32_t x, int32_t const y) {
213+bool Table<void*>::handle_mousepress(uint8_t const btn, int32_t, int32_t const y) {
214 if (get_can_focus())
215 focus();
216
217@@ -413,26 +421,15 @@
218
219 uint32_t const row = (y + scrollpos_ - headerheight_) / get_lineheight();
220 if (row < entry_records_.size()) {
221- select(row);
222- Columns::size_type const nr_cols = columns_.size();
223- for (uint8_t col = 0; col < nr_cols; ++col) {
224- const Column& column = columns_.at(col);
225- x -= column.width;
226- if (x <= 0) {
227- if (column.is_checkbox_column) {
228- play_click();
229- entry_records_.at(row)->toggle(col);
230- }
231- break;
232- }
233- }
234+ play_click();
235+ multiselect(row);
236 }
237
238- if // check if doubleclicked
239- (time - real_last_click_time < DOUBLE_CLICK_INTERVAL && last_selection_ == selection_ &&
240- selection_ != no_selection_index())
241+ // Check if doubleclicked
242+ if (!ctrl_down_ && !shift_down_ && time - real_last_click_time < DOUBLE_CLICK_INTERVAL &&
243+ last_selection_ == selection_ && selection_ != no_selection_index()) {
244 double_clicked(selection_);
245-
246+ }
247 return true;
248 }
249 default:
250@@ -448,14 +445,14 @@
251 void Table<void*>::move_selection(const int32_t offset) {
252 if (!has_selection())
253 return;
254- int32_t new_selection = selection_ + offset;
255+ int32_t new_selection = (is_multiselect_ ? last_multiselect_ : selection_) + offset;
256
257 if (new_selection < 0)
258 new_selection = 0;
259 else if (static_cast<uint32_t>(new_selection) > entry_records_.size() - 1)
260 new_selection = entry_records_.size() - 1;
261
262- select(static_cast<uint32_t>(new_selection));
263+ multiselect(new_selection);
264
265 // Scroll to newly selected entry
266 if (scrollbar_) {
267@@ -483,14 +480,67 @@
268 * Args: i the entry to select
269 */
270 void Table<void*>::select(const uint32_t i) {
271- if (empty() || selection_ == i)
272+ if (empty() || selection_ == i || i == no_selection_index())
273 return;
274
275 selection_ = i;
276+ if (is_multiselect_) {
277+ multiselect_.insert(selection_);
278+ }
279
280 selected(selection_);
281 }
282
283+void Table<void*>::multiselect(uint32_t row) {
284+ if (is_multiselect_) {
285+ // Ranged selection with Shift
286+ if (shift_down_) {
287+ multiselect_.clear();
288+ if (has_selection()) {
289+ const uint32_t last_selected = selection_index();
290+ const uint32_t lower_bound = std::min(row, selection_);
291+ const uint32_t upper_bound = std::max(row, selection_);
292+ for (uint32_t i = lower_bound; i <= upper_bound; ++i) {
293+ toggle_entry(i);
294+ }
295+ select(last_selected);
296+ } else {
297+ select(toggle_entry(row));
298+ }
299+ } else {
300+ // Single selection without Ctrl
301+ if (!ctrl_down_) {
302+ multiselect_.clear();
303+ }
304+ select(toggle_entry(row));
305+ }
306+ last_multiselect_ = row;
307+ } else {
308+ select(row);
309+ }
310+}
311+
312+/**
313+ * Adds/removes the row from multiselect.
314+ * Returns the row that should be selected afterwards, or no_selection_index() if
315+ * the multiselect is empty.
316+ */
317+uint32_t Table<void*>::toggle_entry(uint32_t row) {
318+ assert(is_multiselect_);
319+ if (multiselect_.count(row)) {
320+ multiselect_.erase(row);
321+ // Find last selection
322+ if (multiselect_.empty()) {
323+ return no_selection_index();
324+ } else {
325+ return *multiselect_.lower_bound(0);
326+ }
327+ } else {
328+ multiselect_.insert(row);
329+ return row;
330+ }
331+}
332+
333 /**
334 * Add a new entry to the table.
335 */
336@@ -498,11 +548,6 @@
337 EntryRecord& result = *new EntryRecord(entry);
338 entry_records_.push_back(&result);
339 result.data_.resize(columns_.size());
340- for (size_t i = 0; i < columns_.size(); ++i) {
341- if (columns_.at(i).is_checkbox_column) {
342- result.data_.at(i).d_picture = g_gr->images().get("images/ui_basic/checkbox_empty.png");
343- }
344- }
345
346 if (do_select) {
347 select(entry_records_.size() - 1);
348@@ -524,6 +569,7 @@
349 */
350 void Table<void*>::remove(const uint32_t i) {
351 assert(i < entry_records_.size());
352+ multiselect_.clear();
353
354 const EntryRecordVector::iterator it = entry_records_.begin() + i;
355 delete *it;
356@@ -533,14 +579,18 @@
357 } else if (selection_ > i && selection_ != no_selection_index()) {
358 selection_--;
359 }
360+ if (is_multiselect_ && selection_ != no_selection_index()) {
361+ multiselect_.insert(selection_);
362+ }
363 layout();
364 }
365
366 bool Table<void*>::sort_helper(uint32_t a, uint32_t b) {
367- if (sort_descending_)
368+ if (sort_descending_) {
369 return columns_[sort_column_].compare(b, a);
370- else
371+ } else {
372 return columns_[sort_column_].compare(a, b);
373+ }
374 }
375
376 void Table<void*>::layout() {
377@@ -601,25 +651,27 @@
378 }
379
380 /**
381- * Sort the table alphabetically. Make sure that the current selection stays
382+ * Sort the table alphabetically, or if set_column_compare has been set,
383+ * according to its compare function.
384+ * Make sure that the current selection stays
385 * valid (though it might scroll out of visibility).
386- * Only the subarea [start,end) is sorted.
387- * For example you might want to sort directories for themselves at the
388- * top of list and files at the bottom.
389+ * Only the subarea [lower_bound, upper_bound) is sorted.
390+ * For example, you might want to sort directories for themselves at the
391+ * top of the list, and files at the bottom.
392 */
393-void Table<void*>::sort(const uint32_t Begin, uint32_t End) {
394+void Table<void*>::sort(const uint32_t lower_bound, uint32_t upper_bound) {
395 assert(columns_.at(sort_column_).btn);
396 assert(sort_column_ < columns_.size());
397
398- if (End > size())
399- End = size();
400+ if (upper_bound > size())
401+ upper_bound = size();
402
403 std::vector<uint32_t> indices;
404 std::vector<EntryRecord*> copy;
405
406- indices.reserve(End - Begin);
407- copy.reserve(End - Begin);
408- for (uint32_t i = Begin; i < End; ++i) {
409+ indices.reserve(upper_bound - lower_bound);
410+ copy.reserve(upper_bound - lower_bound);
411+ for (uint32_t i = lower_bound; i < upper_bound; ++i) {
412 indices.push_back(i);
413 copy.push_back(entry_records_[i]);
414 }
415@@ -628,23 +680,24 @@
416 indices.begin(), indices.end(), boost::bind(&Table<void*>::sort_helper, this, _1, _2));
417
418 uint32_t newselection = selection_;
419- for (uint32_t i = Begin; i < End; ++i) {
420- uint32_t from = indices[i - Begin];
421- entry_records_[i] = copy[from - Begin];
422- if (selection_ == from)
423+ std::set<uint32_t> new_multiselect;
424+ for (uint32_t i = lower_bound; i < upper_bound; ++i) {
425+ uint32_t from = indices[i - lower_bound];
426+ entry_records_[i] = copy[from - lower_bound];
427+ if (selection_ == from) {
428 newselection = i;
429+ }
430+ if (is_multiselect_ && multiselect_.count(from) == 1) {
431+ new_multiselect.insert(i);
432+ }
433 }
434 selection_ = newselection;
435-}
436-
437-/**
438- * Default comparison for checkbox columns:
439- * checked items come before unchecked ones.
440- */
441-bool Table<void*>::default_compare_checkbox(uint32_t column, uint32_t a, uint32_t b) {
442- EntryRecord& ea = get_record(a);
443- EntryRecord& eb = get_record(b);
444- return ea.is_checked(column) && !eb.is_checked(column);
445+ if (is_multiselect_) {
446+ multiselect_.clear();
447+ for (const uint32_t entry : new_multiselect) {
448+ multiselect_.insert(entry);
449+ }
450+ }
451 }
452
453 bool Table<void*>::default_compare_string(uint32_t column, uint32_t a, uint32_t b) {
454@@ -653,7 +706,7 @@
455 return ea.get_string(column) < eb.get_string(column);
456 }
457
458-Table<void*>::EntryRecord::EntryRecord(void* const e) : entry_(e), use_clr(false) {
459+Table<void*>::EntryRecord::EntryRecord(void* const e) : entry_(e) {
460 }
461
462 void Table<void*>::EntryRecord::set_picture(uint8_t const col,
463
464=== modified file 'src/ui_basic/table.h'
465--- src/ui_basic/table.h 2017-02-23 19:38:51 +0000
466+++ src/ui_basic/table.h 2017-02-26 12:43:34 +0000
467@@ -21,6 +21,7 @@
468 #define WL_UI_BASIC_TABLE_H
469
470 #include <limits>
471+#include <set>
472 #include <vector>
473
474 #include <boost/function.hpp>
475@@ -35,6 +36,7 @@
476 struct Scrollbar;
477 struct Button;
478
479+enum class TableRows { kSingle, kMulti, kSingleDescending, kMultiDescending };
480 enum class TableColumnType { kFixed, kFlexible };
481
482 /** A table with columns and lines.
483@@ -57,7 +59,7 @@
484 uint32_t w,
485 uint32_t h,
486 const Image* button_background = g_gr->images().get("images/ui_basic/but3.png"),
487- bool descending = false);
488+ TableRows rowtype = TableRows::kSingle);
489 ~Table();
490
491 boost::signals2::signal<void(uint32_t)> selected;
492@@ -68,8 +70,7 @@
493 const std::string& title = std::string(),
494 const std::string& tooltip = std::string(),
495 Align = UI::Align::kLeft,
496- TableColumnType column_type = TableColumnType::kFixed,
497- bool is_checkbox_column = false);
498+ TableColumnType column_type = TableColumnType::kFixed);
499
500 void set_column_title(uint8_t col, const std::string& title);
501
502@@ -78,7 +79,7 @@
503 uint8_t get_sort_colum() const;
504 bool get_sort_descending() const;
505
506- void sort(uint32_t Begin = 0, uint32_t End = std::numeric_limits<uint32_t>::max());
507+ void sort(uint32_t lower_bound = 0, uint32_t upper_bound = std::numeric_limits<uint32_t>::max());
508 void remove(uint32_t);
509
510 EntryRecord& add(void* const entry, const bool select_this = false);
511@@ -89,11 +90,15 @@
512 static uint32_t no_selection_index();
513 bool has_selection() const;
514 uint32_t selection_index() const;
515+ std::set<uint32_t> selections() const;
516+ void clear_selections();
517 EntryRecord& get_record(uint32_t) const;
518 static Entry get(const EntryRecord&);
519 EntryRecord* find(Entry) const;
520
521 void select(uint32_t);
522+ void multiselect(uint32_t row);
523+ uint32_t toggle_entry(uint32_t row);
524 void move_selection(int32_t offset);
525 struct NoSelection : public std::exception {
526 char const* what() const noexcept override {
527@@ -139,10 +144,6 @@
528 return clr;
529 }
530
531- void set_checked(uint8_t col, bool checked);
532- void toggle(uint8_t col);
533- bool is_checked(uint8_t col) const;
534-
535 private:
536 friend class Table<void*>;
537 void* entry_;
538@@ -151,10 +152,6 @@
539 struct Data {
540 const Image* d_picture;
541 std::string d_string;
542- bool d_checked;
543-
544- Data() : d_checked(false) {
545- }
546 };
547 std::vector<Data> data_;
548 };
549@@ -172,7 +169,7 @@
550 uint32_t w,
551 uint32_t h,
552 const Image* button_background = g_gr->images().get("images/ui_basic/but3.png"),
553- bool descending = false);
554+ TableRows rowtype = TableRows::kSingle);
555 ~Table();
556
557 boost::signals2::signal<void(uint32_t)> selected;
558@@ -182,8 +179,7 @@
559 const std::string& title = std::string(),
560 const std::string& tooltip = std::string(),
561 Align = UI::Align::kLeft,
562- TableColumnType column_type = TableColumnType::kFixed,
563- bool is_checkbox_column = false);
564+ TableColumnType column_type = TableColumnType::kFixed);
565
566 void set_column_title(uint8_t col, const std::string& title);
567 void set_column_compare(uint8_t col, const CompareFn& fn);
568@@ -203,7 +199,7 @@
569 sort_descending_ = descending;
570 }
571
572- void sort(uint32_t Begin = 0, uint32_t End = std::numeric_limits<uint32_t>::max());
573+ void sort(uint32_t lower_bound = 0, uint32_t upper_bound = std::numeric_limits<uint32_t>::max());
574 void remove(uint32_t);
575
576 EntryRecord& add(void* entry = nullptr, bool select = false);
577@@ -224,6 +220,12 @@
578 bool has_selection() const {
579 return selection_ != no_selection_index();
580 }
581+ /// The set of highlighted entries in multiselect mode
582+ std::set<uint32_t> selections() const {
583+ return multiselect_;
584+ }
585+ void clear_selections();
586+
587 uint32_t selection_index() const {
588 return selection_;
589 }
590@@ -237,6 +239,8 @@
591 EntryRecord* find(const void* entry) const;
592
593 void select(uint32_t);
594+ void multiselect(uint32_t row);
595+ uint32_t toggle_entry(uint32_t row);
596 void move_selection(int32_t offset);
597 struct NoSelection : public std::exception {
598 char const* what() const noexcept override {
599@@ -274,7 +278,6 @@
600 bool handle_key(bool down, SDL_Keysym code) override;
601
602 private:
603- bool default_compare_checkbox(uint32_t column, uint32_t a, uint32_t b);
604 bool default_compare_string(uint32_t column, uint32_t a, uint32_t b);
605 bool sort_helper(uint32_t a, uint32_t b);
606 void layout() override;
607@@ -283,7 +286,6 @@
608 Button* btn;
609 uint32_t width;
610 Align alignment;
611- bool is_checkbox_column;
612 CompareFn compare;
613 };
614 using Columns = std::vector<Column>;
615@@ -300,12 +302,18 @@
616 UI::Button* scrollbar_filler_button_;
617 int32_t scrollpos_; // in pixels
618 uint32_t selection_;
619+ uint32_t last_multiselect_; // Remembers last selected element in multiselect mode for keyboard
620+ // navigation
621+ std::set<uint32_t> multiselect_;
622 uint32_t last_click_time_;
623 uint32_t last_selection_; // for double clicks
624 Columns::size_type sort_column_;
625 bool sort_descending_;
626 // This column will grow/shrink depending on the scrollbar being present
627 size_t flexible_column_;
628+ bool is_multiselect_;
629+ bool ctrl_down_; // Whether the ctrl key is being pressed
630+ bool shift_down_; // Whether the shift key is being pressed
631
632 void header_button_clicked(Columns::size_type);
633 using EntryRecordVector = std::vector<EntryRecord*>;
634@@ -322,8 +330,8 @@
635 uint32_t w,
636 uint32_t h,
637 const Image* button_background = g_gr->images().get("images/ui_basic/but3.png"),
638- const bool descending = false)
639- : Base(parent, x, y, w, h, button_background, descending) {
640+ TableRows rowtype = TableRows::kSingle)
641+ : Base(parent, x, y, w, h, button_background, rowtype) {
642 }
643
644 EntryRecord& add(Entry const* const entry = 0, bool const select_this = false) {
645@@ -352,8 +360,8 @@
646 uint32_t w,
647 uint32_t h,
648 const Image* button_background = g_gr->images().get("images/ui_basic/but3.png"),
649- const bool descending = false)
650- : Base(parent, x, y, w, h, button_background, descending) {
651+ TableRows rowtype = TableRows::kSingle)
652+ : Base(parent, x, y, w, h, button_background, rowtype) {
653 }
654
655 EntryRecord& add(Entry* const entry = 0, bool const select_this = false) {
656@@ -382,8 +390,8 @@
657 uint32_t w,
658 uint32_t h,
659 const Image* button_background = g_gr->images().get("images/ui_basic/but3.png"),
660- const bool descending = false)
661- : Base(parent, x, y, w, h, button_background, descending) {
662+ TableRows rowtype = TableRows::kSingle)
663+ : Base(parent, x, y, w, h, button_background, rowtype) {
664 }
665
666 EntryRecord& add(const Entry& entry, bool const select_this = false) {
667@@ -416,8 +424,8 @@
668 uint32_t w,
669 uint32_t h,
670 const Image* button_background = g_gr->images().get("images/ui_basic/but3.png"),
671- const bool descending = false)
672- : Base(parent, x, y, w, h, button_background, descending) {
673+ TableRows rowtype = TableRows::kSingle)
674+ : Base(parent, x, y, w, h, button_background, rowtype) {
675 }
676
677 EntryRecord& add(Entry& entry, bool const select_this = false) {
678@@ -452,8 +460,8 @@
679 uint32_t w,
680 uint32_t h,
681 const Image* button_background = g_gr->images().get("images/ui_basic/but3.png"),
682- const bool descending = false)
683- : Base(parent, x, y, w, h, button_background, descending) {
684+ TableRows rowtype = TableRows::kSingle)
685+ : Base(parent, x, y, w, h, button_background, rowtype) {
686 }
687
688 EntryRecord& add(uintptr_t const entry, bool const select_this = false) {
689@@ -484,8 +492,8 @@
690 uint32_t w,
691 uint32_t h,
692 const Image* button_background = g_gr->images().get("images/ui_basic/but3.png"),
693- const bool descending = false)
694- : Base(parent, x, y, w, h, button_background, descending) {
695+ TableRows rowtype = TableRows::kSingle)
696+ : Base(parent, x, y, w, h, button_background, rowtype) {
697 }
698 };
699 }
700
701=== modified file 'src/ui_fsmenu/loadgame.cc'
702--- src/ui_fsmenu/loadgame.cc 2017-02-23 19:38:51 +0000
703+++ src/ui_fsmenu/loadgame.cc 2017-02-26 12:43:34 +0000
704@@ -91,7 +91,7 @@
705 tablew_,
706 tableh_,
707 g_gr->images().get("images/ui_basic/but3.png"),
708- true),
709+ UI::TableRows::kMultiDescending),
710
711 is_replay_(is_replay),
712 // Main title
713@@ -151,11 +151,11 @@
714 g_gr->images().get("images/ui_basic/but0.png"),
715 _("Delete")),
716
717- ta_errormessage_(this,
718- right_column_x_,
719- get_y_from_preceding(ta_mapname_) + 2 * padding_,
720- get_right_column_w(right_column_x_),
721- delete_.get_y() - get_y_from_preceding(ta_mapname_) - 6 * padding_),
722+ ta_long_generic_message_(this,
723+ right_column_x_,
724+ get_y_from_preceding(ta_mapname_) + 2 * padding_,
725+ get_right_column_w(right_column_x_),
726+ delete_.get_y() - get_y_from_preceding(ta_mapname_) - 6 * padding_),
727
728 minimap_y_(get_y_from_preceding(ta_win_condition_) + 3 * padding_),
729 minimap_w_(get_right_column_w(right_column_x_)),
730@@ -271,52 +271,104 @@
731 if (!table_.has_selection()) {
732 return;
733 }
734- const SavegameData& gamedata = games_data_[table_.get_selected()];
735-
736- std::string message =
737- (boost::format("%s %s\n") % label_mapname_.get_text() % gamedata.mapname).str();
738-
739- message = (boost::format("%s %s %s\n") % message % label_win_condition_.get_text() %
740- gamedata.wincondition)
741- .str();
742-
743- message =
744- (boost::format("%s %s %s\n") % message % _("Save Date:") % gamedata.savedatestring).str();
745-
746- message = (boost::format("%s %s %s\n") % message % label_gametime_.get_text() %
747- gametimestring(gamedata.gametime))
748- .str();
749-
750- message =
751- (boost::format("%s %s %s\n\n") % message % label_players_.get_text() % gamedata.nrplayers)
752- .str();
753-
754- message = (boost::format("%s %s %s\n") % message % _("Filename:") % gamedata.filename).str();
755-
756- if (is_replay_) {
757- message =
758- (boost::format("%s\n\n%s") % _("Do you really want to delete this replay?") % message)
759+ std::set<uint32_t> selections = table_.selections();
760+ size_t no_selections = selections.size();
761+ std::string message;
762+ if (no_selections > 1) {
763+ if (is_replay_) {
764+ message = (boost::format(ngettext("Do you really want to delete this %d replay?",
765+ "Do you really want to delete these %d replays?",
766+ no_selections)) %
767+ no_selections)
768+ .str();
769+ } else {
770+ message = (boost::format(ngettext("Do you really want to delete this %d game?",
771+ "Do you really want to delete these %d games?",
772+ no_selections)) %
773+ no_selections)
774+ .str();
775+ }
776+ message = (boost::format("%s\n%s") % message % filename_list_string()).str();
777+
778+ } else {
779+ const SavegameData& gamedata = games_data_[table_.get_selected()];
780+
781+ message = (boost::format("%s %s\n") % label_mapname_.get_text() % gamedata.mapname).str();
782+
783+ message = (boost::format("%s %s %s\n") % message % label_win_condition_.get_text() %
784+ gamedata.wincondition)
785+ .str();
786+
787+ message =
788+ (boost::format("%s %s %s\n") % message % _("Save Date:") % gamedata.savedatestring).str();
789+
790+ message = (boost::format("%s %s %s\n") % message % label_gametime_.get_text() %
791+ gametimestring(gamedata.gametime))
792+ .str();
793+
794+ message =
795+ (boost::format("%s %s %s\n\n") % message % label_players_.get_text() % gamedata.nrplayers)
796 .str();
797- } else {
798- message =
799- (boost::format("%s\n\n%s") % _("Do you really want to delete this game?") % message).str();
800+
801+ message = (boost::format("%s %s %s\n") % message % _("Filename:") % gamedata.filename).str();
802+
803+ if (is_replay_) {
804+ message =
805+ (boost::format("%s\n\n%s") % _("Do you really want to delete this replay?") % message)
806+ .str();
807+ } else {
808+ message =
809+ (boost::format("%s\n\n%s") % _("Do you really want to delete this game?") % message)
810+ .str();
811+ }
812 }
813
814 UI::WLMessageBox confirmationBox(
815- this, _("Confirm deleting file"), message, UI::WLMessageBox::MBoxType::kOkCancel);
816+ this, ngettext("Confirm deleting file", "Confirm deleting files", no_selections), message,
817+ UI::WLMessageBox::MBoxType::kOkCancel);
818+
819 if (confirmationBox.run<UI::Panel::Returncodes>() == UI::Panel::Returncodes::kOk) {
820- g_fs->fs_unlink(gamedata.filename);
821- if (is_replay_) {
822- g_fs->fs_unlink(gamedata.filename + WLGF_SUFFIX);
823+ for (const uint32_t index : selections) {
824+ const std::string& deleteme = games_data_[table_.get(table_.get_record(index))].filename;
825+ g_fs->fs_unlink(deleteme);
826+ if (is_replay_) {
827+ g_fs->fs_unlink(deleteme + WLGF_SUFFIX);
828+ }
829 }
830 fill_table();
831 }
832 }
833
834+std::string FullscreenMenuLoadGame::filename_list_string() {
835+ std::set<uint32_t> selections = table_.selections();
836+ boost::format message;
837+ int counter = 0;
838+ for (const uint32_t index : selections) {
839+ ++counter;
840+ // TODO(GunChleoc): We can exceed the texture size for the font renderer,
841+ // so we have to restrict this for now.
842+ if (counter > 50) {
843+ message = boost::format("%s\n%s") % message % "...";
844+ break;
845+ }
846+ const SavegameData& gamedata = games_data_[table_.get(table_.get_record(index))];
847+
848+ if (gamedata.errormessage.empty()) {
849+ message =
850+ boost::format("%s\n%s") % message %
851+ /** TRANSLATORS %1% = map name, %2% = save date. */
852+ (boost::format(_("%1%, saved on %2%")) % gamedata.mapname % gamedata.savedatestring);
853+ } else {
854+ message = boost::format("%s\n%s") % message % gamedata.filename;
855+ }
856+ }
857+ return message.str();
858+}
859+
860 bool FullscreenMenuLoadGame::set_has_selection() {
861- bool has_selection = table_.has_selection();
862+ bool has_selection = table_.selections().size() < 2;
863 ok_.set_enabled(has_selection);
864- delete_.set_enabled(has_selection);
865+ delete_.set_enabled(table_.has_selection());
866
867 if (!has_selection) {
868 label_mapname_.set_text(std::string());
869@@ -344,13 +396,14 @@
870 }
871
872 void FullscreenMenuLoadGame::entry_selected() {
873+ size_t selections = table_.selections().size();
874 if (set_has_selection()) {
875
876 const SavegameData& gamedata = games_data_[table_.get_selected()];
877- ta_errormessage_.set_text(gamedata.errormessage);
878+ ta_long_generic_message_.set_text(gamedata.errormessage);
879
880 if (gamedata.errormessage.empty()) {
881- ta_errormessage_.set_visible(false);
882+ ta_long_generic_message_.set_visible(false);
883 ta_mapname_.set_text(gamedata.mapname);
884 ta_gametime_.set_text(gametimestring(gamedata.gametime));
885
886@@ -439,9 +492,16 @@
887 minimap_icon_.set_no_frame();
888 minimap_image_.reset();
889
890- ta_errormessage_.set_visible(true);
891+ ta_long_generic_message_.set_visible(true);
892 ok_.set_enabled(false);
893 }
894+ } else if (selections > 1) {
895+ label_mapname_.set_text(
896+ (boost::format(ngettext("Selected %d file:", "Selected %d files:", selections)) %
897+ selections)
898+ .str());
899+ ta_long_generic_message_.set_visible(true);
900+ ta_long_generic_message_.set_text(filename_list_string());
901 }
902 }
903
904
905=== modified file 'src/ui_fsmenu/loadgame.h'
906--- src/ui_fsmenu/loadgame.h 2017-01-25 18:55:59 +0000
907+++ src/ui_fsmenu/loadgame.h 2017-02-26 12:43:34 +0000
908@@ -97,6 +97,7 @@
909 bool set_has_selection();
910 bool compare_date_descending(uint32_t, uint32_t);
911 void clicked_delete();
912+ std::string filename_list_string();
913
914 UI::Table<uintptr_t const> table_;
915
916@@ -116,7 +117,7 @@
917
918 UI::Button delete_;
919
920- UI::MultilineTextarea ta_errormessage_;
921+ UI::MultilineTextarea ta_long_generic_message_;
922
923 int32_t const minimap_y_, minimap_w_, minimap_h_;
924 UI::Icon minimap_icon_;
925
926=== modified file 'src/ui_fsmenu/mapselect.cc'
927--- src/ui_fsmenu/mapselect.cc 2017-02-25 13:27:40 +0000
928+++ src/ui_fsmenu/mapselect.cc 2017-02-26 12:43:34 +0000
929@@ -48,7 +48,7 @@
930 // Main title
931 title_(this, 0, 0, _("Choose a map"), UI::Align::kCenter),
932 checkboxes_(this, 0, 0, UI::Box::Vertical, 0, 0, 2 * padding_),
933- table_(this, tablex_, tabley_, tablew_, tableh_, false),
934+ table_(this, tablex_, tabley_, tablew_, tableh_),
935 map_details_(this,
936 right_column_x_,
937 tabley_,
938
939=== modified file 'src/wlapplication.cc'
940--- src/wlapplication.cc 2017-02-23 17:58:25 +0000
941+++ src/wlapplication.cc 2017-02-26 12:43:34 +0000
942@@ -1397,7 +1397,7 @@
943 if (is_autogenerated_and_expired(filename)) {
944 log("Deleting replay %s\n", filename.c_str());
945 g_fs->fs_unlink(filename);
946- g_fs->fs_unlink(filename + ".wgf");
947+ g_fs->fs_unlink(filename + WLGF_SUFFIX);
948 }
949 }
950 }
951
952=== modified file 'src/wui/game_message_menu.cc'
953--- src/wui/game_message_menu.cc 2017-02-23 17:58:25 +0000
954+++ src/wui/game_message_menu.cc 2017-02-26 12:43:34 +0000
955@@ -60,9 +60,9 @@
956 UI::MultilineTextarea::ScrollMode::kScrollNormalForced),
957 mode(Inbox) {
958
959- list = new UI::Table<uintptr_t>(this, kPadding, kButtonSize + 2 * kPadding,
960- kWindowWidth - 2 * kPadding, kTableHeight,
961- g_gr->images().get("images/ui_basic/but1.png"));
962+ list = new UI::Table<uintptr_t>(
963+ this, kPadding, kButtonSize + 2 * kPadding, kWindowWidth - 2 * kPadding, kTableHeight,
964+ g_gr->images().get("images/ui_basic/but1.png"), UI::TableRows::kMulti);
965 list->selected.connect(boost::bind(&GameMessageMenu::selected, this, _1));
966 list->double_clicked.connect(boost::bind(&GameMessageMenu::double_clicked, this, _1));
967 list->add_column(kWindowWidth - 2 * kPadding - 60 - 60 - 75, _("Title"));
968@@ -118,12 +118,8 @@
969 archivebtn_ = new UI::Button(this, "archive_or_restore_selected_messages", kPadding,
970 kWindowHeight - kPadding - kButtonSize, kButtonSize, kButtonSize,
971 g_gr->images().get("images/ui_basic/but2.png"),
972- g_gr->images().get("images/wui/messages/message_archive.png"),
973- /** TRANSLATORS: %s is a tooltip, Del is the corresponding hotkey */
974- (boost::format(_("Del: %s"))
975- /** TRANSLATORS: Tooltip in the messages window */
976- % _("Archive selected message"))
977- .str());
978+ g_gr->images().get("images/wui/messages/message_archive.png"));
979+ update_archive_button_tooltip();
980 archivebtn_->sigclicked.connect(boost::bind(&GameMessageMenu::archive_or_restore, this));
981
982 togglemodebtn_ = new UI::Button(
983@@ -231,6 +227,14 @@
984 return false; // shouldn't happen
985 }
986
987+bool GameMessageMenu::should_be_hidden(const Widelands::Message& message) {
988+ // Wrong box
989+ return ((mode == Archive) != (message.status() == Message::Status::kArchived)) ||
990+ // Filtered out
991+ (message_filter_ != Message::Type::kAllMessages &&
992+ message.message_type_category() != message_filter_);
993+}
994+
995 static char const* const status_picture_filename[] = {"images/wui/messages/message_new.png",
996 "images/wui/messages/message_read.png",
997 "images/wui/messages/message_archived.png"};
998@@ -248,13 +252,15 @@
999
1000 void GameMessageMenu::think() {
1001 MessageQueue& mq = iplayer().player().messages();
1002+ size_t no_selections = list->selections().size();
1003+ size_t list_size = list->size();
1004
1005 // Update messages in the list and remove messages
1006 // that should no longer be shown
1007 for (uint32_t j = list->size(); j; --j) {
1008 MessageId id_((*list)[j - 1]);
1009 if (Message const* const message = mq[id_]) {
1010- if ((mode == Archive) != (message->status() == Message::Status::kArchived)) {
1011+ if (should_be_hidden(*message)) {
1012 list->remove(j - 1);
1013 } else {
1014 update_record(list->get_record(j - 1), *message);
1015@@ -268,28 +274,13 @@
1016 for (const auto& temp_message : mq) {
1017 MessageId const id = temp_message.first;
1018 const Message& message = *temp_message.second;
1019- Message::Status const status = message.status();
1020- if ((mode == Archive) != (status == Message::Status::kArchived))
1021- continue;
1022- if (!list->find(id.value())) {
1023+ if (!should_be_hidden(message) && !list->find(id.value())) {
1024 UI::Table<uintptr_t>::EntryRecord& er = list->add(id.value());
1025 update_record(er, message);
1026 list->sort();
1027 }
1028 }
1029
1030- // Filter message type
1031- if (message_filter_ != Message::Type::kAllMessages) {
1032- for (uint32_t j = list->size(); j; --j) {
1033- MessageId id_((*list)[j - 1]);
1034- if (Message const* const message = mq[id_]) {
1035- if (message->message_type_category() != message_filter_) {
1036- list->remove(j - 1);
1037- }
1038- }
1039- }
1040- }
1041-
1042 if (list->size()) {
1043 if (!list->has_selection())
1044 list->select(0);
1045@@ -297,6 +288,10 @@
1046 centerviewbtn_->set_enabled(false);
1047 message_body.set_text(std::string());
1048 }
1049+
1050+ if (list_size != list->size() || no_selections != list->selections().size()) {
1051+ update_archive_button_tooltip();
1052+ }
1053 }
1054
1055 void GameMessageMenu::update_record(UI::Table<uintptr_t>::EntryRecord& er,
1056@@ -332,6 +327,7 @@
1057 "<p font-size=8> <br></p></rt>%s") %
1058 message->heading() % message->body())
1059 .str());
1060+ update_archive_button_tooltip();
1061 return;
1062 }
1063 }
1064@@ -411,33 +407,27 @@
1065 }
1066
1067 void GameMessageMenu::archive_or_restore() {
1068+ if (!list->has_selection()) {
1069+ return;
1070+ }
1071 Widelands::Game& game = iplayer().game();
1072- uint32_t const gametime = game.get_gametime();
1073- Widelands::Player& player = iplayer().player();
1074- Widelands::PlayerNumber const plnum = player.player_number();
1075- bool work_done = false;
1076-
1077- switch (mode) {
1078- case Inbox:
1079- // Archive highlighted message
1080- if (!work_done) {
1081- if (!list->has_selection())
1082- return;
1083-
1084+ const Widelands::PlayerNumber plnum = iplayer().player().player_number();
1085+
1086+ std::set<uint32_t> selections = list->selections();
1087+ for (const uint32_t index : selections) {
1088+ const uintptr_t selected_record = list->get(list->get_record(index));
1089+ switch (mode) {
1090+ case Inbox:
1091+ // Archive highlighted message
1092 game.send_player_command(*new Widelands::CmdMessageSetStatusArchived(
1093- gametime, plnum, MessageId(list->get_selected())));
1094- }
1095- break;
1096- case Archive:
1097- // Restore highlighted message
1098- if (!work_done) {
1099- if (!list->has_selection())
1100- return;
1101-
1102+ game.get_gametime(), plnum, MessageId(selected_record)));
1103+ break;
1104+ case Archive:
1105+ // Restore highlighted message
1106 game.send_player_command(*new Widelands::CmdMessageSetStatusRead(
1107- gametime, plnum, MessageId(list->get_selected())));
1108+ game.get_gametime(), plnum, MessageId(selected_record)));
1109+ break;
1110 }
1111- break;
1112 }
1113 }
1114
1115@@ -456,6 +446,7 @@
1116 * @param msgtype the types of messages to show
1117 */
1118 void GameMessageMenu::filter_messages(Widelands::Message::Type const msgtype) {
1119+ list->clear_selections();
1120 switch (msgtype) {
1121 case Widelands::Message::Type::kGeologists:
1122 toggle_filter_messages_button(*geologistsbtn_, msgtype);
1123@@ -590,11 +581,6 @@
1124 mode = Archive;
1125 set_title(_("Messages: Archive"));
1126 archivebtn_->set_pic(g_gr->images().get("images/wui/messages/message_restore.png"));
1127- /** TRANSLATORS: %s is a tooltip, Del is the corresponding hotkey */
1128- archivebtn_->set_tooltip((boost::format(_("Del: %s"))
1129- /** TRANSLATORS: Tooltip in the messages window */
1130- % _("Restore selected message"))
1131- .str());
1132 togglemodebtn_->set_pic(g_gr->images().get("images/wui/messages/message_new.png"));
1133 togglemodebtn_->set_tooltip(_("Show Inbox"));
1134 break;
1135@@ -602,13 +588,52 @@
1136 mode = Inbox;
1137 set_title(_("Messages: Inbox"));
1138 archivebtn_->set_pic(g_gr->images().get("images/wui/messages/message_archive.png"));
1139- /** TRANSLATORS: %s is a tooltip, Del is the corresponding hotkey */
1140- archivebtn_->set_tooltip((boost::format(_("Del: %s"))
1141- /** TRANSLATORS: Tooltip in the messages window */
1142- % _("Archive selected message"))
1143- .str());
1144 togglemodebtn_->set_pic(g_gr->images().get("images/wui/messages/message_archived.png"));
1145 togglemodebtn_->set_tooltip(_("Show Archive"));
1146 break;
1147 }
1148+ update_archive_button_tooltip();
1149+}
1150+
1151+void GameMessageMenu::update_archive_button_tooltip() {
1152+ if (list->empty() || !list->has_selection()) {
1153+ archivebtn_->set_tooltip("");
1154+ archivebtn_->set_enabled(false);
1155+ return;
1156+ }
1157+ archivebtn_->set_enabled(true);
1158+ std::string button_tooltip = "";
1159+ size_t no_selections = list->selections().size();
1160+ switch (mode) {
1161+ case Archive:
1162+ if (no_selections > 1) {
1163+ /** TRANSLATORS: Tooltip in the messages window. There is a separate string for 1 message.
1164+ */
1165+ button_tooltip =
1166+ (boost::format(ngettext("Restore the selected %d message",
1167+ "Restore the selected %d messages", no_selections)) %
1168+ no_selections)
1169+ .str();
1170+ } else {
1171+ /** TRANSLATORS: Tooltip in the messages window */
1172+ button_tooltip = _("Restore selected message");
1173+ }
1174+ break;
1175+ case Inbox:
1176+ if (no_selections > 1) {
1177+ /** TRANSLATORS: Tooltip in the messages window. There is a separate string for 1 message.
1178+ */
1179+ button_tooltip =
1180+ (boost::format(ngettext("Archive the selected %d message",
1181+ "Archive the selected %d messages", no_selections)) %
1182+ no_selections)
1183+ .str();
1184+ } else {
1185+ /** TRANSLATORS: Tooltip in the messages window */
1186+ button_tooltip = _("Archive selected message");
1187+ }
1188+ break;
1189+ }
1190+ /** TRANSLATORS: %s is a tooltip, Del is the corresponding hotkey */
1191+ archivebtn_->set_tooltip((boost::format(_("Del: %s")) % button_tooltip).str());
1192 }
1193
1194=== modified file 'src/wui/game_message_menu.h'
1195--- src/wui/game_message_menu.h 2017-01-25 18:55:59 +0000
1196+++ src/wui/game_message_menu.h 2017-02-26 12:43:34 +0000
1197@@ -59,6 +59,8 @@
1198 bool compare_status(uint32_t a, uint32_t b);
1199 bool compare_type(uint32_t a, uint32_t b);
1200 bool compare_time_sent(uint32_t a, uint32_t b);
1201+ bool should_be_hidden(const Widelands::Message& message);
1202+
1203 void archive_or_restore();
1204 void toggle_mode();
1205 void center_view();
1206@@ -67,6 +69,7 @@
1207 void set_filter_messages_tooltips();
1208 std::string display_message_type_icon(Widelands::Message);
1209 void update_record(UI::Table<uintptr_t>::EntryRecord& er, const Widelands::Message&);
1210+ void update_archive_button_tooltip();
1211
1212 UI::Table<uintptr_t>* list;
1213 UI::MultilineTextarea message_body;
1214
1215=== modified file 'src/wui/maptable.cc'
1216--- src/wui/maptable.cc 2017-02-23 17:58:25 +0000
1217+++ src/wui/maptable.cc 2017-02-26 12:43:34 +0000
1218@@ -26,10 +26,8 @@
1219 #include "graphic/graphic.h"
1220 #include "io/filesystem/filesystem.h"
1221
1222-MapTable::MapTable(
1223- UI::Panel* parent, int32_t x, int32_t y, uint32_t w, uint32_t h, const bool descending)
1224- : UI::Table<uintptr_t>(
1225- parent, x, y, w, h, g_gr->images().get("images/ui_basic/but3.png"), descending) {
1226+MapTable::MapTable(UI::Panel* parent, int32_t x, int32_t y, uint32_t w, uint32_t h)
1227+ : UI::Table<uintptr_t>(parent, x, y, w, h, g_gr->images().get("images/ui_basic/but3.png")) {
1228
1229 /** TRANSLATORS: Column title for number of players in map list */
1230 add_column(35, _("Pl."), _("Number of players"), UI::Align::kCenter);
1231
1232=== modified file 'src/wui/maptable.h'
1233--- src/wui/maptable.h 2017-01-25 18:55:59 +0000
1234+++ src/wui/maptable.h 2017-02-26 12:43:34 +0000
1235@@ -32,7 +32,7 @@
1236 */
1237 class MapTable : public UI::Table<uintptr_t> {
1238 public:
1239- MapTable(UI::Panel* parent, int32_t x, int32_t y, uint32_t w, uint32_t h, const bool descending);
1240+ MapTable(UI::Panel* parent, int32_t x, int32_t y, uint32_t w, uint32_t h);
1241
1242 /// Fill the table with maps and directories.
1243 void fill(const std::vector<MapData>& entries, MapData::DisplayType type);

Subscribers

People subscribed via source and target branches

to status/vote changes: