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

Proposed by GunChleoc
Status: Merged
Merged at revision: 9067
Proposed branch: lp:~widelands-dev/widelands/campaignselect_box
Merge into: lp:widelands
Diff against target: 2711 lines (+1143/-949)
39 files modified
data/campaigns/atl01.wmf/scripting/mission_thread.lua (+1/-2)
data/campaigns/bar01.wmf/scripting/mission_thread.lua (+1/-2)
data/campaigns/bar02.wmf/scripting/mission_thread.lua (+1/-2)
data/campaigns/campaigns.lua (+127/-141)
data/campaigns/dummy.wmf/elemental (+1/-0)
data/campaigns/emp01.wmf/scripting/mission_thread.lua (+1/-1)
data/campaigns/emp02.wmf/scripting/mission_thread.lua (+1/-2)
data/campaigns/emp03.wmf/scripting/mission_thread.lua (+1/-1)
data/campaigns/emp04.wmf/scripting/mission_thread.lua (+1/-3)
data/campaigns/tutorials.lua (+12/-28)
src/base/i18n.cc (+9/-0)
src/base/i18n.h (+11/-2)
src/graphic/text_layout.cc (+1/-0)
src/graphic/text_layout.h (+2/-0)
src/logic/CMakeLists.txt (+0/-11)
src/logic/filesystem_constants.h (+2/-1)
src/scripting/CMakeLists.txt (+0/-1)
src/scripting/lua_game.cc (+10/-30)
src/scripting/lua_game.h (+1/-2)
src/ui_basic/table.cc (+3/-3)
src/ui_basic/table.h (+2/-8)
src/ui_fsmenu/CMakeLists.txt (+12/-1)
src/ui_fsmenu/campaign_select.cc (+44/-397)
src/ui_fsmenu/campaign_select.h (+10/-90)
src/ui_fsmenu/campaigndetails.cc (+82/-0)
src/ui_fsmenu/campaigndetails.h (+41/-0)
src/ui_fsmenu/campaigns.cc (+198/-158)
src/ui_fsmenu/campaigns.h (+59/-13)
src/ui_fsmenu/scenario_select.cc (+230/-0)
src/ui_fsmenu/scenario_select.h (+65/-0)
src/ui_fsmenu/scenariodetails.cc (+74/-0)
src/ui_fsmenu/scenariodetails.h (+41/-0)
src/wlapplication.cc (+12/-9)
src/wui/CMakeLists.txt (+2/-1)
src/wui/load_or_save_game.cc (+4/-6)
src/wui/mapauthordata.h (+72/-0)
src/wui/mapdata.cc (+7/-7)
src/wui/mapdata.h (+1/-27)
src/wui/mapdetails.h (+1/-0)
To merge this branch: bzr merge lp:~widelands-dev/widelands/campaignselect_box
Reviewer Review Type Date Requested Status
Toni Förster Approve
Klaus Halfmann review compile, testplay Approve
Review via email: mp+360901@code.launchpad.net

Commit message

Redesigned the campaign/scenario selection screens to use Box layout

- Tables entries can now be greyed out
- Fixed fullscreen switching
- Converted campaigns definition to Lua
- Mark scenarios as solved rather than as unlocked
- Automatic conversion of legacy campvis file contents if the new file
  doesn't exist yet so that players will not lose their progress
- Files to be included in the "load savegame" screens are now determined
  by file extension

To post a comment you must log in.
Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4335. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/467787988.
Appveyor build 4130. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_campaignselect_box-4130.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Findings:
  compiles locally on OSX.
  src/logic/campaign_visibility.h/.c was move to src/ui_fsmenu/campaigns.h/c
  campaigns.conf -> campaigns.ua (to speed upp reading, I assume)
  Many reefactorings around campains and scenarios
  Travis fails with
    Could not find ICU include directory ?
    Debug build fails with
     ...doc/sphinx/source/autogen_ai_hints.rst:117:
        Definition list ends without a blank line; unexpected unindent.
   AppVeyor / Windows fails witth
     Could not find the following static Boost libraries:
          boost_unit_test_framework
          boost_regex
          boost_system

Questions:
  What is the this mark_scenario_as_solved(..) function about?

I will contiune checking how and if you fixed the relateed bugs.

review: Needs Fixing (review, compile)
Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

* Bug #627361: Show available campaigns and missions greyed out
  Fine for me except:

  - after finishing the last available mission widelands should present a message like
  "Thank you for playing all available missions of widelands."

  Mhh, All excpet frisisans show "unimplemened" as last Scenario,
  but it cannto be played.
  how shall I reeach this this?

campaigns.conf is tee scucessor for campvis ?

Bug #1377660: Fullscreen Menu overhaul

  This is a kind of wishlist dating back to 2014 (!)

Bug #1634750: Convert campaigns.conf and tutorials.conf to Lua

  Yep, that it

#1799809: Resize UI when toggling between fullscreen and windows mode

  Metaserver lobby - does not work for me
  Begin Network game - does not work for me
  Map Select - works for me

  which oone did you tackle with this brach?

Revision history for this message
kaputtnik (franku) wrote :

Klaus you may need to merge trunk to get travis and appveyor get work without errors again.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4402. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/479000710.
Appveyor build 4193. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_campaignselect_box-4193.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Travis has only one (Timeout) Problem with GCC_VERSION="4.8" BUILD_TYPE="Debug"

appveyor. has Issue in Configuration: Debug

No artifacts found matching 'Widelands-_widelands_dev_widelands_campaignselect_box-4193-Debug-x64.exe' path
strip -sv %APPVEYOR_BUILD_FOLDER%\build\src\widelands.exe

No idea what that is about.

So for me this is OK now.

Anyone else?

review: Approve (review compile, testplay)
Revision history for this message
Toni Förster (stonerl) wrote :

Appveyor wasn't able to upload the artifact, but it built without problems

review: Approve
Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4407. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/481978815.
Appveyor build 4198. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_campaignselect_box-4198.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Thanks for the reviews!

This is for Build 21, so let's not merge it yet.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4449. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/491209077.
Appveyor build 4237. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_campaignselect_box-4237.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4732. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/521841214.
Appveyor build 4517. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_campaignselect_box-4517.

Revision history for this message
GunChleoc (gunchleoc) wrote :

