summaryrefslogtreecommitdiff
path: root/wx+/no_flicker.h
blob: 185ee05239e061220f09617756e7da7ec9fdf249 (plain)
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
156
// *****************************************************************************
// * 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) //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)
        *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 wchar_t urlPrefix[] = L"https://";
        const auto itUrl = std::search(it, newText.end(),
                                       urlPrefix, urlPrefix + strLength(urlPrefix));
        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& richCtrl = 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(richCtrl.CanCopy()); //except when no selection
                    richCtrl.Copy();
                    return;
            }

        if (event.ShiftDown())
            switch (event.GetKeyCode())
            {
                case WXK_INSERT:
                case WXK_NUMPAD_INSERT:
                    assert(richCtrl.CanPaste()); //except wxTE_READONLY
                    richCtrl.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
bgstack15