diff --git a/data/campaigns/An_Orcish_Incursion/_main.cfg b/data/campaigns/An_Orcish_Incursion/_main.cfg
index 771bc67586cf..eb82558c7c7a 100644
--- a/data/campaigns/An_Orcish_Incursion/_main.cfg
+++ b/data/campaigns/An_Orcish_Incursion/_main.cfg
@@ -11,6 +11,8 @@
name= _ "An Orcish Incursion"
abbrev= _ "AOI"
rank=10
+ start_year="8 YW"
+ end_year="9 YW"
first_scenario=01_Defend_the_Forest
define="CAMPAIGN_AN_ORCISH_INCURSION"
description=_ "Defend the forests of the elves against the first orcs to reach the Great Continent, learning valuable tactics as you do so.
diff --git a/data/campaigns/Dead_Water/_main.cfg b/data/campaigns/Dead_Water/_main.cfg
index cc799999e541..75bdef922391 100644
--- a/data/campaigns/Dead_Water/_main.cfg
+++ b/data/campaigns/Dead_Water/_main.cfg
@@ -7,6 +7,8 @@
[campaign]
id=Dead_Water
rank=170
+ start_year="626 YW"
+ end_year="627 YW"
icon="units/undead/soulless-swimmer.png~RC(magenta>blue)"
name= _ "Dead Water"
abbrev= _ "DW"
diff --git a/data/campaigns/Delfadors_Memoirs/_main.cfg b/data/campaigns/Delfadors_Memoirs/_main.cfg
index 388b66586afd..dd9ccd4c6b6a 100644
--- a/data/campaigns/Delfadors_Memoirs/_main.cfg
+++ b/data/campaigns/Delfadors_Memoirs/_main.cfg
@@ -22,6 +22,8 @@
name= _ "Delfador’s Memoirs"
abbrev=_ "DM"
rank=160
+ start_year="468 YW"
+ end_year="470 YW"
icon="units/human-magi/elder-mage.png~RC(magenta>red)"
image="data/campaigns/Delfadors_Memoirs/images/campaign_image.png"
first_scenario=01_Overture
diff --git a/data/campaigns/Descent_Into_Darkness/_main.cfg b/data/campaigns/Descent_Into_Darkness/_main.cfg
index 1335ab40531a..a6396e95a220 100644
--- a/data/campaigns/Descent_Into_Darkness/_main.cfg
+++ b/data/campaigns/Descent_Into_Darkness/_main.cfg
@@ -7,6 +7,8 @@
[campaign]
id=Descent_into_Darkness
rank=150
+ start_year="389 YW"
+ end_year="390 YW"
icon="data/campaigns/Descent_Into_Darkness/images/units/dark-mage.png~RC(magenta>red)"
image="data/campaigns/Descent_Into_Darkness/images/campaign_image.png"
name= _ "Descent into Darkness"
diff --git a/data/campaigns/Eastern_Invasion/_main.cfg b/data/campaigns/Eastern_Invasion/_main.cfg
index 17b8e6159193..45a76d8703f5 100644
--- a/data/campaigns/Eastern_Invasion/_main.cfg
+++ b/data/campaigns/Eastern_Invasion/_main.cfg
@@ -7,6 +7,8 @@
[campaign]
id=Eastern_Invasion
rank=130
+ start_year="625 YW"
+ end_year="627 YW"
icon="units/human-loyalists/general.png~RC(magenta>red)"
name= _ "Eastern Invasion"
abbrev= _ "EI"
diff --git a/data/campaigns/Heir_To_The_Throne/_main.cfg b/data/campaigns/Heir_To_The_Throne/_main.cfg
index b90096ced919..43360fe5fbef 100644
--- a/data/campaigns/Heir_To_The_Throne/_main.cfg
+++ b/data/campaigns/Heir_To_The_Throne/_main.cfg
@@ -11,6 +11,8 @@
image="data/campaigns/Heir_To_The_Throne/images/campaign_image.png"
abbrev= _ "HttT"
rank=20
+ start_year="517 YW"
+ end_year="518 YW"
define=CAMPAIGN_HEIR_TO_THE_THRONE
first_scenario=01_The_Elves_Besieged
diff --git a/data/campaigns/Legend_of_Wesmere/_main.cfg b/data/campaigns/Legend_of_Wesmere/_main.cfg
index 0624cf49ed7f..035dd79e945c 100644
--- a/data/campaigns/Legend_of_Wesmere/_main.cfg
+++ b/data/campaigns/Legend_of_Wesmere/_main.cfg
@@ -32,6 +32,8 @@
id=LOW
define=CAMPAIGN_LOW
rank=125
+ start_year="20 YW"
+ end_year="93 YW"
type=hybrid
diff --git a/data/campaigns/Liberty/_main.cfg b/data/campaigns/Liberty/_main.cfg
index fa8f13dcedf6..d4811446e0d5 100644
--- a/data/campaigns/Liberty/_main.cfg
+++ b/data/campaigns/Liberty/_main.cfg
@@ -10,6 +10,7 @@
name= _ "Liberty"
abbrev= _ "Liberty"
rank=110
+ year="501 YW"
first_scenario=01_The_Raid
define=CAMPAIGN_LIBERTY
icon="units/human-outlaws/fugitive.png~RC(magenta>red)"
diff --git a/data/campaigns/Northern_Rebirth/_main.cfg b/data/campaigns/Northern_Rebirth/_main.cfg
index 2324fc40e630..984b50116855 100644
--- a/data/campaigns/Northern_Rebirth/_main.cfg
+++ b/data/campaigns/Northern_Rebirth/_main.cfg
@@ -9,6 +9,8 @@
name= _ "Northern Rebirth"
abbrev= _ "NR"
rank=240
+ start_year="534 YW"
+ end_year="535 YW"
first_scenario=01_Breaking_the_Chains
define=CAMPAIGN_NORTHERN_REBIRTH
diff --git a/data/campaigns/Sceptre_of_Fire/_main.cfg b/data/campaigns/Sceptre_of_Fire/_main.cfg
index 0750de73d2b4..6843b9ecb17a 100644
--- a/data/campaigns/Sceptre_of_Fire/_main.cfg
+++ b/data/campaigns/Sceptre_of_Fire/_main.cfg
@@ -11,6 +11,8 @@
name= _ "The Sceptre of Fire"
abbrev= _ "SoF"
rank=215
+ start_year="25 YW"
+ end_year="40 YW"
define="CAMPAIGN_SCEPTRE_FIRE"
extra_defines=ENABLE_DWARVISH_RUNESMITH
first_scenario="1_A_Bargain_is_Struck"
diff --git a/data/campaigns/Secrets_of_the_Ancients/_main.cfg b/data/campaigns/Secrets_of_the_Ancients/_main.cfg
index bd771cca4a14..19e818e7db97 100644
--- a/data/campaigns/Secrets_of_the_Ancients/_main.cfg
+++ b/data/campaigns/Secrets_of_the_Ancients/_main.cfg
@@ -10,6 +10,8 @@
name= _ "Secrets of the Ancients"
abbrev= _ "SotA"
rank=180
+ start_year="22 YW"
+ end_year="23 YW"
first_scenario=01_Slipping_Away
extra_defines=ENABLE_ANCIENT_LICH,ENABLE_DEATH_KNIGHT
{CAMPAIGN_DIFFICULTY EASY "units/undead-skeletal/skeleton/skeleton-idle-2.png~RC(magenta>red)"( _ "Unpleasant") ( _ "Normal")}
diff --git a/data/campaigns/Son_Of_The_Black_Eye/_main.cfg b/data/campaigns/Son_Of_The_Black_Eye/_main.cfg
index 387fd2f512eb..c48e2a478ab0 100644
--- a/data/campaigns/Son_Of_The_Black_Eye/_main.cfg
+++ b/data/campaigns/Son_Of_The_Black_Eye/_main.cfg
@@ -9,6 +9,8 @@
name= _ "Son of the Black-Eye"
abbrev= _ "SotBE"
rank=220
+ start_year="842 YW"
+ end_year="858 YW"
first_scenario=01_End_of_Peace
define=CAMPAIGN_SON_OF_THE_BLACK_EYE
diff --git a/data/campaigns/The_Hammer_of_Thursagan/_main.cfg b/data/campaigns/The_Hammer_of_Thursagan/_main.cfg
index 8c63a2600c74..bfbb14d6309c 100644
--- a/data/campaigns/The_Hammer_of_Thursagan/_main.cfg
+++ b/data/campaigns/The_Hammer_of_Thursagan/_main.cfg
@@ -11,6 +11,8 @@
image="data/campaigns/The_Hammer_of_Thursagan/images/campaign_image.png"
abbrev= _ "THoT"
rank=140
+ start_year="550 YW"
+ end_year="551 YW"
define=CAMPAIGN_THE_HAMMER_OF_THURSAGAN
first_scenario=01_At_the_East_Gate
diff --git a/data/campaigns/The_Rise_Of_Wesnoth/_main.cfg b/data/campaigns/The_Rise_Of_Wesnoth/_main.cfg
index 2eedc60efc3b..9ca8e03d9da4 100644
--- a/data/campaigns/The_Rise_Of_Wesnoth/_main.cfg
+++ b/data/campaigns/The_Rise_Of_Wesnoth/_main.cfg
@@ -7,6 +7,8 @@
[campaign]
id=The_Rise_of_Wesnoth
rank=230
+ start_year="2 BW"
+ end_year="1 YW"
name= _ "The Rise of Wesnoth"
icon="data/campaigns/The_Rise_Of_Wesnoth/images/units/noble-lord.png"
image="data/campaigns/The_Rise_Of_Wesnoth/images/campaign_image.png"
diff --git a/data/campaigns/The_South_Guard/_main.cfg b/data/campaigns/The_South_Guard/_main.cfg
index 9037850a1302..ad19b5143d2a 100644
--- a/data/campaigns/The_South_Guard/_main.cfg
+++ b/data/campaigns/The_South_Guard/_main.cfg
@@ -11,6 +11,8 @@
define=CAMPAIGN_THE_SOUTH_GUARD
rank=15
+ start_year="607 YW"
+ end_year="608 YW"
icon="data/campaigns/The_South_Guard/images/deoran/horseman-commander-defend.png"
image="data/campaigns/The_South_Guard/images/campaign_image.png"
diff --git a/data/campaigns/Two_Brothers/_main.cfg b/data/campaigns/Two_Brothers/_main.cfg
index 373fe4cb7726..61b93e3f1dd7 100644
--- a/data/campaigns/Two_Brothers/_main.cfg
+++ b/data/campaigns/Two_Brothers/_main.cfg
@@ -7,6 +7,7 @@
[campaign]
id=Two_Brothers
rank=5
+ year="363 YW"
icon="units/human-loyalists/knight/knight.png~RC(magenta>red)~CROP(13,11,72,72)"
image="data/campaigns/Two_Brothers/images/campaign_image.png"
name= _ "A Tale of Two Brothers"
diff --git a/data/campaigns/Under_the_Burning_Suns/_main.cfg b/data/campaigns/Under_the_Burning_Suns/_main.cfg
index 05119147a2f6..0dec5f94dd25 100644
--- a/data/campaigns/Under_the_Burning_Suns/_main.cfg
+++ b/data/campaigns/Under_the_Burning_Suns/_main.cfg
@@ -13,6 +13,7 @@
image="data/campaigns/Under_the_Burning_Suns/images/campaign_image.png"
abbrev= _ "UtBS"
rank=250
+ year="300 AF"
define=CAMPAIGN_UNDER_THE_BURNING_SUNS
first_scenario=01_The_Morning_After
diff --git a/data/gui/window/campaign_dialog.cfg b/data/gui/window/campaign_dialog.cfg
index a6014bea79ca..a09febfa774a 100644
--- a/data/gui/window/campaign_dialog.cfg
+++ b/data/gui/window/campaign_dialog.cfg
@@ -195,6 +195,61 @@
[grid]
+ [row]
+ grow_factor = 1
+
+ [column]
+ grow_factor = 0
+
+ border = "all"
+ border_size = 5
+ horizontal_grow = true
+
+ [grid]
+ [row]
+ [column]
+ grow_factor = 0
+ border = "all"
+ border_size = 5
+ horizontal_grow = true
+
+ [label]
+ label = _"Sort by:"
+ [/label]
+ [/column]
+ [column]
+ grow_factor = 1
+ border = "all"
+ border_size = 5
+ horizontal_grow = true
+
+ [toggle_button]
+ definition = "listbox_header"
+ id = "sort_name"
+ label = _"Name"
+ tooltip = _"Sort by full campaign name in alphabetical order"
+ [/toggle_button]
+ [/column]
+ [column]
+ grow_factor = 1
+ border = "all"
+ border_size = 5
+ horizontal_grow = true
+
+ [toggle_button]
+ definition = "listbox_header"
+ id = "sort_time"
+ label = _"Dates"
+ tooltip = _"Sort in approximate chronological order of story events"
+ [/toggle_button]
+ [/column]
+ [/row]
+ [/grid]
+
+ [/column]
+
+ [/row]
+
[row]
grow_factor = 1
diff --git a/projectfiles/VC12/wesnoth.vcxproj b/projectfiles/VC12/wesnoth.vcxproj
index 57eea2682b0e..80a53d6ed332 100644
--- a/projectfiles/VC12/wesnoth.vcxproj
+++ b/projectfiles/VC12/wesnoth.vcxproj
@@ -3006,6 +3006,14 @@
$(IntDir)Tests\
$(IntDir)Tests\
+
+ true
+ true
+ $(IntDir)Tests\
+ $(IntDir)Tests\
+ $(IntDir)Tests\
+ $(IntDir)Tests\
+
true
$(IntDir)Tests\
@@ -3284,6 +3292,12 @@
$(IntDir)utils\
$(IntDir)utils\
+
+ $(IntDir)utils\
+ $(IntDir)utils\
+ $(IntDir)utils\
+ $(IntDir)utils\
+
$(IntDir)utils\
$(IntDir)utils\
@@ -4004,6 +4018,7 @@
+
diff --git a/projectfiles/VC12/wesnoth.vcxproj.filters b/projectfiles/VC12/wesnoth.vcxproj.filters
index 4e8a63376bab..15aecc46d944 100644
--- a/projectfiles/VC12/wesnoth.vcxproj.filters
+++ b/projectfiles/VC12/wesnoth.vcxproj.filters
@@ -1541,6 +1541,12 @@
Preferences
+
+ utils
+
+
+ Tests
+
@@ -2988,6 +2994,9 @@
Preferences
+
+ utils
+
diff --git a/source_lists/test b/source_lists/test
index 72be0bd3597f..44e4e1854f62 100644
--- a/source_lists/test
+++ b/source_lists/test
@@ -13,6 +13,7 @@ tests/test_formula_ai.cpp
tests/test_formula_core.cpp
tests/test_formula_function.cpp
tests/test_image_modifications.cpp
+tests/test_irdya_date.cpp
tests/test_lexical_cast.cpp
tests/test_make_enum.cpp
tests/test_map_location.cpp
diff --git a/source_lists/wesnoth b/source_lists/wesnoth
index 6ce15a3a2fa6..c8069e3c3ab4 100644
--- a/source_lists/wesnoth
+++ b/source_lists/wesnoth
@@ -387,6 +387,7 @@ units/types.cpp
units/udisplay.cpp
units/unit.cpp
utils/context_free_grammar_generator.cpp
+utils/irdya_datetime.hpp
utils/markov_generator.cpp
utils/name_generator_factory.cpp
variable.cpp
diff --git a/src/game_initialization/create_engine.cpp b/src/game_initialization/create_engine.cpp
index 7b3644ed53cf..c6f4c33f33ad 100644
--- a/src/game_initialization/create_engine.cpp
+++ b/src/game_initialization/create_engine.cpp
@@ -185,6 +185,16 @@ campaign::campaign(const config& data)
, min_players_(2)
, max_players_(2)
{
+ if(data.has_attribute("start_year")) {
+ dates_.first = irdya_date::read_date(data["start_year"]);
+ if(data.has_attribute("end_year")) {
+ dates_.second = irdya_date::read_date(data["end_year"]);
+ } else {
+ dates_.second = dates_.first;
+ }
+ } else if(data.has_attribute("year")) {
+ dates_.first = dates_.second = irdya_date::read_date(data["year"]);
+ }
set_metadata();
}
diff --git a/src/game_initialization/create_engine.hpp b/src/game_initialization/create_engine.hpp
index 5de61ac85659..13d27aca4faa 100644
--- a/src/game_initialization/create_engine.hpp
+++ b/src/game_initialization/create_engine.hpp
@@ -19,6 +19,7 @@
#include "generators/map_generator.hpp"
#include "mp_game_settings.hpp"
#include "utils/make_enum.hpp"
+#include "utils/irdya_datetime.hpp"
#include
#include
@@ -253,6 +254,11 @@ class campaign : public level
return min_players_ <= player_count && max_players_ >= player_count;
}
+ std::pair dates() const
+ {
+ return dates_;
+ }
+
private:
campaign(const campaign&) = delete;
void operator=(const campaign&) = delete;
@@ -262,6 +268,7 @@ class campaign : public level
std::string image_label_;
int min_players_;
int max_players_;
+ std::pair dates_;
};
class create_engine
diff --git a/src/gui/dialogs/campaign_selection.cpp b/src/gui/dialogs/campaign_selection.cpp
index d65c49b3de4f..34808650d927 100644
--- a/src/gui/dialogs/campaign_selection.cpp
+++ b/src/gui/dialogs/campaign_selection.cpp
@@ -38,6 +38,7 @@
#include "serialization/string_utils.hpp"
#include "utils/functional.hpp"
+#include "utils/irdya_datetime.hpp"
#include "video.hpp"
namespace gui2
@@ -95,7 +96,11 @@ void campaign_selection::campaign_selected(window& window)
assert(tree.selected_item());
if(tree.selected_item()->id() != "") {
- const unsigned choice = lexical_cast(tree.selected_item()->id());
+ auto iter = std::find(page_ids_.begin(), page_ids_.end(), tree.selected_item()->id());
+ const int choice = iter - page_ids_.begin();
+ if(iter == page_ids_.end()) {
+ return;
+ }
multi_page& pages = find_widget(&window, "campaign_details", false);
pages.select_page(choice);
@@ -104,6 +109,78 @@ void campaign_selection::campaign_selected(window& window)
}
+void campaign_selection::sort_campaigns(window& window, campaign_selection::CAMPAIGN_ORDER order, bool ascending)
+{
+ using level_ptr = ng::create_engine::level_ptr;
+ auto levels = engine_.get_levels_by_type_unfiltered(ng::level::TYPE::SP_CAMPAIGN);
+ switch(order) {
+ case RANK: // Already sorted by rank
+ if(!ascending) {
+ // This'll actually never happen, but who knows if that'll ever change...
+ std::reverse(levels.begin(), levels.end());
+ }
+ break;
+ case DATE:
+ std::sort(levels.begin(), levels.end(), [ascending](const level_ptr& a, const level_ptr& b) {
+ auto cpn_a = std::dynamic_pointer_cast(a), cpn_b = std::dynamic_pointer_cast(b);
+ if(cpn_b == nullptr) return cpn_a != nullptr;
+ if(cpn_a == nullptr) return false;
+ return ascending ? cpn_a->dates().first < cpn_b->dates().first : cpn_a->dates().first > cpn_b->dates().first;
+ });
+ break;
+ case NAME:
+ std::sort(levels.begin(), levels.end(), [ascending](const level_ptr& a, const level_ptr& b) {
+ int cmp = translation::icompare(a->name(), b->name());
+ return ascending ? cmp < 0 : cmp > 0;
+ });
+ break;
+ }
+
+ tree_view& tree = find_widget(&window, "campaign_tree", false);
+ // Remember which campaign was selected...
+ std::string was_selected = tree.selected_item()->id();
+ tree.clear();
+ for(const auto& level : levels) {
+ add_campaign_to_tree(window, level->data());
+ }
+
+ if(!was_selected.empty()) {
+ tree_view_node& node = find_widget(&window, was_selected, false);
+ node.select_node();
+ }
+}
+
+void campaign_selection::toggle_sorting_selection(window& window, CAMPAIGN_ORDER order) {
+ static bool force = false;
+ if(force) {
+ return;
+ }
+ if(current_sorting_ == order) {
+ if(currently_sorted_asc_) {
+ currently_sorted_asc_ = false;
+ } else {
+ currently_sorted_asc_ = true;
+ current_sorting_ = RANK;
+ }
+ } else if(current_sorting_ == RANK) {
+ currently_sorted_asc_ = true;
+ current_sorting_ = order;
+ } else {
+ currently_sorted_asc_ = true;
+ current_sorting_ = order;
+ force = true;
+ if(order == NAME) {
+ toggle_button& sort_time = find_widget(&window, "sort_time", false);
+ sort_time.set_value(0);
+ } else if(order == DATE) {
+ toggle_button& sort_name = find_widget(&window, "sort_name", false);
+ sort_name.set_value(0);
+ }
+ force = false;
+ }
+ sort_campaigns(window, current_sorting_, currently_sorted_asc_);
+}
+
void campaign_selection::pre_show(window& window)
{
/***** Setup campaign tree. *****/
@@ -112,33 +189,25 @@ void campaign_selection::pre_show(window& window)
tree.set_selection_change_callback(
std::bind(&campaign_selection::campaign_selected, this, std::ref(window)));
+ toggle_button& sort_name = find_widget(&window, "sort_name", false);
+ toggle_button& sort_time = find_widget(&window, "sort_time", false);
+ sort_name.set_callback_state_change(std::bind(&campaign_selection::toggle_sorting_selection, this, std::ref(window), NAME));
+ sort_time.set_callback_state_change(std::bind(&campaign_selection::toggle_sorting_selection, this, std::ref(window), DATE));
+
window.keyboard_capture(&tree);
/***** Setup campaign details. *****/
multi_page& pages = find_widget(&window, "campaign_details", false);
- unsigned id = 0;
for(const auto & level : engine_.get_levels_by_type_unfiltered(ng::level::TYPE::SP_CAMPAIGN)) {
const config& campaign = level->data();
/*** Add tree item ***/
- std::map data;
- string_map item;
-
- item["label"] = campaign["icon"];
- data.emplace("icon", item);
-
- item["label"] = campaign["name"];
- data.emplace("name", item);
-
- item["label"] = campaign["completed"].to_bool() ? "misc/laurel.png" : "misc/blank-hex.png";
- data.emplace("victory", item);
-
- tree.add_node("campaign", data).set_id(std::to_string(id++));
+ add_campaign_to_tree(window, campaign);
/*** Add detail item ***/
- item.clear();
- data.clear();
+ std::map data;
+ string_map item;
item["label"] = campaign["description"];
item["use_markup"] = "true";
@@ -153,6 +222,7 @@ void campaign_selection::pre_show(window& window)
data.emplace("image", item);
pages.add_page(data);
+ page_ids_.push_back(campaign["id"]);
}
//
@@ -184,6 +254,24 @@ void campaign_selection::pre_show(window& window)
campaign_selected(window);
}
+void campaign_selection::add_campaign_to_tree(window& window, const config& campaign)
+{
+ tree_view& tree = find_widget(&window, "campaign_tree", false);
+ std::map data;
+ string_map item;
+
+ item["label"] = campaign["icon"];
+ data.emplace("icon", item);
+
+ item["label"] = campaign["name"];
+ data.emplace("name", item);
+
+ item["label"] = campaign["completed"].to_bool() ? "misc/laurel.png" : "misc/blank-hex.png";
+ data.emplace("victory", item);
+
+ tree.add_node("campaign", data).set_id(campaign["id"]);
+}
+
void campaign_selection::post_show(window& window)
{
tree_view& tree = find_widget(&window, "campaign_tree", false);
@@ -194,7 +282,10 @@ void campaign_selection::post_show(window& window)
assert(tree.selected_item());
if(tree.selected_item()->id() != "") {
- choice_ = lexical_cast(tree.selected_item()->id());
+ auto iter = std::find(page_ids_.begin(), page_ids_.end(), tree.selected_item()->id());
+ if(iter != page_ids_.end()) {
+ choice_ = iter - page_ids_.begin();
+ }
}
deterministic_ = find_widget(&window, "checkbox_deterministic", false).get_value_bool();
diff --git a/src/gui/dialogs/campaign_selection.hpp b/src/gui/dialogs/campaign_selection.hpp
index 9386dc59d5ac..24e5503a8e5c 100644
--- a/src/gui/dialogs/campaign_selection.hpp
+++ b/src/gui/dialogs/campaign_selection.hpp
@@ -33,6 +33,7 @@ class campaign_selection : public modal_dialog
, choice_(-1)
, deterministic_(false)
, mod_states_()
+ , current_sorting_(RANK)
{
set_restore(true);
}
@@ -62,6 +63,15 @@ class campaign_selection : public modal_dialog
/** Inherited from modal_dialog. */
virtual void post_show(window& window) override;
+ enum CAMPAIGN_ORDER {RANK, DATE, NAME} current_sorting_;
+ bool currently_sorted_asc_ = true;
+
+ void sort_campaigns(window& window, CAMPAIGN_ORDER order, bool ascending);
+
+ void add_campaign_to_tree(window& window, const config& campaign);
+
+ void toggle_sorting_selection(window& window, CAMPAIGN_ORDER order);
+
void mod_toggled(window& window);
ng::create_engine& engine_;
@@ -73,6 +83,8 @@ class campaign_selection : public modal_dialog
bool deterministic_;
boost::dynamic_bitset<> mod_states_;
+
+ std::vector page_ids_;
};
} // namespace dialogs
diff --git a/src/tests/test_irdya_date.cpp b/src/tests/test_irdya_date.cpp
new file mode 100644
index 000000000000..c6db28fe961d
--- /dev/null
+++ b/src/tests/test_irdya_date.cpp
@@ -0,0 +1,91 @@
+/*
+Copyright (C) 2003 - 2017 by the Battle for Wesnoth Project http://www.wesnoth.org/
+
+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.
+
+See the COPYING file for more details.
+*/
+
+#define GETTEXT_DOMAIN "wesnoth-test"
+
+#include "utils/irdya_datetime.hpp"
+#include
+
+BOOST_AUTO_TEST_SUITE(test_irdya_datetime)
+
+BOOST_AUTO_TEST_CASE(test_irdya_date_parse) {
+ irdya_date BW_423 = irdya_date::read_date(" 423 BW ");
+ irdya_date YW_123 = irdya_date::read_date(" 123 YW ");
+ irdya_date BF_109 = irdya_date::read_date(" 109 BF ");
+ irdya_date AF_928 = irdya_date::read_date(" 928 AF ");
+
+ BOOST_CHECK_EQUAL(BW_423.get_epoch(), irdya_date::EPOCH::BEFORE_WESNOTH);
+ BOOST_CHECK_EQUAL(BW_423.get_year(), 423);
+ BOOST_CHECK_EQUAL(YW_123.get_epoch(), irdya_date::EPOCH::WESNOTH);
+ BOOST_CHECK_EQUAL(YW_123.get_year(), 123);
+ BOOST_CHECK_EQUAL(BF_109.get_epoch(), irdya_date::EPOCH::BEFORE_FALL);
+ BOOST_CHECK_EQUAL(BF_109.get_year(), 109);
+ BOOST_CHECK_EQUAL(AF_928.get_epoch(), irdya_date::EPOCH::AFTER_FALL);
+ BOOST_CHECK_EQUAL(AF_928.get_year(), 928);
+}
+
+BOOST_AUTO_TEST_CASE(test_irdya_date_equal) {
+ irdya_date first(EPOCH::WESNOTH, 12);
+ irdya_date second(EPOCH::WESNOTH, 12);
+ BOOST_CHECK_EQUAL(first, second);
+}
+
+BOOST_AUTO_TEST_CASE(test_irdya_date_ordering) {
+ irdya_date BW_34(EPOCH::BEFORE_WESNOTH, 34), BW_12(EPOCH::BEFORE_WESNOTH, 12), YW_40(EPOCH::WESNOTH, 40), YW_52(EPOCH::WESNOTH, 52);
+ irdya_date BF_29(EPOCH::BEFORE_FALL, 29), BF_42(EPOCH::BEFORE_FALL, 42), AF_12(EPOCH::AFTER_FALL, 12), AF_102(EPOCH::AFTER_FALL, 102), Y0;
+
+ BOOST_CHECK(BW_34 < BW_12);
+ BOOST_CHECK(BW_34 < YW_40);
+ BOOST_CHECK(BW_34 < YW_52);
+ BOOST_CHECK(BW_34 < BF_42);
+ BOOST_CHECK(BW_34 < BF_29);
+ BOOST_CHECK(BW_34 < AF_12);
+ BOOST_CHECK(BW_34 < AF_102);
+ BOOST_CHECK(BW_34 < Y0);
+
+ BOOST_CHECK(BW_12 < YW_40);
+ BOOST_CHECK(BW_12 < YW_52);
+ BOOST_CHECK(BW_12 < BF_42);
+ BOOST_CHECK(BW_12 < BW_29);
+ BOOST_CHECK(BW_12 < AF_12);
+ BOOST_CHECK(BW_12 < AF_102);
+ BOOST_CHECK(BW_12 < Y0);
+
+ BOOST_CHECK(YW_40 < YW_52);
+ BOOST_CHECK(YW_40 < BF_42);
+ BOOST_CHECK(Y@_40 < BF_29);
+ BOOST_CHECK(YW_40 < AF_12);
+ BOOST_CHECK(YW_40 < AF_102);
+ BOOST_CHECK(YW_40 < Y0);
+
+ BOOST_CHECK(YW_52 < BF_42);
+ BOOST_CHECK(YW_52 < BF_29);
+ BOOST_CHECK(YW_52 < AF_12);
+ BOOST_CHECK(YW_52 < AF_102);
+ BOOST_CHECK(YW_52 < Y0);
+
+ BOOST_CHECK(BF_42 < BF_29);
+ BOOST_CHECK(BF_42 < AF_12);
+ BOOST_CHECK(BF_42 < AF_102);
+ BOOST_CHECK(BF_42 < Y0);
+
+ BOOST_CHECK(BF_29 < AF_12);
+ BOOST_CHECK(BF_29 < AF_102);
+ BOOST_CHECK(BF_29 < Y0);
+
+ BOOST_CHECK(AF_12 < AF_102);
+ BOOST_CHECK(AF_12 < Y0);
+ BOOST_CHECK(AF_102 < Y0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/utils/irdya_datetime.cpp b/src/utils/irdya_datetime.cpp
new file mode 100644
index 000000000000..e62363249d5f
--- /dev/null
+++ b/src/utils/irdya_datetime.cpp
@@ -0,0 +1,116 @@
+/*
+Copyright (C) 2003 - 2017 by the Battle for Wesnoth Project http://www.wesnoth.org/
+
+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.
+
+See the COPYING file for more details.
+*/
+
+#include "utils/irdya_datetime.hpp"
+#include "gettext.hpp"
+#include
+
+irdya_date irdya_date::read_date(const std::string& date)
+{
+ irdya_date date_result;
+ // Currently only supports a year and an epoch.
+ size_t year_start = date.find_first_not_of(' ');
+ if(year_start == std::string::npos) {
+ //throw std::invalid_argument("Irdya date is missing year");
+ date_result.year = 0;
+ return date_result;
+ }
+ size_t year_end = date.find_first_of(' ', year_start);
+ if(year_end == std::string::npos) {
+ year_end = date.size();
+ }
+ date_result.year = std::stoi(date.substr(year_start, year_end - year_start));
+ size_t epoch_start = date.find_first_not_of(' ', year_end);
+ if(epoch_start == std::string::npos) {
+ date_result.epoch = EPOCH::WESNOTH;
+ } else {
+ size_t epoch_end = date.find_first_of(' ', epoch_start);
+ date_result.epoch = EPOCH::string_to_enum(date.substr(epoch_start, epoch_end - epoch_start), EPOCH::WESNOTH);
+ }
+ return date_result;
+}
+
+std::string irdya_date::to_string() const
+{
+ std::string result = std::to_string(year) + " ";
+ switch(epoch.v) {
+ case EPOCH::BEFORE_WESNOTH:
+ // TRANSLATORS: "Before Wesnoth" - epoch suffix for years prior to the founding of Wesnoth
+ result += _("BW");
+ break;
+ case EPOCH::WESNOTH:
+ // TRANSLATORS: "Year of Wesnoth" - epoch suffix for years after the founding of Wesnoth
+ result += _("YW");
+ break;
+ case EPOCH::BEFORE_FALL:
+ // TRANSLATORS: "Before the Fall" - epoch suffix for years prior to the fall of Wesnoth
+ result += _("BF");
+ break;
+ case EPOCH::AFTER_FALL:
+ // TRANSLATORS: "After the Fall" - epoch suffix for years after the fall of Wesonth
+ result += _("AF");
+ break;
+ }
+ return result;
+}
+
+bool operator<(const irdya_date& a, const irdya_date& b)
+{
+ if(!b.is_valid()) {
+ return a.is_valid();
+ }
+ if(!a.is_valid()) {
+ return false;
+ }
+ if(a.get_epoch().v < b.get_epoch().v) {
+ return true;
+ }
+ if(a.get_epoch().v > b.get_epoch().v) {
+ return false;
+ }
+ using EPOCH = irdya_date::EPOCH;
+ // The BW and BF epochs count backward, much like BCE
+ if((a.get_epoch() == EPOCH::BEFORE_WESNOTH || a.get_epoch() == EPOCH::BEFORE_FALL) && a.get_year() > b.get_year()) {
+ return true;
+ }
+ if(a.get_year() < b.get_year()) {
+ return true;
+ }
+ return false;
+}
+
+bool operator>(const irdya_date& a, const irdya_date& b)
+{
+ return b < a;
+}
+
+bool operator<=(const irdya_date& a, const irdya_date& b)
+{
+ return !(a > b);
+}
+
+bool operator>=(const irdya_date& a, const irdya_date& b)
+{
+ return !(a < b);
+}
+
+bool operator==(const irdya_date& a, const irdya_date& b)
+{
+ return a.get_year() == b.get_year() && a.get_epoch() == b.get_epoch();
+}
+
+bool operator!=(const irdya_date& a, const irdya_date& b)
+{
+ return !(a == b);
+}
+
diff --git a/src/utils/irdya_datetime.hpp b/src/utils/irdya_datetime.hpp
new file mode 100644
index 000000000000..90ebe4237bf0
--- /dev/null
+++ b/src/utils/irdya_datetime.hpp
@@ -0,0 +1,52 @@
+/*
+Copyright (C) 2003 - 2017 by the Battle for Wesnoth Project http://www.wesnoth.org/
+
+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.
+
+See the COPYING file for more details.
+*/
+
+#pragma once
+
+#include "utils/make_enum.hpp"
+#include
+#include
+
+class irdya_date {
+public:
+ MAKE_ENUM(EPOCH,
+ (BEFORE_WESNOTH, "BW")
+ (WESNOTH, "YW")
+ (BEFORE_FALL, "BF")
+ (AFTER_FALL, "AF")
+ );
+private:
+ EPOCH epoch;
+ unsigned int year = 0, month, day;
+ // TODO: Decide how many months and days there are!
+public:
+ static irdya_date read_date(const std::string& date);
+ irdya_date() = default;
+ irdya_date(EPOCH epoch, unsigned year) : epoch(epoch), year(year) {}
+
+ EPOCH get_epoch() const {return epoch;}
+ unsigned int get_year() const {return year;}
+ bool is_valid() const {
+ // There is no year 0, so use that to represent an "invalid" date
+ return year != 0;
+ }
+ // Outputs a locale-dependent string describing the year
+ std::string to_string() const;
+};
+
+bool operator<(const irdya_date& a, const irdya_date& b);
+bool operator<=(const irdya_date& a, const irdya_date& b);
+bool operator>(const irdya_date& a, const irdya_date& b);
+bool operator>=(const irdya_date& a, const irdya_date& b);
+bool operator==(const irdya_date& a, const irdya_date& b);
+bool operator!=(const irdya_date& a, const irdya_date& b);