@bunnybot merge

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4768. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/523572522.
Appveyor build 4552. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_campaignselect_box-4552.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'data/campaigns/atl01.wmf/scripting/mission_thread.lua'
--- data/campaigns/atl01.wmf/scripting/mission_thread.lua 2018-11-16 06:41:28 +0000
+++ data/campaigns/atl01.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
@@ -233,8 +233,7 @@
233233
234 -- Success234 -- Success
235 msg_boxes(scenario_won)235 msg_boxes(scenario_won)
236 p1:reveal_scenario("atlanteans01")236 p1:mark_scenario_as_solved("atl01.wmf")
237 p1:reveal_campaign("campsect3")
238end237end
239238
240239
241240
=== modified file 'data/campaigns/bar01.wmf/scripting/mission_thread.lua'
--- data/campaigns/bar01.wmf/scripting/mission_thread.lua 2019-01-08 07:57:22 +0000
+++ data/campaigns/bar01.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
@@ -327,8 +327,7 @@
327 end327 end
328328
329 message_box_objective(plr, msg_mission_complete)329 message_box_objective(plr, msg_mission_complete)
330 plr:reveal_scenario("barbariantut01")330 plr:mark_scenario_as_solved("bar01.wmf")
331 plr:reveal_campaign("campsect1")
332end331end
333332
334333
335334
=== modified file 'data/campaigns/bar02.wmf/scripting/mission_thread.lua'
--- data/campaigns/bar02.wmf/scripting/mission_thread.lua 2018-01-26 09:00:04 +0000
+++ data/campaigns/bar02.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
@@ -376,8 +376,7 @@
376376
377 campaign_message_box(story_msg_7)377 campaign_message_box(story_msg_7)
378378
379 p1:reveal_scenario("barbariantut02")379 p1:mark_scenario_as_solved("bar02.wmf")
380 p1:reveal_campaign("campsect1")
381end380end
382381
383run(initial_message_and_small_food_economy)382run(initial_message_and_small_food_economy)
384383
=== renamed file 'data/campaigns/campaigns.conf' => 'data/campaigns/campaigns.lua'
--- data/campaigns/campaigns.conf 2018-08-13 16:44:58 +0000
+++ data/campaigns/campaigns.lua 2019-04-23 16:17:37 +0000
@@ -1,141 +1,127 @@
1##########################################1--##########################################
2# Campaign configuration - file #2--# Campaign configuration - file #
3##########################################3--##########################################
44
55return {
66 --##########################################
7#####7 --# Descriptions of difficulty levels #
8# Section "global"8 --##########################################
9#9 difficulties = {
10# version = Version-number of this file - used to check, whether campvis-10 {
11# file needs to be updated. Higher the value, if you added a map11 -- This will be prefixed to any text that you might add in each
12# or campaign or if you changed a visibility-value.12 -- campaign's difficulty description.
13# campname?? = Name of the Campaign with the number ??.13 -- TRANSLATORS: The difficulty level of a campign
14# campsect?? = Name of section, to be loaded, if this campaign is selected.14 descname = _"Easy.",
15# campdiff?? = Level of difficulty 1-3 (easy to hard) (this is only a visua-15 -- An image to represent the difficulty level
16# lisation, it has no influences on the real difficulty).16 image = "images/ui_fsmenu/easy.png",
17# campdesc?? = Description of the campaign17 },
18# campvisi?? = Is campaign visible by default? (1=yes, 0=no)18 {
19# * cnewvisi?? = the name of a campaign or a scenario that must be visible, to show19 -- TRANSLATORS: The difficulty level of a campign
20# this entry as well - this string unhides the map, if the player20 descname = _"Medium.",
21# completed the requirements with an older cconfig version.21 image = "images/ui_fsmenu/medium.png",
22#####22 },
2323 {
24[global]24 -- TRANSLATORS: The difficulty level of a campign
25version = 825 descname = _"Hard.",
26# Barbarians Introduction26 image = "images/ui_fsmenu/hard.png",
27campname0=_"The Second Empire"27 },
28campsect0=barbariantut28 {
29camptribe0=_"Barbarians"29 -- TRANSLATORS: The difficulty level of a campign
30campdiff0=130 descname = _"Challenging.",
31campdiffdescr0=_"Easy. Introduces the Barbarians"31 image = "images/ui_fsmenu/challenging.png",
32campdesc0=_"When Chat’Karuth died, he was an old man, father to three strong and ambitious sons, and warlord to an army that could match any enemy willing to rise against the ancient forests. Though at the end of his glorious reign, Chat’Karuth chose his eldest son, Thron, to succeed him as the tribe’s warlord – a decision that left his two brothers unsatisfied. The old warlord knew that. As his father instructed him, Thron left the capital of Al’thunran, the home of the Throne Among the Trees, and withdrew his forces to the high hills where he buried the corpse of his father. There he swore to the gods and his father’s spirit that he’d return to re-established order. While his brothers have raged blind war against Thron and the few forces he left to secure the borders of Al’thunran, the young warlord seeks to reunite his ambitious brothers and force the tribes to march once again under a common banner."32 },
33campvisi0=133 },
34# Empire Introduction34
35campname1=_"The Months of Exile"35 --##########################################
36campsect1=empiretut36 --# The campaigns themselves #
37camptribe1=_"Empire"37 --##########################################
38campdiff1=138 campaigns = {
39campdiffdescr1=_"Easy. Introduces the Empire"39 {
40campdesc1=_"Six months ago, Lutius – a young general of the Empire – was sent with 150 soldiers to the frontier beyond the northern forests where Barbarian tribes were crossing onto land held by the Empire. His task was to defend the Empire’s land. At first, everything was calm. He even talked to a few Barbarian children and thought about a peaceful life – side by side with this archaic folk. He began to feel safer and his army began to drop their attention off the potential enemy. That was their undoing. One night in March his unprepared army was attacked by 100 Barbarian footmen and was completely scattered. Only with his bare life he and a handful of his soldiers survived."40 -- **** Barbarians Introduction ****
41campvisi1=041 -- The name the user sees on screen
42# Atlantean Introduction42 -- TRANSLATORS: The name of a Barbarian campign
43campname2=_"The Run for the Fire"43 descname = _"The Second Empire",
44campsect2=atlanteans44 -- The internal name of the tribe that the user will be playing
45camptribe2=_"Atlanteans"45 tribe = "barbarians",
46campdiff2=246 -- The difficulty of this campaign.
47campdiffdescr2=_"Challenging. Introduces the Atlanteans"47 -- Start counting at 1 in the "difficulties" table above
48campdesc2=_"When their God lost faith in the Atlanteans and drowned their island, one woman’s struggle for justice and a second chance for her people would become the stuff of legends. Leading the remaining Atlanteans into a new future in a new part of the World, Jundlina became the most powerful human of her time, but at a high cost: her humanity and soul."48 -- TRANSLATORS: A short description of a campign
49campvisi2=049 difficulty = { level=1, description=_"Introduces the Barbarians." },
50cnewvisi2=empiretut0150 -- An introduction story
51# Frisian Introduction51 -- TRANSLATORS: A long description of a campign
52campname3=_"From Water to Ice"52 description = _"When Chat’Karuth died, he was an old man, father to three strong and ambitious sons, and warlord to an army that could match any enemy willing to rise against the ancient forests. Though at the end of his glorious reign, Chat’Karuth chose his eldest son, Thron, to succeed him as the tribe’s warlord – a decision that left his two brothers unsatisfied. The old warlord knew that. As his father instructed him, Thron left the capital of Al’thunran, the home of the Throne Among the Trees, and withdrew his forces to the high hills where he buried the corpse of his father. There he swore to the gods and his father’s spirit that he’d return to re-established order. While his brothers have raged blind war against Thron and the few forces he left to secure the borders of Al’thunran, the young warlord seeks to reunite his ambitious brothers and force the tribes to march once again under a common banner.",
53campsect3=frisians53 -- The campaign's scenarios. The first scenario is always visible if
54camptribe3=_"Frisians"54 -- the campaign itself is visible.
55campdiff3=355 -- Paths to the scenarios are relative to data/campaigns
56campdiffdescr3=_"For advanced players. Introduces the Frisians"56 -- Once a scenario has been marked as solved by calling
57campdesc3=_"Living off the ocean is a constant struggle, and even more so for the inhabitants of the Frisian North Sea shore. Was the last storm flood, the most devastating one in human memory, really nothing more than yet another example for the hardships all Frisians have to face – or a sign from the gods that a tribe that only just settled here must seek out an entirely new home?"57 -- `player:mark_scenario_as_solved`, the next scenario in the list will
58campvisi3=058 -- become visible.
59cnewvisi3=atlanteans0159 -- Also, campaigns that have a prerequisite scenarios will be unlocked
6060 -- when any of the referenced scenarios has been solved.
6161 scenarios = {
6262 "bar01.wmf",
63#####63 "bar02.wmf",
64# Sections of the campaign - maps64 "dummy.wmf"
65# Naming MUST be the name of the campaign-section + "??" where ?? is an increasing number.65 }
66#66 },
67# name = name of the map.67 {
68# * newvisi = the name of a campaign or a scenario that must be visible, to show68 -- **** Empire Introduction ****
69# this entry as well - this string unhides the map, if the player69 -- TRANSLATORS: The name of an Empire campign
70# completed the requirements with an older cconfig version.70 descname = _"The Months of Exile",
71# visible = is this map visible(1), or does it need another map to be played first(0).71 tribe = "empire",
72# path = path to the map.72 -- TRANSLATORS: A short description of a campign
73#####73 difficulty = { level=2, description=_"Introduces the Empire." },
7474 -- TRANSLATORS: A long description of a campign
75[barbariantut00]75 description = _"Six months ago, Lutius – a young general of the Empire – was sent with 150 soldiers to the frontier beyond the northern forests where Barbarian tribes were crossing onto land held by the Empire. His task was to defend the Empire’s land. At first, everything was calm. He even talked to a few Barbarian children and thought about a peaceful life – side by side with this archaic folk. He began to feel safer and his army began to drop their attention off the potential enemy. That was their undoing. One night in March his unprepared army was attacked by 100 Barbarian footmen and was completely scattered. Only with his bare life he and a handful of his soldiers survived.",
76name=_"A Place to Call Home"76 -- If `prerequisites` is present, the campaign is greyed out by default.
77visible=177 -- The campaign will become unlocked when any of the referenced scenarios
78path="campaigns/bar01.wmf"78 -- have been solved.
7979 prerequisites = {
80[barbariantut01]80 "bar01.wmf",
81name=_"This Land is Our Land"81 },
82visible=082 scenarios = {
83path="campaigns/bar02.wmf"83 "emp01.wmf",
8484 "emp02.wmf",
85[barbariantut02]85 "emp03.wmf",
86name=_"Not yet implemented"86 "emp04.wmf",
87visible=087 "dummy.wmf"
88path="campaigns/dummy.wmf"88 }
8989 },
90[empiretut00]90 {
91name=_"The Strands of Malac’ Mor"91 -- **** Atlantean Introduction ****
92visible=192 -- TRANSLATORS: The name of an Atlantean campign
93path="campaigns/emp01.wmf"93 descname = _"The Run for the Fire",
9494 tribe = "atlanteans",
95[empiretut01]95 -- TRANSLATORS: A short description of a campign
96name=_"An Outpost for Exile"96 difficulty = { level=3, description=_"Introduces the Atlanteans." },
97visible=097 -- TRANSLATORS: A long description of a campign
98path="campaigns/emp02.wmf"98 description = _"When their God lost faith in the Atlanteans and drowned their island, one woman’s struggle for justice and a second chance for her people would become the stuff of legends. Leading the remaining Atlanteans into a new future in a new part of the World, Jundlina became the most powerful human of her time, but at a high cost: her humanity and soul.",
9999 prerequisites = {
100[empiretut02]100 "emp02.wmf",
101name=_"Neptune’s Revenge"101 },
102visible=0102 scenarios = {
103path="campaigns/emp03.wmf"103 "atl01.wmf",
104104 "dummy.wmf"
105[empiretut03]105 }
106name=_"Surprise, Surprise!"106 },
107visible=0107 {
108path="campaigns/emp04.wmf"108 -- **** Frisian Introduction ****
109109 -- TRANSLATORS: The name of a Frisian campign
110[empiretut04]110 descname = _"From Water to Ice",
111name=_"Not yet implemented"111 tribe = "frisians",
112newvisi="campsect2"112 -- TRANSLATORS: A short description of a campign
113visible=0113 difficulty = { level=4, description=_"Introduces the Frisians." },
114path="campaigns/dummy.wmf"114 -- TRANSLATORS: A long description of a campign
115115 description = _"Living off the ocean is a constant struggle, and even more so for the inhabitants of the Frisian North Sea shore. Was the last storm flood, the most devastating one in human memory, really nothing more than yet another example for the hardships all Frisians have to face – or a sign from the gods that a tribe that only just settled here must seek out an entirely new home?",
116116 prerequisites = {
117[atlanteans00]117 "emp04.wmf",
118name=_"From Nemesis to Genesis"118 "atl01.wmf",
119visible=1119 },
120path="campaigns/atl01.wmf"120 scenarios = {
121121 "fri01.wmf",
122[atlanteans01]122 "fri02.wmf",
123name=_"Not yet implemented"123 "dummy.wmf"
124visible=0124 }
125path="campaigns/dummy.wmf"125 }
126126 }
127127}
128[frisians00]
129name=_"The Great Stormflood"
130visible=1
131path="campaigns/fri01.wmf"
132
133[frisians01]
134name=_"Colder than Ice"
135visible=0
136path="campaigns/fri02.wmf"
137
138[frisians02]
139name=_"Not yet implemented"
140visible=0
141path="campaigns/dummy.wmf"
142128
=== modified file 'data/campaigns/dummy.wmf/elemental'
--- data/campaigns/dummy.wmf/elemental 2017-12-17 19:06:02 +0000
+++ data/campaigns/dummy.wmf/elemental 2019-04-23 16:17:37 +0000
@@ -8,6 +8,7 @@
8name=_"Not yet implemented"8name=_"Not yet implemented"
9# TRANSLATORS: Author for dummy map9# TRANSLATORS: Author for dummy map
10author=_"Nobody"10author=_"Nobody"
11# TRANSLATORS: Description for greyed out dummy scenario displayed in campaign screen
11descr=_"Sorry, this map is not yet implemented."12descr=_"Sorry, this map is not yet implemented."
12hint=13hint=
13tags=14tags=
1415
=== modified file 'data/campaigns/emp01.wmf/scripting/mission_thread.lua'
--- data/campaigns/emp01.wmf/scripting/mission_thread.lua 2017-06-25 12:53:48 +0000
+++ data/campaigns/emp01.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
@@ -89,7 +89,7 @@
89 sleep(25000) -- Sleep a while89 sleep(25000) -- Sleep a while
9090
91 campaign_message_box(diary_page_4)91 campaign_message_box(diary_page_4)
92 p1:reveal_scenario("empiretut01")92 p1:mark_scenario_as_solved("emp01.wmf")
93end93end
9494
95-- Show a funny message when the player has build 10 blockhouses95-- Show a funny message when the player has build 10 blockhouses
9696
=== modified file 'data/campaigns/emp02.wmf/scripting/mission_thread.lua'
--- data/campaigns/emp02.wmf/scripting/mission_thread.lua 2018-11-19 08:09:41 +0000
+++ data/campaigns/emp02.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
@@ -265,8 +265,7 @@
265 campaign_message_box(seven_days_later)265 campaign_message_box(seven_days_later)
266 campaign_message_box(diary_page_11)266 campaign_message_box(diary_page_11)
267267
268 p1:reveal_scenario("empiretut02")268 p1:mark_scenario_as_solved("emp02.wmf")
269 p1:reveal_campaign("campsect2")
270end269end
271270
272run(building_materials)271run(building_materials)
273272
=== modified file 'data/campaigns/emp03.wmf/scripting/mission_thread.lua'
--- data/campaigns/emp03.wmf/scripting/mission_thread.lua 2018-09-13 21:00:11 +0000
+++ data/campaigns/emp03.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
@@ -265,7 +265,7 @@
265 sleep(25000)265 sleep(25000)
266 campaign_message_box(diary_page_5)266 campaign_message_box(diary_page_5)
267267
268 p1:reveal_scenario("empiretut03")268 p1:mark_scenario_as_solved("emp03.wmf")
269end269end
270270
271-- After discovery of Barbarian ruins, we should hurry to build a full training capability271-- After discovery of Barbarian ruins, we should hurry to build a full training capability
272272
=== modified file 'data/campaigns/emp04.wmf/scripting/mission_thread.lua'
--- data/campaigns/emp04.wmf/scripting/mission_thread.lua 2018-11-16 06:41:28 +0000
+++ data/campaigns/emp04.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
@@ -389,9 +389,7 @@
389 sleep(25000)389 sleep(25000)
390 campaign_message_box(diary_page_4)390 campaign_message_box(diary_page_4)
391391
392 p1:reveal_campaign("campsect2")392 p1:mark_scenario_as_solved("emp04.wmf")
393 p1:reveal_campaign("campsect3")
394 p1:reveal_scenario("empiretut04")
395end393end
396394
397-- another production chain that is ineffective and needs to be corrected395-- another production chain that is ineffective and needs to be corrected
398396
=== renamed file 'data/campaigns/tutorials.conf' => 'data/campaigns/tutorials.lua'
--- data/campaigns/tutorials.conf 2018-08-13 16:44:58 +0000
+++ data/campaigns/tutorials.lua 2019-04-23 16:17:37 +0000
@@ -1,28 +1,12 @@
1##########################################1--##########################################
2# Tutorials configuration - file #2--# Tutorials configuration - file #
3##########################################3--##########################################
44
55return {
6#####6 -- The tutorial scenarios in the order that they will appear on screen
7# Sections of the tutorial - maps7 -- The paths are relative to data/campaigns
8# Naming MUST be the name of the tutorial-section + "??" where ?? is an increasing number.8 "tutorial01_basic_control.wmf",
9#9 "tutorial02_warfare.wmf",
10# name = name of the map.10 "tutorial03_seafaring.wmf",
11# path = path to the map.11 "tutorial04_economy.wmf"
12#####12}
13
14[tutorials00]
15name=_"Basic Control"
16path="campaigns/tutorial01_basic_control.wmf"
17
18[tutorials01]
19name=_"Warfare"
20path="campaigns/tutorial02_warfare.wmf"
21
22[tutorials02]
23name=_"Seafaring"
24path="campaigns/tutorial03_seafaring.wmf"
25
26[tutorials03]
27name=_"Economy"
28path="campaigns/tutorial04_economy.wmf"
2913
=== renamed file 'data/images/ui_fsmenu/hard.png' => 'data/images/ui_fsmenu/challenging.png'
=== added file 'data/images/ui_fsmenu/easy.png'
30Binary files data/images/ui_fsmenu/easy.png 1970-01-01 00:00:00 +0000 and data/images/ui_fsmenu/easy.png 2019-04-23 16:17:37 +0000 differ14Binary files data/images/ui_fsmenu/easy.png 1970-01-01 00:00:00 +0000 and data/images/ui_fsmenu/easy.png 2019-04-23 16:17:37 +0000 differ
=== renamed file 'data/images/ui_fsmenu/challenging.png' => 'data/images/ui_fsmenu/hard.png'
=== renamed file 'data/images/ui_fsmenu/easy.png' => 'data/images/ui_fsmenu/medium.png'
=== modified file 'src/base/i18n.cc'
--- src/base/i18n.cc 2019-02-23 11:00:49 +0000
+++ src/base/i18n.cc 2019-04-23 16:17:37 +0000
@@ -335,6 +335,7 @@
335}335}
336336
337std::string localize_list(const std::vector<std::string>& items, ConcatenateWith listtype) {337std::string localize_list(const std::vector<std::string>& items, ConcatenateWith listtype) {
338 i18n::Textdomain td("widelands");
338 std::string result;339 std::string result;
339 for (std::vector<std::string>::const_iterator it = items.begin(); it != items.end(); ++it) {340 for (std::vector<std::string>::const_iterator it = items.begin(); it != items.end(); ++it) {
340 if (it == items.begin()) {341 if (it == items.begin()) {
@@ -365,4 +366,12 @@
365 }366 }
366 return result;367 return result;
367}368}
369
370std::string join_sentences(const std::string& sentence1, const std::string& sentence2) {
371 i18n::Textdomain td("widelands");
372 /** TRANSLATORS: Put 2 sentences one after the other. Languages using Chinese script probably
373 * want to lose the blank space here. */
374 return (boost::format(pgettext("sentence_separator", "%1% %2%")) % sentence1 % sentence2).str();
375}
376
368} // namespace i18n377} // namespace i18n
369378
=== modified file 'src/base/i18n.h'
--- src/base/i18n.h 2019-02-23 11:00:49 +0000
+++ src/base/i18n.h 2019-04-23 16:17:37 +0000
@@ -58,10 +58,19 @@
58void set_localedir(const std::string&);58void set_localedir(const std::string&);
59const std::string& get_localedir();59const std::string& get_localedir();
6060
61// Localize a list of 'items'. The last 2 items are concatenated with "and" or
62// "or", depending on 'concatenate_with'.
63enum class ConcatenateWith { AND, OR, AMPERSAND, COMMA };61enum class ConcatenateWith { AND, OR, AMPERSAND, COMMA };
62/**
63 * Localize a list of 'items'. The last 2 items are concatenated with "and" or
64 * "or" etc, depending on 'concatenate_with'.
65 */
64std::string localize_list(const std::vector<std::string>& items, ConcatenateWith concatenate_with);66std::string localize_list(const std::vector<std::string>& items, ConcatenateWith concatenate_with);
67
68/**
69 * Joins 2 sentences together. Use this rather than manually concatenating
70 * a blank space, because some languages don't use blank spaces.
71 */
72std::string join_sentences(const std::string& sentence1, const std::string& sentence2);
73
65} // namespace i18n74} // namespace i18n
6675
67#endif // end of include guard: WL_BASE_I18N_H76#endif // end of include guard: WL_BASE_I18N_H
6877
=== modified file 'src/graphic/text_layout.cc'
--- src/graphic/text_layout.cc 2019-02-23 11:00:49 +0000
+++ src/graphic/text_layout.cc 2019-04-23 16:17:37 +0000
@@ -222,6 +222,7 @@
222 }222 }
223 NEVER_HERE();223 NEVER_HERE();
224}224}
225
225std::string as_content(const std::string& txt, UI::PanelStyle style) {226std::string as_content(const std::string& txt, UI::PanelStyle style) {
226 switch (style) {227 switch (style) {
227 case UI::PanelStyle::kFsMenu:228 case UI::PanelStyle::kFsMenu:
228229
=== modified file 'src/graphic/text_layout.h'
--- src/graphic/text_layout.h 2019-02-23 11:00:49 +0000
+++ src/graphic/text_layout.h 2019-04-23 16:17:37 +0000
@@ -105,9 +105,11 @@
105 bool noescape = false);105 bool noescape = false);
106106
107/**107/**
108 * Heading in menu info texts
108 * 'is_first' omits the vertical gap before the line.109 * 'is_first' omits the vertical gap before the line.
109 */110 */
110std::string as_heading(const std::string& txt, UI::PanelStyle style, bool is_first = false);111std::string as_heading(const std::string& txt, UI::PanelStyle style, bool is_first = false);
112/// Paragraph in menu info texts
111std::string as_content(const std::string& txt, UI::PanelStyle style);113std::string as_content(const std::string& txt, UI::PanelStyle style);
112114
113/**115/**
114116
=== modified file 'src/logic/CMakeLists.txt'
--- src/logic/CMakeLists.txt 2019-02-27 17:19:00 +0000
+++ src/logic/CMakeLists.txt 2019-04-23 16:17:37 +0000
@@ -56,17 +56,6 @@
56 wui56 wui
57)57)
5858
59wl_library(logic_campaign_visibility
60 SRCS
61 campaign_visibility.cc
62 campaign_visibility.h
63 DEPENDS
64 base_exceptions
65 io_filesystem
66 logic_filesystem_constants
67 profile
68)
69
70wl_library(logic_constants59wl_library(logic_constants
71 SRCS60 SRCS
72 widelands.cc61 widelands.cc
7362
=== modified file 'src/logic/filesystem_constants.h'
--- src/logic/filesystem_constants.h 2019-02-23 11:00:49 +0000
+++ src/logic/filesystem_constants.h 2019-04-23 16:17:37 +0000
@@ -34,6 +34,7 @@
3434
35/// Filesystem names for maps35/// Filesystem names for maps
36const std::string kMapsDir = "maps";36const std::string kMapsDir = "maps";
37const std::string kCampaignsDir = "campaigns";
37const std::string kWidelandsMapExtension = ".wmf";38const std::string kWidelandsMapExtension = ".wmf";
38const std::string kS2MapExtension1 = ".swd";39const std::string kS2MapExtension1 = ".swd";
39const std::string kS2MapExtension2 = ".wld";40const std::string kS2MapExtension2 = ".wld";
@@ -61,7 +62,7 @@
6162
62/// Filesystem names and intervals for savegames63/// Filesystem names and intervals for savegames
63const std::string kSaveDir = "save";64const std::string kSaveDir = "save";
64const std::string kCampVisFile = "save/campvis";65const std::string kCampVisFile = "save/campaigns.conf";
65const std::string kSavegameExtension = ".wgf";66const std::string kSavegameExtension = ".wgf";
66const std::string kAutosavePrefix = "wl_autosave";67const std::string kAutosavePrefix = "wl_autosave";
67// Default autosave interval in minutes68// Default autosave interval in minutes
6869
=== modified file 'src/scripting/CMakeLists.txt'
--- src/scripting/CMakeLists.txt 2018-09-14 08:44:42 +0000
+++ src/scripting/CMakeLists.txt 2019-04-23 16:17:37 +0000
@@ -109,7 +109,6 @@
109 io_fileread109 io_fileread
110 io_filesystem110 io_filesystem
111 logic111 logic
112 logic_campaign_visibility
113 logic_constants112 logic_constants
114 logic_filesystem_constants113 logic_filesystem_constants
115 logic_game_controller114 logic_game_controller
116115
=== modified file 'src/scripting/lua_game.cc'
--- src/scripting/lua_game.cc 2019-04-09 16:43:49 +0000
+++ src/scripting/lua_game.cc 2019-04-23 16:17:37 +0000
@@ -25,7 +25,7 @@
2525
26#include "economy/economy.h"26#include "economy/economy.h"
27#include "economy/flag.h"27#include "economy/flag.h"
28#include "logic/campaign_visibility.h"28#include "logic/filesystem_constants.h"
29#include "logic/game_controller.h"29#include "logic/game_controller.h"
30#include "logic/map_objects/tribes/tribe_descr.h"30#include "logic/map_objects/tribes/tribe_descr.h"
31#include "logic/message.h"31#include "logic/message.h"
@@ -93,8 +93,7 @@
93 METHOD(LuaPlayer, add_objective),93 METHOD(LuaPlayer, add_objective),
94 METHOD(LuaPlayer, reveal_fields),94 METHOD(LuaPlayer, reveal_fields),
95 METHOD(LuaPlayer, hide_fields),95 METHOD(LuaPlayer, hide_fields),
96 METHOD(LuaPlayer, reveal_scenario),96 METHOD(LuaPlayer, mark_scenario_as_solved),
97 METHOD(LuaPlayer, reveal_campaign),
98 METHOD(LuaPlayer, get_ships),97 METHOD(LuaPlayer, get_ships),
99 METHOD(LuaPlayer, get_buildings),98 METHOD(LuaPlayer, get_buildings),
100 METHOD(LuaPlayer, get_suitability),99 METHOD(LuaPlayer, get_suitability),
@@ -623,42 +622,23 @@
623}622}
624623
625/* RST624/* RST
626 .. method:: reveal_scenario(name)625 .. method:: mark_scenario_as_solved(name)
627626
628 This reveals a scenario inside a campaign. This only works for the627 Marks a campaign scenario as solved. Reads the scenario definition in data/campaigns/campaigns.lua
628 to check which scenario and/or campaign should be revealed as a result. This only works for the
629 interactive player and most likely also only in single player games.629 interactive player and most likely also only in single player games.
630630
631 :arg name: name of the scenario to reveal631 :arg name: name of the scenario to be marked as solved
632 :type name: :class:`string`632 :type name: :class:`string`
633*/633*/
634// UNTESTED634// UNTESTED
635int LuaPlayer::reveal_scenario(lua_State* L) {635int LuaPlayer::mark_scenario_as_solved(lua_State* L) {
636 if (get_game(L).get_ipl()->player_number() != player_number())636 if (get_game(L).get_ipl()->player_number() != player_number())
637 report_error(L, "Can only be called for interactive player!");637 report_error(L, "Can only be called for interactive player!");
638638
639 CampaignVisibilitySave cvs;639 Profile campvis(kCampVisFile.c_str());
640 cvs.set_map_visibility(luaL_checkstring(L, 2), true);640 campvis.pull_section("scenarios").set_bool(luaL_checkstring(L, 2), true);
641641 campvis.write(kCampVisFile.c_str(), false);
642 return 0;
643}
644
645/* RST
646 .. method:: reveal_campaign(name)
647
648 This reveals a campaign. This only works for the
649 interactive player and most likely also only in single player games.
650
651 :arg name: name of the campaign to reveal
652 :type name: :class:`string`
653*/
654// UNTESTED
655int LuaPlayer::reveal_campaign(lua_State* L) {
656 if (get_game(L).get_ipl()->player_number() != player_number()) {
657 report_error(L, "Can only be called for interactive player!");
658 }
659
660 CampaignVisibilitySave cvs;
661 cvs.set_campaign_visibility(luaL_checkstring(L, 2), true);
662642
663 return 0;643 return 0;
664}644}
665645
=== modified file 'src/scripting/lua_game.h'
--- src/scripting/lua_game.h 2019-02-23 11:00:49 +0000
+++ src/scripting/lua_game.h 2019-04-23 16:17:37 +0000
@@ -89,8 +89,7 @@
89 int add_objective(lua_State* L);89 int add_objective(lua_State* L);
90 int reveal_fields(lua_State* L);90 int reveal_fields(lua_State* L);
91 int hide_fields(lua_State* L);91 int hide_fields(lua_State* L);
92 int reveal_scenario(lua_State* L);92 int mark_scenario_as_solved(lua_State* L);
93 int reveal_campaign(lua_State* L);
94 int get_ships(lua_State* L);93 int get_ships(lua_State* L);
95 int get_buildings(lua_State* L);94 int get_buildings(lua_State* L);
96 int get_suitability(lua_State* L);95 int get_suitability(lua_State* L);
9796
=== modified file 'src/ui_basic/table.cc'
--- src/ui_basic/table.cc 2019-02-23 11:00:49 +0000
+++ src/ui_basic/table.cc 2019-04-23 16:17:37 +0000
@@ -300,8 +300,8 @@
300 curx += curw;300 curx += curw;
301 continue;301 continue;
302 }302 }
303 std::shared_ptr<const UI::RenderedText> rendered_text =303 std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh->render(
304 UI::g_fh->render(as_uifont(richtext_escape(entry_string)));304 as_uifont(richtext_escape(entry_string), UI_FONT_SIZE_SMALL, er.get_color()));
305305
306 // Fix text alignment for BiDi languages if the entry contains an RTL character. We want306 // Fix text alignment for BiDi languages if the entry contains an RTL character. We want
307 // this always on, e.g. for mixed language savegame filenames.307 // this always on, e.g. for mixed language savegame filenames.
@@ -718,7 +718,7 @@
718 return ea.get_string(column) < eb.get_string(column);718 return ea.get_string(column) < eb.get_string(column);
719}719}
720720
721Table<void*>::EntryRecord::EntryRecord(void* const e) : entry_(e) {721Table<void*>::EntryRecord::EntryRecord(void* const e) : entry_(e), clr(UI_FONT_CLR_FG) {
722}722}
723723
724void Table<void*>::EntryRecord::set_picture(uint8_t const col,724void Table<void*>::EntryRecord::set_picture(uint8_t const col,
725725
=== modified file 'src/ui_basic/table.h'
--- src/ui_basic/table.h 2019-02-23 11:00:49 +0000
+++ src/ui_basic/table.h 2019-04-23 16:17:37 +0000
@@ -88,7 +88,7 @@
88 void remove(uint32_t);88 void remove(uint32_t);
89 void remove_entry(Entry);89 void remove_entry(Entry);
9090
91 EntryRecord& add(void* const entry, const bool select_this = false);91 EntryRecord& add(void* const entry, bool const select_this = false);
9292
93 uint32_t size() const;93 uint32_t size() const;
94 bool empty() const;94 bool empty() const;
@@ -142,13 +142,8 @@
142 return entry_;142 return entry_;
143 }143 }
144 void set_color(const RGBColor& c) {144 void set_color(const RGBColor& c) {
145 use_clr = true;
146 clr = c;145 clr = c;
147 }146 }
148
149 bool use_color() const {
150 return use_clr;
151 }
152 RGBColor get_color() const {147 RGBColor get_color() const {
153 return clr;148 return clr;
154 }149 }
@@ -156,7 +151,6 @@
156 private:151 private:
157 friend class Table<void*>;152 friend class Table<void*>;
158 void* entry_;153 void* entry_;
159 bool use_clr;
160 RGBColor clr;154 RGBColor clr;
161 struct Data {155 struct Data {
162 const Image* d_picture;156 const Image* d_picture;
@@ -214,7 +208,7 @@
214 void remove(uint32_t);208 void remove(uint32_t);
215 void remove_entry(const void* const entry);209 void remove_entry(const void* const entry);
216210
217 EntryRecord& add(void* entry = nullptr, bool select = false);211 EntryRecord& add(void* entry = nullptr, bool const select_this = false);
218212
219 uint32_t size() const {213 uint32_t size() const {
220 return entry_records_.size();214 return entry_records_.size();
221215
=== modified file 'src/ui_fsmenu/CMakeLists.txt'
--- src/ui_fsmenu/CMakeLists.txt 2018-02-13 16:52:12 +0000
+++ src/ui_fsmenu/CMakeLists.txt 2019-04-23 16:17:37 +0000
@@ -126,10 +126,18 @@
126126
127wl_library(ui_fsmenu_maploading127wl_library(ui_fsmenu_maploading
128 SRCS128 SRCS
129 campaigndetails.cc
130 campaigndetails.h
131 campaigns.cc
132 campaigns.h
129 campaign_select.cc133 campaign_select.cc
130 campaign_select.h134 campaign_select.h
131 mapselect.cc135 mapselect.cc
132 mapselect.h136 mapselect.h
137 scenariodetails.cc
138 scenariodetails.h
139 scenario_select.cc
140 scenario_select.h
133 DEPENDS141 DEPENDS
134 base_exceptions142 base_exceptions
135 base_i18n143 base_i18n
@@ -137,13 +145,16 @@
137 graphic145 graphic
138 graphic_fonthandler146 graphic_fonthandler
139 graphic_text_constants147 graphic_text_constants
148 graphic_surface
140 io_filesystem149 io_filesystem
141 logic_campaign_visibility
142 logic_filesystem_constants150 logic_filesystem_constants
143 logic_game_controller151 logic_game_controller
144 logic_game_settings152 logic_game_settings
153 logic_tribe_basic_info
145 map_io_map_loader154 map_io_map_loader
146 profile155 profile
156 scripting_lua_interface
157 scripting_lua_table
147 ui_basic158 ui_basic
148 ui_fsmenu_base159 ui_fsmenu_base
149 ui_fsmenu_loading_common160 ui_fsmenu_loading_common
150161
=== modified file 'src/ui_fsmenu/campaign_select.cc'
--- src/ui_fsmenu/campaign_select.cc 2019-02-23 11:00:49 +0000
+++ src/ui_fsmenu/campaign_select.cc 2019-04-23 16:17:37 +0000
@@ -27,67 +27,28 @@
27#include "base/wexception.h"27#include "base/wexception.h"
28#include "graphic/graphic.h"28#include "graphic/graphic.h"
29#include "graphic/text_constants.h"29#include "graphic/text_constants.h"
30#include "logic/campaign_visibility.h"30#include "logic/filesystem_constants.h"
31#include "map_io/widelands_map_loader.h"
32#include "profile/profile.h"31#include "profile/profile.h"
3332#include "scripting/lua_interface.h"
34/*33#include "scripting/lua_table.h"
35 * UI 1 - Selection of Campaign
36 *
37 */
3834
39/**35/**
40 * CampaignSelect UI36 * CampaignSelect UI
41 * Loads a list of all visible campaigns37 * Loads a list of all visible campaigns
42 */38 */
43FullscreenMenuCampaignSelect::FullscreenMenuCampaignSelect()39FullscreenMenuCampaignSelect::FullscreenMenuCampaignSelect(Campaigns* campvis)
44 : FullscreenMenuLoadMapOrGame(),40 : FullscreenMenuLoadMapOrGame(),
45 table_(this, tablex_, tabley_, tablew_, tableh_, UI::PanelStyle::kFsMenu),41 table_(this, 0, 0, 0, 0, UI::PanelStyle::kFsMenu),
4642
47 // Main Title43 // Main Title
48 title_(this, get_w() / 2, tabley_ / 3, _("Choose a campaign"), UI::Align::kCenter),44 title_(this, 0, 0, _("Choose a campaign"), UI::Align::kCenter),
4945
50 // Campaign description46 // Campaign description
51 label_campname_(this, right_column_x_, tabley_),47 campaign_details_(this),
52 ta_campname_(this,48 campaigns_(campvis) {
53 right_column_x_ + indent_,
54 get_y_from_preceding(label_campname_) + padding_,
55 get_right_column_w(right_column_x_) - indent_,
56 label_height_,
57 UI::PanelStyle::kFsMenu),
58
59 label_tribename_(this, right_column_x_, get_y_from_preceding(ta_campname_) + 2 * padding_),
60 ta_tribename_(this,
61 right_column_x_ + indent_,
62 get_y_from_preceding(label_tribename_) + padding_,
63 get_right_column_w(right_column_x_ + indent_),
64 label_height_,
65 UI::PanelStyle::kFsMenu),
66
67 label_difficulty_(this, right_column_x_, get_y_from_preceding(ta_tribename_) + 2 * padding_),
68 ta_difficulty_(this,
69 right_column_x_ + indent_,
70 get_y_from_preceding(label_difficulty_) + padding_,
71 get_right_column_w(right_column_x_ + indent_),
72 2 * label_height_ - padding_,
73 UI::PanelStyle::kFsMenu),
74
75 label_description_(this,
76 right_column_x_,
77 get_y_from_preceding(ta_difficulty_) + 2 * padding_,
78 _("Description:")),
79 ta_description_(this,
80 right_column_x_ + indent_,
81 get_y_from_preceding(label_description_) + padding_,
82 get_right_column_w(right_column_x_ + indent_),
83 buty_ - get_y_from_preceding(label_description_) - 4 * padding_,
84 UI::PanelStyle::kFsMenu) {
85 title_.set_fontsize(UI_FONT_SIZE_BIG);49 title_.set_fontsize(UI_FONT_SIZE_BIG);
86 back_.set_tooltip(_("Return to the main menu"));50 back_.set_tooltip(_("Return to the main menu"));
87 ok_.set_tooltip(_("Play this campaign"));51 ok_.set_tooltip(_("Play this campaign"));
88 ta_campname_.set_tooltip(_("The name of this campaign"));
89 ta_tribename_.set_tooltip(_("The tribe you will be playing"));
90 ta_difficulty_.set_tooltip(_("The difficulty of this campaign"));
9152
92 ok_.sigclicked.connect(53 ok_.sigclicked.connect(
93 boost::bind(&FullscreenMenuCampaignSelect::clicked_ok, boost::ref(*this)));54 boost::bind(&FullscreenMenuCampaignSelect::clicked_ok, boost::ref(*this)));
@@ -99,7 +60,7 @@
9960
100 /** TRANSLATORS: Campaign difficulty table header */61 /** TRANSLATORS: Campaign difficulty table header */
101 table_.add_column(45, _("Diff."), _("Difficulty"));62 table_.add_column(45, _("Diff."), _("Difficulty"));
102 table_.add_column(100, _("Tribe"), _("Tribe Name"));63 table_.add_column(130, _("Tribe"), _("Tribe Name"));
103 table_.add_column(64 table_.add_column(
104 0, _("Campaign Name"), _("Campaign Name"), UI::Align::kLeft, UI::TableColumnType::kFlexible);65 0, _("Campaign Name"), _("Campaign Name"), UI::Align::kLeft, UI::TableColumnType::kFlexible);
105 table_.set_column_compare(66 table_.set_column_compare(
@@ -107,10 +68,19 @@
107 table_.set_sort_column(0);68 table_.set_sort_column(0);
108 table_.focus();69 table_.focus();
109 fill_table();70 fill_table();
71 layout();
110}72}
11173
112void FullscreenMenuCampaignSelect::layout() {74void FullscreenMenuCampaignSelect::layout() {
113 // TODO(GunChleoc): Implement when we have box layout for the details.75 FullscreenMenuLoadMapOrGame::layout();
76 title_.set_pos(Vector2i(0, tabley_ / 3));
77 title_.set_size(get_w(), title_.get_h());
78 table_.set_size(tablew_, tableh_);
79 table_.set_pos(Vector2i(tablex_, tabley_));
80 campaign_details_.set_size(get_right_column_w(right_column_x_), tableh_ - buth_ - 4 * padding_);
81 campaign_details_.set_desired_size(
82 get_right_column_w(right_column_x_), tableh_ - buth_ - 4 * padding_);
83 campaign_details_.set_pos(Vector2i(right_column_x_, tabley_));
114}84}
11585
116/**86/**
@@ -120,385 +90,62 @@
120 if (!table_.has_selection()) {90 if (!table_.has_selection()) {
121 return;91 return;
122 }92 }
123 get_campaign();93 const CampaignData& campaign_data = *campaigns_->get_campaign(table_.get_selected());
94 if (!campaign_data.visible) {
95 return;
96 }
124 end_modal<FullscreenMenuBase::MenuTarget>(FullscreenMenuBase::MenuTarget::kOk);97 end_modal<FullscreenMenuBase::MenuTarget>(FullscreenMenuBase::MenuTarget::kOk);
125}98}
12699
127int32_t FullscreenMenuCampaignSelect::get_campaign() {100size_t FullscreenMenuCampaignSelect::get_campaign_index() const {
128 return campaign;101 return table_.get_selected();
129}102}
130103
131/// Pictorial descriptions of difficulty levels.
132static char const* const difficulty_picture_filenames[] = {
133 "images/novalue.png", "images/ui_fsmenu/easy.png", "images/ui_fsmenu/challenging.png",
134 "images/ui_fsmenu/hard.png"};
135
136bool FullscreenMenuCampaignSelect::set_has_selection() {104bool FullscreenMenuCampaignSelect::set_has_selection() {
137 bool has_selection = table_.has_selection();105 const bool has_selection = table_.has_selection();
138 ok_.set_enabled(has_selection);106 ok_.set_enabled(has_selection);
139
140 if (!has_selection) {
141 label_campname_.set_text(std::string());
142 label_tribename_.set_text(std::string());
143 label_difficulty_.set_text(std::string());
144 label_description_.set_text(std::string());
145
146 ta_campname_.set_text(std::string());
147 ta_tribename_.set_text(std::string());
148 ta_difficulty_.set_text(std::string());
149 ta_description_.set_text(std::string());
150
151 } else {
152 label_campname_.set_text(_("Campaign Name:"));
153 label_tribename_.set_text(_("Tribe:"));
154 label_difficulty_.set_text(_("Difficulty:"));
155 label_description_.set_text(_("Description:"));
156 }
157 return has_selection;107 return has_selection;
158}108}
159109
160void FullscreenMenuCampaignSelect::entry_selected() {110void FullscreenMenuCampaignSelect::entry_selected() {
161 if (set_has_selection()) {111 if (set_has_selection()) {
162 const CampaignListData& campaign_data = campaigns_data_[table_.get_selected()];112 const CampaignData& campaign_data = *campaigns_->get_campaign(table_.get_selected());
163 campaign = campaign_data.index;113 ok_.set_enabled(campaign_data.visible);
164114 campaign_details_.update(campaign_data);
165 ta_campname_.set_text(campaign_data.name);
166 ta_tribename_.set_text(campaign_data.tribename);
167 ta_difficulty_.set_text(campaign_data.difficulty_description);
168 ta_description_.set_text(campaign_data.description);
169 }115 }
170 ta_description_.scroll_to_top();
171}116}
172117
173/**118/**
174 * fill the campaign list119 * fill the campaign list
175 */120 */
176void FullscreenMenuCampaignSelect::fill_table() {121void FullscreenMenuCampaignSelect::fill_table() {
177 campaigns_data_.clear();
178 table_.clear();122 table_.clear();
179123
180 // Read in the campaign config124 for (size_t i = 0; i < campaigns_->no_of_campaigns(); ++i) {
181 Profile prof("campaigns/campaigns.conf", nullptr, "maps");125 const CampaignData& campaign_data = *campaigns_->get_campaign(i);
182 Section& s = prof.get_safe_section("global");126
183127 UI::Table<uintptr_t const>::EntryRecord& tableEntry = table_.add(i);
184 // Read in campvis-file128 tableEntry.set_picture(0, campaign_data.difficulty_image);
185 CampaignVisibilitySave cvs;129 tableEntry.set_string(1, campaign_data.tribename);
186 Profile campvis(cvs.get_path().c_str());130 tableEntry.set_string(2, campaign_data.descname);
187 Section& c = campvis.get_safe_section("campaigns");131 if (!campaign_data.visible) {
188132 tableEntry.set_color(UI_FONT_CLR_DISABLED);
189 // Predefine variables, used in while-loop
190 uint32_t i = 0;
191 std::string csection = (boost::format("campsect%u") % i).str();
192 std::string cname;
193 std::string ctribename;
194 std::string cdifficulty;
195 std::string cdiff_descr;
196 std::string cdescription;
197
198 while (s.get_string(csection.c_str())) {
199
200 cname = (boost::format("campname%u") % i).str();
201 ctribename = (boost::format("camptribe%u") % i).str();
202 cdifficulty = (boost::format("campdiff%u") % i).str();
203 cdiff_descr = (boost::format("campdiffdescr%u") % i).str();
204 cdescription = (boost::format("campdesc%u") % i).str();
205
206 // Only list visible campaigns
207 if (c.get_bool(csection.c_str())) {
208
209 uint32_t difficulty = s.get_int(cdifficulty.c_str());
210 if (sizeof(difficulty_picture_filenames) / sizeof(*difficulty_picture_filenames) <=
211 difficulty) {
212 difficulty = 0;
213 }
214
215 CampaignListData campaign_data;
216
217 campaign_data.index = i;
218
219 {
220 i18n::Textdomain td("maps");
221 campaign_data.name = _(s.get_string(cname.c_str(), ""));
222 campaign_data.tribename = _(s.get_string(ctribename.c_str(), ""));
223 campaign_data.difficulty = difficulty;
224 campaign_data.difficulty_description = _(s.get_string(cdiff_descr.c_str(), ""));
225 campaign_data.description = _(s.get_string(cdescription.c_str(), ""));
226 }
227
228 campaigns_data_.push_back(campaign_data);
229
230 UI::Table<uintptr_t>::EntryRecord& tableEntry = table_.add(i);
231 tableEntry.set_picture(0, g_gr->images().get(difficulty_picture_filenames[difficulty]));
232 tableEntry.set_string(1, campaign_data.tribename);
233 tableEntry.set_string(2, campaign_data.name);
234 }133 }
235134 }
236 // Increase counter & csection
237 ++i;
238 csection = (boost::format("campsect%u") % i).str();
239
240 } // while (s.get_string(csection.c_str()))
241 table_.sort();
242135
243 if (table_.size()) {136 if (table_.size()) {
137 table_.sort();
244 table_.select(0);138 table_.select(0);
245 }139 }
246 set_has_selection();140 set_has_selection();
247}141}
248142
249bool FullscreenMenuCampaignSelect::compare_difficulty(uint32_t rowa, uint32_t rowb) {143bool FullscreenMenuCampaignSelect::compare_difficulty(uint32_t rowa, uint32_t rowb) {
250 const CampaignListData& r1 = campaigns_data_[table_[rowa]];144 const CampaignData& r1 = *campaigns_->get_campaign(table_[rowa]);
251 const CampaignListData& r2 = campaigns_data_[table_[rowb]];145 const CampaignData& r2 = *campaigns_->get_campaign(table_[rowb]);
252146
253 if (r1.difficulty < r2.difficulty) {147 if (r1.difficulty_level < r2.difficulty_level) {
254 return true;148 return true;
255 }149 }
256 return r1.index < r2.index;150 return table_[rowa] < table_[rowb];
257}
258
259/*
260 * UI 2 - Selection of a map
261 *
262 */
263
264/**
265 * CampaignMapSelect UI.
266 *
267 * Loads a list of all visible maps of selected campaign and let's the user
268 * choose one.
269 */
270FullscreenMenuCampaignMapSelect::FullscreenMenuCampaignMapSelect(bool is_tutorial)
271 : FullscreenMenuLoadMapOrGame(),
272 table_(this, tablex_, tabley_, tablew_, tableh_, UI::PanelStyle::kFsMenu),
273
274 // Main title
275 title_(this,
276 get_w() / 2,
277 tabley_ / 3,
278 is_tutorial ? _("Choose a tutorial") : _("Choose a scenario"),
279 UI::Align::kCenter),
280 subtitle_(this,
281 get_w() / 6,
282 get_y_from_preceding(title_) + 6 * padding_,
283 get_w() * 2 / 3,
284 4 * label_height_,
285 UI::PanelStyle::kFsMenu,
286 "",
287 UI::Align::kCenter),
288
289 // Map description
290 label_mapname_(this, right_column_x_, tabley_),
291 ta_mapname_(this,
292 right_column_x_ + indent_,
293 get_y_from_preceding(label_mapname_) + padding_,
294 get_right_column_w(right_column_x_ + indent_),
295 label_height_,
296 UI::PanelStyle::kFsMenu),
297
298 label_author_(this, right_column_x_, get_y_from_preceding(ta_mapname_) + 2 * padding_),
299 ta_author_(this,
300 right_column_x_ + indent_,
301 get_y_from_preceding(label_author_) + padding_,
302 get_right_column_w(right_column_x_ + indent_),
303 2 * label_height_,
304 UI::PanelStyle::kFsMenu),
305
306 label_description_(this, right_column_x_, get_y_from_preceding(ta_author_) + padding_),
307 ta_description_(this,
308 right_column_x_ + indent_,
309 get_y_from_preceding(label_description_) + padding_,
310 get_right_column_w(right_column_x_ + indent_),
311 buty_ - get_y_from_preceding(label_description_) - 4 * padding_,
312 UI::PanelStyle::kFsMenu),
313
314 is_tutorial_(is_tutorial) {
315 title_.set_fontsize(UI_FONT_SIZE_BIG);
316 back_.set_tooltip(_("Return to the main menu"));
317 if (is_tutorial_) {
318 ok_.set_tooltip(_("Play this tutorial"));
319 ta_mapname_.set_tooltip(_("The name of this tutorial"));
320 ta_description_.set_tooltip(_("What you will learn in this tutorial"));
321 } else {
322 ok_.set_tooltip(_("Play this scenario"));
323 ta_mapname_.set_tooltip(_("The name of this scenario"));
324 }
325
326 ok_.sigclicked.connect(
327 boost::bind(&FullscreenMenuCampaignMapSelect::clicked_ok, boost::ref(*this)));
328 back_.sigclicked.connect(
329 boost::bind(&FullscreenMenuCampaignMapSelect::clicked_back, boost::ref(*this)));
330 table_.selected.connect(boost::bind(&FullscreenMenuCampaignMapSelect::entry_selected, this));
331 table_.double_clicked.connect(
332 boost::bind(&FullscreenMenuCampaignMapSelect::clicked_ok, boost::ref(*this)));
333
334 std::string number_tooltip;
335 std::string name_tooltip;
336 if (is_tutorial_) {
337 number_tooltip = _("The order in which the tutorials should be played");
338 name_tooltip = _("Tutorial Name");
339 } else {
340 number_tooltip = _("The number of this scenario in the campaign");
341 name_tooltip = _("Scenario Name");
342 }
343
344 /** TRANSLATORS: Campaign scenario number table header */
345 table_.add_column(35, _("#"), number_tooltip);
346 table_.add_column(
347 0, name_tooltip, name_tooltip, UI::Align::kLeft, UI::TableColumnType::kFlexible);
348 table_.set_sort_column(0);
349
350 table_.focus();
351}
352
353void FullscreenMenuCampaignMapSelect::layout() {
354 // TODO(GunChleoc): Implement when we have box layout for the details.
355 table_.layout();
356}
357
358std::string FullscreenMenuCampaignMapSelect::get_map() {
359 return campmapfile;
360}
361
362// Telling this class what campaign we have and since we know what campaign we have, fill it.
363void FullscreenMenuCampaignMapSelect::set_campaign(uint32_t const i) {
364 campaign = i;
365 fill_table();
366}
367
368bool FullscreenMenuCampaignMapSelect::set_has_selection() {
369 bool has_selection = table_.has_selection();
370 ok_.set_enabled(has_selection);
371
372 if (!has_selection) {
373 label_mapname_.set_text(std::string());
374 label_author_.set_text(std::string());
375 label_description_.set_text(std::string());
376
377 ta_mapname_.set_text(std::string());
378 ta_author_.set_text(std::string());
379 ta_description_.set_text(std::string());
380
381 } else {
382 is_tutorial_ ? label_mapname_.set_text(_("Tutorial:")) :
383 label_mapname_.set_text(_("Scenario:"));
384 label_description_.set_text(_("Description:"));
385 }
386 return has_selection;
387}
388
389void FullscreenMenuCampaignMapSelect::entry_selected() {
390 if (set_has_selection()) {
391 const CampaignScenarioData& scenario_data = scenarios_data_[table_.get_selected()];
392 campmapfile = scenario_data.path;
393 Widelands::Map map;
394
395 std::unique_ptr<Widelands::MapLoader> ml(map.get_correct_loader(campmapfile));
396 if (!ml) {
397 throw wexception(_("Invalid path to file in campaigns.conf: %s"), campmapfile.c_str());
398 }
399
400 map.set_filename(campmapfile);
401 ml->preload_map(true);
402
403 // Localizing this, because some author fields now have "edited by" text.
404 MapAuthorData authors(_(map.get_author()));
405
406 ta_author_.set_text(authors.get_names());
407 if (is_tutorial_) {
408 ta_author_.set_tooltip(ngettext("The designer of this tutorial",
409 "The designers of this tutorial", authors.get_number()));
410 } else {
411 ta_author_.set_tooltip(ngettext("The designer of this scenario",
412 "The designers of this scenario", authors.get_number()));
413 }
414 label_author_.set_text(ngettext("Author:", "Authors:", authors.get_number()));
415
416 {
417 i18n::Textdomain td("maps");
418 ta_mapname_.set_text(_(map.get_name()));
419 ta_description_.set_text(_(map.get_description()));
420 }
421 ta_description_.scroll_to_top();
422
423 // The dummy scenario can't be played, so we disable the OK button.
424 if (campmapfile == "campaigns/dummy.wmf") {
425 ok_.set_enabled(false);
426 }
427 }
428}
429
430/**
431 * fill the campaign-map list
432 */
433void FullscreenMenuCampaignMapSelect::fill_table() {
434 // read in the campaign config
435 std::unique_ptr<Profile> prof;
436 std::string campsection;
437 if (is_tutorial_) {
438 prof.reset(new Profile("campaigns/tutorials.conf", nullptr, "maps"));
439
440 // Set subtitle of the page
441 const std::string subtitle1 = _("Pick a tutorial from the list, then hit \"OK\".");
442 const std::string subtitle2 =
443 _("You can see a description of the currently selected tutorial on the right.");
444 subtitle_.set_text((boost::format("%s\n%s") % subtitle1 % subtitle2).str());
445
446 // Get section of campaign-maps
447 campsection = "tutorials";
448
449 } else {
450 prof.reset(new Profile("campaigns/campaigns.conf", nullptr, "maps"));
451
452 Section& global_s = prof->get_safe_section("global");
453
454 // Set subtitle of the page
455 const char* campaign_tribe =
456 _(global_s.get_string((boost::format("camptribe%u") % campaign).str().c_str()));
457 const char* campaign_name;
458 {
459 i18n::Textdomain td("maps");
460 campaign_name =
461 _(global_s.get_string((boost::format("campname%u") % campaign).str().c_str()));
462 }
463 subtitle_.set_text((boost::format("%s — %s") % campaign_tribe % campaign_name).str());
464
465 // Get section of campaign-maps
466 campsection = global_s.get_string((boost::format("campsect%u") % campaign).str().c_str());
467 }
468
469 // Create the entry we use to load the section of the map
470 uint32_t i = 0;
471 std::string mapsection = campsection + (boost::format("%02i") % i).str();
472
473 // Read in campvis-file
474 CampaignVisibilitySave cvs;
475 Profile campvis(cvs.get_path().c_str());
476 Section& c = campvis.get_safe_section("campmaps");
477
478 // Add all visible entries to the list.
479 while (Section* const s = prof->get_section(mapsection)) {
480 if (is_tutorial_ || c.get_bool(mapsection.c_str())) {
481
482 CampaignScenarioData scenario_data;
483 scenario_data.index = i + 1;
484 scenario_data.name = s->get_string("name", "");
485 scenario_data.path = s->get_string("path");
486 scenarios_data_.push_back(scenario_data);
487
488 UI::Table<uintptr_t>::EntryRecord& tableEntry = table_.add(i);
489 tableEntry.set_string(0, (boost::format("%u") % scenario_data.index).str());
490 tableEntry.set_picture(
491 1, g_gr->images().get("images/ui_basic/ls_wlmap.png"), scenario_data.name);
492 }
493
494 // Increase counter & mapsection
495 ++i;
496 mapsection = campsection + (boost::format("%02i") % i).str();
497 }
498 table_.sort();
499
500 if (table_.size()) {
501 table_.select(0);
502 }
503 set_has_selection();
504}151}
505152
=== modified file 'src/ui_fsmenu/campaign_select.h'
--- src/ui_fsmenu/campaign_select.h 2019-02-23 11:00:49 +0000
+++ src/ui_fsmenu/campaign_select.h 2019-04-23 16:17:37 +0000
@@ -20,26 +20,22 @@
20#ifndef WL_UI_FSMENU_CAMPAIGN_SELECT_H20#ifndef WL_UI_FSMENU_CAMPAIGN_SELECT_H
21#define WL_UI_FSMENU_CAMPAIGN_SELECT_H21#define WL_UI_FSMENU_CAMPAIGN_SELECT_H
2222
23#include "ui_basic/button.h"23#include <vector>
24#include "ui_basic/multilinetextarea.h"24
25#include "ui_basic/table.h"25#include "ui_basic/table.h"
26#include "ui_basic/textarea.h"26#include "ui_basic/textarea.h"
27#include "ui_fsmenu/base.h"27#include "ui_fsmenu/campaigndetails.h"
28#include "ui_fsmenu/campaigns.h"
28#include "ui_fsmenu/load_map_or_game.h"29#include "ui_fsmenu/load_map_or_game.h"
2930
30/*31/*
31 * Fullscreen Menu for all Campaigns32 * Fullscreen Menu for selecting a campaign
32 */
33
34/*
35 * UI 1 - Selection of Campaign
36 *
37 */33 */
38class FullscreenMenuCampaignSelect : public FullscreenMenuLoadMapOrGame {34class FullscreenMenuCampaignSelect : public FullscreenMenuLoadMapOrGame {
39public:35public:
40 FullscreenMenuCampaignSelect();36 FullscreenMenuCampaignSelect(Campaigns* campvis);
4137
42 int32_t get_campaign();38 size_t get_campaign_index() const;
4339
44protected:40protected:
45 void clicked_ok() override;41 void clicked_ok() override;
@@ -52,90 +48,14 @@
52 /// Updates buttons and text labels and returns whether a table entry is selected.48 /// Updates buttons and text labels and returns whether a table entry is selected.
53 bool set_has_selection();49 bool set_has_selection();
5450
55 /**
56 * Data about a campaign that we're interested in.
57 */
58 struct CampaignListData {
59 uint32_t index;
60 std::string name;
61 std::string tribename;
62 uint32_t difficulty;
63 std::string difficulty_description;
64 std::string description;
65
66 CampaignListData() : index(0), difficulty(0) {
67 }
68 };
69
70 bool compare_difficulty(uint32_t, uint32_t);51 bool compare_difficulty(uint32_t, uint32_t);
7152
72 UI::Table<uintptr_t const> table_;53 UI::Table<uintptr_t const> table_;
7354
74 UI::Textarea title_;55 UI::Textarea title_;
75 UI::Textarea label_campname_;56 CampaignDetails campaign_details_;
76 UI::MultilineTextarea ta_campname_;57
77 UI::Textarea label_tribename_;58 Campaigns* campaigns_;
78 UI::MultilineTextarea ta_tribename_;
79 UI::Textarea label_difficulty_;
80 UI::MultilineTextarea ta_difficulty_;
81 UI::Textarea label_description_;
82 UI::MultilineTextarea ta_description_;
83
84 std::vector<CampaignListData> campaigns_data_;
85
86 /// Variables used for exchange between the two Campaign UIs and
87 /// Game::run_campaign
88 int32_t campaign;
89};
90/*
91 * UI 2 - Selection of a map
92 *
93 */
94class FullscreenMenuCampaignMapSelect : public FullscreenMenuLoadMapOrGame {
95public:
96 explicit FullscreenMenuCampaignMapSelect(bool is_tutorial = false);
97
98 std::string get_map();
99 void set_campaign(uint32_t);
100
101protected:
102 void entry_selected() override;
103 void fill_table() override;
104
105private:
106 void layout() override;
107
108 /// Updates buttons and text labels and returns whether a table entry is selected.
109 bool set_has_selection();
110 /**
111 * Data about a campaign scenario that we're interested in.
112 */
113 struct CampaignScenarioData {
114 uint32_t index;
115 std::string name;
116 std::string path;
117
118 CampaignScenarioData() : index(0) {
119 }
120 };
121
122 UI::Table<uintptr_t const> table_;
123
124 UI::Textarea title_;
125 UI::MultilineTextarea subtitle_;
126 UI::Textarea label_mapname_;
127 UI::MultilineTextarea ta_mapname_;
128 UI::Textarea label_author_;
129 UI::MultilineTextarea ta_author_;
130 UI::Textarea label_description_;
131 UI::MultilineTextarea ta_description_;
132
133 uint32_t campaign;
134 std::string campmapfile;
135
136 std::vector<CampaignScenarioData> scenarios_data_;
137
138 bool is_tutorial_;
139};59};
14060
141#endif // end of include guard: WL_UI_FSMENU_CAMPAIGN_SELECT_H61#endif // end of include guard: WL_UI_FSMENU_CAMPAIGN_SELECT_H
14262
=== added file 'src/ui_fsmenu/campaigndetails.cc'
--- src/ui_fsmenu/campaigndetails.cc 1970-01-01 00:00:00 +0000
+++ src/ui_fsmenu/campaigndetails.cc 2019-04-23 16:17:37 +0000
@@ -0,0 +1,82 @@
1/*
2 * Copyright (C) 2017 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#include "ui_fsmenu/campaigndetails.h"
20
21#include <boost/format.hpp>
22
23#include "base/i18n.h"
24#include "graphic/text_constants.h"
25#include "ui_basic/scrollbar.h"
26
27CampaignDetails::CampaignDetails(Panel* parent)
28 : UI::Box(parent, 0, 0, UI::Box::Vertical),
29 name_label_(this,
30 0,
31 0,
32 UI::Scrollbar::kSize,
33 0,
34 UI::PanelStyle::kFsMenu,
35 "",
36 UI::Align::kLeft,
37 UI::MultilineTextarea::ScrollMode::kNoScrolling),
38 descr_(this, 0, 0, UI::Scrollbar::kSize, 0, UI::PanelStyle::kFsMenu) {
39
40 constexpr int kPadding = 4;
41 add(&name_label_, UI::Box::Resizing::kFullSize);
42 add_space(kPadding);
43 add(&descr_, UI::Box::Resizing::kExpandBoth);
44}
45
46void CampaignDetails::update(const CampaignData& campaigndata) {
47 name_label_.set_text((boost::format("<rt>%s%s</rt>") %
48 /** TRANSLATORS: Header for campaign name */
49 as_heading(_("Campaign"), UI::PanelStyle::kFsMenu, true) %
50 as_content(campaigndata.descname, UI::PanelStyle::kFsMenu))
51 .str());
52
53 std::string description = "";
54
55 if (campaigndata.visible) {
56 /** TRANSLATORS: Header for campaign tribe */
57 description = (boost::format("%s%s") % as_heading(_("Tribe"), UI::PanelStyle::kFsMenu) %
58 as_content(campaigndata.tribename, UI::PanelStyle::kFsMenu))
59 .str();
60 description =
61 /** TRANSLATORS: Header for campaign difficulty */
62 (boost::format("%s%s") % description % as_heading(_("Difficulty"), UI::PanelStyle::kFsMenu)).str();
63 description = (boost::format("%s%s") % description %
64 as_content(campaigndata.difficulty_description, UI::PanelStyle::kFsMenu))
65 .str();
66
67 description =
68 /** TRANSLATORS: Header for campaign description */
69 (boost::format("%s%s") % description % as_heading(_("Description"), UI::PanelStyle::kFsMenu))
70 .str();
71 description = (boost::format("%s%s") % description %
72 as_content(campaigndata.description, UI::PanelStyle::kFsMenu))
73 .str();
74 }
75 description =
76 (boost::format("%s%s") % description % as_content(campaigndata.description, UI::PanelStyle::kFsMenu))
77 .str();
78
79 description = (boost::format("<rt>%s</rt>") % description).str();
80 descr_.set_text(description);
81 descr_.scroll_to_top();
82}
083
=== added file 'src/ui_fsmenu/campaigndetails.h'
--- src/ui_fsmenu/campaigndetails.h 1970-01-01 00:00:00 +0000
+++ src/ui_fsmenu/campaigndetails.h 2019-04-23 16:17:37 +0000
@@ -0,0 +1,41 @@
1/*
2 * Copyright (C) 2017 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20#ifndef WL_UI_FSMENU_CAMPAIGNDETAILS_H
21#define WL_UI_FSMENU_CAMPAIGNDETAILS_H
22
23#include "ui_basic/box.h"
24#include "ui_basic/multilinetextarea.h"
25#include "ui_fsmenu/campaigns.h"
26
27/**
28 * Show a Box with information about a campaign.
29 */
30class CampaignDetails : public UI::Box {
31public:
32 explicit CampaignDetails(Panel* parent);
33
34 void update(const CampaignData& campaigndata);
35
36private:
37 UI::MultilineTextarea name_label_;
38 UI::MultilineTextarea descr_;
39};
40
41#endif // end of include guard: WL_UI_FSMENU_CAMPAIGNDETAILS_H
042
=== renamed file 'src/logic/campaign_visibility.cc' => 'src/ui_fsmenu/campaigns.cc'
--- src/logic/campaign_visibility.cc 2019-02-23 11:00:49 +0000
+++ src/ui_fsmenu/campaigns.cc 2019-04-23 16:17:37 +0000
@@ -17,166 +17,206 @@
17 *17 *
18 */18 */
1919
20#include "logic/campaign_visibility.h"20#include "ui_fsmenu/campaigns.h"
2121
22#include <cstdio>22#include <map>
23#include <cstdlib>23#include <memory>
2424
25#include <sys/stat.h>25#include "base/log.h"
2626#include "graphic/graphic.h"
27#include "base/wexception.h"
28#include "io/filesystem/filesystem.h"27#include "io/filesystem/filesystem.h"
29#include "logic/filesystem_constants.h"28#include "logic/filesystem_constants.h"
29#include "logic/map_objects/tribes/tribe_basic_info.h"
30#include "profile/profile.h"30#include "profile/profile.h"
3131#include "scripting/lua_interface.h"
32/**32
33 * Get the path of campaign visibility save-file33namespace {
34 */34const std::string kCampVisFileLegacy = "save/campvis";
35std::string CampaignVisibilitySave::get_path() {35}
36 g_fs->ensure_directory_exists(kSaveDir); // Make sure save directory exists36
3737Campaigns::Campaigns() {
38 // check if campaigns visibility-save is available38 // Load solved scenarios
39 std::unique_ptr<Profile> campvis;
39 if (!(g_fs->file_exists(kCampVisFile))) {40 if (!(g_fs->file_exists(kCampVisFile))) {
40 make_campvis(kCampVisFile);41 // There is no campaigns.conf file - create one.
41 }42 campvis.reset(new Profile(kCampVisFile.c_str()));
4243 campvis->pull_section("scenarios");
43 // check if campaigns visibility-save is up to date44 campvis->write(kCampVisFile.c_str(), true);
44 Profile ca(kCampVisFile.c_str());45 if (g_fs->file_exists(kCampVisFileLegacy)) {
4546 update_legacy_campvis();
46 // 1st version of campvis had no global section47 }
47 if (!ca.get_section("global"))48 }
48 update_campvis(kCampVisFile);49 campvis.reset(new Profile(kCampVisFile.c_str()));
49 else {50 Section& campvis_scenarios = campvis->get_safe_section("scenarios");
50 Section& ca_s = ca.get_safe_section("global");51
51 Profile cc("campaigns/campaigns.conf");52 // Now load the campaign info
52 Section& cc_s = cc.get_safe_section("global");53 LuaInterface lua;
53 if (cc_s.get_int("version") > ca_s.get_int("version"))54 std::unique_ptr<LuaTable> table(lua.run_script("campaigns/campaigns.lua"));
54 update_campvis(kCampVisFile);55
55 }56 // Read difficulty images
5657 std::unique_ptr<LuaTable> difficulties_table(table->get_table("difficulties"));
57 return kCampVisFile;58 std::vector<std::pair<const std::string, const Image*>> difficulty_levels;
58}59 for (const auto& difficulty_level_table :
5960 difficulties_table->array_entries<std::unique_ptr<LuaTable>>()) {
60/**61 difficulty_levels.push_back(
61 * Create the campaign visibility save-file of the user62 std::make_pair(_(difficulty_level_table->get_string("descname")),
62 */63 g_gr->images().get(difficulty_level_table->get_string("image"))));
63void CampaignVisibilitySave::make_campvis(const std::string& savepath) {64 }
64 // Only prepare campvis-file -> data will be written via update_campvis65
65 Profile campvis(savepath.c_str());66 // Read the campaigns themselves
66 campvis.pull_section("global");67 std::unique_ptr<LuaTable> campaigns_table(table->get_table("campaigns"));
67 campvis.pull_section("campaigns");68 i18n::Textdomain td("maps");
68 campvis.pull_section("campmaps");69
69 campvis.write(savepath.c_str(), true);70 for (const auto& campaign_table : campaigns_table->array_entries<std::unique_ptr<LuaTable>>()) {
7071 CampaignData* campaign_data = new CampaignData();
71 update_campvis(savepath);72 campaign_data->descname = _(campaign_table->get_string("descname"));
72}73 campaign_data->tribename =
7374 Widelands::get_tribeinfo(campaign_table->get_string("tribe")).descname;
74/**75 campaign_data->description = _(campaign_table->get_string("description"));
75 * Update the campaign visibility save-file of the user76 if (campaign_table->has_key("prerequisites")) {
76 */77 for (const std::string& prerequisite :
77void CampaignVisibilitySave::update_campvis(const std::string& savepath) {78 campaign_table->get_table("prerequisites")->array_entries<std::string>()) {
78 // Variable declaration79 campaign_data->prerequisites.insert(prerequisite);
79 int32_t i = 0;80 }
80 int32_t imap = 0;81 }
81 char csection[24];82
82 char number[12];83 campaign_data->visible = false;
83 std::string mapsection;84
84 std::string cms;85 // Collect difficulty information
8586 std::unique_ptr<LuaTable> difficulty_table(campaign_table->get_table("difficulty"));
86 // Prepare campaigns.conf and campvis87 campaign_data->difficulty_level = difficulty_table->get_int("level");
87 Profile cconfig("campaigns/campaigns.conf");88 campaign_data->difficulty_image =
88 Section& cconf_s = cconfig.get_safe_section("global");89 difficulty_levels.at(campaign_data->difficulty_level - 1).second;
89 Profile campvisr(savepath.c_str());90 campaign_data->difficulty_description =
90 Profile campvisw(savepath.c_str());91 difficulty_levels.at(campaign_data->difficulty_level - 1).first;
9192 const std::string difficulty_description = _(difficulty_table->get_string("description"));
92 // Write down global section93 if (!difficulty_description.empty()) {
93 campvisw.pull_section("global").set_int("version", cconf_s.get_int("version", 1));94 campaign_data->difficulty_description =
9495 i18n::join_sentences(campaign_data->difficulty_description, difficulty_description);
95 // Write down visibility of campaigns96 }
96 Section& campv_c = campvisr.get_safe_section("campaigns");97
97 Section& campv_m = campvisr.get_safe_section("campmaps");98 // Scenarios
98 {99 std::unique_ptr<LuaTable> scenarios_table(campaign_table->get_table("scenarios"));
99 Section& vis = campvisw.pull_section("campaigns");100 for (const std::string& path : scenarios_table->array_entries<std::string>()) {
100 sprintf(csection, "campsect%i", i);101 ScenarioData* scenario_data = new ScenarioData();
101 char cvisible[24];102 scenario_data->path = path;
102 char cnewvisi[24];103 if (campvis_scenarios.get_bool(scenario_data->path.c_str(), false)) {
103 while (cconf_s.get_string(csection)) {104 solved_scenarios_.insert(scenario_data->path);
104 sprintf(cvisible, "campvisi%i", i);105 }
105 sprintf(cnewvisi, "cnewvisi%i", i);106
106 bool visible = cconf_s.get_bool(cvisible) || campv_c.get_bool(csection);107 scenario_data->is_tutorial = false;
107 if (!visible) {108 scenario_data->playable = scenario_data->path != "dummy.wmf";
108 const char* newvisi = cconf_s.get_string(cnewvisi, "");109 scenario_data->visible = false;
109 if (sizeof(newvisi) > 1) {110 campaign_data->scenarios.push_back(
110 visible = campv_m.get_bool(newvisi, false) || campv_c.get_bool(newvisi, false);111 std::unique_ptr<ScenarioData>(std::move(scenario_data)));
111 }112 }
112 }113
113 vis.set_bool(csection, visible);114 campaigns_.push_back(std::unique_ptr<CampaignData>(std::move(campaign_data)));
114 ++i;115 }
115 sprintf(csection, "campsect%i", i);116
116 }117 // Finally, calculate the visibility
117 }118 update_visibility_info();
118119}
119 // Write down visibility of campaign maps120
120 Section& vis = campvisw.pull_section("campmaps");121void Campaigns::update_visibility_info() {
121 i = 0;122 for (auto& campaign : campaigns_) {
122123 if (campaign->prerequisites.empty()) {
123 sprintf(csection, "campsect%i", i);124 // A campaign is visible if it has no prerequisites
124 while (cconf_s.get_string(csection)) {125 campaign->visible = true;
125 mapsection = cconf_s.get_string(csection);126 } else {
126127 // A campaign is visible if one of its prerequisites has been fulfilled
127 cms = mapsection;128 for (const std::string prerequisite : campaign->prerequisites) {
128 sprintf(number, "%02i", imap);129 if (solved_scenarios_.count(prerequisite) == 1) {
129 cms += number;130 campaign->visible = true;
130131 break;
131 while (Section* const s = cconfig.get_section(cms.c_str())) {132 }
132 bool visible = s->get_bool("visible") || campv_m.get_bool(cms.c_str());133 }
133 if (!visible) {134 }
134 const char* newvisi = s->get_string("newvisi", "");135 if (!campaign->visible) {
135 if (sizeof(newvisi) > 1) {136 // A campaign is also visible if one of its scenarios has been solved
136 visible = campv_m.get_bool(newvisi, false) || campv_c.get_bool(newvisi, false);137 for (size_t i = 0; i < campaign->scenarios.size(); ++i) {
137 }138 auto& scenario = campaign->scenarios.at(i);
138 }139 if (solved_scenarios_.count(scenario->path) == 1) {
139 vis.set_bool(cms.c_str(), visible);140 campaign->visible = true;
140141 break;
141 ++imap;142 }
142 cms = mapsection;143 }
143 sprintf(number, "%02i", imap);144 }
144 cms += number;145 // Now set scenario visibility
145 }146 if (campaign->visible) {
146147 for (size_t i = 0; i < campaign->scenarios.size(); ++i) {
147 ++i;148 auto& scenario = campaign->scenarios.at(i);
148 sprintf(csection, "campsect%i", i);149 if (i == 0) {
149 imap = 0;150 // The first scenario in a visible campaign is always visible
150 }151 scenario->visible = true;
151 campvisw.write(savepath.c_str(), true);152 } else {
152}153 // A scenario is visible if its predecessor was solved
153154 scenario->visible =
154/**155 solved_scenarios_.count(campaign->scenarios.at(i - 1)->path) == 1;
155 * Set an campaign entry in campvis visible or invisible.156 }
156 * If it doesn't exist, create it.157 if (!scenario->visible) {
157 * \param entry entry to be changed158 // If a scenario is invisible, subsequent scenarios are also invisible
158 * \param visible should the map be visible?159 break;
159 */160 }
160void CampaignVisibilitySave::set_campaign_visibility(const std::string& entry, bool visible) {161 }
161 std::string savepath = get_path();162 }
162 Profile campvis(savepath.c_str());163 }
163164}
164 campvis.pull_section("campaigns").set_bool(entry.c_str(), visible);165
165166/**
166 campvis.write(savepath.c_str(), false);167 * Handle legacy campvis file
167}168 */
168169// TODO(GunChleoc): Remove after Build 22
169/**170void Campaigns::update_legacy_campvis() {
170 * Set an campaignmap entry in campvis visible or invisible.171 Profile legacy_campvis(kCampVisFileLegacy.c_str());
171 * If it doesn't exist, create it.172 if (legacy_campvis.get_section("campmaps") == nullptr) {
172 * \param entry entry to be changed173 return;
173 * \param visible should the map be visible?174 }
174 */175
175void CampaignVisibilitySave::set_map_visibility(const std::string& entry, bool visible) {176 log("Converting legacy campvis\n");
176 std::string savepath = get_path();177
177 Profile campvis(savepath.c_str());178 using LegacyList = std::vector<std::pair<std::string, std::string>>;
178179
179 campvis.pull_section("campmaps").set_bool(entry.c_str(), visible);180 std::vector<LegacyList> legacy_scenarios;
180181
181 campvis.write(savepath.c_str(), false);182 legacy_scenarios.push_back(
183 {{"fri02.wmf", "frisians01"}, {"fri01.wmf", "frisians00"}, {"atl01.wmf", "atlanteans00"}});
184
185 legacy_scenarios.push_back(
186 {{"fri02.wmf", "frisians01"}, {"fri01.wmf", "frisians00"}, {"emp04.wmf", "empiretut03"}});
187
188 legacy_scenarios.push_back({{"atl02.wmf", "atlanteans01"},
189 {"atl01.wmf", "atlanteans00"},
190 {"emp02.wmf", "empiretut01"},
191 {"emp01.wmf", "empiretut00"}});
192
193 legacy_scenarios.push_back({
194 {"emp04.wmf", "empiretut03"},
195 {"emp03.wmf", "empiretut02"},
196 {"emp02.wmf", "empiretut01"},
197 {"emp01.wmf", "empiretut00"},
198 {"bar02.wmf", "barbariantut01"},
199 {"bar01.wmf", "barbariantut00"},
200 });
201
202 Section& campvis_scenarios = legacy_campvis.get_safe_section("campmaps");
203 std::set<std::string> solved_legacy_scenarios;
204 for (const auto& legacy_list : legacy_scenarios) {
205 bool set_solved = false;
206 for (const auto& legacy_scenario : legacy_list) {
207 if (set_solved) {
208 solved_legacy_scenarios.insert(legacy_scenario.first);
209 }
210 set_solved = campvis_scenarios.get_bool(legacy_scenario.second.c_str(), false);
211 }
212 }
213
214 // Now write everything
215 Profile write_campvis(kCampVisFile.c_str());
216 Section& write_scenarios = write_campvis.pull_section("scenarios");
217 for (const auto& scenario : solved_legacy_scenarios) {
218 write_scenarios.set_bool(scenario.c_str(), true);
219 }
220
221 write_campvis.write(kCampVisFile.c_str(), true);
182}222}
183223
=== renamed file 'src/logic/campaign_visibility.h' => 'src/ui_fsmenu/campaigns.h'
--- src/logic/campaign_visibility.h 2019-02-23 11:00:49 +0000
+++ src/ui_fsmenu/campaigns.h 2019-04-23 16:17:37 +0000
@@ -17,22 +17,68 @@
17 *17 *
18 */18 */
1919
20#ifndef WL_LOGIC_CAMPAIGN_VISIBILITY_H20#ifndef WL_UI_FSMENU_CAMPAIGNS_H
21#define WL_LOGIC_CAMPAIGN_VISIBILITY_H21#define WL_UI_FSMENU_CAMPAIGNS_H
2222
23#include <cstring>23#include <memory>
24#include <string>24#include <string>
2525#include <unordered_set>
26#include <stdint.h>26#include <vector>
2727
28struct CampaignVisibilitySave {28#include "graphic/image.h"
29 std::string get_path();29#include "scripting/lua_table.h"
30 void set_campaign_visibility(const std::string&, bool);30#include "wui/mapauthordata.h"
31 void set_map_visibility(const std::string&, bool);31
32/**
33 * Data about a campaign or tutorial scenario that we're interested in.
34 */
35struct ScenarioData {
36 std::string path;
37 std::string descname;
38 std::string description;
39 MapAuthorData authors;
40 bool is_tutorial;
41 bool playable;
42 bool visible;
43
44 ScenarioData() = default;
45};
46
47/**
48 * Data about a campaign that we're interested in.
49 */
50struct CampaignData {
51 std::string descname;
52 std::string tribename;
53 uint32_t difficulty_level;
54 const Image* difficulty_image;
55 std::string difficulty_description;
56 std::string description;
57 std::set<std::string> prerequisites;
58 bool visible;
59 std::vector<std::unique_ptr<ScenarioData>> scenarios;
60
61 CampaignData() = default;
62};
63
64struct Campaigns {
65 Campaigns();
66
67 size_t no_of_campaigns() const {
68 return campaigns_.size();
69 }
70
71 CampaignData* get_campaign(size_t campaign_index) const {
72 assert(campaign_index < campaigns_.size());
73 return campaigns_.at(campaign_index).get();
74 }
3275
33private:76private:
34 void make_campvis(const std::string&);77 void update_visibility_info();
35 void update_campvis(const std::string&);78 static void update_legacy_campvis();
79
80 std::vector<std::unique_ptr<CampaignData>> campaigns_;
81 std::unordered_set<std::string> solved_scenarios_;
36};82};
3783
38#endif // end of include guard: WL_LOGIC_CAMPAIGN_VISIBILITY_H84#endif // end of include guard: WL_UI_FSMENU_CAMPAIGNS_H
3985
=== added file 'src/ui_fsmenu/scenario_select.cc'
--- src/ui_fsmenu/scenario_select.cc 1970-01-01 00:00:00 +0000
+++ src/ui_fsmenu/scenario_select.cc 2019-04-23 16:17:37 +0000
@@ -0,0 +1,230 @@
1/*
2 * Copyright (C) 2002-2017 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20#include "ui_fsmenu/scenario_select.h"
21
22#include <memory>
23
24#include <boost/format.hpp>
25
26#include "base/i18n.h"
27#include "base/wexception.h"
28#include "graphic/graphic.h"
29#include "graphic/text_constants.h"
30#include "logic/filesystem_constants.h"
31#include "map_io/widelands_map_loader.h"
32#include "profile/profile.h"
33#include "scripting/lua_interface.h"
34#include "scripting/lua_table.h"
35#include "ui_basic/scrollbar.h"
36#include "ui_fsmenu/campaigns.h"
37
38/**
39 * FullscreenMenuScenarioSelect UI.
40 *
41 * Loads a list of all visible maps of selected campaign or all tutorials and
42 * lets the user choose one.
43 */
44FullscreenMenuScenarioSelect::FullscreenMenuScenarioSelect(CampaignData* camp)
45 : FullscreenMenuLoadMapOrGame(),
46 is_tutorial_(camp == nullptr),
47 table_(this, tablex_, tabley_, tablew_, tableh_, UI::PanelStyle::kFsMenu),
48 header_box_(this, 0, 0, UI::Box::Vertical),
49
50 // Main title
51 title_(&header_box_,
52 0,
53 0,
54 is_tutorial_ ? _("Choose a tutorial") : _("Choose a scenario"),
55 UI::Align::kCenter),
56 subtitle_(&header_box_,
57 0,
58 0,
59 UI::Scrollbar::kSize,
60 0,
61 UI::PanelStyle::kFsMenu,
62 "",
63 UI::Align::kCenter,
64 UI::MultilineTextarea::ScrollMode::kNoScrolling),
65 scenario_details_(this),
66 campaign_(camp) {
67 title_.set_fontsize(UI_FONT_SIZE_BIG);
68
69 // Set subtitle of the page
70 if (campaign_ == nullptr) {
71 const std::string subtitle1 = _("Pick a tutorial from the list, then hit “OK”.");
72 const std::string subtitle2 =
73 _("You can see a description of the currently selected tutorial on the right.");
74 subtitle_.set_text((boost::format("%s\n%s") % subtitle1 % subtitle2).str());
75 } else {
76 subtitle_.set_text(
77 (boost::format("%s — %s") % campaign_->tribename % campaign_->descname).str());
78 }
79
80 header_box_.add_inf_space();
81 header_box_.add_inf_space();
82 header_box_.add_inf_space();
83 header_box_.add(&title_, UI::Box::Resizing::kFullSize);
84 header_box_.add_inf_space();
85 header_box_.add(&subtitle_, UI::Box::Resizing::kFullSize);
86 header_box_.add_inf_space();
87 header_box_.add_inf_space();
88 header_box_.add_inf_space();
89
90 back_.set_tooltip(is_tutorial_ ? _("Return to the main menu") :
91 _("Return to campaign selection"));
92 ok_.set_tooltip(is_tutorial_ ? _("Play this tutorial") : _("Play this scenario"));
93
94 ok_.sigclicked.connect(
95 boost::bind(&FullscreenMenuScenarioSelect::clicked_ok, boost::ref(*this)));
96 back_.sigclicked.connect(
97 boost::bind(&FullscreenMenuScenarioSelect::clicked_back, boost::ref(*this)));
98 table_.selected.connect(boost::bind(&FullscreenMenuScenarioSelect::entry_selected, this));
99 table_.double_clicked.connect(
100 boost::bind(&FullscreenMenuScenarioSelect::clicked_ok, boost::ref(*this)));
101
102 std::string number_tooltip;
103 std::string name_tooltip;
104 if (is_tutorial_) {
105 number_tooltip = _("The order in which the tutorials should be played");
106 name_tooltip = _("Tutorial Name");
107 } else {
108 number_tooltip = _("The number of this scenario in the campaign");
109 name_tooltip = _("Scenario Name");
110 }
111
112 /** TRANSLATORS: Campaign scenario number table header */
113 table_.add_column(35, _("#"), number_tooltip);
114 table_.add_column(
115 0, name_tooltip, name_tooltip, UI::Align::kLeft, UI::TableColumnType::kFlexible);
116 table_.set_sort_column(0);
117 table_.focus();
118 fill_table();
119 layout();
120}
121
122void FullscreenMenuScenarioSelect::layout() {
123 FullscreenMenuLoadMapOrGame::layout();
124 header_box_.set_size(get_w(), tabley_);
125 table_.set_size(tablew_, tableh_);
126 table_.set_pos(Vector2i(tablex_, tabley_));
127 scenario_details_.set_size(get_right_column_w(right_column_x_), tableh_ - buth_ - 4 * padding_);
128 scenario_details_.set_pos(Vector2i(right_column_x_, tabley_));
129}
130
131std::string FullscreenMenuScenarioSelect::get_map() {
132 if (set_has_selection()) {
133 return g_fs->FileSystem::fix_cross_file(kCampaignsDir + "/" +
134 scenarios_data_.at(table_.get_selected()).path);
135 }
136 return "";
137}
138
139bool FullscreenMenuScenarioSelect::set_has_selection() {
140 const bool has_selection = table_.has_selection();
141 ok_.set_enabled(has_selection);
142 return has_selection;
143}
144
145void FullscreenMenuScenarioSelect::clicked_ok() {
146 if (!table_.has_selection()) {
147 return;
148 }
149 const ScenarioData& scenario_data = scenarios_data_[table_.get_selected()];
150 if (!scenario_data.playable) {
151 return;
152 }
153 end_modal<FullscreenMenuBase::MenuTarget>(FullscreenMenuBase::MenuTarget::kOk);
154}
155
156void FullscreenMenuScenarioSelect::entry_selected() {
157 if (set_has_selection()) {
158 const ScenarioData& scenario_data = scenarios_data_[table_.get_selected()];
159 scenario_details_.update(scenario_data);
160
161 // The dummy scenario can't be played, so we disable the OK button.
162 ok_.set_enabled(scenario_data.playable);
163 }
164}
165
166/**
167 * fill the campaign-map list
168 */
169void FullscreenMenuScenarioSelect::fill_table() {
170 if (is_tutorial_) {
171 // Load the tutorials
172 LuaInterface lua;
173 std::unique_ptr<LuaTable> table(lua.run_script("campaigns/tutorials.lua"));
174 for (const std::string& path : table->array_entries<std::string>()) {
175 ScenarioData scenario_data;
176 scenario_data.path = path;
177 scenario_data.playable = true;
178 scenario_data.is_tutorial = true;
179 scenarios_data_.push_back(scenario_data);
180 }
181 } else {
182 // Load the current campaign
183 for (auto& scenario_data : campaign_->scenarios) {
184 if (scenario_data->visible) {
185 scenario_data->is_tutorial = false;
186 scenario_data->playable = scenario_data->path != "dummy.wmf";
187 scenarios_data_.push_back(*scenario_data.get());
188 } else {
189 break;
190 }
191 }
192 }
193
194 for (size_t i = 0; i < scenarios_data_.size(); ++i) {
195 // Get details info from maps
196 ScenarioData* scenario_data = &scenarios_data_.at(i);
197 const std::string full_path =
198 g_fs->FileSystem::fix_cross_file(kCampaignsDir + "/" + scenario_data->path);
199 Widelands::Map map;
200 std::unique_ptr<Widelands::MapLoader> ml(map.get_correct_loader(full_path));
201 if (!ml) {
202 throw wexception(
203 _("Invalid path to file in campaigns.lua of tutorials.lua: %s"), full_path.c_str());
204 }
205
206 map.set_filename(full_path);
207 ml->preload_map(true);
208
209 {
210 i18n::Textdomain td("maps");
211 scenario_data->authors.set_authors(map.get_author());
212 scenario_data->descname = _(map.get_name());
213 scenario_data->description = _(map.get_description());
214 }
215
216 // Now add to table
217 UI::Table<uintptr_t>::EntryRecord& te = table_.add(i);
218 te.set_string(0, (boost::format("%d") % (i + 1)).str());
219 te.set_picture(
220 1, g_gr->images().get("images/ui_basic/ls_wlmap.png"), scenario_data->descname);
221 if (!scenario_data->playable) {
222 te.set_color(UI_FONT_CLR_DISABLED);
223 }
224 }
225
226 if (!table_.empty()) {
227 table_.select(0);
228 }
229 entry_selected();
230}
0231
=== added file 'src/ui_fsmenu/scenario_select.h'
--- src/ui_fsmenu/scenario_select.h 1970-01-01 00:00:00 +0000
+++ src/ui_fsmenu/scenario_select.h 2019-04-23 16:17:37 +0000
@@ -0,0 +1,65 @@
1/*
2 * Copyright (C) 2002-2017 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20#ifndef WL_UI_FSMENU_SCENARIO_SELECT_H
21#define WL_UI_FSMENU_SCENARIO_SELECT_H
22
23#include "ui_basic/box.h"
24#include "ui_basic/multilinetextarea.h"
25#include "ui_basic/table.h"
26#include "ui_basic/textarea.h"
27#include "ui_fsmenu/load_map_or_game.h"
28#include "ui_fsmenu/scenariodetails.h"
29
30/*
31 * Fullscreen Menu for selecting a campaign or tutorial scenario
32 */
33class FullscreenMenuScenarioSelect : public FullscreenMenuLoadMapOrGame {
34public:
35 // If camp is not set, we'll be loading the tutorials
36 explicit FullscreenMenuScenarioSelect(CampaignData* camp = nullptr);
37
38 std::string get_map();
39
40protected:
41 void clicked_ok() override;
42 void entry_selected() override;
43 void fill_table() override;
44
45private:
46 void layout() override;
47
48 /// Updates buttons and text labels and returns whether a table entry is selected.
49 bool set_has_selection();
50
51 bool is_tutorial_;
52 UI::Table<uintptr_t const> table_;
53
54 UI::Box header_box_;
55
56 UI::Textarea title_;
57 UI::MultilineTextarea subtitle_;
58 ScenarioDetails scenario_details_;
59
60 CampaignData* campaign_;
61
62 std::vector<ScenarioData> scenarios_data_;
63};
64
65#endif // end of include guard: WL_UI_FSMENU_SCENARIO_SELECT_H
066
=== added file 'src/ui_fsmenu/scenariodetails.cc'
--- src/ui_fsmenu/scenariodetails.cc 1970-01-01 00:00:00 +0000
+++ src/ui_fsmenu/scenariodetails.cc 2019-04-23 16:17:37 +0000
@@ -0,0 +1,74 @@
1/*
2 * Copyright (C) 2017 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#include "ui_fsmenu/scenariodetails.h"
20
21#include <boost/format.hpp>
22
23#include "base/i18n.h"
24#include "graphic/text_constants.h"
25#include "ui_basic/scrollbar.h"
26
27ScenarioDetails::ScenarioDetails(Panel* parent)
28 : UI::Box(parent, 0, 0, UI::Box::Vertical),
29 name_label_(this,
30 0,
31 0,
32 UI::Scrollbar::kSize,
33 0,
34 UI::PanelStyle::kFsMenu,
35 "",
36 UI::Align::kLeft,
37 UI::MultilineTextarea::ScrollMode::kNoScrolling),
38 descr_(this, 0, 0, UI::Scrollbar::kSize, 0, UI::PanelStyle::kFsMenu) {
39
40 constexpr int kPadding = 4;
41 add(&name_label_, UI::Box::Resizing::kFullSize);
42 add_space(kPadding);
43 add(&descr_, UI::Box::Resizing::kExpandBoth);
44}
45
46void ScenarioDetails::update(const ScenarioData& scenariodata) {
47 name_label_.set_text(
48 (boost::format("<rt>%s%s</rt>") %
49 as_heading(scenariodata.is_tutorial ? _("Tutorial") : _("Scenario"), UI::PanelStyle::kFsMenu, true) %
50 as_content(scenariodata.descname, UI::PanelStyle::kFsMenu))
51 .str());
52
53 if (scenariodata.playable) {
54 std::string description =
55 (boost::format("%s%s") %
56 as_heading(
57 ngettext("Author", "Authors", scenariodata.authors.get_number()), UI::PanelStyle::kFsMenu) %
58 as_content(scenariodata.authors.get_names(), UI::PanelStyle::kFsMenu))
59 .str();
60
61 description =
62 (boost::format("%s%s") % description % as_heading(_("Description"), UI::PanelStyle::kFsMenu))
63 .str();
64 description = (boost::format("%s%s") % description %
65 as_content(scenariodata.description, UI::PanelStyle::kFsMenu))
66 .str();
67
68 description = (boost::format("<rt>%s</rt>") % description).str();
69 descr_.set_text(description);
70 } else {
71 descr_.set_text("");
72 }
73 descr_.scroll_to_top();
74}
075
=== added file 'src/ui_fsmenu/scenariodetails.h'
--- src/ui_fsmenu/scenariodetails.h 1970-01-01 00:00:00 +0000
+++ src/ui_fsmenu/scenariodetails.h 2019-04-23 16:17:37 +0000
@@ -0,0 +1,41 @@
1/*
2 * Copyright (C) 2017 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20#ifndef WL_UI_FSMENU_SCENARIODETAILS_H
21#define WL_UI_FSMENU_SCENARIODETAILS_H
22
23#include "ui_basic/box.h"
24#include "ui_basic/multilinetextarea.h"
25#include "ui_fsmenu/campaigns.h"
26
27/**
28 * Show a Box with information about a campaign or tutorial scenario.
29 */
30class ScenarioDetails : public UI::Box {
31public:
32 explicit ScenarioDetails(Panel* parent);
33
34 void update(const ScenarioData& scenariodata);
35
36private:
37 UI::MultilineTextarea name_label_;
38 UI::MultilineTextarea descr_;
39};
40
41#endif // end of include guard: WL_UI_FSMENU_SCENARIODETAILS_H
042
=== modified file 'src/wlapplication.cc'
--- src/wlapplication.cc 2019-03-09 12:13:02 +0000
+++ src/wlapplication.cc 2019-04-23 16:17:37 +0000
@@ -82,6 +82,7 @@
82#include "ui_basic/progresswindow.h"82#include "ui_basic/progresswindow.h"
83#include "ui_fsmenu/about.h"83#include "ui_fsmenu/about.h"
84#include "ui_fsmenu/campaign_select.h"84#include "ui_fsmenu/campaign_select.h"
85#include "ui_fsmenu/campaigns.h"
85#include "ui_fsmenu/internet_lobby.h"86#include "ui_fsmenu/internet_lobby.h"
86#include "ui_fsmenu/intro.h"87#include "ui_fsmenu/intro.h"
87#include "ui_fsmenu/launch_spg.h"88#include "ui_fsmenu/launch_spg.h"
@@ -91,6 +92,7 @@
91#include "ui_fsmenu/multiplayer.h"92#include "ui_fsmenu/multiplayer.h"
92#include "ui_fsmenu/netsetup_lan.h"93#include "ui_fsmenu/netsetup_lan.h"
93#include "ui_fsmenu/options.h"94#include "ui_fsmenu/options.h"
95#include "ui_fsmenu/scenario_select.h"
94#include "ui_fsmenu/singleplayer.h"96#include "ui_fsmenu/singleplayer.h"
95#include "wlapplication_messages.h"97#include "wlapplication_messages.h"
96#include "wui/game_tips.h"98#include "wui/game_tips.h"
@@ -1141,8 +1143,7 @@
1141 Widelands::Game game;1143 Widelands::Game game;
1142 std::string filename;1144 std::string filename;
1143 // Start UI for the tutorials.1145 // Start UI for the tutorials.
1144 FullscreenMenuCampaignMapSelect select_campaignmap(true);1146 FullscreenMenuScenarioSelect select_campaignmap;
1145 select_campaignmap.set_campaign(0);
1146 if (select_campaignmap.run<FullscreenMenuBase::MenuTarget>() ==1147 if (select_campaignmap.run<FullscreenMenuBase::MenuTarget>() ==
1147 FullscreenMenuBase::MenuTarget::kOk) {1148 FullscreenMenuBase::MenuTarget::kOk) {
1148 filename = select_campaignmap.get_map();1149 filename = select_campaignmap.get_map();
@@ -1369,20 +1370,22 @@
1369 Widelands::Game game;1370 Widelands::Game game;
1370 std::string filename;1371 std::string filename;
1371 for (;;) { // Campaign UI - Loop1372 for (;;) { // Campaign UI - Loop
1372 int32_t campaign;1373 std::unique_ptr<Campaigns> campaign_visibility(new Campaigns());
1374
1375 size_t campaign_index;
1373 { // First start UI for selecting the campaign.1376 { // First start UI for selecting the campaign.
1374 FullscreenMenuCampaignSelect select_campaign;1377 FullscreenMenuCampaignSelect select_campaign(campaign_visibility.get());
1375 if (select_campaign.run<FullscreenMenuBase::MenuTarget>() ==1378 if (select_campaign.run<FullscreenMenuBase::MenuTarget>() ==
1376 FullscreenMenuBase::MenuTarget::kOk)1379 FullscreenMenuBase::MenuTarget::kOk) {
1377 campaign = select_campaign.get_campaign();1380 campaign_index = select_campaign.get_campaign_index();
1378 else { // back was pressed1381 } else { // back was pressed
1379 filename = "";1382 filename = "";
1380 break;1383 break;
1381 }1384 }
1382 }1385 }
1383 // Then start UI for the selected campaign.1386 // Then start UI for the selected campaign.
1384 FullscreenMenuCampaignMapSelect select_campaignmap;1387 CampaignData* campaign_data = campaign_visibility->get_campaign(campaign_index);
1385 select_campaignmap.set_campaign(campaign);1388 FullscreenMenuScenarioSelect select_campaignmap(campaign_data);
1386 if (select_campaignmap.run<FullscreenMenuBase::MenuTarget>() ==1389 if (select_campaignmap.run<FullscreenMenuBase::MenuTarget>() ==
1387 FullscreenMenuBase::MenuTarget::kOk) {1390 FullscreenMenuBase::MenuTarget::kOk) {
1388 filename = select_campaignmap.get_map();1391 filename = select_campaignmap.get_map();
13891392
=== modified file 'src/wui/CMakeLists.txt'
--- src/wui/CMakeLists.txt 2018-11-13 12:18:10 +0000
+++ src/wui/CMakeLists.txt 2019-04-23 16:17:37 +0000
@@ -87,6 +87,7 @@
8787
88wl_library(wui_common_mapdetails88wl_library(wui_common_mapdetails
89 SRCS89 SRCS
90 mapauthordata.h
90 mapdetails.cc91 mapdetails.cc
91 mapdetails.h92 mapdetails.h
92 mapdata.cc93 mapdata.cc
@@ -102,9 +103,9 @@
102 graphic103 graphic
103 graphic_fonthandler104 graphic_fonthandler
104 graphic_text_constants105 graphic_text_constants
106 graphic_text_layout
105 io_filesystem107 io_filesystem
106 logic108 logic
107 logic_constants
108 logic_game_controller109 logic_game_controller
109 logic_game_settings110 logic_game_settings
110 map_io_map_loader111 map_io_map_loader
111112
=== modified file 'src/wui/load_or_save_game.cc'
--- src/wui/load_or_save_game.cc 2019-03-30 19:46:16 +0000
+++ src/wui/load_or_save_game.cc 2019-04-23 16:17:37 +0000
@@ -366,22 +366,20 @@
366366
367 if (filetype_ == FileType::kReplay) {367 if (filetype_ == FileType::kReplay) {
368 gamefiles = filter(g_fs->list_directory(kReplayDir), [](const std::string& fn) {368 gamefiles = filter(g_fs->list_directory(kReplayDir), [](const std::string& fn) {
369 return boost::ends_with(fn, kReplayExtension);369 return boost::algorithm::ends_with(fn, kReplayExtension);
370 });370 });
371 // Update description column title for replays371 // Update description column title for replays
372 table_.set_column_tooltip(2, show_filenames_ ? _("Filename: Map name (start of replay)") :372 table_.set_column_tooltip(2, show_filenames_ ? _("Filename: Map name (start of replay)") :
373 _("Map name (start of replay)"));373 _("Map name (start of replay)"));
374 } else {374 } else {
375 gamefiles = g_fs->list_directory(kSaveDir);375 gamefiles = filter(g_fs->list_directory(kSaveDir), [](const std::string& fn) {
376 return boost::algorithm::ends_with(fn, kSavegameExtension);
377 });
376 }378 }
377379
378 Widelands::GamePreloadPacket gpdp;380 Widelands::GamePreloadPacket gpdp;
379381
380 for (const std::string& gamefilename : gamefiles) {382 for (const std::string& gamefilename : gamefiles) {
381 if (gamefilename == kCampVisFile || gamefilename == g_fs->fix_cross_file(kCampVisFile)) {
382 continue;
383 }
384
385 SavegameData gamedata;383 SavegameData gamedata;
386384
387 std::string savename = gamefilename;385 std::string savename = gamefilename;
388386
=== added file 'src/wui/mapauthordata.h'
--- src/wui/mapauthordata.h 1970-01-01 00:00:00 +0000
+++ src/wui/mapauthordata.h 2019-04-23 16:17:37 +0000
@@ -0,0 +1,72 @@
1/*
2 * Copyright (C) 2002-2017 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20#ifndef WL_WUI_MAPAUTHORDATA_H
21#define WL_WUI_MAPAUTHORDATA_H
22
23#include <set>
24#include <string>
25#include <vector>
26
27#include <boost/algorithm/string.hpp>
28#include <boost/format.hpp>
29
30#include "base/i18n.h"
31#include "io/filesystem/filesystem.h"
32#include "logic/map.h"
33
34/**
35 * Author data for a map or scenario.
36 */
37struct MapAuthorData {
38 const std::string& get_names() const {
39 return names_;
40 }
41 size_t get_number() const {
42 return number_;
43 }
44
45 void set_authors(const std::string& author_list) {
46 std::vector<std::string> authors;
47 {
48 i18n::Textdomain td("maps");
49 const std::string loc_author_list = _(author_list);
50 boost::split(authors, loc_author_list, boost::is_any_of(","));
51 }
52 names_ = i18n::localize_list(authors, i18n::ConcatenateWith::AMPERSAND);
53 number_ = authors.size();
54 }
55
56 // We allow empty authors, because those will often be loaded
57 // later from the maps
58 MapAuthorData() = default;
59
60 // Parses author list string into localized contatenated list
61 // string. Use , as list separator and no whitespaces between
62 // author names.
63 explicit MapAuthorData(const std::string& author_list) {
64 set_authors(author_list);
65 }
66
67private:
68 std::string names_;
69 size_t number_;
70};
71
72#endif // end of include guard: WL_WUI_MAPAUTHORDATA_H
073
=== modified file 'src/wui/mapdata.cc'
--- src/wui/mapdata.cc 2019-02-23 11:00:49 +0000
+++ src/wui/mapdata.cc 2019-04-23 16:17:37 +0000
@@ -41,16 +41,16 @@
41 const std::string& init_filename,41 const std::string& init_filename,
42 const MapData::MapType& init_maptype,42 const MapData::MapType& init_maptype,
43 const MapData::DisplayType& init_displaytype)43 const MapData::DisplayType& init_displaytype)
44 : MapData(init_filename, _("No Name"), _("No Author"), init_maptype, init_displaytype) {44 : MapData(init_filename,
45 _("No Name"),
46 map.get_author().empty() ? _("No Author") : map.get_author(),
47 init_maptype,
48 init_displaytype) {
4549
46 i18n::Textdomain td("maps");50 i18n::Textdomain td("maps");
47 if (!map.get_name().empty()) {51 if (!map.get_name().empty()) {
48 name = map.get_name();52 name = map.get_name();
49 }53 localized_name = _(name);
50 localized_name = _(name);
51 // Localizing this, because some author fields now have "edited by" text.
52 if (!map.get_author().empty()) {
53 authors = map.get_author();
54 }54 }
55 description = map.get_description().empty() ? "" : _(map.get_description());55 description = map.get_description().empty() ? "" : _(map.get_description());
56 hint = map.get_hint().empty() ? "" : _(map.get_hint());56 hint = map.get_hint().empty() ? "" : _(map.get_hint());
@@ -68,7 +68,7 @@
68MapData::MapData(const std::string& init_filename, const std::string& init_localized_name)68MapData::MapData(const std::string& init_filename, const std::string& init_localized_name)
69 : MapData(init_filename,69 : MapData(init_filename,
70 init_localized_name,70 init_localized_name,
71 "",71 _("No Author"),
72 MapData::MapType::kDirectory,72 MapData::MapType::kDirectory,
73 MapData::DisplayType::kMapnamesLocalized) {73 MapData::DisplayType::kMapnamesLocalized) {
74}74}
7575
=== modified file 'src/wui/mapdata.h'
--- src/wui/mapdata.h 2019-02-23 11:00:49 +0000
+++ src/wui/mapdata.h 2019-04-23 16:17:37 +0000
@@ -30,33 +30,7 @@
30#include "base/i18n.h"30#include "base/i18n.h"
31#include "io/filesystem/filesystem.h"31#include "io/filesystem/filesystem.h"
32#include "logic/map.h"32#include "logic/map.h"
33#include "logic/widelands.h"33#include "wui/mapauthordata.h"
34
35/**
36 * Author data for a map or scenario.
37 */
38struct MapAuthorData {
39 const std::string& get_names() const {
40 return names_;
41 }
42 size_t get_number() const {
43 return number_;
44 }
45
46 // Parses author list string into localized contatenated list
47 // string. Use , as list separator and no whitespaces between
48 // author names.
49 MapAuthorData(const std::string& author_list) {
50 std::vector<std::string> authors;
51 boost::split(authors, author_list, boost::is_any_of(","));
52 names_ = i18n::localize_list(authors, i18n::ConcatenateWith::AMPERSAND);
53 number_ = authors.size();
54 }
55
56private:
57 std::string names_;
58 size_t number_;
59};
6034
61/**35/**
62 * Data about a map that we're interested in.36 * Data about a map that we're interested in.
6337
=== modified file 'src/wui/mapdetails.h'
--- src/wui/mapdetails.h 2019-02-23 11:00:49 +0000
+++ src/wui/mapdetails.h 2019-04-23 16:17:37 +0000
@@ -20,6 +20,7 @@
20#ifndef WL_WUI_MAPDETAILS_H20#ifndef WL_WUI_MAPDETAILS_H
21#define WL_WUI_MAPDETAILS_H21#define WL_WUI_MAPDETAILS_H
2222
23#include "graphic/text_layout.h"
23#include "ui_basic/box.h"24#include "ui_basic/box.h"
24#include "ui_basic/multilinetextarea.h"25#include "ui_basic/multilinetextarea.h"
25#include "ui_basic/panel.h"26#include "ui_basic/panel.h"

Subscribers

People subscribed via source and target branches

to status/vote changes: