mirror of
https://github.com/minetest/minetest.git
synced 2024-11-27 01:53:45 +01:00
Add button_url[] and hypertext element to allow mods to open web pages (#13825)
Fixes #12500
This commit is contained in:
parent
6c4a110679
commit
24cc33e704
@ -158,7 +158,7 @@ return {
|
|||||||
"style[label_button;border=false]" ..
|
"style[label_button;border=false]" ..
|
||||||
"button[0.1,3.4;5.3,0.5;label_button;" ..
|
"button[0.1,3.4;5.3,0.5;label_button;" ..
|
||||||
core.formspec_escape(version.project .. " " .. version.string) .. "]" ..
|
core.formspec_escape(version.project .. " " .. version.string) .. "]" ..
|
||||||
"button[1.5,4.1;2.5,0.8;homepage;minetest.net]" ..
|
"button_url[1.5,4.1;2.5,0.8;homepage;minetest.net;https://www.minetest.net/]" ..
|
||||||
"hypertext[5.5,0.25;9.75,6.6;credits;" .. minetest.formspec_escape(hypertext) .. "]"
|
"hypertext[5.5,0.25;9.75,6.6;credits;" .. minetest.formspec_escape(hypertext) .. "]"
|
||||||
|
|
||||||
-- Render information
|
-- Render information
|
||||||
@ -188,10 +188,6 @@ return {
|
|||||||
end,
|
end,
|
||||||
|
|
||||||
cbf_button_handler = function(this, fields, name, tabdata)
|
cbf_button_handler = function(this, fields, name, tabdata)
|
||||||
if fields.homepage then
|
|
||||||
core.open_url("https://www.minetest.net")
|
|
||||||
end
|
|
||||||
|
|
||||||
if fields.share_debug then
|
if fields.share_debug then
|
||||||
local path = core.get_user_path() .. DIR_DELIM .. "debug.txt"
|
local path = core.get_user_path() .. DIR_DELIM .. "debug.txt"
|
||||||
core.share_file(path)
|
core.share_file(path)
|
||||||
|
@ -3017,6 +3017,16 @@ Elements
|
|||||||
centered on `H`. With the new coordinate system, `H` will modify the height.
|
centered on `H`. With the new coordinate system, `H` will modify the height.
|
||||||
* `label` is the text on the button
|
* `label` is the text on the button
|
||||||
|
|
||||||
|
### `button_url[<X>,<Y>;<W>,<H>;<name>;<label>;<url>]`
|
||||||
|
|
||||||
|
* Clickable button. When clicked, fields will be sent and the user will be given the
|
||||||
|
option to open the URL in a browser.
|
||||||
|
* With the old coordinate system, buttons are a set height, but will be vertically
|
||||||
|
centered on `H`. With the new coordinate system, `H` will modify the height.
|
||||||
|
* To make this into an `image_button`, you can use formspec styling.
|
||||||
|
* `label` is the text on the button.
|
||||||
|
* `url` must be a valid web URL, starting with `http://` or `https://`.
|
||||||
|
|
||||||
### `image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]`
|
### `image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]`
|
||||||
|
|
||||||
* `texture name` is the filename of an image
|
* `texture name` is the filename of an image
|
||||||
@ -3043,6 +3053,11 @@ Elements
|
|||||||
* When clicked, fields will be sent and the form will quit.
|
* When clicked, fields will be sent and the form will quit.
|
||||||
* Same as `button` in all other respects.
|
* Same as `button` in all other respects.
|
||||||
|
|
||||||
|
### `button_url_exit[<X>,<Y>;<W>,<H>;<name>;<label>;<url>]`
|
||||||
|
|
||||||
|
* When clicked, fields will be sent and the form will quit.
|
||||||
|
* Same as `button_url` in all other respects.
|
||||||
|
|
||||||
### `image_button_exit[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]`
|
### `image_button_exit[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]`
|
||||||
|
|
||||||
* When clicked, fields will be sent and the form will quit.
|
* When clicked, fields will be sent and the form will quit.
|
||||||
@ -3538,11 +3553,13 @@ Changes the style of the text.
|
|||||||
Sets global style.
|
Sets global style.
|
||||||
|
|
||||||
Global only styles:
|
Global only styles:
|
||||||
|
|
||||||
* `background`: Text background, a `colorspec` or `none`.
|
* `background`: Text background, a `colorspec` or `none`.
|
||||||
* `margin`: Page margins in pixel.
|
* `margin`: Page margins in pixel.
|
||||||
* `valign`: Text vertical alignment (`top`, `middle`, `bottom`).
|
* `valign`: Text vertical alignment (`top`, `middle`, `bottom`).
|
||||||
|
|
||||||
Inheriting styles (affects child elements):
|
Inheriting styles (affects child elements):
|
||||||
|
|
||||||
* `color`: Default text color. Given color is a `colorspec`.
|
* `color`: Default text color. Given color is a `colorspec`.
|
||||||
* `hovercolor`: Color of <action> tags when mouse is over.
|
* `hovercolor`: Color of <action> tags when mouse is over.
|
||||||
* `size`: Default text size.
|
* `size`: Default text size.
|
||||||
@ -3556,6 +3573,7 @@ tags appear.
|
|||||||
`<tag name=... color=... hovercolor=... font=... size=...>`
|
`<tag name=... color=... hovercolor=... font=... size=...>`
|
||||||
|
|
||||||
Defines or redefines tag style. This can be used to define new tags.
|
Defines or redefines tag style. This can be used to define new tags.
|
||||||
|
|
||||||
* `name`: Name of the tag to define or change.
|
* `name`: Name of the tag to define or change.
|
||||||
* `color`: Text color. Given color is a `colorspec`.
|
* `color`: Text color. Given color is a `colorspec`.
|
||||||
* `hovercolor`: Text color when element hovered (only for `action` tags). Given color is a `colorspec`.
|
* `hovercolor`: Text color when element hovered (only for `action` tags). Given color is a `colorspec`.
|
||||||
@ -3588,6 +3606,7 @@ Other tags can be added using `<tag ...>` tag.
|
|||||||
Make that text a clickable text triggering an action.
|
Make that text a clickable text triggering an action.
|
||||||
|
|
||||||
* `name`: Name of the action (mandatory).
|
* `name`: Name of the action (mandatory).
|
||||||
|
* `url`: URL to open when the action is triggered (optional).
|
||||||
|
|
||||||
When clicked, the formspec is send to the server. The value of the text field
|
When clicked, the formspec is send to the server. The value of the text field
|
||||||
sent to `on_player_receive_fields` will be "action:" concatenated to the action
|
sent to `on_player_receive_fields` will be "action:" concatenated to the action
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
local color = minetest.colorize
|
local color = minetest.colorize
|
||||||
|
|
||||||
|
-- \208\176 is a cyrillic small a
|
||||||
|
local unsafe_url = minetest.formspec_escape("https://u:p@wikipedi\208\176.org:1233/heIIoll?a=b#c")
|
||||||
|
|
||||||
local clip_fs = [[
|
local clip_fs = [[
|
||||||
style_type[label,button,image_button,item_image_button,
|
style_type[label,button,image_button,item_image_button,
|
||||||
tabheader,scrollbar,table,animated_image
|
tabheader,scrollbar,table,animated_image
|
||||||
,field,textarea,checkbox,dropdown;noclip=%c]
|
,field,textarea,checkbox,dropdown;noclip=%c]
|
||||||
|
|
||||||
label[0,0;A clipping test]
|
label[0,0;A clipping test]
|
||||||
button[0,1;3,0.8;clip_button;A clipping test]
|
button_url[0,1;3,0.8;clip_button;A clipping test;]] .. unsafe_url .. [[]
|
||||||
image_button[0,2;3,0.8;testformspec_button_image.png;clip_image_button;A clipping test]
|
image_button[0,2;3,0.8;testformspec_button_image.png;clip_image_button;A clipping test]
|
||||||
item_image_button[0,3;3,0.8;testformspec:item;clip_item_image_button;A clipping test]
|
item_image_button[0,3;3,0.8;testformspec:item;clip_item_image_button;A clipping test]
|
||||||
tabheader[0,4.7;3,0.63;clip_tabheader;Clip,Test,Text,Tabs;1;false;false]
|
tabheader[0,4.7;3,0.63;clip_tabheader;Clip,Test,Text,Tabs;1;false;false]
|
||||||
@ -92,6 +95,7 @@ This is a normal text.
|
|||||||
<t_green>color=green</t_green>
|
<t_green>color=green</t_green>
|
||||||
Action: <action name=color><t_green>color=green</t_green></action>
|
Action: <action name=color><t_green>color=green</t_green></action>
|
||||||
Action: <action name=hovercolor><t_hover>hovercolor=yellow</t_hover></action>
|
Action: <action name=hovercolor><t_hover>hovercolor=yellow</t_hover></action>
|
||||||
|
Action URL: <action name=open url=https://example.com/?a=b#c>open URL</action>
|
||||||
<t_size>size=24</t_size>
|
<t_size>size=24</t_size>
|
||||||
<t_mono>font=mono</t_mono>
|
<t_mono>font=mono</t_mono>
|
||||||
<t_multi>color=green font=mono size=24</t_multi>
|
<t_multi>color=green font=mono size=24</t_multi>
|
||||||
@ -145,7 +149,7 @@ local hypertext_fs = "hypertext[0,0;11,9;hypertext;"..minetest.formspec_escape(h
|
|||||||
local style_fs = [[
|
local style_fs = [[
|
||||||
style[one_btn1;bgcolor=red;textcolor=yellow;bgcolor_hovered=orange;
|
style[one_btn1;bgcolor=red;textcolor=yellow;bgcolor_hovered=orange;
|
||||||
bgcolor_pressed=purple]
|
bgcolor_pressed=purple]
|
||||||
button[0,0;2.5,0.8;one_btn1;Button]
|
button_url_exit[0,0;2.5,0.8;one_btn1;Button;]] .. unsafe_url .. [[]
|
||||||
|
|
||||||
style[one_btn2;border=false;textcolor=cyan] ]]..
|
style[one_btn2;border=false;textcolor=cyan] ]]..
|
||||||
"button[0,1.05;2.5,0.8;one_btn2;Text " .. color("#FF0", "Yellow") .. [[]
|
"button[0,1.05;2.5,0.8;one_btn2;Text " .. color("#FF0", "Yellow") .. [[]
|
||||||
|
@ -50,6 +50,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "gui/guiFormSpecMenu.h"
|
#include "gui/guiFormSpecMenu.h"
|
||||||
#include "gui/guiKeyChangeMenu.h"
|
#include "gui/guiKeyChangeMenu.h"
|
||||||
#include "gui/guiPasswordChange.h"
|
#include "gui/guiPasswordChange.h"
|
||||||
|
#include "gui/guiOpenURL.h"
|
||||||
#include "gui/guiVolumeChange.h"
|
#include "gui/guiVolumeChange.h"
|
||||||
#include "gui/mainmenumanager.h"
|
#include "gui/mainmenumanager.h"
|
||||||
#include "gui/profilergraph.h"
|
#include "gui/profilergraph.h"
|
||||||
@ -1815,6 +1816,12 @@ inline bool Game::handleCallbacks()
|
|||||||
g_gamecallback->keyconfig_requested = false;
|
g_gamecallback->keyconfig_requested = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!g_gamecallback->show_open_url_dialog.empty()) {
|
||||||
|
(new GUIOpenURLMenu(guienv, guiroot, -1,
|
||||||
|
&g_menumgr, texture_src, g_gamecallback->show_open_url_dialog))->drop();
|
||||||
|
g_gamecallback->show_open_url_dialog.clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (g_gamecallback->keyconfig_changed) {
|
if (g_gamecallback->keyconfig_changed) {
|
||||||
input->keycache.populate(); // update the cache with new settings
|
input->keycache.populate(); // update the cache with new settings
|
||||||
g_gamecallback->keyconfig_changed = false;
|
g_gamecallback->keyconfig_changed = false;
|
||||||
|
@ -13,6 +13,7 @@ set(gui_SRCS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/guiInventoryList.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/guiInventoryList.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/guiItemImage.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/guiItemImage.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/guiKeyChangeMenu.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/guiKeyChangeMenu.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/guiOpenURL.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/guiScene.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/guiScene.cpp
|
||||||
|
@ -976,14 +976,18 @@ void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &elemen
|
|||||||
void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
|
void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
|
||||||
const std::string &type)
|
const std::string &type)
|
||||||
{
|
{
|
||||||
|
int expected_parts = (type == "button_url" || type == "button_url_exit") ? 5 : 4;
|
||||||
std::vector<std::string> parts;
|
std::vector<std::string> parts;
|
||||||
if (!precheckElement("button", element, 4, 4, parts))
|
if (!precheckElement("button", element, expected_parts, expected_parts, parts))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::vector<std::string> v_pos = split(parts[0],',');
|
std::vector<std::string> v_pos = split(parts[0],',');
|
||||||
std::vector<std::string> v_geom = split(parts[1],',');
|
std::vector<std::string> v_geom = split(parts[1],',');
|
||||||
std::string name = parts[2];
|
std::string name = parts[2];
|
||||||
std::string label = parts[3];
|
std::string label = parts[3];
|
||||||
|
std::string url;
|
||||||
|
if (type == "button_url" || type == "button_url_exit")
|
||||||
|
url = parts[4];
|
||||||
|
|
||||||
MY_CHECKPOS("button",0);
|
MY_CHECKPOS("button",0);
|
||||||
MY_CHECKGEOM("button",1);
|
MY_CHECKGEOM("button",1);
|
||||||
@ -1018,8 +1022,10 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
|
|||||||
258 + m_fields.size()
|
258 + m_fields.size()
|
||||||
);
|
);
|
||||||
spec.ftype = f_Button;
|
spec.ftype = f_Button;
|
||||||
if(type == "button_exit")
|
if (type == "button_exit" || type == "button_url_exit")
|
||||||
spec.is_exit = true;
|
spec.is_exit = true;
|
||||||
|
if (type == "button_url" || type == "button_url_exit")
|
||||||
|
spec.url = url;
|
||||||
|
|
||||||
GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc,
|
GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc,
|
||||||
data->current_parent, spec.fid, spec.flabel.c_str());
|
data->current_parent, spec.fid, spec.flabel.c_str());
|
||||||
@ -2897,7 +2903,7 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == "button" || type == "button_exit") {
|
if (type == "button" || type == "button_exit" || type == "button_url" || type == "button_url_exit") {
|
||||||
parseButton(data, description, type);
|
parseButton(data, description, type);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -4968,6 +4974,17 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
|||||||
m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
|
m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
|
||||||
|
|
||||||
s.send = true;
|
s.send = true;
|
||||||
|
|
||||||
|
if (!s.url.empty()) {
|
||||||
|
if (m_client) {
|
||||||
|
// in game
|
||||||
|
g_gamecallback->showOpenURLDialog(s.url);
|
||||||
|
} else {
|
||||||
|
// main menu
|
||||||
|
porting::open_url(s.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (s.is_exit) {
|
if (s.is_exit) {
|
||||||
if (m_allowclose) {
|
if (m_allowclose) {
|
||||||
acceptInput(quit_mode_accept);
|
acceptInput(quit_mode_accept);
|
||||||
|
@ -137,6 +137,7 @@ class GUIFormSpecMenu : public GUIModalMenu
|
|||||||
std::string fname;
|
std::string fname;
|
||||||
std::wstring flabel;
|
std::wstring flabel;
|
||||||
std::wstring fdefault;
|
std::wstring fdefault;
|
||||||
|
std::string url;
|
||||||
s32 fid;
|
s32 fid;
|
||||||
bool send;
|
bool send;
|
||||||
FormspecFieldType ftype;
|
FormspecFieldType ftype;
|
||||||
|
@ -27,6 +27,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "inventory.h"
|
#include "inventory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "irrlicht_changes/CGUITTFont.h"
|
#include "irrlicht_changes/CGUITTFont.h"
|
||||||
|
#include "mainmenumanager.h"
|
||||||
|
#include "porting.h"
|
||||||
|
|
||||||
using namespace irr::gui;
|
using namespace irr::gui;
|
||||||
|
|
||||||
@ -1106,6 +1108,18 @@ bool GUIHyperText::OnEvent(const SEvent &event)
|
|||||||
newEvent.GUIEvent.EventType = EGET_BUTTON_CLICKED;
|
newEvent.GUIEvent.EventType = EGET_BUTTON_CLICKED;
|
||||||
Parent->OnEvent(newEvent);
|
Parent->OnEvent(newEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto url_it = tag->attrs.find("url");
|
||||||
|
if (url_it != tag->attrs.end()) {
|
||||||
|
if (g_gamecallback) {
|
||||||
|
// in game
|
||||||
|
g_gamecallback->showOpenURLDialog(url_it->second);
|
||||||
|
} else {
|
||||||
|
// main menu
|
||||||
|
porting::open_url(url_it->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
196
src/gui/guiOpenURL.cpp
Normal file
196
src/gui/guiOpenURL.cpp
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
Part of Minetest
|
||||||
|
Copyright (C) 2023-24 rubenwardy
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "guiOpenURL.h"
|
||||||
|
#include "guiButton.h"
|
||||||
|
#include "guiEditBoxWithScrollbar.h"
|
||||||
|
#include <IGUIEditBox.h>
|
||||||
|
#include <IGUIFont.h>
|
||||||
|
#include "client/renderingengine.h"
|
||||||
|
#include "porting.h"
|
||||||
|
#include "gettext.h"
|
||||||
|
#include "util/colorize.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int ID_url = 256;
|
||||||
|
constexpr int ID_open = 259;
|
||||||
|
constexpr int ID_cancel = 261;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUIOpenURLMenu::GUIOpenURLMenu(gui::IGUIEnvironment* env,
|
||||||
|
gui::IGUIElement* parent, s32 id,
|
||||||
|
IMenuManager *menumgr,
|
||||||
|
ISimpleTextureSource *tsrc, const std::string &url
|
||||||
|
):
|
||||||
|
GUIModalMenu(env, parent, id, menumgr),
|
||||||
|
m_tsrc(tsrc),
|
||||||
|
url(url)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void GUIOpenURLMenu::regenerateGui(v2u32 screensize)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Remove stuff
|
||||||
|
*/
|
||||||
|
removeAllChildren();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculate new sizes and positions
|
||||||
|
*/
|
||||||
|
const float s = m_gui_scale;
|
||||||
|
DesiredRect = core::rect<s32>(
|
||||||
|
screensize.X / 2 - 580 * s / 2,
|
||||||
|
screensize.Y / 2 - 250 * s / 2,
|
||||||
|
screensize.X / 2 + 580 * s / 2,
|
||||||
|
screensize.Y / 2 + 250 * s / 2
|
||||||
|
);
|
||||||
|
recalculateAbsolutePosition(false);
|
||||||
|
|
||||||
|
v2s32 size = DesiredRect.getSize();
|
||||||
|
v2s32 topleft_client(40 * s, 0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get URL text
|
||||||
|
*/
|
||||||
|
bool ok = true;
|
||||||
|
std::string text;
|
||||||
|
#ifdef USE_CURL
|
||||||
|
try {
|
||||||
|
text = colorize_url(url);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
text = std::string(e.what()) + " (url = " + url + ")";
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
text = url;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add stuff
|
||||||
|
*/
|
||||||
|
s32 ypos = 40 * s;
|
||||||
|
|
||||||
|
{
|
||||||
|
core::rect<s32> rect(0, 0, 500 * s, 20 * s);
|
||||||
|
rect += topleft_client + v2s32(20 * s, ypos);
|
||||||
|
|
||||||
|
std::wstring title = ok
|
||||||
|
? wstrgettext("Open URL?")
|
||||||
|
: wstrgettext("Unable to open URL");
|
||||||
|
gui::StaticText::add(Environment, title, rect,
|
||||||
|
false, true, this, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ypos += 50 * s;
|
||||||
|
|
||||||
|
{
|
||||||
|
core::rect<s32> rect(0, 0, 440 * s, 60 * s);
|
||||||
|
|
||||||
|
auto font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED,
|
||||||
|
ok ? FM_Mono : FM_Standard);
|
||||||
|
int scrollbar_width = Environment->getSkin()->getSize(gui::EGDS_SCROLLBAR_SIZE);
|
||||||
|
int max_cols = (rect.getWidth() - scrollbar_width - 10) / font->getDimension(L"x").Width;
|
||||||
|
|
||||||
|
text = wrap_rows(text, max_cols, true);
|
||||||
|
|
||||||
|
rect += topleft_client + v2s32(20 * s, ypos);
|
||||||
|
IGUIEditBox *e = new GUIEditBoxWithScrollBar(utf8_to_wide(text).c_str(), true, Environment,
|
||||||
|
this, ID_url, rect, m_tsrc, false, true);
|
||||||
|
e->setMultiLine(true);
|
||||||
|
e->setWordWrap(true);
|
||||||
|
e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT);
|
||||||
|
e->setDrawBorder(true);
|
||||||
|
e->setDrawBackground(true);
|
||||||
|
e->setOverrideFont(font);
|
||||||
|
e->drop();
|
||||||
|
}
|
||||||
|
|
||||||
|
ypos += 80 * s;
|
||||||
|
if (ok) {
|
||||||
|
core::rect<s32> rect(0, 0, 100 * s, 40 * s);
|
||||||
|
rect = rect + v2s32(size.X / 2 - 150 * s, ypos);
|
||||||
|
GUIButton::addButton(Environment, rect, m_tsrc, this, ID_open,
|
||||||
|
wstrgettext("Open").c_str());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
core::rect<s32> rect(0, 0, 100 * s, 40 * s);
|
||||||
|
rect = rect + v2s32(size.X / 2 + 50 * s, ypos);
|
||||||
|
GUIButton::addButton(Environment, rect, m_tsrc, this, ID_cancel,
|
||||||
|
wstrgettext("Cancel").c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUIOpenURLMenu::drawMenu()
|
||||||
|
{
|
||||||
|
gui::IGUISkin *skin = Environment->getSkin();
|
||||||
|
if (!skin)
|
||||||
|
return;
|
||||||
|
video::IVideoDriver *driver = Environment->getVideoDriver();
|
||||||
|
|
||||||
|
video::SColor bgcolor(140, 0, 0, 0);
|
||||||
|
driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
|
||||||
|
|
||||||
|
gui::IGUIElement::draw();
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
getAndroidUIInput();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GUIOpenURLMenu::OnEvent(const SEvent &event)
|
||||||
|
{
|
||||||
|
if (event.EventType == EET_KEY_INPUT_EVENT) {
|
||||||
|
if ((event.KeyInput.Key == KEY_ESCAPE ||
|
||||||
|
event.KeyInput.Key == KEY_CANCEL) &&
|
||||||
|
event.KeyInput.PressedDown) {
|
||||||
|
quitMenu();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) {
|
||||||
|
porting::open_url(url);
|
||||||
|
quitMenu();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.EventType == EET_GUI_EVENT) {
|
||||||
|
if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST &&
|
||||||
|
isVisible()) {
|
||||||
|
if (!canTakeFocus(event.GUIEvent.Element)) {
|
||||||
|
infostream << "GUIOpenURLMenu: Not allowing focus change."
|
||||||
|
<< std::endl;
|
||||||
|
// Returning true disables focus change
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) {
|
||||||
|
switch (event.GUIEvent.Caller->getID()) {
|
||||||
|
case ID_open:
|
||||||
|
porting::open_url(url);
|
||||||
|
quitMenu();
|
||||||
|
return true;
|
||||||
|
case ID_cancel:
|
||||||
|
quitMenu();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Parent != nullptr && Parent->OnEvent(event);
|
||||||
|
}
|
50
src/gui/guiOpenURL.h
Normal file
50
src/gui/guiOpenURL.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
Part of Minetest
|
||||||
|
Copyright (C) 2023 rubenwardy
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "irrlichttypes_extrabloated.h"
|
||||||
|
#include "modalMenu.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Client;
|
||||||
|
class ISimpleTextureSource;
|
||||||
|
|
||||||
|
class GUIOpenURLMenu : public GUIModalMenu
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GUIOpenURLMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
|
||||||
|
IMenuManager *menumgr,
|
||||||
|
ISimpleTextureSource *tsrc, const std::string &url);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove and re-add (or reposition) stuff
|
||||||
|
*/
|
||||||
|
void regenerateGui(v2u32 screensize);
|
||||||
|
|
||||||
|
void drawMenu();
|
||||||
|
|
||||||
|
bool OnEvent(const SEvent &event);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::wstring getLabelByID(s32 id) { return L""; }
|
||||||
|
std::string getNameByID(s32 id) { return ""; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ISimpleTextureSource *m_tsrc;
|
||||||
|
std::string url;
|
||||||
|
};
|
@ -34,7 +34,7 @@ public:
|
|||||||
virtual void disconnect() = 0;
|
virtual void disconnect() = 0;
|
||||||
virtual void changePassword() = 0;
|
virtual void changePassword() = 0;
|
||||||
virtual void changeVolume() = 0;
|
virtual void changeVolume() = 0;
|
||||||
|
virtual void showOpenURLDialog(const std::string &url) = 0;
|
||||||
virtual void signalKeyConfigChange() = 0;
|
virtual void signalKeyConfigChange() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -108,44 +108,47 @@ public:
|
|||||||
MainGameCallback() = default;
|
MainGameCallback() = default;
|
||||||
virtual ~MainGameCallback() = default;
|
virtual ~MainGameCallback() = default;
|
||||||
|
|
||||||
virtual void exitToOS()
|
void exitToOS() override
|
||||||
{
|
{
|
||||||
shutdown_requested = true;
|
shutdown_requested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void disconnect()
|
void disconnect() override
|
||||||
{
|
{
|
||||||
disconnect_requested = true;
|
disconnect_requested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void changePassword()
|
void changePassword() override
|
||||||
{
|
{
|
||||||
changepassword_requested = true;
|
changepassword_requested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void changeVolume()
|
void changeVolume() override
|
||||||
{
|
{
|
||||||
changevolume_requested = true;
|
changevolume_requested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void keyConfig()
|
void keyConfig() override
|
||||||
{
|
{
|
||||||
keyconfig_requested = true;
|
keyconfig_requested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void signalKeyConfigChange()
|
void signalKeyConfigChange() override
|
||||||
{
|
{
|
||||||
keyconfig_changed = true;
|
keyconfig_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showOpenURLDialog(const std::string &url) override {
|
||||||
|
show_open_url_dialog = url;
|
||||||
|
}
|
||||||
|
|
||||||
bool disconnect_requested = false;
|
bool disconnect_requested = false;
|
||||||
bool changepassword_requested = false;
|
bool changepassword_requested = false;
|
||||||
bool changevolume_requested = false;
|
bool changevolume_requested = false;
|
||||||
bool keyconfig_requested = false;
|
bool keyconfig_requested = false;
|
||||||
bool shutdown_requested = false;
|
bool shutdown_requested = false;
|
||||||
|
|
||||||
bool keyconfig_changed = false;
|
bool keyconfig_changed = false;
|
||||||
|
std::string show_open_url_dialog = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
extern MainGameCallback *g_gamecallback;
|
extern MainGameCallback *g_gamecallback;
|
||||||
|
@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "util/numeric.h"
|
#include "util/numeric.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "util/base64.h"
|
#include "util/base64.h"
|
||||||
|
#include "util/colorize.h"
|
||||||
|
|
||||||
class TestUtilities : public TestBase {
|
class TestUtilities : public TestBase {
|
||||||
public:
|
public:
|
||||||
@ -59,6 +60,7 @@ public:
|
|||||||
void testBase64();
|
void testBase64();
|
||||||
void testSanitizeDirName();
|
void testSanitizeDirName();
|
||||||
void testIsBlockInSight();
|
void testIsBlockInSight();
|
||||||
|
void testColorizeURL();
|
||||||
};
|
};
|
||||||
|
|
||||||
static TestUtilities g_test_instance;
|
static TestUtilities g_test_instance;
|
||||||
@ -92,6 +94,7 @@ void TestUtilities::runTests(IGameDef *gamedef)
|
|||||||
TEST(testBase64);
|
TEST(testBase64);
|
||||||
TEST(testSanitizeDirName);
|
TEST(testSanitizeDirName);
|
||||||
TEST(testIsBlockInSight);
|
TEST(testIsBlockInSight);
|
||||||
|
TEST(testColorizeURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -710,3 +713,19 @@ void TestUtilities::testIsBlockInSight()
|
|||||||
UASSERT(isBlockInSight({-1, 0, 0}, cam_pos, cam_dir, fov, range));
|
UASSERT(isBlockInSight({-1, 0, 0}, cam_pos, cam_dir, fov, range));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestUtilities::testColorizeURL()
|
||||||
|
{
|
||||||
|
#ifdef USE_CURL
|
||||||
|
#define RED COLOR_CODE("#faa")
|
||||||
|
#define GREY COLOR_CODE("#aaa")
|
||||||
|
#define WHITE COLOR_CODE("#fff")
|
||||||
|
|
||||||
|
std::string result = colorize_url("http://example.com/");
|
||||||
|
UASSERT(result == (GREY "http://" WHITE "example.com" GREY "/"));
|
||||||
|
|
||||||
|
result = colorize_url(u8"https://u:p@wikipedi\u0430.org:1234/heIIoll?a=b#c");
|
||||||
|
UASSERT(result ==
|
||||||
|
(GREY "https://u:p@" WHITE "wikipedi" RED "%d0%b0" WHITE ".org" GREY ":1234/heIIoll?a=b#c"));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
set(UTIL_SRCS
|
set(UTIL_SRCS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/areastore.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/areastore.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/auth.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/auth.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/colorize.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/enriched_string.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/enriched_string.cpp
|
||||||
|
113
src/util/colorize.cpp
Normal file
113
src/util/colorize.cpp
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
Part of Minetest
|
||||||
|
Copyright (C) 2024 rubenwardy
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "colorize.h"
|
||||||
|
|
||||||
|
#ifdef USE_CURL
|
||||||
|
|
||||||
|
#include <curl/urlapi.h>
|
||||||
|
#include "log.h"
|
||||||
|
#include "string.h"
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
std::string colorize_url(const std::string &url)
|
||||||
|
{
|
||||||
|
// Forbid escape codes in URL
|
||||||
|
if (url.find('\x1b') != std::string::npos) {
|
||||||
|
throw std::runtime_error("Unable to open URL as it contains escape codes");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto urlHandleRAII = std::unique_ptr<CURLU, decltype(&curl_url_cleanup)>(
|
||||||
|
curl_url(), curl_url_cleanup);
|
||||||
|
CURLU *urlHandle = urlHandleRAII.get();
|
||||||
|
|
||||||
|
auto rc = curl_url_set(urlHandle, CURLUPART_URL, url.c_str(), 0);
|
||||||
|
if (rc != CURLUE_OK) {
|
||||||
|
throw std::runtime_error("Unable to open URL as it is not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto url_get = [&] (CURLUPart what) -> std::string {
|
||||||
|
char *tmp = nullptr;
|
||||||
|
curl_url_get(urlHandle, what, &tmp, 0);
|
||||||
|
std::string ret(tmp ? tmp : "");
|
||||||
|
curl_free(tmp);
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto scheme = url_get(CURLUPART_SCHEME);
|
||||||
|
auto user = url_get(CURLUPART_USER);
|
||||||
|
auto password = url_get(CURLUPART_PASSWORD);
|
||||||
|
auto host = url_get(CURLUPART_HOST);
|
||||||
|
auto port = url_get(CURLUPART_PORT);
|
||||||
|
auto path = url_get(CURLUPART_PATH);
|
||||||
|
auto query = url_get(CURLUPART_QUERY);
|
||||||
|
auto fragment = url_get(CURLUPART_FRAGMENT);
|
||||||
|
auto zoneid = url_get(CURLUPART_ZONEID);
|
||||||
|
|
||||||
|
std::ostringstream os;
|
||||||
|
|
||||||
|
std::string_view red = COLOR_CODE("#faa");
|
||||||
|
std::string_view white = COLOR_CODE("#fff");
|
||||||
|
std::string_view grey = COLOR_CODE("#aaa");
|
||||||
|
|
||||||
|
os << grey << scheme << "://";
|
||||||
|
if (!user.empty())
|
||||||
|
os << user;
|
||||||
|
if (!password.empty())
|
||||||
|
os << ":" << password;
|
||||||
|
if (!(user.empty() && password.empty()))
|
||||||
|
os << "@";
|
||||||
|
|
||||||
|
// Print hostname, escaping unsafe characters
|
||||||
|
os << white;
|
||||||
|
bool was_alphanum = true;
|
||||||
|
std::string host_s = host;
|
||||||
|
for (size_t i = 0; i < host_s.size(); i++) {
|
||||||
|
char c = host_s[i];
|
||||||
|
bool is_alphanum = isalnum(c) || ispunct(c);
|
||||||
|
if (is_alphanum == was_alphanum) {
|
||||||
|
// skip
|
||||||
|
} else if (is_alphanum) {
|
||||||
|
os << white;
|
||||||
|
} else {
|
||||||
|
os << red;
|
||||||
|
}
|
||||||
|
was_alphanum = is_alphanum;
|
||||||
|
|
||||||
|
if (is_alphanum) {
|
||||||
|
os << c;
|
||||||
|
} else {
|
||||||
|
os << "%" << std::setfill('0') << std::setw(2) << std::hex
|
||||||
|
<< (static_cast<unsigned int>(c) & 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os << grey;
|
||||||
|
if (!zoneid.empty())
|
||||||
|
os << "%" << zoneid;
|
||||||
|
if (!port.empty())
|
||||||
|
os << ":" << port;
|
||||||
|
os << path;
|
||||||
|
if (!query.empty())
|
||||||
|
os << "?" << query;
|
||||||
|
if (!fragment.empty())
|
||||||
|
os << "#" << fragment;
|
||||||
|
|
||||||
|
return os.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
34
src/util/colorize.h
Normal file
34
src/util/colorize.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
Part of Minetest
|
||||||
|
Copyright (C) 2024 rubenwardy
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#define COLOR_CODE(color) "\x1b(c@" color ")"
|
||||||
|
|
||||||
|
#ifdef USE_CURL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Colorize URL to highlight the hostname and any unsafe characters
|
||||||
|
*
|
||||||
|
* Throws an exception if the url is invalid
|
||||||
|
*/
|
||||||
|
std::string colorize_url(const std::string &url);
|
||||||
|
|
||||||
|
#endif
|
@ -593,6 +593,40 @@ void str_replace(std::string &str, char from, char to)
|
|||||||
std::replace(str.begin(), str.end(), from, to);
|
std::replace(str.begin(), str.end(), from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string wrap_rows(std::string_view from, unsigned row_len, bool has_color_codes)
|
||||||
|
{
|
||||||
|
std::string to;
|
||||||
|
to.reserve(from.size());
|
||||||
|
std::string last_color_code;
|
||||||
|
|
||||||
|
unsigned character_idx = 0;
|
||||||
|
bool inside_colorize = false;
|
||||||
|
for (size_t i = 0; i < from.size(); i++) {
|
||||||
|
if (!IS_UTF8_MULTB_INNER(from[i])) {
|
||||||
|
if (inside_colorize) {
|
||||||
|
last_color_code += from[i];
|
||||||
|
if (from[i] == ')') {
|
||||||
|
inside_colorize = false;
|
||||||
|
} else {
|
||||||
|
// keep reading
|
||||||
|
}
|
||||||
|
} else if (has_color_codes && from[i] == '\x1b') {
|
||||||
|
inside_colorize = true;
|
||||||
|
last_color_code = "\x1b";
|
||||||
|
} else {
|
||||||
|
// Wrap string after last inner byte of char
|
||||||
|
if (character_idx > 0 && character_idx % row_len == 0) {
|
||||||
|
to += '\n' + last_color_code;
|
||||||
|
}
|
||||||
|
character_idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
to += from[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
/* Translated strings have the following format:
|
/* Translated strings have the following format:
|
||||||
* \x1bT marks the beginning of a translated string
|
* \x1bT marks the beginning of a translated string
|
||||||
* \x1bE marks its end
|
* \x1bE marks its end
|
||||||
|
@ -512,7 +512,7 @@ inline bool string_allowed_blacklist(std::string_view str,
|
|||||||
* Create a string based on \p from where a newline is forcefully inserted
|
* Create a string based on \p from where a newline is forcefully inserted
|
||||||
* every \p row_len characters.
|
* every \p row_len characters.
|
||||||
*
|
*
|
||||||
* @note This function does not honour word wraps and blindy inserts a newline
|
* @note This function does not honour word wraps and blindly inserts a newline
|
||||||
* every \p row_len characters whether it breaks a word or not. It is
|
* every \p row_len characters whether it breaks a word or not. It is
|
||||||
* intended to be used for, for example, showing paths in the GUI.
|
* intended to be used for, for example, showing paths in the GUI.
|
||||||
*
|
*
|
||||||
@ -521,26 +521,10 @@ inline bool string_allowed_blacklist(std::string_view str,
|
|||||||
*
|
*
|
||||||
* @param from The (utf-8) string to be wrapped into rows.
|
* @param from The (utf-8) string to be wrapped into rows.
|
||||||
* @param row_len The row length (in characters).
|
* @param row_len The row length (in characters).
|
||||||
|
* @param has_color_codes Whether the source string has colorize codes.
|
||||||
* @return A new string with the wrapping applied.
|
* @return A new string with the wrapping applied.
|
||||||
*/
|
*/
|
||||||
inline std::string wrap_rows(std::string_view from, unsigned row_len)
|
std::string wrap_rows(std::string_view from, unsigned row_len, bool has_color_codes = false);
|
||||||
{
|
|
||||||
std::string to;
|
|
||||||
to.reserve(from.size());
|
|
||||||
|
|
||||||
unsigned character_idx = 0;
|
|
||||||
for (size_t i = 0; i < from.size(); i++) {
|
|
||||||
if (!IS_UTF8_MULTB_INNER(from[i])) {
|
|
||||||
// Wrap string after last inner byte of char
|
|
||||||
if (character_idx > 0 && character_idx % row_len == 0)
|
|
||||||
to += '\n';
|
|
||||||
character_idx++;
|
|
||||||
}
|
|
||||||
to += from[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return to;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user