Rich Text Label widget for Help Browser GUI2 port (#8655)
A rich text label widget that can show text marked up with help markup. Also includes the GUI Test Window, accessible in the title screen after launching wesnoth using --clock option. It can be used as dialog template/example or as a place to test GUI2 code.
This commit is contained in:
parent
9dd2d5d94b
commit
43f5644e36
31 changed files with 2057 additions and 112 deletions
|
@ -33,6 +33,7 @@ test_gui2/modal_dialog_test_game_save
|
|||
test_gui2/modal_dialog_test_game_save_message
|
||||
test_gui2/modal_dialog_test_game_save_oos
|
||||
test_gui2/modal_dialog_test_generator_settings
|
||||
test_gui2/modal_dialog_test_gui_test_dialog
|
||||
test_gui2/modal_dialog_test_hotkey_bind
|
||||
test_gui2/modal_dialog_test_install_dependencies
|
||||
test_gui2/modal_dialog_test_language_selection
|
||||
|
|
89
data/gui/widget/rich_label_default.cfg
Normal file
89
data/gui/widget/rich_label_default.cfg
Normal file
|
@ -0,0 +1,89 @@
|
|||
#textdomain wesnoth-lib
|
||||
###
|
||||
### Definition of a rich label.
|
||||
###
|
||||
### Defines the following labels
|
||||
### - default, the one for general usage.
|
||||
### - title, for titles in dialogs.
|
||||
|
||||
#define _GUI_RESOLUTION RESOLUTION FONT_FAMILY FONT_SIZE FONT_STYLE FONT_COLOR_ENABLED FONT_COLOR_DISABLED
|
||||
[resolution]
|
||||
|
||||
{RESOLUTION}
|
||||
|
||||
min_width = 0
|
||||
min_height = 0
|
||||
|
||||
default_width = 0
|
||||
default_height = 0
|
||||
|
||||
max_width = 0
|
||||
max_height = 0
|
||||
|
||||
text_font_family = {FONT_FAMILY}
|
||||
text_font_size = {FONT_SIZE}
|
||||
text_font_style = {FONT_STYLE}
|
||||
|
||||
[state_enabled]
|
||||
|
||||
[draw]
|
||||
[/draw]
|
||||
|
||||
[/state_enabled]
|
||||
|
||||
[state_disabled]
|
||||
|
||||
[draw]
|
||||
[/draw]
|
||||
|
||||
[/state_disabled]
|
||||
|
||||
[/resolution]
|
||||
#enddef
|
||||
|
||||
#define _GUI_DEFINITION ID DESCRIPTION FONT_FAMILY FONT_SIZE FONT_STYLE FONT_COLOR
|
||||
[rich_label_definition]
|
||||
id = {ID}
|
||||
description = {DESCRIPTION}
|
||||
|
||||
{_GUI_RESOLUTION
|
||||
()
|
||||
({FONT_FAMILY})
|
||||
({GUI_FONT_SIZE_{FONT_SIZE}})
|
||||
({FONT_STYLE})
|
||||
({GUI__FONT_COLOR_ENABLED__{FONT_COLOR} ALPHA=""})
|
||||
({GUI__FONT_COLOR_DISABLED__{FONT_COLOR} ALPHA=""})
|
||||
}
|
||||
|
||||
{_GUI_RESOLUTION
|
||||
({GUI_BIG_RESOLUTION})
|
||||
({FONT_FAMILY})
|
||||
({GUI_SCALE_RESOLUTION {GUI_FONT_SIZE_{FONT_SIZE}}})
|
||||
({FONT_STYLE})
|
||||
({GUI__FONT_COLOR_ENABLED__{FONT_COLOR} ALPHA=""})
|
||||
({GUI__FONT_COLOR_DISABLED__{FONT_COLOR} ALPHA=""})
|
||||
}
|
||||
|
||||
[/rich_label_definition]
|
||||
#enddef
|
||||
|
||||
{_GUI_DEFINITION "default" "default label" () DEFAULT () DEFAULT }
|
||||
{_GUI_DEFINITION "default_bold" "default label, bold font" () DEFAULT "bold" DEFAULT }
|
||||
{_GUI_DEFINITION "default_italic" "default label, italic font" () DEFAULT "italic" DEFAULT }
|
||||
{_GUI_DEFINITION "title" "label used for titles" () TITLE () TITLE }
|
||||
{_GUI_DEFINITION "default_large" "default, large font size" () LARGE () DEFAULT }
|
||||
{_GUI_DEFINITION "default_huge" "default, huge font size" () HUGE () DEFAULT }
|
||||
{_GUI_DEFINITION "default_small" "default, small font size" () SMALL () DEFAULT }
|
||||
{_GUI_DEFINITION "default_tiny" "default, small font size" () TINY () DEFAULT }
|
||||
{_GUI_DEFINITION "gold" "regular gold label" () DEFAULT () TITLE }
|
||||
{_GUI_DEFINITION "gold_small" "small gold label" () SMALL () TITLE }
|
||||
{_GUI_DEFINITION "gold_large" "small gold label" () LARGE () TITLE }
|
||||
{_GUI_DEFINITION "bad" "regular red label" () DEFAULT () BAD }
|
||||
{_GUI_DEFINITION "bad_small" "small red label" () SMALL () BAD }
|
||||
|
||||
{_GUI_DEFINITION "monospace" "fixed width scroll label" monospace DEFAULT () DEFAULT }
|
||||
|
||||
#undef _GUI_SCALE_RES SIZE
|
||||
#undef _GUI_BIG_RES
|
||||
#undef _GUI_DEFINITION
|
||||
#undef _GUI_RESOLUTION
|
78
data/gui/window/gui_test_dialog.cfg
Normal file
78
data/gui/window/gui_test_dialog.cfg
Normal file
|
@ -0,0 +1,78 @@
|
|||
#textdomain wesnoth-test
|
||||
###
|
||||
### GUI Test Window
|
||||
### To be used for testing GUI2 WML code
|
||||
### Put your gui2 wml code here, then launch it via ./wesnoth --clock
|
||||
### That should show the GUI Test Window button on the title screen
|
||||
###
|
||||
|
||||
[window]
|
||||
id = "gui_test_dialog"
|
||||
description = "GUI Test Dialog"
|
||||
|
||||
[resolution]
|
||||
definition = "default"
|
||||
|
||||
automatic_placement = true
|
||||
vertical_placement = "center"
|
||||
horizontal_placement = "center"
|
||||
height = 600
|
||||
|
||||
[tooltip]
|
||||
id = "tooltip"
|
||||
[/tooltip]
|
||||
|
||||
[helptip]
|
||||
id = "tooltip"
|
||||
[/helptip]
|
||||
|
||||
[grid]
|
||||
|
||||
[row]
|
||||
|
||||
[column]
|
||||
|
||||
[rich_label]
|
||||
width = 500
|
||||
label = _ "GUI Test Dialog"
|
||||
[/rich_label]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
|
||||
[column]
|
||||
horizontal_alignment = "right"
|
||||
|
||||
[grid]
|
||||
|
||||
[row]
|
||||
grow_factor = 0
|
||||
|
||||
[column]
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "right"
|
||||
|
||||
[button]
|
||||
id = "ok"
|
||||
definition = "default"
|
||||
label = _ "OK"
|
||||
[/button]
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[/grid]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[/grid]
|
||||
|
||||
[/resolution]
|
||||
|
||||
[/window]
|
|
@ -264,6 +264,7 @@ where
|
|||
# This debug feature doesn't need to be translated, so put it in the test textdomain.
|
||||
#textdomain wesnoth-test
|
||||
{_GUI_BUTTON "clock" _"Clock" _"Show debug clock"}
|
||||
{_GUI_BUTTON "test_dialog" _"Test Dialog" _"Show GUI Test Dialog"}
|
||||
#textdomain wesnoth-lib
|
||||
[/grid]
|
||||
|
||||
|
|
|
@ -137,8 +137,13 @@
|
|||
{REQUIRED_KEY "font_size" f_unsigned}
|
||||
{DEFAULT_KEY "font_style" font_style ""}
|
||||
{DEFAULT_KEY "highlight_color" string "#215380"}
|
||||
{DEFAULT_KEY "highlight_start" f_unsigned 0}
|
||||
{DEFAULT_KEY "highlight_end" f_unsigned 0}
|
||||
{DEFAULT_KEY "highlight_start" string ""}
|
||||
{DEFAULT_KEY "highlight_end" string ""}
|
||||
{DEFAULT_KEY "actions" string ""}
|
||||
{DEFAULT_KEY "attr_name" string ""}
|
||||
{DEFAULT_KEY "attr_start" string ""}
|
||||
{DEFAULT_KEY "attr_end" string ""}
|
||||
{DEFAULT_KEY "attr_data" string ""}
|
||||
{DEFAULT_KEY "maximum_height" f_int -1}
|
||||
{DEFAULT_KEY "maximum_width" f_int -1}
|
||||
{DEFAULT_KEY "text" f_t_string ""}
|
||||
|
@ -152,7 +157,6 @@
|
|||
{DEFAULT_KEY "w" f_unsigned 0}
|
||||
{DEFAULT_KEY "x" f_unsigned 0}
|
||||
{DEFAULT_KEY "y" f_unsigned 0}
|
||||
{DEFAULT_KEY "actions" string ""}
|
||||
[/tag]
|
||||
[/tag]
|
||||
[/tag]
|
||||
|
|
|
@ -217,6 +217,31 @@
|
|||
{DEFAULT_KEY "link_color" string "#ffff00"}
|
||||
[/tag]
|
||||
[/tag]
|
||||
[tag]
|
||||
name="rich_label_definition"
|
||||
min="0"
|
||||
max="infinite"
|
||||
super="$generic/widget_definition"
|
||||
[tag]
|
||||
name="resolution"
|
||||
min="0"
|
||||
max="infinite"
|
||||
super="$generic/widget_definition/resolution"
|
||||
[tag]
|
||||
name="state_disabled"
|
||||
min="0"
|
||||
max="1"
|
||||
super="$generic/state"
|
||||
[/tag]
|
||||
[tag]
|
||||
name="state_enabled"
|
||||
min="0"
|
||||
max="1"
|
||||
super="$generic/state"
|
||||
[/tag]
|
||||
{DEFAULT_KEY "link_color" string "#ffff00"}
|
||||
[/tag]
|
||||
[/tag]
|
||||
[tag]
|
||||
name="listbox_definition"
|
||||
min="0"
|
||||
|
|
|
@ -185,6 +185,16 @@
|
|||
{DEFAULT_KEY "wrap" bool false}
|
||||
{DEFAULT_KEY "link_aware" bool false}
|
||||
[/tag]
|
||||
[tag]
|
||||
name="rich_label"
|
||||
min="0"
|
||||
max="infinite"
|
||||
super="$generic/widget_instance"
|
||||
{DEFAULT_KEY "text_alignment" h_align "left"}
|
||||
{DEFAULT_KEY "link_aware" bool false}
|
||||
{DEFAULT_KEY "width" unsigned 500}
|
||||
|
||||
[/tag]
|
||||
[tag]
|
||||
name="grid_listbox"
|
||||
min="0"
|
||||
|
|
|
@ -629,6 +629,8 @@
|
|||
<Unit filename="../../src/gui/dialogs/editor/tod_new_schedule.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/end_credits.cpp" />
|
||||
<Unit filename="../../src/gui/dialogs/end_credits.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/gui_test_dialog.cpp" />
|
||||
<Unit filename="../../src/gui/dialogs/gui_test_dialog.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/prompt.cpp" />
|
||||
<Unit filename="../../src/gui/dialogs/prompt.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/file_dialog.cpp" />
|
||||
|
@ -823,6 +825,8 @@
|
|||
<Unit filename="../../src/gui/widgets/repeating_button.cpp" />
|
||||
<Unit filename="../../src/gui/widgets/repeating_button.hpp" />
|
||||
<Unit filename="../../src/gui/widgets/retval.hpp" />
|
||||
<Unit filename="../../src/gui/widgets/rich_label.cpp" />
|
||||
<Unit filename="../../src/gui/widgets/rich_label.hpp" />
|
||||
<Unit filename="../../src/gui/widgets/scroll_label.cpp" />
|
||||
<Unit filename="../../src/gui/widgets/scroll_label.hpp" />
|
||||
<Unit filename="../../src/gui/widgets/scroll_text.cpp" />
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
000000000000000000000008 /* achievements_dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000003 /* achievements_dialog.cpp */; };
|
||||
000000000000000000000011 /* network_download_file.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000009 /* network_download_file.cpp */; };
|
||||
000000000000000000000012 /* network_download_file.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000009 /* network_download_file.cpp */; };
|
||||
00324A11ACB0DB64FBD97896 /* community_dialog.hpp in Headers */ = {isa = PBXBuildFile; fileRef = CD5045B9A988F034F8D17C85 /* community_dialog.hpp */; };
|
||||
022640C59A7BD907C810FA99 /* rich_label.hpp in Headers */ = {isa = PBXBuildFile; fileRef = C8FF4A8788B6B7E6BE7AB7BB /* rich_label.hpp */; };
|
||||
02A44BEAA567595C902031CF /* edit_pbl_translation.hpp in Headers */ = {isa = PBXBuildFile; fileRef = C679447D91FD3623CC852FF8 /* edit_pbl_translation.hpp */; };
|
||||
04C748F7835C62498D27442D /* edit_pbl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6FA542D78393E8FF067775DA /* edit_pbl.cpp */; };
|
||||
0554467DB5FE99D85ABCDCA0 /* edit_pbl_translation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 00574699A982AA23F12B39E0 /* edit_pbl_translation.cpp */; };
|
||||
|
@ -30,6 +32,7 @@
|
|||
393E4C9DAEE19E12B2B168B5 /* edit_unit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A05D48F0A2C022FC128C8B3E /* edit_unit.cpp */; };
|
||||
3C254DF5B7DF196F2041955F /* mp_report.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 58C649488B3014E6F7254B62 /* mp_report.cpp */; };
|
||||
3CC6488695A7293C9CFC2CB6 /* tab_container.hpp in Headers */ = {isa = PBXBuildFile; fileRef = BF3B41AF9FEBC9C3A11736A2 /* tab_container.hpp */; };
|
||||
3DED4C43AC1B737C15549CEC /* rich_label.hpp in Headers */ = {isa = PBXBuildFile; fileRef = C8FF4A8788B6B7E6BE7AB7BB /* rich_label.hpp */; };
|
||||
3E9A4297B4A2828C569C8927 /* statistics_record.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27764FB68F02032F1C0B6748 /* statistics_record.cpp */; };
|
||||
4291489DA38012477DA3BA7C /* general.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84234C54BB84519421FD4136 /* general.cpp */; };
|
||||
44CA4F8598147FDAE871B7CB /* prompt.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D4594633BF3F8A06D6AE752F /* prompt.hpp */; };
|
||||
|
@ -667,12 +670,14 @@
|
|||
62D24F2F1519982500350848 /* editor_toolkit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 62D24F2B1519982500350848 /* editor_toolkit.cpp */; };
|
||||
62D24F321519987400350848 /* context_manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 62D24F311519987400350848 /* context_manager.cpp */; };
|
||||
62D24F351519995200350848 /* palette_manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 62D24F341519995200350848 /* palette_manager.cpp */; };
|
||||
6A1F44688986D07EB5DBEF75 /* gui_test_dialog.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0110429EAA81AED07D53B749 /* gui_test_dialog.hpp */; };
|
||||
6D574EACA3483ABEE72819F0 /* statistics_record.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27764FB68F02032F1C0B6748 /* statistics_record.cpp */; };
|
||||
77D94146A5FA29849D1A9BD8 /* multiline_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0B0F48CE9CF65D9813BE6CDC /* multiline_text.cpp */; };
|
||||
7A0347D48BDB52B1430D9E79 /* migrate_version_selection.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B3DE4F95AF72C6F6BC37E695 /* migrate_version_selection.hpp */; };
|
||||
7A7146D7893AA09891352019 /* test_schema_validator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7CF14AB694764953E2CB3AF7 /* test_schema_validator.cpp */; };
|
||||
7BFC4DF5BFF8CF75855BA662 /* prompt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 67044415B63F5888193BD7A6 /* prompt.cpp */; };
|
||||
7FDF4E8D9C94E7DA8F41F7BB /* tod_new_schedule.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 5D46466DBCD81B13621C7342 /* tod_new_schedule.hpp */; };
|
||||
805143B8BABF92CA79BEC8F5 /* gui_test_dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7FBD4033B4B52E9424819B5F /* gui_test_dialog.cpp */; };
|
||||
867141839BDB89BFE876E310 /* carryover_show_gold.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 09A440B1A671C45BE2924FB4 /* carryover_show_gold.cpp */; };
|
||||
87744447951D17AA38BE5F48 /* mp_report.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 58C649488B3014E6F7254B62 /* mp_report.cpp */; };
|
||||
8C704B6F99C824A4073EEBEE /* prompt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 67044415B63F5888193BD7A6 /* prompt.cpp */; };
|
||||
|
@ -1128,6 +1133,7 @@
|
|||
97714C7A9FF444E29DCEF0BA /* carryover_show_gold.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 09A440B1A671C45BE2924FB4 /* carryover_show_gold.cpp */; };
|
||||
9B4B41D29C90B05F03DE21B0 /* edit_pbl_translation.hpp in Headers */ = {isa = PBXBuildFile; fileRef = C679447D91FD3623CC852FF8 /* edit_pbl_translation.hpp */; };
|
||||
9C2743DE8100448B66F7E0AF /* combobox.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 875E45698F8A8D5B750E7317 /* combobox.cpp */; };
|
||||
9C6342BC8A95B6D23D384486 /* gui_test_dialog.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0110429EAA81AED07D53B749 /* gui_test_dialog.hpp */; };
|
||||
AC4242F78B39C571E34AF48F /* edit_unit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A05D48F0A2C022FC128C8B3E /* edit_unit.cpp */; };
|
||||
B508D193100146E300B12852 /* engine_fai.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B508D191100146E300B12852 /* engine_fai.cpp */; };
|
||||
B513B2290ED36BFB0006E551 /* libcairo.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B513B2270ED36BFB0006E551 /* libcairo.2.dylib */; };
|
||||
|
@ -1284,6 +1290,8 @@
|
|||
D09A4D40A36568E32D8723F7 /* combobox.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B3534BCB9BB2673B5E513D67 /* combobox.hpp */; };
|
||||
D1254FCA82471825B83AA786 /* multiline_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0B0F48CE9CF65D9813BE6CDC /* multiline_text.cpp */; };
|
||||
D2E9440BBCDCE2A75C93F85D /* migrate_version_selection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F7B1444E9B79504502208B82 /* migrate_version_selection.cpp */; };
|
||||
DC764C9F94D8B634B47A92B0 /* rich_label.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E5E2430098E9A628933A1DB1 /* rich_label.cpp */; };
|
||||
DDA14069BCE29DE0FE71B970 /* gui_test_dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7FBD4033B4B52E9424819B5F /* gui_test_dialog.cpp */; };
|
||||
DF974010BB46B844E83F7DDA /* tod_new_schedule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61F473D9AC43768A445E218 /* tod_new_schedule.cpp */; };
|
||||
E1DA41878F0C255769B8239D /* scroll_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 755D4555A1DEA29125E7F338 /* scroll_text.cpp */; };
|
||||
E2F24C0CBC863C3DC8A041EF /* edit_pbl.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B2CC45FEA71445AE817CAA6B /* edit_pbl.hpp */; };
|
||||
|
@ -1439,6 +1447,7 @@
|
|||
ECFB9FA8193BFAD900146ED0 /* carryover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ECFB9FA7193BFAD900146ED0 /* carryover.cpp */; };
|
||||
ECFB9FAA193BFB4B00146ED0 /* game_board.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ECFB9FA9193BFB4B00146ED0 /* game_board.cpp */; };
|
||||
ECFB9FAC193BFB6E00146ED0 /* rect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ECFB9FAB193BFB6E00146ED0 /* rect.cpp */; };
|
||||
F00C4D628A6DEFF4F2A66243 /* rich_label.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E5E2430098E9A628933A1DB1 /* rich_label.cpp */; };
|
||||
F13D4C33BAA4CB9E9AACFCC2 /* spinner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4214F3DA80B54080C4B548F /* spinner.cpp */; };
|
||||
F40A13BC1A3A88BA00C4D071 /* apple_notification.mm in Sources */ = {isa = PBXBuildFile; fileRef = F40A13BB1A3A88BA00C4D071 /* apple_notification.mm */; };
|
||||
F419A1F414E21246002F9ADC /* game_end_exceptions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F419A1F314E21246002F9ADC /* game_end_exceptions.cpp */; };
|
||||
|
@ -1573,6 +1582,7 @@
|
|||
000000000000000000000009 /* network_download_file.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = network_download_file.cpp; sourceTree = "<group>"; };
|
||||
000000000000000000000010 /* network_download_file.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = network_download_file.hpp; sourceTree = "<group>"; };
|
||||
00574699A982AA23F12B39E0 /* edit_pbl_translation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = edit_pbl_translation.cpp; sourceTree = "<group>"; };
|
||||
0110429EAA81AED07D53B749 /* gui_test_dialog.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = gui_test_dialog.hpp; path = gui_test_dialog.hpp; sourceTree = "<group>"; };
|
||||
09A440B1A671C45BE2924FB4 /* carryover_show_gold.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = carryover_show_gold.cpp; sourceTree = "<group>"; };
|
||||
0B0F48CE9CF65D9813BE6CDC /* multiline_text.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = multiline_text.cpp; path = multiline_text.cpp; sourceTree = "<group>"; };
|
||||
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
|
@ -2209,6 +2219,7 @@
|
|||
6FA542D78393E8FF067775DA /* edit_pbl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = edit_pbl.cpp; sourceTree = "<group>"; };
|
||||
755D4555A1DEA29125E7F338 /* scroll_text.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = scroll_text.cpp; path = scroll_text.cpp; sourceTree = "<group>"; };
|
||||
7CF14AB694764953E2CB3AF7 /* test_schema_validator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = test_schema_validator.cpp; path = test_schema_validator.cpp; sourceTree = "<group>"; };
|
||||
7FBD4033B4B52E9424819B5F /* gui_test_dialog.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = gui_test_dialog.cpp; path = gui_test_dialog.cpp; sourceTree = "<group>"; };
|
||||
84234C54BB84519421FD4136 /* general.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = general.cpp; sourceTree = "<group>"; };
|
||||
875E45698F8A8D5B750E7317 /* combobox.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = combobox.cpp; path = combobox.cpp; sourceTree = "<group>"; };
|
||||
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -2735,11 +2746,13 @@
|
|||
BF3B41AF9FEBC9C3A11736A2 /* tab_container.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = tab_container.hpp; path = tab_container.hpp; sourceTree = "<group>"; };
|
||||
C61F473D9AC43768A445E218 /* tod_new_schedule.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = tod_new_schedule.cpp; sourceTree = "<group>"; };
|
||||
C679447D91FD3623CC852FF8 /* edit_pbl_translation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = edit_pbl_translation.hpp; sourceTree = "<group>"; };
|
||||
C8FF4A8788B6B7E6BE7AB7BB /* rich_label.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = rich_label.hpp; path = rich_label.hpp; sourceTree = "<group>"; };
|
||||
D4594633BF3F8A06D6AE752F /* prompt.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = prompt.hpp; sourceTree = "<group>"; };
|
||||
D911474D925FA88D5B856A0E /* test_sdl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = test_sdl.cpp; path = test_sdl.cpp; sourceTree = "<group>"; };
|
||||
D9A141EAAE90E98B6F6171D6 /* choose_addon.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = choose_addon.hpp; sourceTree = "<group>"; };
|
||||
DA034C90BB2E6C060B0A0B93 /* back_edge_detector.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = back_edge_detector.hpp; path = back_edge_detector.hpp; sourceTree = "<group>"; };
|
||||
E4214F3DA80B54080C4B548F /* spinner.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = spinner.cpp; path = spinner.cpp; sourceTree = "<group>"; };
|
||||
E5E2430098E9A628933A1DB1 /* rich_label.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = rich_label.cpp; path = rich_label.cpp; sourceTree = "<group>"; };
|
||||
EC0341DF1ECF46FE000F2E2B /* config_attribute_value.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = config_attribute_value.cpp; sourceTree = "<group>"; };
|
||||
EC0341E01ECF46FE000F2E2B /* config_attribute_value.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = config_attribute_value.hpp; sourceTree = "<group>"; };
|
||||
EC0680231EA920A300EEE03B /* random_deterministic.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = random_deterministic.cpp; sourceTree = "<group>"; };
|
||||
|
@ -3860,6 +3873,8 @@
|
|||
D4594633BF3F8A06D6AE752F /* prompt.hpp */,
|
||||
F7B1444E9B79504502208B82 /* migrate_version_selection.cpp */,
|
||||
B3DE4F95AF72C6F6BC37E695 /* migrate_version_selection.hpp */,
|
||||
7FBD4033B4B52E9424819B5F /* gui_test_dialog.cpp */,
|
||||
0110429EAA81AED07D53B749 /* gui_test_dialog.hpp */,
|
||||
);
|
||||
path = dialogs;
|
||||
sourceTree = "<group>";
|
||||
|
@ -4111,6 +4126,8 @@
|
|||
B3534BCB9BB2673B5E513D67 /* combobox.hpp */,
|
||||
162C4B1E9F7373592D0F3B89 /* tab_container.cpp */,
|
||||
BF3B41AF9FEBC9C3A11736A2 /* tab_container.hpp */,
|
||||
E5E2430098E9A628933A1DB1 /* rich_label.cpp */,
|
||||
C8FF4A8788B6B7E6BE7AB7BB /* rich_label.hpp */,
|
||||
);
|
||||
path = widgets;
|
||||
sourceTree = "<group>";
|
||||
|
@ -5155,6 +5172,8 @@
|
|||
8E1D442FB4DA43385F58F77F /* combobox.hpp in Headers */,
|
||||
B7B34687A61290490C1616D3 /* tab_container.hpp in Headers */,
|
||||
365D4F89BD511BC074E639D7 /* migrate_version_selection.hpp in Headers */,
|
||||
3DED4C43AC1B737C15549CEC /* rich_label.hpp in Headers */,
|
||||
9C6342BC8A95B6D23D384486 /* gui_test_dialog.hpp in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -5171,6 +5190,8 @@
|
|||
D09A4D40A36568E32D8723F7 /* combobox.hpp in Headers */,
|
||||
3CC6488695A7293C9CFC2CB6 /* tab_container.hpp in Headers */,
|
||||
7A0347D48BDB52B1430D9E79 /* migrate_version_selection.hpp in Headers */,
|
||||
022640C59A7BD907C810FA99 /* rich_label.hpp in Headers */,
|
||||
6A1F44688986D07EB5DBEF75 /* gui_test_dialog.hpp in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -5928,6 +5949,8 @@
|
|||
1C3D48879EAC414AE3DB122E /* combobox.cpp in Sources */,
|
||||
1BC74FED857215A162E9E0F2 /* tab_container.cpp in Sources */,
|
||||
D2E9440BBCDCE2A75C93F85D /* migrate_version_selection.cpp in Sources */,
|
||||
F00C4D628A6DEFF4F2A66243 /* rich_label.cpp in Sources */,
|
||||
805143B8BABF92CA79BEC8F5 /* gui_test_dialog.cpp in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -6614,6 +6637,8 @@
|
|||
9C2743DE8100448B66F7E0AF /* combobox.cpp in Sources */,
|
||||
19B14238AD52EC06ED2094F1 /* tab_container.cpp in Sources */,
|
||||
4E4A4DBC9F44444A5867EC2B /* migrate_version_selection.cpp in Sources */,
|
||||
DC764C9F94D8B634B47A92B0 /* rich_label.cpp in Sources */,
|
||||
DDA14069BCE29DE0FE71B970 /* gui_test_dialog.cpp in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -16,6 +16,7 @@ gui/widgets/multimenu_button.cpp
|
|||
gui/widgets/panel.cpp
|
||||
gui/widgets/password_box.cpp
|
||||
gui/widgets/progress_bar.cpp
|
||||
gui/widgets/rich_label.cpp
|
||||
gui/widgets/repeating_button.cpp
|
||||
gui/widgets/scroll_label.cpp
|
||||
gui/widgets/scroll_text.cpp
|
||||
|
|
|
@ -211,6 +211,7 @@ gui/dialogs/game_save.cpp
|
|||
gui/dialogs/game_stats.cpp
|
||||
gui/dialogs/game_version_dialog.cpp
|
||||
gui/dialogs/gamestate_inspector.cpp
|
||||
gui/dialogs/gui_test_dialog.cpp
|
||||
gui/dialogs/help_browser.cpp
|
||||
gui/dialogs/hotkey_bind.cpp
|
||||
gui/dialogs/label_settings.cpp
|
||||
|
@ -273,7 +274,6 @@ gui/widgets/generator.cpp
|
|||
gui/widgets/grid.cpp
|
||||
gui/widgets/helper.cpp
|
||||
gui/widgets/pane.cpp
|
||||
gui/widgets/scroll_text.cpp
|
||||
gui/widgets/scrollbar.cpp
|
||||
gui/widgets/scrollbar_container.cpp
|
||||
gui/widgets/settings.cpp
|
||||
|
|
|
@ -445,7 +445,7 @@ public:
|
|||
optional_config_impl<const config> get_deprecated_child(config_key_type old_key, const std::string& in_tag, DEP_LEVEL level, const std::string& message) const;
|
||||
|
||||
/**
|
||||
* Get a deprecated child rangw and log a deprecation message
|
||||
* Get a deprecated child range and log a deprecation message
|
||||
* @param old_key The deprecated child to return if present
|
||||
* @param in_tag The name of the tag this child appears in
|
||||
* @param level The deprecation level
|
||||
|
|
|
@ -29,7 +29,8 @@ const color_t
|
|||
PETRIFIED_COLOR {160, 160, 160},
|
||||
TITLE_COLOR {186, 172, 125},
|
||||
LABEL_COLOR {107, 140, 255},
|
||||
BIGMAP_COLOR {255, 255, 255};
|
||||
BIGMAP_COLOR {255, 255, 255},
|
||||
BLUE_COLOR {0 , 0 , 255};
|
||||
|
||||
const color_t DISABLED_COLOR = PETRIFIED_COLOR.inverse();
|
||||
|
||||
|
@ -42,4 +43,38 @@ const color_t
|
|||
inactive_ability_color {146, 146, 146},
|
||||
unit_type_color {245, 230, 193},
|
||||
race_color {166, 146, 117};
|
||||
|
||||
color_t string_to_color(const std::string &cmp_str)
|
||||
{
|
||||
// TODO needs a more generic mechanism so that more common color names are recognized
|
||||
if (cmp_str == "green") {
|
||||
return font::GOOD_COLOR;
|
||||
}
|
||||
if (cmp_str == "red") {
|
||||
return font::BAD_COLOR;
|
||||
}
|
||||
if (cmp_str == "black") {
|
||||
return font::BLACK_COLOR;
|
||||
}
|
||||
if (cmp_str == "yellow") {
|
||||
return font::YELLOW_COLOR;
|
||||
}
|
||||
if (cmp_str == "white") {
|
||||
return font::BIGMAP_COLOR;
|
||||
}
|
||||
if (cmp_str == "blue") {
|
||||
return font::BLUE_COLOR;
|
||||
}
|
||||
|
||||
if (cmp_str.at(0) == '#' && cmp_str.size() == 7) {
|
||||
// #rrggbb color, pango format.
|
||||
return color_t::from_hex_string(cmp_str.substr(1));
|
||||
} else if (cmp_str.size() == 6) {
|
||||
// rrggbb color, wesnoth format
|
||||
return color_t::from_hex_string(cmp_str);
|
||||
}
|
||||
return font::NORMAL_COLOR;
|
||||
}
|
||||
|
||||
} // namespace font
|
||||
|
||||
|
|
|
@ -48,4 +48,11 @@ extern const color_t
|
|||
inactive_ability_color,
|
||||
unit_type_color,
|
||||
race_color;
|
||||
|
||||
/**
|
||||
* Return the color the string represents. Return font::NORMAL_COLOR if
|
||||
* the string is empty or can't be matched against any other color.
|
||||
*/
|
||||
color_t string_to_color(const std::string &s);
|
||||
|
||||
}
|
||||
|
|
|
@ -84,9 +84,16 @@ pango_text::pango_text()
|
|||
, maximum_length_(std::string::npos)
|
||||
, calculation_dirty_(true)
|
||||
, length_(0)
|
||||
, attribute_start_offset_(0)
|
||||
, attribute_end_offset_(0)
|
||||
, highlight_color_()
|
||||
, attrib_hash_(0)
|
||||
, pixel_scale_(1)
|
||||
, surface_buffer_()
|
||||
{
|
||||
// Initialize global list
|
||||
global_attribute_list_ = pango_attr_list_new();
|
||||
|
||||
// With 72 dpi the sizes are the same as with SDL_TTF so hardcoded.
|
||||
pango_cairo_context_set_resolution(context_.get(), 72.0);
|
||||
|
||||
|
@ -342,6 +349,184 @@ point pango_text::get_column_line(const point& position) const
|
|||
}
|
||||
}
|
||||
|
||||
void pango_text::add_attribute_size(const unsigned start_offset, const unsigned end_offset, int size)
|
||||
{
|
||||
attribute_start_offset_ = start_offset;
|
||||
attribute_end_offset_ = end_offset;
|
||||
|
||||
if (attribute_start_offset_ != attribute_end_offset_) {
|
||||
PangoAttribute *attr = pango_attr_size_new_absolute(PANGO_SCALE * size);
|
||||
attr->start_index = attribute_start_offset_;
|
||||
attr->end_index = attribute_end_offset_;
|
||||
|
||||
DBG_GUI_D << "attribute: size";
|
||||
DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
|
||||
|
||||
// Update hash
|
||||
boost::hash_combine(attrib_hash_, attribute_start_offset_);
|
||||
boost::hash_combine(attrib_hash_, attribute_end_offset_);
|
||||
boost::hash_combine(attrib_hash_, size);
|
||||
|
||||
// Insert all attributes
|
||||
pango_attr_list_insert(global_attribute_list_, attr);
|
||||
}
|
||||
}
|
||||
|
||||
void pango_text::add_attribute_weight(const unsigned start_offset, const unsigned end_offset, PangoWeight weight)
|
||||
{
|
||||
attribute_start_offset_ = start_offset;
|
||||
attribute_end_offset_ = end_offset;
|
||||
|
||||
if (attribute_start_offset_ != attribute_end_offset_) {
|
||||
PangoAttribute *attr = pango_attr_weight_new(weight);
|
||||
attr->start_index = attribute_start_offset_;
|
||||
attr->end_index = attribute_end_offset_;
|
||||
|
||||
DBG_GUI_D << "attribute: weight";
|
||||
DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
|
||||
|
||||
// Update hash
|
||||
boost::hash_combine(attrib_hash_, attribute_start_offset_);
|
||||
boost::hash_combine(attrib_hash_, attribute_end_offset_);
|
||||
boost::hash_combine(attrib_hash_, weight);
|
||||
|
||||
// Insert all attributes
|
||||
pango_attr_list_insert(global_attribute_list_, attr);
|
||||
}
|
||||
}
|
||||
|
||||
void pango_text::add_attribute_style(const unsigned start_offset, const unsigned end_offset, PangoStyle style)
|
||||
{
|
||||
attribute_start_offset_ = start_offset;
|
||||
attribute_end_offset_ = end_offset;
|
||||
|
||||
if (attribute_start_offset_ != attribute_end_offset_) {
|
||||
|
||||
PangoAttribute *attr = pango_attr_style_new(style);
|
||||
attr->start_index = attribute_start_offset_;
|
||||
attr->end_index = attribute_end_offset_;
|
||||
|
||||
DBG_GUI_D << "attribute: style";
|
||||
DBG_GUI_D << "attribute start: " << attribute_start_offset_ << " end : " << attribute_end_offset_;
|
||||
|
||||
// Update hash
|
||||
boost::hash_combine(attrib_hash_, attribute_start_offset_);
|
||||
boost::hash_combine(attrib_hash_, attribute_end_offset_);
|
||||
|
||||
// Insert all attributes
|
||||
pango_attr_list_insert(global_attribute_list_, attr);
|
||||
}
|
||||
}
|
||||
|
||||
void pango_text::add_attribute_underline(const unsigned start_offset, const unsigned end_offset, PangoUnderline underline)
|
||||
{
|
||||
attribute_start_offset_ = start_offset;
|
||||
attribute_end_offset_ = end_offset;
|
||||
|
||||
if (attribute_start_offset_ != attribute_end_offset_) {
|
||||
PangoAttribute *attr = pango_attr_underline_new(underline);
|
||||
attr->start_index = attribute_start_offset_;
|
||||
attr->end_index = attribute_end_offset_;
|
||||
|
||||
DBG_GUI_D << "attribute: underline";
|
||||
DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
|
||||
|
||||
// Update hash
|
||||
boost::hash_combine(attrib_hash_, attribute_start_offset_);
|
||||
boost::hash_combine(attrib_hash_, attribute_end_offset_);
|
||||
boost::hash_combine(attrib_hash_, underline);
|
||||
|
||||
// Insert all attributes
|
||||
pango_attr_list_insert(global_attribute_list_, attr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void pango_text::add_attribute_fg_color(const unsigned start_offset, const unsigned end_offset, const color_t& color)
|
||||
{
|
||||
attribute_start_offset_ = start_offset;
|
||||
attribute_end_offset_ = end_offset;
|
||||
|
||||
if (attribute_start_offset_ != attribute_end_offset_) {
|
||||
int col_r = color.r / 255.0 * 65535.0;
|
||||
int col_g = color.g / 255.0 * 65535.0;
|
||||
int col_b = color.b / 255.0 * 65535.0;
|
||||
|
||||
PangoAttribute *attr = pango_attr_foreground_new(col_r, col_g, col_b);
|
||||
attr->start_index = start_offset;
|
||||
attr->end_index = end_offset;
|
||||
|
||||
DBG_GUI_D << "attribute: fg color";
|
||||
DBG_GUI_D << "attribute start: " << attribute_start_offset_ << " end : " << attribute_end_offset_;
|
||||
DBG_GUI_D << "color: " << col_r << "," << col_g << "," << col_b;
|
||||
|
||||
// Update hash
|
||||
boost::hash_combine(attrib_hash_, attribute_start_offset_);
|
||||
boost::hash_combine(attrib_hash_, attribute_end_offset_);
|
||||
boost::hash_combine(attrib_hash_, color.to_rgba_bytes());
|
||||
|
||||
// Insert all attributes
|
||||
pango_attr_list_insert(global_attribute_list_, attr);
|
||||
}
|
||||
}
|
||||
|
||||
void pango_text::add_attribute_font_family(const unsigned start_offset, const unsigned end_offset, std::string family)
|
||||
{
|
||||
attribute_start_offset_ = start_offset;
|
||||
attribute_end_offset_ = end_offset;
|
||||
|
||||
if (attribute_start_offset_ != attribute_end_offset_) {
|
||||
PangoAttribute *attr = pango_attr_family_new(family.c_str());
|
||||
attr->start_index = attribute_start_offset_;
|
||||
attr->end_index = attribute_end_offset_;
|
||||
|
||||
DBG_GUI_D << "attribute: font family";
|
||||
DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
|
||||
DBG_GUI_D << "font family: " << family;
|
||||
|
||||
// Update hash
|
||||
boost::hash_combine(attrib_hash_, attribute_start_offset_);
|
||||
boost::hash_combine(attrib_hash_, attribute_end_offset_);
|
||||
boost::hash_combine(attrib_hash_, family);
|
||||
|
||||
// Insert all attributes
|
||||
pango_attr_list_insert(global_attribute_list_, attr);
|
||||
}
|
||||
}
|
||||
|
||||
void pango_text::set_highlight_area(const unsigned start_offset, const unsigned end_offset, const color_t& color) {
|
||||
attribute_start_offset_ = start_offset;
|
||||
attribute_end_offset_ = end_offset;
|
||||
highlight_color_ = color;
|
||||
|
||||
if (attribute_start_offset_ != attribute_end_offset_) {
|
||||
// Highlight
|
||||
int col_r = highlight_color_.r / 255.0 * 65535.0;
|
||||
int col_g = highlight_color_.g / 255.0 * 65535.0;
|
||||
int col_b = highlight_color_.b / 255.0 * 65535.0;
|
||||
|
||||
DBG_GUI_D << "highlight start: " << attribute_start_offset_ << "end : " << attribute_end_offset_;
|
||||
DBG_GUI_D << "highlight color: " << col_r << "," << col_g << "," << col_b;
|
||||
|
||||
PangoAttribute *attr = pango_attr_background_new(col_r, col_g, col_b);
|
||||
attr->start_index = attribute_start_offset_;
|
||||
attr->end_index = attribute_end_offset_;
|
||||
|
||||
// Update hash
|
||||
boost::hash_combine(attrib_hash_, attribute_start_offset_);
|
||||
boost::hash_combine(attrib_hash_, attribute_end_offset_);
|
||||
boost::hash_combine(attrib_hash_, highlight_color_.to_rgba_bytes());
|
||||
|
||||
// Insert all attributes
|
||||
pango_attr_list_insert(global_attribute_list_, attr);
|
||||
}
|
||||
}
|
||||
|
||||
void pango_text::clear_attribute_list() {
|
||||
global_attribute_list_ = pango_attr_list_new();
|
||||
pango_layout_set_attributes(layout_.get(), global_attribute_list_);
|
||||
}
|
||||
|
||||
bool pango_text::set_text(const std::string& text, const bool markedup)
|
||||
{
|
||||
if(markedup != markedup_text_ || text != text_) {
|
||||
|
@ -357,28 +542,16 @@ bool pango_text::set_text(const std::string& text, const bool markedup)
|
|||
<< "' contains invalid utf-8, trimmed the invalid parts.";
|
||||
}
|
||||
|
||||
if (highlight_start_offset_ != highlight_end_offset_) {
|
||||
/** Highlight */
|
||||
PangoAttrList *attribute_list = pango_attr_list_new();
|
||||
int col_r = highlight_color_.r / 255.0 * 65535.0;
|
||||
int col_g = highlight_color_.g / 255.0 * 65535.0;
|
||||
int col_b = highlight_color_.b / 255.0 * 65535.0;
|
||||
DBG_GUI_D << "highlight start : " << highlight_start_offset_ << "end : " << highlight_end_offset_;
|
||||
DBG_GUI_D << "highlight rgb : " << col_r << "," << col_g << "," << col_b;
|
||||
PangoAttribute *attr = pango_attr_background_new(col_r, col_g, col_b);
|
||||
attr->start_index = highlight_start_offset_;
|
||||
attr->end_index = highlight_end_offset_;
|
||||
pango_attr_list_insert(attribute_list, attr);
|
||||
|
||||
pango_layout_set_attributes(layout_.get(), attribute_list);
|
||||
}
|
||||
pango_layout_set_attributes(layout_.get(), global_attribute_list_);
|
||||
// Clear list. Using pango_attr_list_unref() causes segfault
|
||||
global_attribute_list_ = pango_attr_list_new();
|
||||
|
||||
if(markedup) {
|
||||
if(!this->set_markup(narrow, *layout_)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (highlight_start_offset_ == highlight_end_offset_) {
|
||||
if (attribute_start_offset_ == attribute_end_offset_) {
|
||||
/*
|
||||
* pango_layout_set_text after pango_layout_set_markup might
|
||||
* leave the layout in an undefined state regarding markup so
|
||||
|
@ -1034,9 +1207,9 @@ std::size_t hash<font::pango_text>::operator()(const font::pango_text& t) const
|
|||
boost::hash_combine(hash, t.alignment_);
|
||||
boost::hash_combine(hash, t.ellipse_mode_);
|
||||
boost::hash_combine(hash, t.add_outline_);
|
||||
boost::hash_combine(hash, t.highlight_start_offset_);
|
||||
boost::hash_combine(hash, t.highlight_end_offset_);
|
||||
boost::hash_combine(hash, t.highlight_color_.to_rgba_bytes());
|
||||
|
||||
// Hash for the global attribute list
|
||||
boost::hash_combine(hash, t.attrib_hash_);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
|
|
@ -305,12 +305,17 @@ public:
|
|||
* @param end_offset Column offset of the cursor where selection/highlight ends
|
||||
* @param color Highlight color
|
||||
*/
|
||||
void set_highlight_area(const unsigned start_offset, const unsigned end_offset, const color_t& color)
|
||||
{
|
||||
highlight_start_offset_ = start_offset;
|
||||
highlight_end_offset_ = end_offset;
|
||||
highlight_color_ = color;
|
||||
}
|
||||
void set_highlight_area(const unsigned start_offset, const unsigned end_offset, const color_t& color);
|
||||
|
||||
void add_attribute_weight(const unsigned start_offset, const unsigned end_offset, PangoWeight weight);
|
||||
void add_attribute_style(const unsigned start_offset, const unsigned end_offset, PangoStyle style);
|
||||
void add_attribute_underline(const unsigned start_offset, const unsigned end_offset, PangoUnderline underline);
|
||||
void add_attribute_fg_color(const unsigned start_offset, const unsigned end_offset, const color_t& color);
|
||||
void add_attribute_size(const unsigned start_offset, const unsigned end_offset, int size);
|
||||
void add_attribute_font_family(const unsigned start_offset, const unsigned end_offset, std::string family);
|
||||
|
||||
/** Clear all attributes */
|
||||
void clear_attribute_list();
|
||||
|
||||
private:
|
||||
|
||||
|
@ -409,10 +414,19 @@ private:
|
|||
/** Length of the text. */
|
||||
mutable std::size_t length_;
|
||||
|
||||
unsigned highlight_start_offset_;
|
||||
unsigned highlight_end_offset_;
|
||||
unsigned attribute_start_offset_;
|
||||
unsigned attribute_end_offset_;
|
||||
color_t highlight_color_;
|
||||
|
||||
/**
|
||||
* Global pango attribute list. All attributes in this list
|
||||
* will be applied one by one to the text
|
||||
*/
|
||||
PangoAttrList* global_attribute_list_;
|
||||
|
||||
/** Hash for the global_attribute_list_ */
|
||||
std::size_t attrib_hash_;
|
||||
|
||||
/** The pixel scale, used to render high-DPI text. */
|
||||
int pixel_scale_;
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "gui/auxiliary/typed_formula.hpp"
|
||||
#include "gui/core/log.hpp"
|
||||
#include "gui/widgets/helper.hpp"
|
||||
#include "font/standard_colors.hpp"
|
||||
#include "picture.hpp"
|
||||
#include "sdl/point.hpp"
|
||||
#include "sdl/rect.hpp"
|
||||
|
@ -39,6 +40,8 @@
|
|||
#include "video.hpp" // read_pixels_low_res, only used for blurring
|
||||
#include "wml_exception.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace gui2
|
||||
{
|
||||
|
||||
|
@ -326,9 +329,23 @@ void image_shape::draw(wfl::map_formula_callable& variables)
|
|||
local_variables.add("clip_x", wfl::variant(x));
|
||||
local_variables.add("clip_y", wfl::variant(y));
|
||||
|
||||
if (variables.has_key("fake_draw") && variables.query_value("fake_draw").as_bool()) {
|
||||
variables.add("image_original_width", wfl::variant(tex.w()));
|
||||
variables.add("image_original_height", wfl::variant(tex.h()));
|
||||
variables.add("image_width", wfl::variant(w ? w : tex.w()));
|
||||
variables.add("image_height", wfl::variant(h ? h : tex.h()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute the provided actions for this context.
|
||||
wfl::variant(variables.fake_ptr()).execute_variant(actions_formula_.evaluate(local_variables));
|
||||
|
||||
// Useful for relative, grid like positioning
|
||||
variables.add("item_x", wfl::variant(x));
|
||||
variables.add("item_y", wfl::variant(y));
|
||||
variables.add("item_width", wfl::variant(w ? w : tex.w()));
|
||||
variables.add("item_height", wfl::variant(h ? h : tex.h()));
|
||||
|
||||
// If w or h is 0, assume it means the whole image.
|
||||
if (!w) { w = tex.w(); }
|
||||
if (!h) { h = tex.h(); }
|
||||
|
@ -409,12 +426,17 @@ text_shape::text_shape(const config& cfg, wfl::action_function_symbol_table& fun
|
|||
, maximum_width_(cfg["maximum_width"], -1)
|
||||
, characters_per_line_(cfg["text_characters_per_line"])
|
||||
, maximum_height_(cfg["maximum_height"], -1)
|
||||
, highlight_start_(cfg["highlight_start"], 0)
|
||||
, highlight_end_(cfg["highlight_end"], 0)
|
||||
, highlight_start_(cfg["highlight_start"])
|
||||
, highlight_end_(cfg["highlight_end"])
|
||||
, highlight_color_(cfg["highlight_color"], color_t::from_hex_string("215380"))
|
||||
, attr_start_(cfg["attr_start"])
|
||||
, attr_end_(cfg["attr_end"])
|
||||
, attr_name_(cfg["attr_name"])
|
||||
, attr_data_(cfg["attr_data"])
|
||||
, outline_(cfg["outline"], false)
|
||||
, actions_formula_(cfg["actions"], &functions)
|
||||
{
|
||||
|
||||
if(!font_size_.has_formula()) {
|
||||
VALIDATE(font_size_(), _("Text has a font size of 0."));
|
||||
}
|
||||
|
@ -440,8 +462,72 @@ void text_shape::draw(wfl::map_formula_callable& variables)
|
|||
}
|
||||
|
||||
font::pango_text& text_renderer = font::get_text_renderer();
|
||||
text_renderer.clear_attribute_list();
|
||||
|
||||
text_renderer.set_highlight_area(highlight_start_(variables), highlight_end_(variables), highlight_color_(variables));
|
||||
std::vector<std::string> starts = utils::split(highlight_start_, ',');
|
||||
std::vector<std::string> stops = utils::split(highlight_end_, ',');
|
||||
|
||||
for(size_t i = 0; i < std::min(starts.size(), stops.size()); i++) {
|
||||
typed_formula<int> hstart(starts.at(i));
|
||||
typed_formula<int> hstop(stops.at(i));
|
||||
text_renderer.set_highlight_area(hstart(variables), hstop(variables), highlight_color_(variables));
|
||||
}
|
||||
|
||||
// TODO check the strings before parsing them
|
||||
starts = utils::split(attr_start_, ',');
|
||||
stops = utils::split(attr_end_, ',');
|
||||
std::vector<std::string> styles = utils::split(attr_name_, ',');
|
||||
std::vector<std::string> data = utils::split(attr_data_, ',');
|
||||
|
||||
for(size_t i = 0, data_index = 0; i < std::min(starts.size(), stops.size()); i++) {
|
||||
if (styles.at(i).empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
typed_formula<int> attr_start(starts.at(i));
|
||||
typed_formula<int> attr_stop(stops.at(i));
|
||||
|
||||
// Note that the value corresponding to the attribute is not the i-th item
|
||||
// but rather the data_index-th one. Using data_index so that we can get rid of excess commas
|
||||
// that is, things like 'attr_data=value1,,,valu2,value3'
|
||||
if (styles.at(i) == "color"||styles.at(i) == "fgcolor"||styles.at(i) == "foreground") {
|
||||
text_renderer.add_attribute_fg_color(attr_start(variables), attr_stop(variables), font::string_to_color(data.at(data_index)));
|
||||
data_index++;
|
||||
} else if (styles.at(i) == "bgcolor"||styles.at(i) == "background") {
|
||||
text_renderer.set_highlight_area(attr_start(variables), attr_stop(variables), font::string_to_color(data.at(data_index)));
|
||||
data_index++;
|
||||
} else if (styles.at(i) == "font_size"||styles.at(i) == "size") {
|
||||
text_renderer.add_attribute_size(attr_start(variables), attr_stop(variables), std::stoi(data.at(data_index)));
|
||||
data_index++;
|
||||
} else if (styles.at(i) == "font_family"||styles.at(i) == "face") {
|
||||
text_renderer.add_attribute_font_family(attr_start(variables), attr_stop(variables), data.at(data_index));
|
||||
data_index++;
|
||||
} else if (styles.at(i) == "weight") {
|
||||
text_renderer.add_attribute_weight(attr_start(variables), attr_stop(variables), decode_text_weight(data.at(data_index)));
|
||||
data_index++;
|
||||
} else if (styles.at(i) == "style") {
|
||||
text_renderer.add_attribute_style(attr_start(variables), attr_stop(variables), decode_text_style(data.at(data_index)));
|
||||
data_index++;
|
||||
} else {
|
||||
font::pango_text::FONT_STYLE attr_style = decode_font_style(styles.at(i));
|
||||
switch(attr_style)
|
||||
{
|
||||
case font::pango_text::STYLE_BOLD:
|
||||
text_renderer.add_attribute_weight(attr_start(variables), attr_stop(variables), PANGO_WEIGHT_BOLD);
|
||||
break;
|
||||
case font::pango_text::STYLE_ITALIC:
|
||||
text_renderer.add_attribute_style(attr_start(variables), attr_stop(variables), PANGO_STYLE_ITALIC);
|
||||
break;
|
||||
case font::pango_text::STYLE_UNDERLINE:
|
||||
text_renderer.add_attribute_underline(attr_start(variables), attr_stop(variables), PANGO_UNDERLINE_SINGLE);
|
||||
break;
|
||||
default:
|
||||
// Unsupported formatting or normal text
|
||||
text_renderer.add_attribute_weight(attr_start(variables), attr_stop(variables), PANGO_WEIGHT_NORMAL);
|
||||
text_renderer.add_attribute_style(attr_start(variables), attr_stop(variables), PANGO_STYLE_NORMAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text_renderer
|
||||
.set_link_aware(link_aware_(variables))
|
||||
|
@ -468,6 +554,12 @@ void text_shape::draw(wfl::map_formula_callable& variables)
|
|||
local_variables.add("text_width", wfl::variant(tw));
|
||||
local_variables.add("text_height", wfl::variant(th));
|
||||
|
||||
if (variables.has_key("fake_draw") && variables.query_value("fake_draw").as_bool()) {
|
||||
variables.add("text_width", wfl::variant(tw));
|
||||
variables.add("text_height", wfl::variant(th));
|
||||
return;
|
||||
}
|
||||
|
||||
const int x = x_(local_variables);
|
||||
const int y = y_(local_variables);
|
||||
const int w = w_(local_variables);
|
||||
|
@ -477,6 +569,12 @@ void text_shape::draw(wfl::map_formula_callable& variables)
|
|||
// Execute the provided actions for this context.
|
||||
wfl::variant(variables.fake_ptr()).execute_variant(actions_formula_.evaluate(local_variables));
|
||||
|
||||
// Useful for relative, grid like positioning
|
||||
variables.add("item_x", wfl::variant(x));
|
||||
variables.add("item_y", wfl::variant(y));
|
||||
variables.add("item_width", wfl::variant(tw));
|
||||
variables.add("item_height", wfl::variant(th));
|
||||
|
||||
texture tex = text_renderer.render_and_get_texture();
|
||||
if(!tex) {
|
||||
DBG_GUI_D << "Text: Rendering '" << text << "' resulted in an empty canvas, leave.";
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "sdl/texture.hpp"
|
||||
#include "sdl/rect.hpp"
|
||||
|
||||
namespace wfl { class variant; }
|
||||
struct point;
|
||||
|
||||
namespace gui2
|
||||
|
@ -155,6 +156,11 @@ public:
|
|||
variables_.add(key, std::move(value));
|
||||
}
|
||||
|
||||
wfl::variant get_variable(const std::string& key)
|
||||
{
|
||||
return variables_.query_value(key);
|
||||
}
|
||||
|
||||
private:
|
||||
/** Vector with the shapes to draw. */
|
||||
std::vector<std::unique_ptr<shape>> shapes_;
|
||||
|
|
|
@ -283,12 +283,29 @@ private:
|
|||
typed_formula<int> maximum_height_;
|
||||
|
||||
/** Start and end offsets for highlight */
|
||||
typed_formula<int> highlight_start_;
|
||||
typed_formula<int> highlight_end_;
|
||||
std::string highlight_start_;
|
||||
std::string highlight_end_;
|
||||
|
||||
/** The color to be used for highlighting */
|
||||
typed_formula<color_t> highlight_color_;
|
||||
|
||||
/** Generic start and end offsets for various attributes */
|
||||
std::string attr_start_;
|
||||
std::string attr_end_;
|
||||
|
||||
/**
|
||||
* The attribute type
|
||||
* Possible values :
|
||||
* color/foreground, bgcolor/background, font_size/size,
|
||||
* bold, italic, underline
|
||||
* The first three require extra data
|
||||
* the color for the first two, and font size for the last
|
||||
*/
|
||||
std::string attr_name_;
|
||||
|
||||
/** extra data for the attribute, if any */
|
||||
std::string attr_data_;
|
||||
|
||||
/** Whether to apply a text outline. */
|
||||
typed_formula<bool> outline_;
|
||||
|
||||
|
|
40
src/gui/dialogs/gui_test_dialog.cpp
Normal file
40
src/gui/dialogs/gui_test_dialog.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Copyright (C) 2023 - 2024
|
||||
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#define GETTEXT_DOMAIN "wesnoth-lib"
|
||||
|
||||
#include "gui/dialogs/gui_test_dialog.hpp"
|
||||
|
||||
#include "gui/auxiliary/find_widget.hpp"
|
||||
#include "gui/widgets/text_box.hpp"
|
||||
|
||||
namespace gui2::dialogs
|
||||
{
|
||||
|
||||
REGISTER_DIALOG(gui_test_dialog)
|
||||
|
||||
gui_test_dialog::gui_test_dialog()
|
||||
: modal_dialog(window_id())
|
||||
{
|
||||
}
|
||||
|
||||
void gui_test_dialog::pre_show(window& /*win*/)
|
||||
{
|
||||
}
|
||||
|
||||
void gui_test_dialog::post_show(window& /*win*/)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace gui2::dialogs
|
46
src/gui/dialogs/gui_test_dialog.hpp
Normal file
46
src/gui/dialogs/gui_test_dialog.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Copyright (C) 2023 - 2024
|
||||
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gui/dialogs/modal_dialog.hpp"
|
||||
|
||||
namespace gui2
|
||||
{
|
||||
|
||||
namespace dialogs
|
||||
{
|
||||
|
||||
/**
|
||||
* @ingroup GUIWindowDefinitionWML
|
||||
*
|
||||
* A test dialog for testing various gui2 features
|
||||
*/
|
||||
class gui_test_dialog : public modal_dialog
|
||||
{
|
||||
public:
|
||||
gui_test_dialog();
|
||||
|
||||
/** The execute function. See @ref modal_dialog for more information. */
|
||||
DEFINE_SIMPLE_EXECUTE_WRAPPER(gui_test_dialog)
|
||||
|
||||
private:
|
||||
virtual void pre_show(window& /*window*/) override;
|
||||
virtual void post_show(window& /*window*/) override;
|
||||
|
||||
virtual const std::string& window_id() const override;
|
||||
};
|
||||
|
||||
} // namespace dialogs
|
||||
} // namespace gui2
|
|
@ -37,6 +37,7 @@
|
|||
#include "gui/dialogs/preferences_dialog.hpp"
|
||||
#include "gui/dialogs/screenshot_notification.hpp"
|
||||
#include "gui/dialogs/simple_item_selector.hpp"
|
||||
#include "gui/dialogs/gui_test_dialog.hpp"
|
||||
#include "language.hpp"
|
||||
#include "log.hpp"
|
||||
#include "preferences/game.hpp"
|
||||
|
@ -362,6 +363,17 @@ void title_screen::init_callbacks()
|
|||
clock->set_visible(show_debug_clock_button ? widget::visibility::visible : widget::visibility::invisible);
|
||||
}
|
||||
|
||||
//
|
||||
// GUI Test and Debug Window
|
||||
//
|
||||
register_button(*this, "test_dialog", hotkey::HOTKEY_NULL,
|
||||
std::bind(&title_screen::show_gui_test_dialog, this));
|
||||
|
||||
auto test_dialog = find_widget<button>(this, "test_dialog", false, false);
|
||||
if(test_dialog) {
|
||||
test_dialog->set_visible(show_debug_clock_button ? widget::visibility::visible : widget::visibility::invisible);
|
||||
}
|
||||
|
||||
//
|
||||
// Static labels (version and language)
|
||||
//
|
||||
|
@ -453,6 +465,11 @@ void title_screen::show_debug_clock_window()
|
|||
}
|
||||
}
|
||||
|
||||
void title_screen::show_gui_test_dialog()
|
||||
{
|
||||
gui2::dialogs::gui_test_dialog::execute();
|
||||
}
|
||||
|
||||
void title_screen::hotkey_callback_select_tests()
|
||||
{
|
||||
game_config_manager::get()->load_game_config_for_create(false, true);
|
||||
|
|
|
@ -85,6 +85,9 @@ private:
|
|||
/** Shows the debug clock. */
|
||||
void show_debug_clock_window();
|
||||
|
||||
/** Shows the gui test window. */
|
||||
void show_gui_test_dialog();
|
||||
|
||||
void hotkey_callback_select_tests();
|
||||
|
||||
void show_achievements();
|
||||
|
|
|
@ -55,6 +55,42 @@ color_t decode_color(const std::string& color)
|
|||
return color_t::from_rgba_string(color);
|
||||
}
|
||||
|
||||
PangoWeight decode_text_weight(const std::string& weight)
|
||||
{
|
||||
if(weight == "thin") {
|
||||
return PANGO_WEIGHT_THIN;
|
||||
} else if (weight == "light") {
|
||||
return PANGO_WEIGHT_LIGHT;
|
||||
} else if (weight == "semibold") {
|
||||
return PANGO_WEIGHT_SEMIBOLD;
|
||||
} else if (weight == "bold") {
|
||||
return PANGO_WEIGHT_BOLD;
|
||||
} else if (weight == "heavy") {
|
||||
return PANGO_WEIGHT_HEAVY;
|
||||
}
|
||||
|
||||
if(!weight.empty() && weight != "normal") {
|
||||
ERR_GUI_E << "Invalid text weight '" << weight << "', falling back to 'normal'.";
|
||||
}
|
||||
|
||||
return PANGO_WEIGHT_NORMAL;
|
||||
}
|
||||
|
||||
PangoStyle decode_text_style(const std::string& style)
|
||||
{
|
||||
if(style == "italic") {
|
||||
return PANGO_STYLE_ITALIC;
|
||||
} else if(style == "oblique") {
|
||||
return PANGO_STYLE_OBLIQUE;
|
||||
}
|
||||
|
||||
if(!style.empty() && style != "normal") {
|
||||
ERR_GUI_E << "Invalid text style '" << style << "', falling back to 'normal'.";
|
||||
}
|
||||
|
||||
return PANGO_STYLE_NORMAL;
|
||||
}
|
||||
|
||||
PangoAlignment decode_text_alignment(const std::string& alignment)
|
||||
{
|
||||
if(alignment == "center") {
|
||||
|
|
|
@ -51,6 +51,24 @@ color_t decode_color(const std::string& color);
|
|||
*/
|
||||
PangoAlignment decode_text_alignment(const std::string& alignment);
|
||||
|
||||
/**
|
||||
* Converts a text weight string to a PangoWeight.
|
||||
*
|
||||
* @param weight A weight string, possible values: "thin", "light", "normal", "semibold", "bold", "heavy"
|
||||
*
|
||||
* @returns The corresponding PangoWeight.
|
||||
*/
|
||||
PangoWeight decode_text_weight(const std::string& weight);
|
||||
|
||||
/**
|
||||
* Converts a text style string to a PangoStyle.
|
||||
*
|
||||
* @param style A style string, possible values: "normal", "italic", "oblique
|
||||
*
|
||||
* @returns The corresponding PangoStyle.
|
||||
*/
|
||||
PangoStyle decode_text_style(const std::string& style);
|
||||
|
||||
/**
|
||||
* Converts a text alignment to its string representation.
|
||||
*
|
||||
|
|
839
src/gui/widgets/rich_label.cpp
Normal file
839
src/gui/widgets/rich_label.cpp
Normal file
|
@ -0,0 +1,839 @@
|
|||
/*
|
||||
Copyright (C) 2024
|
||||
by Subhraman Sarkar (babaissarkar) <suvrax@gmail.com>
|
||||
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#define GETTEXT_DOMAIN "wesnoth-lib"
|
||||
|
||||
#include "gui/widgets/rich_label.hpp"
|
||||
|
||||
#include "gui/core/log.hpp"
|
||||
|
||||
#include "gui/core/widget_definition.hpp"
|
||||
#include "gui/core/register_widget.hpp"
|
||||
#include "gui/dialogs/message.hpp"
|
||||
|
||||
#include "cursor.hpp"
|
||||
#include "desktop/clipboard.hpp"
|
||||
#include "desktop/open.hpp"
|
||||
#include "help/help_impl.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "log.hpp"
|
||||
#include "serialization/unicode.hpp"
|
||||
#include "serialization/string_utils.hpp"
|
||||
#include "wml_exception.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
static lg::log_domain log_rich_label("gui/widget/rich_label");
|
||||
#define DBG_GUI_RL LOG_STREAM(debug, log_rich_label)
|
||||
|
||||
namespace gui2
|
||||
{
|
||||
|
||||
// ------------ WIDGET -----------{
|
||||
|
||||
REGISTER_WIDGET(rich_label)
|
||||
|
||||
rich_label::rich_label(const implementation::builder_rich_label& builder)
|
||||
: styled_widget(builder, type())
|
||||
, state_(ENABLED)
|
||||
, can_wrap_(true)
|
||||
, link_aware_(true)
|
||||
, link_color_(font::YELLOW_COLOR)
|
||||
, can_shrink_(true)
|
||||
, text_alpha_(ALPHA_OPAQUE)
|
||||
, unparsed_text_()
|
||||
, w_(0)
|
||||
, h_(0)
|
||||
, x_(0)
|
||||
, padding_(5)
|
||||
, txt_height_(0)
|
||||
, prev_blk_height_(0)
|
||||
{
|
||||
connect_signal<event::LEFT_BUTTON_CLICK>(
|
||||
std::bind(&rich_label::signal_handler_left_button_click, this, std::placeholders::_3));
|
||||
connect_signal<event::MOUSE_MOTION>(
|
||||
std::bind(&rich_label::signal_handler_mouse_motion, this, std::placeholders::_3, std::placeholders::_5));
|
||||
connect_signal<event::MOUSE_LEAVE>(
|
||||
std::bind(&rich_label::signal_handler_mouse_leave, this, std::placeholders::_3));
|
||||
}
|
||||
|
||||
wfl::map_formula_callable rich_label::setup_text_renderer(config text_cfg, unsigned width) {
|
||||
// Set up fake render to calculate text position
|
||||
wfl::action_function_symbol_table functions;
|
||||
wfl::map_formula_callable variables;
|
||||
variables.add("text", wfl::variant(text_cfg["text"].str()));
|
||||
variables.add("width", wfl::variant(width > 0 ? width : w_));
|
||||
variables.add("text_wrap_mode", wfl::variant(PANGO_ELLIPSIZE_NONE));
|
||||
variables.add("fake_draw", wfl::variant(true));
|
||||
tshape_ = std::make_unique<gui2::text_shape>(text_cfg, functions);
|
||||
tshape_->draw(variables);
|
||||
return variables;
|
||||
}
|
||||
|
||||
point rich_label::get_text_size(config text_cfg, unsigned width) {
|
||||
wfl::map_formula_callable variables = setup_text_renderer(text_cfg, width);
|
||||
return point(variables.query_value("text_width").as_int(), variables.query_value("text_height").as_int());
|
||||
}
|
||||
|
||||
point rich_label::get_image_size(config img_cfg) {
|
||||
wfl::action_function_symbol_table functions;
|
||||
wfl::map_formula_callable variables;
|
||||
variables.add("fake_draw", wfl::variant(true));
|
||||
ishape_ = std::make_unique<gui2::image_shape>(img_cfg, functions);
|
||||
ishape_->draw(variables);
|
||||
return point(variables.query_value("image_width").as_int(), variables.query_value("image_height").as_int());
|
||||
}
|
||||
|
||||
void rich_label::add_text_with_attribute(config& curr_item, std::string text, std::string attr_name, std::string extra_data) {
|
||||
size_t start = curr_item["text"].str().size();
|
||||
|
||||
curr_item["text"] = curr_item["text"].str() + text;
|
||||
|
||||
if (!attr_name.empty()) {
|
||||
append_if_not_empty(&curr_item["attr_name"], ",");
|
||||
curr_item["attr_name"] = curr_item["attr_name"].str() + attr_name;
|
||||
|
||||
append_if_not_empty(&curr_item["attr_start"], ",");
|
||||
curr_item["attr_start"] = curr_item["attr_start"].str() + std::to_string(start);
|
||||
|
||||
append_if_not_empty(&curr_item["attr_end"], ",");
|
||||
curr_item["attr_end"] = curr_item["attr_end"].str() + std::to_string(curr_item["text"].str().size());
|
||||
|
||||
if (!extra_data.empty()) {
|
||||
append_if_not_empty(&curr_item["attr_data"], ",");
|
||||
curr_item["attr_data"] = curr_item["attr_data"].str() + extra_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rich_label::add_text_with_attributes(config& curr_item, std::string text, std::vector<std::string> attr_names, std::vector<std::string> extra_data) {
|
||||
|
||||
size_t start = curr_item["text"].str().size();
|
||||
curr_item["text"] = curr_item["text"].str() + text;
|
||||
|
||||
if (!attr_names.empty()) {
|
||||
append_if_not_empty(&curr_item["attr_name"], ",");
|
||||
curr_item["attr_name"] = curr_item["attr_name"].str() + utils::join(attr_names);
|
||||
|
||||
for (size_t i = 0; i < attr_names.size(); i++) {
|
||||
append_if_not_empty(&curr_item["attr_start"], ",");
|
||||
curr_item["attr_start"] = curr_item["attr_start"].str() + std::to_string(start);
|
||||
append_if_not_empty(&curr_item["attr_end"], ",");
|
||||
curr_item["attr_end"] = curr_item["attr_end"].str() + std::to_string(curr_item["text"].str().size());
|
||||
}
|
||||
|
||||
if (!extra_data.empty()) {
|
||||
append_if_not_empty(&curr_item["attr_data"], ",");
|
||||
curr_item["attr_data"] = curr_item["attr_data"].str() + utils::join(extra_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rich_label::add_image(config& curr_item, std::string name, std::string align, bool floating, point& img_size) {
|
||||
curr_item["name"] = name;
|
||||
|
||||
if (align.empty()) {
|
||||
align = "left";
|
||||
}
|
||||
|
||||
if (align == "right") {
|
||||
curr_item["x"] = floating ? "(width - image_width - img_x)" : "(width - image_width - pos_x)";
|
||||
} else if (align == "middle" || align == "center") {
|
||||
// works for single image only
|
||||
curr_item["x"] = floating ? "(img_x + (width - image_width)/2.0)" : "(pos_x + (width - image_width)/2.0)";
|
||||
} else {
|
||||
// left aligned images are default for now
|
||||
curr_item["x"] = floating ? "(img_x)" : "(pos_x)";
|
||||
}
|
||||
curr_item["y"] = floating ? "(img_y + pos_y)" : "(pos_y)";
|
||||
curr_item["h"] = "(image_height)";
|
||||
curr_item["w"] = "(image_width)";
|
||||
|
||||
// Sizing
|
||||
if (floating) {
|
||||
img_size.x = get_image_size(curr_item).x;
|
||||
img_size.y += get_image_size(curr_item).y;
|
||||
} else {
|
||||
img_size.x += get_image_size(curr_item).x + padding_;
|
||||
img_size.y = get_image_size(curr_item).y;
|
||||
}
|
||||
|
||||
std::stringstream actions;
|
||||
actions << "([";
|
||||
if (floating) {
|
||||
|
||||
if (align == "left") {
|
||||
x_ = img_size.x + padding_;
|
||||
actions << "set_var('pos_x', image_width + padding)";
|
||||
} else if (align == "right") {
|
||||
x_ = 0;
|
||||
actions << "set_var('pos_x', 0)";
|
||||
actions << ",";
|
||||
actions << "set_var('ww', image_width)";
|
||||
}
|
||||
|
||||
img_size.y += padding_;
|
||||
actions << "," << "set_var('img_y', img_y + image_height + padding)";
|
||||
|
||||
} else {
|
||||
x_ = img_size.x;
|
||||
actions << "set_var('pos_x', pos_x + image_width + padding)";
|
||||
}
|
||||
actions << "])";
|
||||
|
||||
curr_item["actions"] = actions.str();
|
||||
actions.str("");
|
||||
}
|
||||
|
||||
void rich_label::add_link(config& curr_item, std::string name, std::string dest, int img_width) {
|
||||
// TODO algorithm needs to be text_alignment independent
|
||||
|
||||
DBG_GUI_RL << "add_link, x=" << x_ << " width=" << img_width;
|
||||
|
||||
setup_text_renderer(curr_item, w_ - x_ - img_width);
|
||||
point t_start = get_xy_from_offset(utf8::size(curr_item["text"].str()));
|
||||
|
||||
DBG_GUI_RL << "link text start:" << t_start;
|
||||
|
||||
std::string link_text = name.empty() ? dest : name;
|
||||
add_text_with_attribute(curr_item, link_text, "color", link_color_.to_hex_string().substr(1));
|
||||
|
||||
setup_text_renderer(curr_item, w_ - x_ - img_width);
|
||||
point t_end = get_xy_from_offset(utf8::size(curr_item["text"].str()));
|
||||
DBG_GUI_RL << "link text end:" << t_end;
|
||||
|
||||
point link_start(x_ + t_start.x, prev_blk_height_ + t_start.y);
|
||||
t_end.y += font::get_max_height(font::SIZE_NORMAL);
|
||||
|
||||
// TODO link after right aligned images
|
||||
|
||||
// Add link
|
||||
if (t_end.x > t_start.x) {
|
||||
point link_size = t_end - t_start;
|
||||
rect link_rect = {
|
||||
link_start.x,
|
||||
link_start.y,
|
||||
link_size.x,
|
||||
link_size.y,
|
||||
};
|
||||
links_.push_back(std::pair(link_rect, dest));
|
||||
|
||||
DBG_GUI_RL << "added link at rect: " << link_rect;
|
||||
|
||||
} else {
|
||||
//link straddles two lines, break into two rects
|
||||
point t_size(w_ - link_start.x - (x_ == 0 ? img_width : 0), t_end.y - t_start.y);
|
||||
point link_start2(x_, link_start.y + font::get_max_height(font::SIZE_NORMAL));
|
||||
point t_size2(t_end.x, t_end.y - t_start.y);
|
||||
|
||||
rect link_rect = {
|
||||
link_start.x,
|
||||
link_start.y,
|
||||
t_size.x,
|
||||
t_size.y,
|
||||
};
|
||||
|
||||
rect link_rect2 = {
|
||||
link_start2.x,
|
||||
link_start2.y,
|
||||
t_size2.x,
|
||||
t_size2.y,
|
||||
};
|
||||
|
||||
links_.push_back(std::pair(link_rect, dest));
|
||||
links_.push_back(std::pair(link_rect2, dest));
|
||||
|
||||
DBG_GUI_RL << "added link at rect 1: " << link_rect;
|
||||
DBG_GUI_RL << "added link at rect 2: " << link_rect2;
|
||||
}
|
||||
}
|
||||
|
||||
size_t rich_label::get_split_location(std::string text, int img_height) {
|
||||
point wrap_position = get_column_line(point(w_, img_height));
|
||||
|
||||
size_t len = 0;
|
||||
for (int i = 0; i < wrap_position.y; i++) {
|
||||
len += utf8::size(font::get_text_renderer().get_lines()[i]);
|
||||
}
|
||||
len += wrap_position.x;
|
||||
|
||||
// break only at word boundary
|
||||
char c;
|
||||
while((c = text.at(len)) != ' ') {
|
||||
len--;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void rich_label::set_label(const t_string& text)
|
||||
{
|
||||
// Initialization
|
||||
w_ = (w_ == 0) ? styled_widget::calculate_best_size().x : w_;
|
||||
DBG_GUI_RL << "Width: " << w_;
|
||||
h_ = 0;
|
||||
unparsed_text_ = text;
|
||||
text_dom_.clear();
|
||||
links_.clear();
|
||||
help::topic_text marked_up_text(text);
|
||||
std::vector<std::string> parsed_text = marked_up_text.parsed_text();
|
||||
|
||||
config* curr_item = nullptr;
|
||||
optional_config_impl<config> child;
|
||||
|
||||
bool is_image = false;
|
||||
bool floating = false;
|
||||
bool new_text_block = false;
|
||||
bool needs_size_update = true;
|
||||
bool in_table = false;
|
||||
point img_size;
|
||||
unsigned col_width = 0;
|
||||
unsigned max_col_height = 0;
|
||||
prev_blk_height_ = 0;
|
||||
txt_height_ = 0;
|
||||
|
||||
for (size_t i = 0; i < parsed_text.size(); i++) {
|
||||
bool last_entry = (i == parsed_text.size() - 1);
|
||||
std::string line = parsed_text.at(i);
|
||||
|
||||
if (!line.empty() && line.at(0) == '[') {
|
||||
config cfg;
|
||||
::read(cfg, line);
|
||||
|
||||
if ((child = cfg.optional_child("img"))) {
|
||||
|
||||
std::string name = child["src"];
|
||||
floating = child["float"].to_bool();
|
||||
std::string align = child["align"];
|
||||
|
||||
curr_item = &(text_dom_.add_child("image"));
|
||||
add_image(*curr_item, name, align, floating, img_size);
|
||||
|
||||
is_image = true;
|
||||
new_text_block = true;
|
||||
|
||||
DBG_GUI_RL << "image: src=" << name << ", size=" << get_image_size(*curr_item);
|
||||
|
||||
} else {
|
||||
|
||||
if (is_image && (!floating)) {
|
||||
x_ = 0;
|
||||
(*curr_item)["actions"] = "([set_var('pos_x', 0), set_var('pos_y', pos_y + image_height + padding)])";
|
||||
}
|
||||
|
||||
if (curr_item == nullptr || new_text_block) {
|
||||
prev_blk_height_ += txt_height_ + padding_;
|
||||
txt_height_ = 0;
|
||||
|
||||
curr_item = &(text_dom_.add_child("text"));
|
||||
default_text_config(curr_item);
|
||||
new_text_block = false;
|
||||
}
|
||||
|
||||
// }---------- TEXT TAGS -----------{
|
||||
int tmp_h = get_text_size(*curr_item, w_ - x_).y;
|
||||
|
||||
if ((child = cfg.optional_child("ref"))) {
|
||||
|
||||
add_link(*curr_item, child["text"], child["dst"], img_size.x);
|
||||
is_image = false;
|
||||
|
||||
DBG_GUI_RL << "ref: dst=" << child["dst"];
|
||||
|
||||
} else if ((child = cfg.optional_child("bold")) || (child = cfg.optional_child("b"))) {
|
||||
|
||||
add_text_with_attribute(*curr_item, child["text"], "bold");
|
||||
is_image = false;
|
||||
|
||||
DBG_GUI_RL << "bold: text=" << child["text"];
|
||||
|
||||
} else if ((child = cfg.optional_child("italic")) || (child = cfg.optional_child("i"))) {
|
||||
|
||||
add_text_with_attribute(*curr_item, child["text"], "italic");
|
||||
is_image = false;
|
||||
|
||||
DBG_GUI_RL << "italic: text=" << child["text"];
|
||||
|
||||
} else if ((child = cfg.optional_child("underline")) || (child = cfg.optional_child("u"))) {
|
||||
|
||||
add_text_with_attribute(*curr_item, child["text"], "underline");
|
||||
is_image = false;
|
||||
|
||||
DBG_GUI_RL << "u: text=" << child["text"];
|
||||
|
||||
} else if ((child = cfg.optional_child("header")) || (child = cfg.optional_child("h"))) {
|
||||
|
||||
// Header starts in a new line
|
||||
|
||||
append_if_not_empty(&((*curr_item)["text"]), "\n");
|
||||
append_if_not_empty(&((*curr_item)["attr_name"]), ",");
|
||||
append_if_not_empty(&((*curr_item)["attr_start"]), ",");
|
||||
append_if_not_empty(&((*curr_item)["attr_end"]), ",");
|
||||
append_if_not_empty(&((*curr_item)["attr_data"]), ",");
|
||||
|
||||
std::stringstream header_text;
|
||||
header_text << child["text"].str() + "\n";
|
||||
std::vector<std::string> attrs = {"color", "size"};
|
||||
std::vector<std::string> attr_data;
|
||||
attr_data.push_back(font::TITLE_COLOR.to_hex_string().substr(1));
|
||||
attr_data.push_back(std::to_string(font::SIZE_TITLE));
|
||||
|
||||
add_text_with_attributes((*curr_item), header_text.str(), attrs, attr_data);
|
||||
|
||||
is_image = false;
|
||||
|
||||
DBG_GUI_RL << "h: text=" << child["text"];
|
||||
|
||||
} else if ((child = cfg.optional_child("span")) || (child = cfg.optional_child("format"))) {
|
||||
|
||||
std::vector<std::string> attrs;
|
||||
std::vector<std::string> attr_data;
|
||||
|
||||
DBG_GUI_RL << "span/format: text=" << child["text"];
|
||||
DBG_GUI_RL << "attributes:";
|
||||
|
||||
for (const auto& attr : child.value().attribute_range()) {
|
||||
if (attr.first != "text") {
|
||||
attrs.push_back(attr.first);
|
||||
attr_data.push_back(attr.second);
|
||||
DBG_GUI_RL << attr.first << "=" << attr.second;
|
||||
}
|
||||
}
|
||||
|
||||
add_text_with_attributes((*curr_item), child["text"], attrs, attr_data);
|
||||
is_image = false;
|
||||
|
||||
// }---------- TABLE TAGS -----------{
|
||||
} else if ((child = cfg.optional_child("table"))) {
|
||||
|
||||
in_table = true;
|
||||
|
||||
// setup column width
|
||||
unsigned columns = child["col"].to_int();
|
||||
unsigned width = child["width"].to_int();
|
||||
width = width > 0 ? width : w_;
|
||||
col_width = width/columns;
|
||||
|
||||
// start on a new line
|
||||
(*curr_item)["actions"] = boost::str(boost::format("([set_var('pos_x', 0), set_var('pos_y', pos_y + if(ih > text_height, ih, text_height)), set_var('tw', width - pos_x - %d), set_var('ih', 0)])") % col_width);
|
||||
x_ = 0;
|
||||
prev_blk_height_ += std::max(img_size.y, get_text_size(*curr_item, w_ - img_size.x).y);
|
||||
txt_height_ = 0;
|
||||
|
||||
new_text_block = true;
|
||||
|
||||
DBG_GUI_RL << "start table : " << "col=" << columns;
|
||||
DBG_GUI_RL << "col_width : " << col_width;
|
||||
|
||||
} else if (cfg.optional_child("jump")) {
|
||||
|
||||
if (col_width > 0) {
|
||||
|
||||
max_col_height = std::max(max_col_height, txt_height_);
|
||||
max_col_height = std::max(max_col_height, static_cast<unsigned>(img_size.y));
|
||||
txt_height_ = 0;
|
||||
x_ += col_width;
|
||||
|
||||
DBG_GUI_RL << "jump to next column";
|
||||
|
||||
(*curr_item)["actions"] = boost::str(boost::format("([set_var('pos_x', pos_x + %d), set_var('tw', width - pos_x - %d)])") % col_width % col_width);
|
||||
|
||||
if (!is_image) {
|
||||
new_text_block = true;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (cfg.optional_child("break") || cfg.optional_child("br")) {
|
||||
|
||||
if (in_table) {
|
||||
|
||||
max_col_height = std::max(max_col_height, txt_height_);
|
||||
max_col_height = std::max(max_col_height, static_cast<unsigned>(img_size.y));
|
||||
|
||||
//linebreak
|
||||
x_ = 0;
|
||||
prev_blk_height_ += max_col_height;
|
||||
max_col_height = 0;
|
||||
txt_height_ = 0;
|
||||
|
||||
(*curr_item)["actions"] = boost::str(boost::format("([set_var('pos_x', 0), set_var('pos_y', pos_y + %d + %d), set_var('tw', width - pos_x - %d)])") % max_col_height % padding_ % col_width);
|
||||
|
||||
}
|
||||
|
||||
DBG_GUI_RL << "linebreak: " << (in_table ? max_col_height : w_);
|
||||
|
||||
if (!is_image) {
|
||||
new_text_block = true;
|
||||
}
|
||||
|
||||
} else if (cfg.optional_child("endtable")) {
|
||||
|
||||
DBG_GUI_RL << "end table: " << max_col_height;
|
||||
max_col_height = std::max(max_col_height, txt_height_);
|
||||
max_col_height = std::max(max_col_height, static_cast<unsigned>(img_size.y));
|
||||
(*curr_item)["actions"] = boost::str(boost::format("([set_var('pos_x', 0), set_var('pos_y', pos_y + %d), set_var('tw', 0)])") % max_col_height);
|
||||
|
||||
//linebreak and reset col_width
|
||||
col_width = 0;
|
||||
x_ = 0;
|
||||
prev_blk_height_ += max_col_height;
|
||||
max_col_height = 0;
|
||||
txt_height_ = 0;
|
||||
|
||||
if (!last_entry) {
|
||||
new_text_block = true;
|
||||
}
|
||||
|
||||
in_table = false;
|
||||
}
|
||||
|
||||
if (needs_size_update) {
|
||||
int ah = get_text_size(*curr_item, w_ - x_).y;
|
||||
// update text size and widget height
|
||||
if (tmp_h > ah) {
|
||||
tmp_h = 0;
|
||||
}
|
||||
|
||||
txt_height_ += ah - tmp_h;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!line.empty()) {
|
||||
DBG_GUI_RL << "text: text=" << line.substr(1, 20) << "...";
|
||||
|
||||
// Start the text in a new paragraph if a newline follows after an image
|
||||
if (is_image && (!floating)) {
|
||||
if ((line.at(0) == '\n')) {
|
||||
x_ = 0;
|
||||
(*curr_item)["actions"] = "([set_var('pos_x', 0), set_var('pos_y', pos_y + image_height + padding)])";
|
||||
line = line.substr(1, line.size());
|
||||
needs_size_update = true;
|
||||
} else {
|
||||
needs_size_update = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (curr_item == nullptr || new_text_block) {
|
||||
prev_blk_height_ += txt_height_ + padding_;
|
||||
txt_height_ = 0;
|
||||
|
||||
curr_item = &(text_dom_.add_child("text"));
|
||||
default_text_config(curr_item);
|
||||
new_text_block = false;
|
||||
}
|
||||
|
||||
(*curr_item)["font_size"] = font::SIZE_NORMAL;
|
||||
|
||||
int tmp_h = get_text_size(*curr_item, w_ - x_).y;
|
||||
|
||||
(*curr_item)["text"] = (*curr_item)["text"].str() + line;
|
||||
|
||||
point text_size;
|
||||
text_size.x = get_text_size(*curr_item, w_ - (x_ == 0 ? img_size.x : x_)).x - x_;
|
||||
text_size.y = get_text_size(*curr_item, w_ - (x_ == 0 ? img_size.x : x_)).y;
|
||||
|
||||
if ( floating && (img_size.y > 0) && (text_size.y > img_size.y) ) {
|
||||
DBG_GUI_RL << "wrap start";
|
||||
|
||||
size_t len = get_split_location((*curr_item)["text"].str(), img_size.y);
|
||||
t_string* removed_part = new t_string((*curr_item)["text"].str().substr(len+1));
|
||||
(*curr_item)["text"] = (*curr_item)["text"].str().substr(0, len);
|
||||
(*curr_item)["actions"] = "([set_var('pos_x', 0), set_var('ww', 0), set_var('pos_y', pos_y + text_height)])";
|
||||
|
||||
// New text block
|
||||
x_ = 0;
|
||||
prev_blk_height_ += img_size.y + padding_;
|
||||
// TODO excess line gets added, so that needs to be compensated
|
||||
txt_height_ = 0;
|
||||
img_size = point(0,0);
|
||||
floating = false;
|
||||
|
||||
curr_item = &(text_dom_.add_child("text"));
|
||||
default_text_config(curr_item);
|
||||
|
||||
add_text_with_attribute(*curr_item, *removed_part);
|
||||
|
||||
} else if ((img_size.y > 0) && (text_size.y < img_size.y)) {
|
||||
DBG_GUI_RL << "no wrap";
|
||||
if (is_image) {
|
||||
(*curr_item)["actions"] = "([set_var('pos_y', pos_y + image_height)])";
|
||||
} else {
|
||||
(*curr_item)["actions"] = "([set_var('pos_y', pos_y + text_height)])";
|
||||
}
|
||||
}
|
||||
|
||||
int ah = get_text_size(*curr_item, w_ - x_).y;
|
||||
// update text size and widget height
|
||||
if (tmp_h > ah) {
|
||||
tmp_h = 0;
|
||||
}
|
||||
|
||||
txt_height_ += ah - tmp_h;
|
||||
|
||||
is_image = false;
|
||||
}
|
||||
|
||||
// Height Update
|
||||
if (!is_image && !floating && img_size.y > 0) {
|
||||
if (needs_size_update) {
|
||||
prev_blk_height_ += img_size.y;
|
||||
}
|
||||
img_size = point(0,0);
|
||||
}
|
||||
|
||||
|
||||
DBG_GUI_RL << "Item :" << curr_item->debug();
|
||||
DBG_GUI_RL << "X: " << x_;
|
||||
DBG_GUI_RL << "Prev block height: " << prev_blk_height_ << " Current text block height: " << txt_height_;
|
||||
DBG_GUI_RL << "Height: " << h_;
|
||||
h_ = txt_height_ + prev_blk_height_;
|
||||
|
||||
// reset all variables to zero, otherwise they grow infinitely
|
||||
if (last_entry) {
|
||||
if (static_cast<unsigned>(img_size.y) > h_) {
|
||||
h_ = img_size.y;
|
||||
}
|
||||
h_ += font::get_line_spacing_factor() * font::get_max_height(font::SIZE_NORMAL);
|
||||
|
||||
config& break_cfg = text_dom_.add_child("text");
|
||||
default_text_config(&break_cfg);
|
||||
break_cfg["text"] = " ";
|
||||
break_cfg["actions"] = "([set_var('pos_x', 0), set_var('pos_y', 0), set_var('img_x', 0), set_var('img_y', 0)])";
|
||||
}
|
||||
|
||||
DBG_GUI_RL << "-----------";
|
||||
|
||||
} // for loop ends
|
||||
|
||||
} // function ends
|
||||
|
||||
void rich_label::default_text_config(config* txt_ptr, t_string text) {
|
||||
if (txt_ptr != nullptr) {
|
||||
(*txt_ptr)["text"] = text;
|
||||
(*txt_ptr)["font_size"] = font::SIZE_NORMAL;
|
||||
(*txt_ptr)["text_alignment"] = encode_text_alignment(get_text_alignment());
|
||||
(*txt_ptr)["x"] = "(pos_x)";
|
||||
(*txt_ptr)["y"] = "(pos_y)";
|
||||
(*txt_ptr)["w"] = "(text_width)";
|
||||
(*txt_ptr)["h"] = "(text_height)";
|
||||
// tw -> table width, used for wrapping text inside table cols
|
||||
// ww -> wrap width, used for wrapping around floating image
|
||||
(*txt_ptr)["maximum_width"] = "(width - pos_x - ww - tw)";
|
||||
(*txt_ptr)["actions"] = "([set_var('pos_y', pos_y+text_height)])";
|
||||
}
|
||||
}
|
||||
|
||||
void rich_label::update_canvas()
|
||||
{
|
||||
for(canvas& tmp : get_canvases()) {
|
||||
tmp.set_variable("pos_x", wfl::variant(0));
|
||||
tmp.set_variable("pos_y", wfl::variant(0));
|
||||
tmp.set_variable("img_x", wfl::variant(0));
|
||||
tmp.set_variable("img_y", wfl::variant(0));
|
||||
tmp.set_variable("tw", wfl::variant(0));
|
||||
tmp.set_variable("ww", wfl::variant(0));
|
||||
tmp.set_variable("padding", wfl::variant(padding_));
|
||||
// Disable ellipsization so that text wrapping can work
|
||||
tmp.set_variable("text_wrap_mode", wfl::variant(PANGO_ELLIPSIZE_NONE));
|
||||
tmp.set_cfg(text_dom_, true);
|
||||
tmp.set_variable("text_alpha", wfl::variant(text_alpha_));
|
||||
}
|
||||
}
|
||||
|
||||
void rich_label::set_text_alpha(unsigned short alpha)
|
||||
{
|
||||
if(alpha != text_alpha_) {
|
||||
text_alpha_ = alpha;
|
||||
update_canvas();
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void rich_label::set_active(const bool active)
|
||||
{
|
||||
if(get_active() != active) {
|
||||
set_state(active ? ENABLED : DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
void rich_label::set_link_aware(bool link_aware)
|
||||
{
|
||||
if(link_aware != link_aware_) {
|
||||
link_aware_ = link_aware;
|
||||
update_canvas();
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void rich_label::set_link_color(const color_t& color)
|
||||
{
|
||||
if(color != link_color_) {
|
||||
link_color_ = color;
|
||||
update_canvas();
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void rich_label::set_state(const state_t state)
|
||||
{
|
||||
if(state != state_) {
|
||||
state_ = state;
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void rich_label::signal_handler_left_button_click(bool& handled)
|
||||
{
|
||||
DBG_GUI_E << "rich_label click";
|
||||
|
||||
if(!get_link_aware()) {
|
||||
return; // without marking event as "handled"
|
||||
}
|
||||
|
||||
point mouse = get_mouse_position();
|
||||
|
||||
mouse.x -= get_x();
|
||||
mouse.y -= get_y();
|
||||
|
||||
DBG_GUI_RL << "(mouse)" << mouse.x << "," << mouse.y;
|
||||
DBG_GUI_RL << "link count :" << links_.size();
|
||||
|
||||
for (const auto& entry : links_) {
|
||||
DBG_GUI_RL << "link [" << entry.first.x << "," << entry.first.y << ","
|
||||
<< entry.first.x + entry.first.w << "," << entry.first.y + entry.first.h << "]";
|
||||
|
||||
if (entry.first.contains(mouse)) {
|
||||
DBG_GUI_RL << "Clicked link! dst = " << entry.second;
|
||||
if (link_handler_) {
|
||||
link_handler_(entry.second);
|
||||
} else {
|
||||
DBG_GUI_RL << "No registered link handler found";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
handled = true;
|
||||
}
|
||||
|
||||
void rich_label::signal_handler_mouse_motion(bool& handled, const point& coordinate)
|
||||
{
|
||||
DBG_GUI_E << "rich_label mouse motion";
|
||||
|
||||
if(!get_link_aware()) {
|
||||
return; // without marking event as "handled"
|
||||
}
|
||||
|
||||
point mouse = coordinate;
|
||||
|
||||
mouse.x -= get_x();
|
||||
mouse.y -= get_y();
|
||||
|
||||
for (const auto& entry : links_) {
|
||||
if (entry.first.contains(mouse)) {
|
||||
update_mouse_cursor(true);
|
||||
handled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
update_mouse_cursor(false);
|
||||
}
|
||||
|
||||
void rich_label::signal_handler_mouse_leave(bool& handled)
|
||||
{
|
||||
DBG_GUI_E << "rich_label mouse leave";
|
||||
|
||||
if(!get_link_aware()) {
|
||||
return; // without marking event as "handled"
|
||||
}
|
||||
|
||||
// We left the widget, so just unconditionally reset the cursor
|
||||
update_mouse_cursor(false);
|
||||
|
||||
handled = true;
|
||||
}
|
||||
|
||||
void rich_label::update_mouse_cursor(bool enable)
|
||||
{
|
||||
// Someone else may set the mouse cursor for us to something unusual (e.g.
|
||||
// the WAIT cursor) so we ought to mess with that only if it's set to
|
||||
// NORMAL or HYPERLINK.
|
||||
|
||||
if(enable && cursor::get() == cursor::NORMAL) {
|
||||
cursor::set(cursor::HYPERLINK);
|
||||
} else if(!enable && cursor::get() == cursor::HYPERLINK) {
|
||||
cursor::set(cursor::NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
// }---------- DEFINITION ---------{
|
||||
|
||||
rich_label_definition::rich_label_definition(const config& cfg)
|
||||
: styled_widget_definition(cfg)
|
||||
{
|
||||
DBG_GUI_P << "Parsing rich_label " << id;
|
||||
|
||||
load_resolutions<resolution>(cfg);
|
||||
}
|
||||
|
||||
rich_label_definition::resolution::resolution(const config& cfg)
|
||||
: resolution_definition(cfg)
|
||||
, link_color(cfg["link_color"].empty() ? font::YELLOW_COLOR : color_t::from_rgba_string(cfg["link_color"].str()))
|
||||
{
|
||||
// Note the order should be the same as the enum state_t is rich_label.hpp.
|
||||
state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_enabled", missing_mandatory_wml_tag("rich_label_definition][resolution", "state_enabled")));
|
||||
state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_disabled", missing_mandatory_wml_tag("rich_label_definition][resolution", "state_disabled")));
|
||||
}
|
||||
|
||||
// }---------- BUILDER -----------{
|
||||
|
||||
namespace implementation
|
||||
{
|
||||
|
||||
builder_rich_label::builder_rich_label(const config& cfg)
|
||||
: builder_styled_widget(cfg)
|
||||
, text_alignment(decode_text_alignment(cfg["text_alignment"]))
|
||||
, link_aware(cfg["link_aware"].to_bool(true))
|
||||
, width(cfg["width"].to_int(500))
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<widget> builder_rich_label::build() const
|
||||
{
|
||||
auto lbl = std::make_unique<rich_label>(*this);
|
||||
|
||||
const auto conf = lbl->cast_config_to<rich_label_definition>();
|
||||
assert(conf);
|
||||
|
||||
lbl->set_text_alignment(text_alignment);
|
||||
lbl->set_link_aware(link_aware);
|
||||
lbl->set_link_color(conf->link_color);
|
||||
lbl->set_width(width);
|
||||
lbl->set_label(lbl->get_label());
|
||||
|
||||
DBG_GUI_G << "Window builder: placed rich_label '" << id << "' with definition '"
|
||||
<< definition << "'.";
|
||||
|
||||
return lbl;
|
||||
}
|
||||
|
||||
} // namespace implementation
|
||||
|
||||
// }------------ END --------------
|
||||
|
||||
} // namespace gui2
|
314
src/gui/widgets/rich_label.hpp
Normal file
314
src/gui/widgets/rich_label.hpp
Normal file
|
@ -0,0 +1,314 @@
|
|||
/*
|
||||
Copyright (C) 2024
|
||||
by Subhraman Sarkar (babaissarkar) <suvrax@gmail.com>
|
||||
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gui/widgets/styled_widget.hpp"
|
||||
|
||||
#include "font/standard_colors.hpp"
|
||||
#include "gui/core/canvas_private.hpp"
|
||||
#include "gui/core/widget_definition.hpp"
|
||||
#include "help/help_impl.hpp"
|
||||
#include "serialization/parser.hpp"
|
||||
|
||||
namespace gui2
|
||||
{
|
||||
namespace implementation
|
||||
{
|
||||
struct builder_rich_label;
|
||||
}
|
||||
|
||||
// ------------ WIDGET -----------{
|
||||
|
||||
/**
|
||||
*
|
||||
* A rich_label takes marked up text and shows it correctly formatted and wrapped but no scrollbars are provided.
|
||||
*/
|
||||
class rich_label : public styled_widget
|
||||
{
|
||||
friend struct implementation::builder_rich_label;
|
||||
|
||||
public:
|
||||
explicit rich_label(const implementation::builder_rich_label& builder);
|
||||
|
||||
virtual bool can_wrap() const override
|
||||
{
|
||||
return can_wrap_ || characters_per_line_ != 0;
|
||||
}
|
||||
|
||||
virtual unsigned get_characters_per_line() const override
|
||||
{
|
||||
return characters_per_line_;
|
||||
}
|
||||
|
||||
virtual bool get_link_aware() const override
|
||||
{
|
||||
return link_aware_;
|
||||
}
|
||||
|
||||
virtual color_t get_link_color() const override
|
||||
{
|
||||
return link_color_;
|
||||
}
|
||||
|
||||
virtual void set_active(const bool active) override;
|
||||
|
||||
virtual bool get_active() const override
|
||||
{
|
||||
return state_ != DISABLED;
|
||||
}
|
||||
|
||||
virtual unsigned get_state() const override
|
||||
{
|
||||
return state_;
|
||||
}
|
||||
|
||||
bool disable_click_dismiss() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool can_mouse_focus() const override
|
||||
{
|
||||
return !tooltip().empty() || get_link_aware();
|
||||
}
|
||||
|
||||
virtual void update_canvas() override;
|
||||
|
||||
/* **** ***** ***** setters / getters for members ***** ****** **** */
|
||||
|
||||
void set_can_wrap(const bool wrap)
|
||||
{
|
||||
can_wrap_ = wrap;
|
||||
}
|
||||
|
||||
void set_characters_per_line(const unsigned characters_per_line)
|
||||
{
|
||||
characters_per_line_ = characters_per_line;
|
||||
}
|
||||
|
||||
void set_link_aware(bool l);
|
||||
|
||||
void set_link_color(const color_t& color);
|
||||
|
||||
void set_can_shrink(bool can_shrink)
|
||||
{
|
||||
can_shrink_ = can_shrink;
|
||||
}
|
||||
|
||||
void set_width(unsigned width)
|
||||
{
|
||||
w_ = width;
|
||||
}
|
||||
|
||||
void set_text_alpha(unsigned short alpha);
|
||||
|
||||
const t_string& get_label() const
|
||||
{
|
||||
return unparsed_text_.empty() ? styled_widget::get_label() : unparsed_text_;
|
||||
}
|
||||
|
||||
void set_label(const t_string& text) override;
|
||||
|
||||
void register_link_callback(std::function<void(std::string)> link_handler)
|
||||
{
|
||||
link_handler_ = link_handler;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Possible states of the widget.
|
||||
*
|
||||
* Note the order of the states must be the same as defined in settings.hpp.
|
||||
*/
|
||||
enum state_t {
|
||||
ENABLED,
|
||||
DISABLED,
|
||||
};
|
||||
|
||||
void set_state(const state_t state);
|
||||
|
||||
/**
|
||||
* Current state of the widget.
|
||||
*
|
||||
* The state of the widget determines what to render and how the widget
|
||||
* reacts to certain 'events'.
|
||||
*/
|
||||
state_t state_;
|
||||
|
||||
/** Holds the rich_label can wrap or not. */
|
||||
bool can_wrap_;
|
||||
|
||||
/**
|
||||
* The maximum number of characters per line.
|
||||
*
|
||||
* The maximum is not an exact maximum, it uses the average character width.
|
||||
*/
|
||||
unsigned characters_per_line_;
|
||||
|
||||
/**
|
||||
* Whether the rich_label is link aware, rendering links with special formatting
|
||||
* and handling click events.
|
||||
*/
|
||||
bool link_aware_;
|
||||
|
||||
/**
|
||||
* What color links will be rendered in.
|
||||
*/
|
||||
color_t link_color_;
|
||||
|
||||
bool can_shrink_;
|
||||
|
||||
unsigned short text_alpha_;
|
||||
|
||||
/** Inherited from styled_widget. */
|
||||
virtual bool text_can_shrink() override
|
||||
{
|
||||
return can_shrink_;
|
||||
}
|
||||
|
||||
/** structure tree of the marked up text after parsing */
|
||||
config text_dom_;
|
||||
|
||||
/** The unparsed/raw text */
|
||||
t_string unparsed_text_;
|
||||
|
||||
/** shapes used for size calculation */
|
||||
std::unique_ptr<text_shape> tshape_;
|
||||
std::unique_ptr<image_shape> ishape_;
|
||||
|
||||
/** Width and height of the canvas */
|
||||
unsigned w_, h_, x_;
|
||||
|
||||
/** Padding */
|
||||
unsigned padding_;
|
||||
|
||||
/** Height of current text block */
|
||||
unsigned txt_height_;
|
||||
|
||||
/** Height of all previous blocks, combined */
|
||||
unsigned prev_blk_height_;
|
||||
|
||||
/** template for canvas text config */
|
||||
void default_text_config(config* txt_ptr, t_string text = "");
|
||||
|
||||
void add_text_with_attribute(config& curr_item, std::string text, std::string attr_name = "", std::string extra_data = "");
|
||||
void add_text_with_attributes(config& curr_item, std::string text, std::vector<std::string> attr_names, std::vector<std::string> extra_data);
|
||||
void add_image(config& curr_item, std::string name, std::string align, bool floating, point& img_size);
|
||||
void add_link(config& curr_item, std::string name, std::string dest, int img_width);
|
||||
|
||||
void append_if_not_empty(config_attribute_value* key, std::string suffix) {
|
||||
if (!key->str().empty()) {
|
||||
*key = key->str() + suffix;
|
||||
}
|
||||
}
|
||||
|
||||
/** size calculation functions */
|
||||
point get_text_size(config text_cfg, unsigned width = 0);
|
||||
point get_image_size(config img_cfg);
|
||||
|
||||
wfl::map_formula_callable setup_text_renderer(config text_cfg, unsigned width = 0);
|
||||
|
||||
size_t get_split_location(std::string text, int img_height);
|
||||
|
||||
/** link variables and functions */
|
||||
std::vector<std::pair<rect, std::string>> links_;
|
||||
|
||||
std::function<void(std::string)> link_handler_;
|
||||
|
||||
point get_column_line(const point& position) const
|
||||
{
|
||||
return font::get_text_renderer().get_column_line(position);
|
||||
}
|
||||
|
||||
point get_xy_from_offset(const unsigned offset) const
|
||||
{
|
||||
return font::get_text_renderer().get_cursor_position(offset);
|
||||
}
|
||||
|
||||
point calculate_best_size() const override
|
||||
{
|
||||
return point(w_, h_);
|
||||
}
|
||||
|
||||
public:
|
||||
/** Static type getter that does not rely on the widget being constructed. */
|
||||
static const std::string& type();
|
||||
|
||||
private:
|
||||
/** Inherited from styled_widget, implemented by REGISTER_WIDGET. */
|
||||
virtual const std::string& get_control_type() const override;
|
||||
|
||||
/* **** ***** ***** signal handlers ***** ****** **** */
|
||||
|
||||
/**
|
||||
* Left click signal handler: checks if we clicked on a hyperlink
|
||||
*/
|
||||
void signal_handler_left_button_click(bool& handled);
|
||||
|
||||
/**
|
||||
* Mouse motion signal handler: checks if the cursor is on a hyperlink
|
||||
*/
|
||||
void signal_handler_mouse_motion(bool& handled, const point& coordinate);
|
||||
|
||||
/**
|
||||
* Mouse leave signal handler: checks if the cursor left a hyperlink
|
||||
*/
|
||||
void signal_handler_mouse_leave(bool& handled);
|
||||
|
||||
/**
|
||||
* Implementation detail for (re)setting the hyperlink cursor.
|
||||
*/
|
||||
void update_mouse_cursor(bool enable);
|
||||
};
|
||||
|
||||
// }---------- DEFINITION ---------{
|
||||
|
||||
struct rich_label_definition : public styled_widget_definition
|
||||
{
|
||||
|
||||
explicit rich_label_definition(const config& cfg);
|
||||
|
||||
struct resolution : public resolution_definition
|
||||
{
|
||||
explicit resolution(const config& cfg);
|
||||
|
||||
color_t link_color;
|
||||
};
|
||||
};
|
||||
|
||||
// }---------- BUILDER -----------{
|
||||
|
||||
namespace implementation
|
||||
{
|
||||
|
||||
struct builder_rich_label : public builder_styled_widget
|
||||
{
|
||||
builder_rich_label(const config& cfg);
|
||||
|
||||
using builder_styled_widget::build;
|
||||
|
||||
virtual std::unique_ptr<widget> build() const override;
|
||||
|
||||
PangoAlignment text_alignment;
|
||||
bool link_aware;
|
||||
unsigned width;
|
||||
};
|
||||
|
||||
} // namespace implementation
|
||||
|
||||
// }------------ END --------------
|
||||
|
||||
} // namespace gui2
|
|
@ -47,6 +47,7 @@
|
|||
#include "serialization/unicode.hpp" // for iterator
|
||||
#include "color.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <cassert> // for assert
|
||||
#include <algorithm> // for sort, find, transform, etc
|
||||
#include <iterator> // for back_insert_iterator, etc
|
||||
|
@ -1335,6 +1336,8 @@ std::vector<std::string> parse_text(const std::string &text)
|
|||
{
|
||||
std::vector<std::string> res;
|
||||
bool last_char_escape = false;
|
||||
bool found_slash = false;
|
||||
bool in_quotes = false;
|
||||
const char escape_char = '\\';
|
||||
std::stringstream ss;
|
||||
std::size_t pos;
|
||||
|
@ -1343,50 +1346,71 @@ std::vector<std::string> parse_text(const std::string &text)
|
|||
const char c = text[pos];
|
||||
if (c == escape_char && !last_char_escape) {
|
||||
last_char_escape = true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (state == OTHER) {
|
||||
if (c == '<') {
|
||||
if (last_char_escape) {
|
||||
ss << c;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
res.push_back(ss.str());
|
||||
ss.str("");
|
||||
state = ELEMENT_NAME;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
ss << c;
|
||||
}
|
||||
}
|
||||
else if (state == ELEMENT_NAME) {
|
||||
if (c == '/') {
|
||||
std::string msg = "Erroneous / in element name.";
|
||||
throw parse_error(msg);
|
||||
}
|
||||
else if (c == '>') {
|
||||
// End of this name.
|
||||
} else if (state == ELEMENT_NAME) {
|
||||
if ((c == '/') && (!in_quotes)) {
|
||||
found_slash = true;
|
||||
} else if (c == '\'') {
|
||||
// toggle quoting
|
||||
in_quotes = !in_quotes;
|
||||
} else if (c == '>') {
|
||||
in_quotes = false;
|
||||
|
||||
// end of this tag.
|
||||
std::stringstream s;
|
||||
const std::string element_name = ss.str();
|
||||
std::string element_name = ss.str();
|
||||
ss.str("");
|
||||
s << "</" << element_name << ">";
|
||||
const std::string end_element_name = s.str();
|
||||
std::size_t end_pos = text.find(end_element_name, pos);
|
||||
if (end_pos == std::string::npos) {
|
||||
std::stringstream msg;
|
||||
msg << "Unterminated element: " << element_name;
|
||||
throw parse_error(msg.str());
|
||||
|
||||
// process any attributes in the start tag
|
||||
std::size_t attr_pos = element_name.find(" ");
|
||||
std::string attrs = "";
|
||||
if (attr_pos != std::string::npos) {
|
||||
attrs = element_name.substr(attr_pos+1);
|
||||
element_name = element_name.substr(0, attr_pos);
|
||||
}
|
||||
|
||||
if (found_slash) {
|
||||
// empty tag
|
||||
res.push_back(convert_to_wml(element_name, attrs));
|
||||
found_slash = false;
|
||||
pos = text.find(">", pos);
|
||||
} else {
|
||||
// non-empty tag
|
||||
s << "</" << element_name << ">";
|
||||
const std::string end_element_name = s.str();
|
||||
std::size_t end_pos = text.find(end_element_name, pos);
|
||||
if (end_pos == std::string::npos) {
|
||||
std::stringstream msg;
|
||||
msg << "Unterminated element: " << element_name;
|
||||
throw parse_error(msg.str());
|
||||
}
|
||||
s.str("");
|
||||
const std::string contents = attrs + " " + text.substr(pos + 1, end_pos - pos - 1);
|
||||
const std::string element = convert_to_wml(element_name, contents);
|
||||
res.push_back(element);
|
||||
pos = end_pos + end_element_name.size() - 1;
|
||||
}
|
||||
s.str("");
|
||||
const std::string contents = text.substr(pos + 1, end_pos - pos - 1);
|
||||
const std::string element = convert_to_wml(element_name, contents);
|
||||
res.push_back(element);
|
||||
pos = end_pos + end_element_name.size() - 1;
|
||||
state = OTHER;
|
||||
}
|
||||
else {
|
||||
ss << c;
|
||||
} else {
|
||||
if (found_slash) {
|
||||
found_slash = false;
|
||||
std::string msg = "Erroneous / in element name.";
|
||||
throw parse_error(msg);
|
||||
} else {
|
||||
ss << c;
|
||||
}
|
||||
}
|
||||
}
|
||||
last_char_escape = false;
|
||||
|
@ -1404,17 +1428,23 @@ std::vector<std::string> parse_text(const std::string &text)
|
|||
return res;
|
||||
}
|
||||
|
||||
std::string convert_to_wml(const std::string &element_name, const std::string &contents)
|
||||
std::string convert_to_wml(std::string& element_name, const std::string& contents)
|
||||
{
|
||||
std::stringstream ss;
|
||||
bool in_quotes = false;
|
||||
bool last_char_escape = false;
|
||||
const char escape_char = '\\';
|
||||
std::vector<std::string> attributes;
|
||||
std::stringstream buff;
|
||||
|
||||
// Remove any leading and trailing space from element name
|
||||
boost::algorithm::trim(element_name);
|
||||
|
||||
// Find the different attributes.
|
||||
// No checks are made for the equal sign or something like that.
|
||||
// Attributes are just separated by spaces or newlines.
|
||||
// Attributes that contain spaces must be in single quotes.
|
||||
// No equal key forces that token to be considered as plain text
|
||||
// and it gets attached to the default 'text' key.
|
||||
for (std::size_t pos = 0; pos < contents.size(); ++pos) {
|
||||
const char c = contents[pos];
|
||||
if (c == escape_char && !last_char_escape) {
|
||||
|
@ -1427,7 +1457,13 @@ std::string convert_to_wml(const std::string &element_name, const std::string &c
|
|||
}
|
||||
else if ((c == ' ' || c == '\n') && !last_char_escape && !in_quotes) {
|
||||
// Space or newline, end of attribute.
|
||||
attributes.push_back(ss.str());
|
||||
std::size_t eq_pos = ss.str().find("=");
|
||||
if (eq_pos == std::string::npos) {
|
||||
// no = sign found, assuming plain text
|
||||
buff << " " << ss.str();
|
||||
} else {
|
||||
attributes.push_back(ss.str());
|
||||
}
|
||||
ss.str("");
|
||||
}
|
||||
else {
|
||||
|
@ -1436,47 +1472,41 @@ std::string convert_to_wml(const std::string &element_name, const std::string &c
|
|||
last_char_escape = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_quotes) {
|
||||
std::stringstream msg;
|
||||
msg << "Unterminated single quote after: '" << ss.str() << "'";
|
||||
throw parse_error(msg.str());
|
||||
}
|
||||
|
||||
if (!ss.str().empty()) {
|
||||
attributes.push_back(ss.str());
|
||||
std::size_t eq_pos = ss.str().find("=");
|
||||
if (eq_pos == std::string::npos) {
|
||||
// no = sign found, assuming plain text
|
||||
buff << " " << ss.str();
|
||||
} else {
|
||||
attributes.push_back(ss.str());
|
||||
}
|
||||
}
|
||||
ss.str("");
|
||||
// Create the WML.
|
||||
ss << "[" << element_name << "]\n";
|
||||
for (std::vector<std::string>::const_iterator it = attributes.begin();
|
||||
it != attributes.end(); ++it) {
|
||||
ss << *it << "\n";
|
||||
//for (std::vector<std::string>::const_iterator it = attributes.begin();
|
||||
// it != attributes.end(); ++it) {
|
||||
for (auto& elem : attributes) {
|
||||
boost::algorithm::trim(elem);
|
||||
ss << elem << "\n";
|
||||
}
|
||||
ss << "[/" << element_name << "]\n";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
color_t string_to_color(const std::string &cmp_str)
|
||||
{
|
||||
if (cmp_str == "green") {
|
||||
return font::GOOD_COLOR;
|
||||
std::string text = buff.str();
|
||||
boost::algorithm::trim(text);
|
||||
if (!text.empty()) {
|
||||
ss << "text=\"" << text << "\"\n";
|
||||
}
|
||||
if (cmp_str == "red") {
|
||||
return font::BAD_COLOR;
|
||||
}
|
||||
if (cmp_str == "black") {
|
||||
return font::BLACK_COLOR;
|
||||
}
|
||||
if (cmp_str == "yellow") {
|
||||
return font::YELLOW_COLOR;
|
||||
}
|
||||
if (cmp_str == "white") {
|
||||
return font::BIGMAP_COLOR;
|
||||
}
|
||||
// a #rrggbb color in pango format.
|
||||
if (*cmp_str.c_str() == '#' && cmp_str.size() == 7) {
|
||||
return color_t::from_hex_string(cmp_str.substr(1));
|
||||
}
|
||||
return font::NORMAL_COLOR;
|
||||
ss << "[/" << element_name << "]";
|
||||
|
||||
buff.str("");
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::vector<std::string> split_in_width(const std::string &s, const int font_size,
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "color.hpp"
|
||||
#include "exceptions.hpp" // for error
|
||||
#include "font/constants.hpp"
|
||||
#include "font/standard_colors.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include <optional>
|
||||
#include <list> // for list
|
||||
|
@ -304,23 +305,20 @@ const section *find_section(const section &sec, const std::string &id);
|
|||
section *find_section(section &sec, const std::string &id);
|
||||
|
||||
/**
|
||||
* Parse a text string. Return a vector with the different parts of the
|
||||
* text. Each markup item is a separate part while the text between
|
||||
* markups are separate parts.
|
||||
* Parse a xml style marked up text string. Return a vector with
|
||||
* the different parts of the text. Each markup item and the text
|
||||
* between markups are separate parts. Each line of returned vector
|
||||
* is valid WML.
|
||||
*/
|
||||
std::vector<std::string> parse_text(const std::string &text);
|
||||
|
||||
/**
|
||||
* Convert the contents to wml attributes, surrounded within
|
||||
* [element_name]...[/element_name]. Return the resulting WML.
|
||||
* Convert the the text between start and end xml tags for element_name to
|
||||
* valid wml attributes, surrounded between [element_name]...[/element_name].
|
||||
* The attributes in the start tag are also used.
|
||||
* @return the resulting WML.
|
||||
*/
|
||||
std::string convert_to_wml(const std::string &element_name, const std::string &contents);
|
||||
|
||||
/**
|
||||
* Return the color the string represents. Return font::NORMAL_COLOR if
|
||||
* the string is empty or can't be matched against any other color.
|
||||
*/
|
||||
color_t string_to_color(const std::string &s);
|
||||
std::string convert_to_wml(std::string &element_name, const std::string &contents);
|
||||
|
||||
/** Make a best effort to word wrap s. All parts are less than width. */
|
||||
std::vector<std::string> split_in_width(const std::string &s, const int font_size, const unsigned width);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "config.hpp" // for config, etc
|
||||
#include "draw.hpp" // for blit, fill
|
||||
#include "font/sdl_ttf_compat.hpp"
|
||||
#include "font/standard_colors.hpp" // for string_to_color
|
||||
#include "game_config.hpp" // for debug
|
||||
#include "help/help_impl.hpp" // for parse_error, box_width, etc
|
||||
#include "lexical_cast.hpp"
|
||||
|
@ -294,7 +295,7 @@ void help_text_area::handle_format_cfg(const config &cfg)
|
|||
bool bold = cfg["bold"].to_bool();
|
||||
bool italic = cfg["italic"].to_bool();
|
||||
int font_size = cfg["font_size"].to_int(normal_font_size);
|
||||
color_t color = help::string_to_color(cfg["color"]);
|
||||
color_t color = font::string_to_color(cfg["color"]);
|
||||
add_text_item(text, "", false, font_size, bold, italic, color);
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
#include "gui/dialogs/game_stats.hpp"
|
||||
#include "gui/dialogs/game_version_dialog.hpp"
|
||||
#include "gui/dialogs/gamestate_inspector.hpp"
|
||||
#include "gui/dialogs/gui_test_dialog.hpp"
|
||||
#include "gui/dialogs/help_browser.hpp"
|
||||
#include "gui/dialogs/hotkey_bind.hpp"
|
||||
#include "gui/dialogs/label_settings.hpp"
|
||||
|
@ -520,6 +521,10 @@ BOOST_AUTO_TEST_CASE(modal_dialog_test_generator_settings)
|
|||
{
|
||||
test<generator_settings>();
|
||||
}
|
||||
BOOST_AUTO_TEST_CASE(modal_dialog_test_gui_test_dialog)
|
||||
{
|
||||
test<gui_test_dialog>();
|
||||
}
|
||||
BOOST_AUTO_TEST_CASE(modal_dialog_test_hotkey_bind)
|
||||
{
|
||||
test<hotkey_bind>();
|
||||
|
@ -1458,4 +1463,14 @@ struct dialog_tester<editor_edit_unit>
|
|||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct dialog_tester<gui_test_dialog>
|
||||
{
|
||||
dialog_tester() {}
|
||||
gui_test_dialog* create()
|
||||
{
|
||||
return new gui_test_dialog();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
Loading…
Add table
Reference in a new issue