1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
// *****************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************
#ifndef NO_FLICKER_H_893421590321532
#define NO_FLICKER_H_893421590321532
#include <zen/string_tools.h>
#include <zen/scope_guard.h>
#include <wx/textctrl.h>
#include <wx/stattext.h>
#include <wx/richtext/richtextctrl.h>
#include <wx/wupdlock.h>
namespace zen
{
namespace
{
void setText(wxTextCtrl& control, const wxString& newText, bool* additionalLayoutChange = nullptr)
{
const wxString& label = control.GetValue(); //perf: don't call twice!
if (additionalLayoutChange && !*additionalLayoutChange && control.IsShown()) //never revert from true to false!
*additionalLayoutChange = label.length() != newText.length(); //avoid screen flicker: update layout only when necessary
if (label != newText)
control.ChangeValue(newText);
}
void setText(wxStaticText& control, const wxString& newText, bool* additionalLayoutChange = nullptr)
{
//wxControl::EscapeMnemonics() (& -> &&) => wxControl::GetLabelText/SetLabelText
//e.g. "filenames in the sync progress dialog": https://sourceforge.net/p/freefilesync/bugs/279/
const wxString& label = control.GetLabelText(); //perf: don't call twice!
if (additionalLayoutChange && !*additionalLayoutChange && control.IsShown()) //"better" or overkill(?): IsShownOnScreen()
*additionalLayoutChange = label.length() != newText.length(); //avoid screen flicker: update layout only when necessary
if (label != newText)
control.SetLabelText(newText);
}
void setTextWithUrls(wxRichTextCtrl& richCtrl, const wxString& newText)
{
enum class BlockType
{
text,
url,
};
std::vector<std::pair<BlockType, wxString>> blocks;
for (auto it = newText.begin();;)
{
const std::wstring_view urlPrefix = L"https://";
const auto itUrl = std::search(it, newText.end(), urlPrefix.begin(), urlPrefix.end());
if (it != itUrl)
blocks.emplace_back(BlockType::text, wxString(it, itUrl));
if (itUrl == newText.end())
break;
auto itUrlEnd = std::find_if(itUrl, newText.end(), [](wchar_t c) { return isWhiteSpace(c); });
blocks.emplace_back(BlockType::url, wxString(itUrl, itUrlEnd));
it = itUrlEnd;
}
richCtrl.BeginSuppressUndo();
ZEN_ON_SCOPE_EXIT(richCtrl.EndSuppressUndo());
//fix mouse scroll speed: why the FUCK is this even necessary!
richCtrl.SetLineHeight(richCtrl.GetCharHeight());
//get rid of margins and space between text blocks/"paragraphs"
richCtrl.SetMargins({0, 0});
richCtrl.BeginParagraphSpacing(0, 0);
ZEN_ON_SCOPE_EXIT(richCtrl.EndParagraphSpacing());
richCtrl.Clear();
wxRichTextAttr urlStyle;
urlStyle.SetTextColour(*wxBLUE);
urlStyle.SetFontUnderlined(true);
for (auto& [type, text] : blocks)
switch (type)
{
case BlockType::text:
if (endsWith(text, L"\n\n")) //bug: multiple newlines before a URL are condensed to only one;
//Why? fuck knows why! no such issue with double newlines *after* URL => hack this shit
text.RemoveLast().Append(ZERO_WIDTH_SPACE).Append(L'\n');
richCtrl.WriteText(text);
break;
case BlockType::url:
{
richCtrl.BeginStyle(urlStyle);
ZEN_ON_SCOPE_EXIT(richCtrl.EndStyle());
richCtrl.BeginURL(text);
ZEN_ON_SCOPE_EXIT(richCtrl.EndURL());
richCtrl.WriteText(text);
}
break;
}
//register only once! => use a global function pointer, so that Unbind() works correctly:
using LaunchUrlFun = void(*)(wxTextUrlEvent& event);
static const LaunchUrlFun launchUrl = [](wxTextUrlEvent& event) { wxLaunchDefaultBrowser(event.GetString()); };
[[maybe_unused]] const bool unbindOk1 = richCtrl.Unbind(wxEVT_TEXT_URL, launchUrl);
if (std::any_of(blocks.begin(), blocks.end(), [](const auto& item) { return item.first == BlockType::url; }))
/**/richCtrl.Bind(wxEVT_TEXT_URL, launchUrl);
struct UserData : public wxObject
{
explicit UserData(wxRichTextCtrl& rtc) : richCtrl(rtc) {}
wxRichTextCtrl& richCtrl;
};
using KeyEventsFun = void(*)(wxKeyEvent& event);
static const KeyEventsFun onKeyEvents = [](wxKeyEvent& event)
{
wxRichTextCtrl& richCtrl2 = dynamic_cast<UserData*>(event.GetEventUserData())->richCtrl; //unclear if we can rely on event.GetEventObject() == richCtrl
//CTRL/SHIFT + INS is broken for wxRichTextCtrl on Windows/Linux (apparently never was a thing on macOS)
if (event.ControlDown())
switch (event.GetKeyCode())
{
case WXK_INSERT:
case WXK_NUMPAD_INSERT:
assert(richCtrl2.CanCopy()); //except when no selection
richCtrl2.Copy();
return;
}
if (event.ShiftDown())
switch (event.GetKeyCode())
{
case WXK_INSERT:
case WXK_NUMPAD_INSERT:
assert(richCtrl2.CanPaste()); //except wxTE_READONLY
richCtrl2.Paste();
return;
}
event.Skip();
};
[[maybe_unused]] const bool unbindOk2 = richCtrl.Unbind(wxEVT_KEY_DOWN, onKeyEvents);
/**/ richCtrl. Bind(wxEVT_KEY_DOWN, onKeyEvents, wxID_ANY, wxID_ANY, new UserData(richCtrl) /*pass ownership*/);
}
}
}
#endif //NO_FLICKER_H_893421590321532
|