summaryrefslogtreecommitdiff
path: root/wx+/no_flicker.h
blob: 7fa4ae23821e798fc9ab67541bd983e109f47530 (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
// *****************************************************************************
// * 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;
        }

    if (std::any_of(blocks.begin(), blocks.end(), [](const auto& item) { return item.first == BlockType::url; }))
    {
        //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 unbindOk = richCtrl.Unbind(wxEVT_TEXT_URL, launchUrl);
        richCtrl.Bind(wxEVT_TEXT_URL, launchUrl);
    }
}
}
}

#endif //NO_FLICKER_H_893421590321532
bgstack15