source: filezilla/trunk/fuentes/src/interface/wrapengine.cpp @ 130

Last change on this file since 130 was 130, checked in by jrpelegrina, 4 years ago

First release to xenial

File size: 28.1 KB
Line 
1#include <filezilla.h>
2#include "wrapengine.h"
3#include "filezillaapp.h"
4#include "ipcmutex.h"
5#include "xmlfunctions.h"
6#include "buildinfo.h"
7#include "Options.h"
8#include "local_filesys.h"
9
10#include <wx/statbox.h>
11#include <wx/wizard.h>
12
13#ifdef __WXGTK3__
14#include <gtk/gtk.h>
15#endif
16
17#include <algorithm>
18
19bool CWrapEngine::m_use_cache = true;
20
21#define WRAPDEBUG 0
22
23#if WRAPDEBUG
24#define WRAPASSERT(x) wxASSERT(x)
25#else
26#define WRAPASSERT(x)
27#endif
28
29// Chinese equivalents to ".", "," and ":"
30static const wxChar noWrapChars_Chinese[] = { '.', ',', ':', 0x3002, 0xFF0C, 0xFF1A, 0};
31// Remark: Chinese (Taiwan) uses ascii punctuation marks though, but those
32// don't have to be added, as only characters >= 128 will be wrapped.
33
34bool CWrapEngine::CanWrapBefore(const wxChar& c)
35{
36        // Check if this is a punctuation character, we're not allowed
37        // to wrap before such a character
38        for (auto p = m_noWrapChars; p && *p; ++p) {
39                if (*p == c) {
40                        return false;
41                }
42        }
43        return true;
44}
45
46bool CWrapEngine::WrapTextChinese(wxWindow* parent, wxString &text, unsigned long maxLength)
47{
48        WRAPASSERT(text.Find(_T("  ")) == -1);
49        WRAPASSERT(text.Find(_T(" \n")) == -1);
50        WRAPASSERT(text.Find(_T("\n ")) == -1);
51        WRAPASSERT(text.empty() || text.Last() != ' ');
52        WRAPASSERT(text.empty() || text.Last() != '\n');
53
54        // See comment at start of WrapText function what this function does
55        wxString wrappedText;
56
57        int width = 0, height = 0;
58
59        const wxChar* str = text.c_str();
60        // Scan entire string
61        while (*str)
62        {
63                unsigned int lineLength = 0;
64
65                const wxChar* p = str;
66
67                // Position of last wrappable character
68                const wxChar* wrappable = 0;
69
70                bool lastAmpersand = false;
71                while (*p)
72                {
73                        if (*p == '&')
74                        {
75                                if (!lastAmpersand)
76                                {
77                                        lastAmpersand = true;
78                                        p++;
79                                        continue;
80                                }
81                                else
82                                        lastAmpersand = false;
83                        }
84                        std::map<wxChar, unsigned int>::const_iterator iter = m_charWidths.find(*p);
85                        if (iter == m_charWidths.end())
86                        {
87                                // Get width of all individual characters, record width of the current line
88                                parent->GetTextExtent(*p, &width, &height, 0, 0, &m_font);
89                                if ((unsigned int)width > maxLength)
90                                        return false;
91                                m_charWidths[*p] = width;
92                                lineLength += width;
93                        }
94                        else
95                                lineLength += iter->second;
96
97                        WRAPASSERT(*p != '\r');
98                        if (*p == '\n')
99                        {
100                                // Wrap on newline
101                                wrappedText += wxString(str, p - str + 1);
102                                str = p + 1;
103                                break;
104                        }
105                        else if (p != str) // Don't wrap at first character
106                        {
107                                if (*p == ' ')
108                                        // Remember position of last space
109                                        wrappable = p;
110                                else if (*p >= 128)
111                                {
112                                        if (CanWrapBefore(*p))
113                                                wrappable = p;
114                                }
115                                else if (*(p - 1) >= 128 && CanWrapBefore(*p))
116                                {
117                                        // Beginning of embedded English text, can wrap before
118                                        wrappable = p;
119                                }
120                        }
121
122                        if (lineLength > maxLength && wrappable)
123                        {
124                                wxString tmp = wxString(str, wrappable - str);
125                                if (!tmp.empty() && tmp.Last() == ' ')
126                                        tmp.RemoveLast();
127                                wrappedText += tmp + _T("\n");
128                                if (*wrappable != ' ')
129                                        str = wrappable;
130                                else
131                                        str = wrappable + 1;
132                                break;
133                        }
134
135                        p++;
136                }
137                if (!*p)
138                {
139                        if (lineLength > maxLength)
140                        {
141                                if (!wrappable)
142                                        return false;
143
144                                const wxString& tmp = wxString(str, wrappable - str);
145                                wrappedText += tmp + _T("\n");
146                                if (*wrappable != ' ')
147                                        str = wrappable;
148                                else
149                                        str = wrappable + 1;
150                        }
151                        wrappedText += str;
152                        break;
153                }
154        }
155
156#if WRAPDEBUG
157        wxString temp = wrappedText;
158        wxASSERT(temp.Find(_T("  ")) == -1);
159        wxASSERT(temp.Find(_T(" \n")) == -1);
160        wxASSERT(temp.Find(_T("\n ")) == -1);
161        wxASSERT(temp.empty() || temp.Last() != ' ');
162        wxASSERT(temp.empty() || temp.Last() != '\n');
163        temp.Replace(_T("&"), _T(""));
164        while (!temp.empty())
165        {
166                wxString piece;
167                int pos = temp.Find(_T("\n"));
168                if (pos == -1)
169                {
170                        piece = temp;
171                        temp = _T("");
172                }
173                else
174                {
175                        piece = temp.Left(pos);
176                        temp = temp.Mid(pos + 1);
177                }
178                parent->GetTextExtent(piece, &width, &height, 0, 0, &m_font);
179                wxASSERT(width <= (int)maxLength);
180        }
181#endif
182
183        text = wrappedText;
184
185        return true;
186}
187
188bool CWrapEngine::WrapText(wxWindow* parent, wxString& text, unsigned long maxLength)
189{
190        /*
191        This function wraps the given string so that it's width in pixels does
192        not exceed maxLength.
193        In the general case, wrapping is done on word boundaries. Thus we scan the
194        string for spaces, measuer the length of the words and wrap if line becomes
195        too long.
196        It has to be done wordwise, as with some languages/fonts, the width in
197        pixels of a line is smaller than the sum of the widths of every character.
198
199        A special case are some languages, e.g. Chinese, which don't separate words
200        with spaces. In such languages it is allowed to break lines after any
201        character.
202
203        Though there are a few exceptions:
204        - Don't wrap before punctuation marks
205        - Wrap embedded English text fragments only on spaces
206
207        For this kind of languages, a different wrapping algorithm is used.
208        */
209
210        if (!m_font.IsOk())
211                m_font = parent->GetFont();
212
213#if WRAPDEBUG
214        const wxString original = text;
215#endif
216
217        if (m_wrapOnEveryChar)
218        {
219                bool res = WrapTextChinese(parent, text, maxLength);
220#if WRAPDEBUG
221                wxString unwrapped = UnwrapText(text);
222                wxASSERT(original == unwrapped);
223#endif
224                return res;
225        }
226
227        wxString wrappedText;
228
229        int width = 0, height = 0;
230
231        if (m_spaceWidth == -1) {
232                parent->GetTextExtent(_T(" "), &m_spaceWidth, &height, 0, 0, &m_font);
233        }
234
235        int strLen = text.Length();
236        int wrapAfter = -1;
237        int start = 0;
238        unsigned int lineLength = 0;
239
240        bool url = false;
241        bool containsURL = false;
242        for (int i = 0; i <= strLen; i++) {
243                if ((i < strLen - 2 && text[i] == ':' && text[i + 1] == '/' && text[i + 2] == '/') || // absolute
244                        (i < strLen && text[i] == '/' && (!i || text[i - 1] == ' '))) // relative
245                {
246                        url = true;
247                        containsURL = true;
248                }
249                if (i < strLen && text[i] != ' ') {
250                        // If url, wrap on slashes and ampersands, but not first slash of something://
251                        if (!url ||
252                                 ((i < strLen - 1 && (text[i] != '/' || text[i + 1] == '/')) && (i < strLen - 1 && (text[i] != '&' || text[i + 1] == '&')) && text[i] != '?'))
253                        continue;
254                }
255
256                wxString segment;
257                if (wrapAfter == -1) {
258                        if (i < strLen && (text[i] == '/' || text[i] == '?' || text[i] == '&'))
259                                segment = text.Mid(start, i - start + 1);
260                        else
261                                segment = text.Mid(start, i - start);
262                        wrapAfter = i;
263                }
264                else {
265                        if (i < strLen && (text[i] == '/' || text[i] == '?' || text[i] == '&'))
266                                segment = text.Mid(wrapAfter + 1, i - wrapAfter);
267                        else
268                                segment = text.Mid(wrapAfter + 1, i - wrapAfter - 1);
269                }
270
271                segment = wxStripMenuCodes(segment);
272                parent->GetTextExtent(segment, &width, &height, 0, 0, &m_font);
273
274                if (lineLength + m_spaceWidth + width > maxLength) {
275                        // Cannot be appended to current line without overflow, so start a new line
276                        if (!wrappedText.empty())
277                                wrappedText += _T("\n");
278                        wrappedText += text.Mid(start, wrapAfter - start);
279                        if (wrapAfter < strLen && text[wrapAfter] != ' ' && text[wrapAfter] != '\0')
280                                wrappedText += text[wrapAfter];
281
282                        if (width + m_spaceWidth >= (int)maxLength) {
283                                // Current segment too big to even fit into a line just by itself
284
285                                if( i != wrapAfter ) {
286                                        if (!wrappedText.empty())
287                                                wrappedText += _T("\n");
288                                        wrappedText += text.Mid(wrapAfter + 1, i - wrapAfter - 1);
289                                }
290
291                                start = i + 1;
292                                wrapAfter = -1;
293                                lineLength = 0;
294                        }
295                        else {
296                                start = wrapAfter + 1;
297                                wrapAfter = i;
298                                lineLength = width;
299                        }
300                }
301                else if (lineLength + m_spaceWidth + width + m_spaceWidth >= maxLength) {
302                        if (!wrappedText.empty())
303                                wrappedText += _T("\n");
304                        wrappedText += text.Mid(start, i - start);
305                        if (i < strLen && text[i] != ' ' && text[i] != '\0')
306                                wrappedText += text[i];
307                        start = i + 1;
308                        wrapAfter = -1;
309                        lineLength = 0;
310                }
311                else {
312                        if (lineLength)
313                                lineLength += m_spaceWidth;
314                        lineLength += width;
315                        wrapAfter = i;
316                }
317
318                if (i < strLen && text[i] == ' ')
319                        url = false;
320        }
321        if (start < strLen) {
322                if (!wrappedText.empty())
323                        wrappedText += _T("\n");
324                wrappedText += text.Mid(start);
325        }
326
327        text = wrappedText;
328
329#if WRAPDEBUG
330                wxString unwrapped = UnwrapText(text);
331                wxASSERT(original == unwrapped || containsURL);
332#else
333        (void)containsURL;
334#endif
335        return true;
336}
337
338bool CWrapEngine::WrapText(wxWindow* parent, int id, unsigned long maxLength)
339{
340        wxStaticText* pText = wxDynamicCast(parent->FindWindow(id), wxStaticText);
341        if (!pText)
342                return false;
343
344        wxString text = pText->GetLabel();
345        if (!WrapText(parent, text, maxLength))
346                return false;
347
348        pText->SetLabel(text);
349
350        return true;
351}
352
353#if WRAPDEBUG >= 3
354        #define plvl { for (int i = 0; i < level; i++) printf(" "); }
355#endif
356
357int CWrapEngine::WrapRecursive(wxWindow* wnd, wxSizer* sizer, int max)
358{
359        // This function auto-wraps static texts.
360
361#if WRAPDEBUG >= 3
362        static int level = 1;
363        plvl printf("Enter with max = %d, current = %d, sizer is %s\n", max, wnd ? wnd->GetRect().GetWidth() : -1, static_cast<char const*>(wxString(sizer->GetClassInfo()->GetClassName())));
364#endif
365
366        if (max <= 0)
367        {
368#if WRAPDEBUG >= 3
369                plvl printf("Leave: max <= 0\n");
370#endif
371                return wrap_failed;
372        }
373
374        int result = 0;
375
376        for (unsigned int i = 0; i < sizer->GetChildren().GetCount(); ++i) {
377                wxSizerItem* item = sizer->GetItem(i);
378                if (!item || !item->IsShown())
379                        continue;
380
381                int rborder = 0;
382                if (item->GetFlag() & wxRIGHT)
383                        rborder = item->GetBorder();
384                int lborder = 0;
385                if (item->GetFlag() & wxLEFT)
386                        lborder = item->GetBorder();
387
388                wxRect rect = item->GetRect();
389
390                wxSize min = item->GetMinSize();
391                if (!min.IsFullySpecified())
392                        min = item->CalcMin();
393                wxASSERT(min.GetWidth() + rborder + lborder <= sizer->GetMinSize().GetWidth());
394
395                if (min.GetWidth() + item->GetPosition().x + lborder + rborder <= max)
396                        continue;
397
398                wxWindow* window;
399                wxSizer* subSizer = 0;
400                if ((window = item->GetWindow())) {
401                        wxStaticText* text = wxDynamicCast(window, wxStaticText);
402                        if (text) {
403#ifdef __WXMAC__
404                                const int offset = 3;
405#else
406                                const int offset = 2;
407#endif
408                                if (max - rect.GetLeft() - rborder - offset <= 0)
409                                        continue;
410
411                                wxString str = text->GetLabel();
412                                if (!WrapText(text, str, max - wxMax(0, rect.GetLeft()) - rborder - offset))
413                                {
414#if WRAPDEBUG >= 3
415                                        plvl printf("Leave: WrapText failed\n");
416#endif
417                                        return result | wrap_failed;
418                                }
419                                text->SetLabel(str);
420                                result |= wrap_didwrap;
421
422#ifdef __WXGTK3__
423                                gtk_widget_set_size_request(text->GetHandle(), -1, -1);
424                                GtkRequisition req;
425                                gtk_widget_get_preferred_size(text->GetHandle(), 0, &req);
426                                text->CacheBestSize(wxSize(req.width, req.height));
427#endif
428                                continue;
429                        }
430
431                        wxNotebook* book = wxDynamicCast(window, wxNotebook);
432                        if (book) {
433                                int maxPageWidth = 0;
434                                for (unsigned int j = 0; j < book->GetPageCount(); ++j) {
435                                        wxNotebookPage* page = book->GetPage(j);
436                                        maxPageWidth = wxMax(maxPageWidth, page->GetRect().GetWidth());
437                                }
438
439                                for (unsigned int j = 0; j < book->GetPageCount(); ++j) {
440                                        wxNotebookPage* page = book->GetPage(j);
441                                        wxRect pageRect = page->GetRect();
442                                        int pageMax = max - rect.GetLeft() - pageRect.GetLeft() - rborder - rect.GetWidth() + maxPageWidth;
443
444                                        result |= WrapRecursive(wnd, page->GetSizer(), pageMax);
445                                        if (result & wrap_failed) {
446#if WRAPDEBUG >= 3
447                                                plvl printf("Leave: WrapRecursive on notebook page failed\n");
448#endif
449                                                return result;
450                                        }
451                                }
452                                continue;
453                        }
454
455                        if (wxDynamicCast(window, wxCheckBox) || wxDynamicCast(window, wxRadioButton) || wxDynamicCast(window, wxChoice))
456                        {
457#if WRAPDEBUG >= 3
458                                plvl printf("Leave: WrapRecursive on unshrinkable control failed: %s\n",
459                                        static_cast<char const*>(wxString(window->GetClassInfo()->GetClassName())));
460#endif
461                                result |= wrap_failed;
462                                return result;
463                        }
464
465                        // We assume here that all other oversized controls can scale
466                }
467                else if ((subSizer = item->GetSizer()))
468                {
469                        int subBorder = 0;
470                        wxWindow* subWnd = wnd;
471
472                        // Add border of static box sizer
473                        wxStaticBoxSizer* sboxSizer;
474                        if ((sboxSizer = wxDynamicCast(subSizer, wxStaticBoxSizer)))
475                        {
476                                int top, other;
477                                sboxSizer->GetStaticBox()->GetBordersForSizer(&top, &other);
478                                subBorder += other * 2;
479                                subWnd = sboxSizer->GetStaticBox();
480                        }
481
482#if WRAPDEBUG >= 3
483                        level++;
484#endif
485                        result |= WrapRecursive(subWnd, subSizer, max - lborder - rborder - subBorder);
486#if WRAPDEBUG >= 3
487                        level--;
488#endif
489                        if (result & wrap_failed)
490                        {
491#if WRAPDEBUG >= 3
492                                plvl printf("Leave: WrapRecursive on sizer failed\n");
493#endif
494                                return result;
495                        }
496                }
497        }
498
499        wxStaticBoxSizer* sboxSizer = wxDynamicCast(sizer, wxStaticBoxSizer);
500        if( sboxSizer ) {
501#ifdef __WXGTK3__
502                gtk_widget_set_size_request(sboxSizer->GetStaticBox()->GetHandle(), -1, -1);
503                GtkRequisition req;
504                gtk_widget_get_preferred_size(sboxSizer->GetStaticBox()->GetHandle(), 0, &req);
505                sboxSizer->GetStaticBox()->CacheBestSize(wxSize(req.width, req.height));
506#elif defined(__WXMAC__) || defined(__WXGTK__)
507                sboxSizer->GetStaticBox()->CacheBestSize(wxSize(0, 0));
508#else
509                sboxSizer->GetStaticBox()->CacheBestSize(wxDefaultSize);
510#endif
511        }
512
513#if WRAPDEBUG >= 3
514        plvl printf("Leave: Success, new min: %d\n", sizer->CalcMin().x);
515#endif
516
517        return result;
518}
519
520bool CWrapEngine::WrapRecursive(wxWindow* wnd, double ratio, const char* name /*=""*/, wxSize canvas /*=wxSize()*/, wxSize minRequestedSize /*wxSize()*/)
521{
522        std::vector<wxWindow*> windows;
523        windows.push_back(wnd);
524        return (WrapRecursive(windows, ratio, name, canvas, minRequestedSize) & wrap_failed) == 0;
525}
526
527void CWrapEngine::UnwrapRecursive_Wrapped(std::vector<int> const& wrapped, std::vector<wxWindow*> &windows, bool remove_fitting /*=false*/)
528{
529        unsigned int i = 0;
530        for (auto const& wrap : wrapped) {
531                UnwrapRecursive(windows[i], windows[i]->GetSizer());
532                windows[i]->GetSizer()->Layout();
533
534                if (!(wrap & wrap_didwrap) && !(wrap & wrap_failed)) {
535                        if (!wrap && remove_fitting) {
536                                // Page didn't need to be wrapped with current wrap offset,
537                                // remove it since desired width will only be larger in further wrappings.
538                                windows.erase(windows.begin() + i);
539                                continue;
540                        }
541                }
542
543                i++;
544        }
545}
546
547bool CWrapEngine::WrapRecursive(std::vector<wxWindow*>& windows, double ratio, const char* name, wxSize canvas, wxSize const& minRequestedSize)
548{
549#ifdef __WXMAC__
550        const int offset = 6;
551#elif defined(__WXGTK__)
552        const int offset = 0;
553#else
554        const int offset = 0;
555#endif
556
557        int maxWidth = GetWidthFromCache(name);
558        if (maxWidth) {
559                for (auto iter = windows.begin(); iter != windows.end(); ++iter) {
560                        wxSizer* pSizer = (*iter)->GetSizer();
561                        if (!pSizer)
562                                continue;
563
564                        pSizer->Layout();
565
566#if WRAPDEBUG
567                        int res =
568#endif
569                        WrapRecursive(*iter, pSizer, maxWidth - offset);
570                        WRAPASSERT(!(res & wrap_failed));
571                        pSizer->Layout();
572                        pSizer->Fit(*iter);
573#if WRAPDEBUG
574                        wxSize size = pSizer->GetMinSize();
575#endif
576                        WRAPASSERT(size.x <= maxWidth);
577                }
578                return true;
579        }
580
581        std::vector<wxWindow*> all_windows = windows;
582
583        wxSize size = minRequestedSize;
584
585        for (auto const& window : windows) {
586                wxSizer* pSizer = window->GetSizer();
587                if (!pSizer)
588                        return false;
589
590                pSizer->Layout();
591                size.IncTo(pSizer->GetMinSize());
592        }
593
594        double currentRatio = ((double)(size.GetWidth() + canvas.x) / (size.GetHeight() + canvas.y));
595        if (ratio >= currentRatio) {
596                // Nothing to do, can't wrap anything
597                return true;
598        }
599
600        int max = size.GetWidth();
601        int min = wxMin(size.GetWidth(), size.GetHeight());
602        if (ratio < 0)
603                min = (int)(min * ratio);
604        if (min > canvas.x)
605                min -= canvas.x;
606        int desiredWidth = (min + max) / 2;
607        int actualWidth = size.GetWidth();
608
609        double bestRatioDiff = currentRatio - ratio;
610        int bestWidth = max;
611
612#if WRAPDEBUG > 0
613        printf("Target ratio: %f\n", (float)ratio);
614        printf("Canvas: % 4d % 4d\n", canvas.x, canvas.y);
615        printf("Initial min and max: %d %d\n", min, max);
616#endif
617
618        for (;;) {
619                std::vector<int> didwrap;
620
621                size = minRequestedSize;
622                int res = 0;
623                for (auto const& window : windows) {
624                        wxSizer* pSizer = window->GetSizer();
625                        res = WrapRecursive(window, pSizer, desiredWidth - offset);
626                        if (res & wrap_didwrap)
627                                pSizer->Layout();
628                        didwrap.push_back(res);
629                        wxSize minSize = pSizer->GetMinSize();
630                        if (minSize.x > desiredWidth)
631                                res |= wrap_failed;
632                        size.IncTo(minSize);
633                        if (res & wrap_failed)
634                                break;
635                }
636
637#if WRAPDEBUG > 0
638                printf("After current wrapping: size=%dx%d  desiredWidth=%d  min=%d  max=%d  res=%d\n", size.GetWidth(), size.GetHeight(), desiredWidth, min, max, res);
639#endif
640                if (size.GetWidth() > desiredWidth) {
641                        // Wrapping failed
642
643                        UnwrapRecursive_Wrapped(didwrap, windows, true);
644
645                        min = desiredWidth;
646                        if (max - min < 5)
647                                break;
648
649                        desiredWidth = (min + max) / 2;
650
651#if WRAPDEBUG > 0
652                        printf("Wrapping failed, new min: %d\n", min);
653#endif
654                        continue;
655                }
656                actualWidth = size.GetWidth();
657
658                double newRatio = ((double)(size.GetWidth() + canvas.x) / (size.GetHeight() + canvas.y));
659#if WRAPDEBUG > 0
660                printf("New ratio: %f\n", (float)newRatio);
661#endif
662
663                if (newRatio < ratio) {
664                        UnwrapRecursive_Wrapped(didwrap, windows, true);
665
666                        if (ratio - newRatio < bestRatioDiff) {
667                                bestRatioDiff = ratio - newRatio;
668                                bestWidth = std::max(desiredWidth, actualWidth);
669                        }
670
671                        if (min >= actualWidth)
672                                min = desiredWidth;
673                        else
674                                min = actualWidth;
675                }
676                else if (newRatio > ratio) {
677                        UnwrapRecursive_Wrapped(didwrap, windows);
678                        if (newRatio - ratio < bestRatioDiff) {
679                                bestRatioDiff = newRatio - ratio;
680                                bestWidth = std::max(desiredWidth, actualWidth);
681                        }
682
683                        if (max == actualWidth)
684                                break;
685                        max = std::max(desiredWidth, actualWidth);
686                }
687                else {
688                        UnwrapRecursive_Wrapped(didwrap, windows);
689
690                        bestRatioDiff = ratio - newRatio;
691                        max = std::max(desiredWidth, actualWidth);
692                        break;
693                }
694
695                if (max - min < 2)
696                        break;
697                desiredWidth = (min + max) / 2;
698        }
699#if WRAPDEBUG > 0
700                printf("Performing final wrap with bestwidth %d\n", bestWidth);
701#endif
702
703        for (auto const& window : all_windows) {
704                wxSizer *pSizer = window->GetSizer();
705
706                int res = WrapRecursive(window, pSizer, bestWidth - offset);
707
708                if (res & wrap_didwrap) {
709                        pSizer->Layout();
710                        pSizer->Fit(window);
711                }
712#if WRAPDEBUG
713                size = pSizer->GetMinSize();
714                WRAPASSERT(size.x <= bestWidth);
715#endif
716        }
717
718        SetWidthToCache(name, bestWidth);
719
720        return true;
721}
722
723wxString CWrapEngine::UnwrapText(const wxString& text)
724{
725        wxString unwrapped;
726        int lang = wxGetApp().GetCurrentLanguage();
727        if (lang == wxLANGUAGE_CHINESE || lang == wxLANGUAGE_CHINESE_SIMPLIFIED ||
728                lang == wxLANGUAGE_CHINESE_TRADITIONAL || lang == wxLANGUAGE_CHINESE_HONGKONG ||
729                lang == wxLANGUAGE_CHINESE_MACAU || lang == wxLANGUAGE_CHINESE_SINGAPORE ||
730                lang == wxLANGUAGE_CHINESE_TAIWAN)
731        {
732                wxChar const* p = text.c_str();
733                bool wasAscii = false;
734                while (*p) {
735                        if (*p == '\n') {
736                                if (wasAscii)
737                                        unwrapped += ' ';
738                                else if (*(p + 1) < 127)
739                                {
740                                        if ((*(p + 1) != '(' || *(p + 2) != '&') && CanWrapBefore(*(p - 1)))
741                                                unwrapped += ' ';
742                                }
743                        }
744                        else if (*p != '\r')
745                                unwrapped += *p;
746
747                        if (*p < 127)
748                                wasAscii = true;
749                        else
750                                wasAscii = false;
751
752                        p++;
753                }
754        }
755        else
756        {
757                unwrapped = text;
758
759                // Special handling for unwrapping of URLs
760                int pos;
761                while ( (pos = unwrapped.Find(_T("&&\n"))) > 0 )
762                {
763                        if (unwrapped[pos - 1] == ' ')
764                                unwrapped = unwrapped.Left(pos + 2) + _T(" ") + unwrapped.Mid(pos + 3);
765                        else
766                                unwrapped = unwrapped.Left(pos + 2) + unwrapped.Mid(pos + 3);
767                }
768
769                unwrapped.Replace(_T("\n"), _T(" "));
770                unwrapped.Replace(_T("\r"), _T(""));
771        }
772        return unwrapped;
773}
774
775bool CWrapEngine::UnwrapRecursive(wxWindow* wnd, wxSizer* sizer)
776{
777        for (unsigned int i = 0; i < sizer->GetChildren().GetCount(); i++)
778        {
779                wxSizerItem* item = sizer->GetItem(i);
780                if (!item)
781                        continue;
782
783                wxWindow* window;
784                wxSizer* subSizer;
785                if ((window = item->GetWindow()))
786                {
787                        wxStaticText* text = wxDynamicCast(window, wxStaticText);
788                        if (text)
789                        {
790                                wxString unwrapped = UnwrapText(text->GetLabel());
791                                text->SetLabel(unwrapped);
792
793                                continue;
794                        }
795
796                        wxNotebook* book = wxDynamicCast(window, wxNotebook);
797                        if (book)
798                        {
799                                for (unsigned int j = 0; j < book->GetPageCount(); ++j)
800                                {
801                                        wxNotebookPage* page = book->GetPage(j);
802                                        UnwrapRecursive(wnd, page->GetSizer());
803                                }
804                                continue;
805                        }
806                }
807                else if ((subSizer = item->GetSizer()))
808                {
809                        UnwrapRecursive(wnd, subSizer);
810                }
811        }
812
813        return true;
814}
815
816int CWrapEngine::GetWidthFromCache(const char* name)
817{
818        if (!m_use_cache)
819                return 0;
820
821        if (!name || !*name)
822                return 0;
823
824        // We have to synchronize access to layout.xml so that multiple processes don't write
825        // to the same file or one is reading while the other one writes.
826        CInterProcessMutex mutex(MUTEX_LAYOUT);
827
828        CXmlFile xml(wxGetApp().GetSettingsFile(_T("layout")));
829        auto root = xml.Load();
830        auto  element = root.child("Layout");
831        if (!element) {
832                return 0;
833        }
834
835        wxString language = wxGetApp().GetCurrentLanguageCode();
836        if (language.empty())
837                language = _T("default");
838
839        auto xLanguage = FindElementWithAttribute(element, "Language", "id", language.mb_str());
840        if (!xLanguage) {
841                return 0;
842        }
843
844        auto dialog = FindElementWithAttribute(xLanguage, "Dialog", "name", name);
845        if (!dialog) {
846                return 0;
847        }
848
849        int value = GetAttributeInt(dialog, "width");
850
851        return value;
852}
853
854void CWrapEngine::SetWidthToCache(const char* name, int width)
855{
856        if (!m_use_cache)
857                return;
858
859        if (!name || !*name)
860                return;
861
862        // We have to synchronize access to layout.xml so that multiple processes don't write
863        // to the same file or one is reading while the other one writes.
864        CInterProcessMutex mutex(MUTEX_LAYOUT);
865
866        CXmlFile xml(wxGetApp().GetSettingsFile(_T("layout")));
867        auto root = xml.Load();
868        auto element = root.child("Layout");
869        if (!element) {
870                return;
871        }
872
873        wxString language = wxGetApp().GetCurrentLanguageCode();
874        if (language.empty())
875                language = _T("default");
876
877        auto xLanguage = FindElementWithAttribute(element, "Language", "id", language.mb_str());
878        if (!xLanguage) {
879                return;
880        }
881
882        auto dialog = FindElementWithAttribute(xLanguage, "Dialog", "name", name);
883        if (!dialog) {
884                dialog = xLanguage.append_child("Dialog");
885                SetTextAttribute(dialog, "name", name);
886        }
887
888        SetAttributeInt(dialog, "width", width);
889        xml.Save(false);
890}
891
892CWrapEngine::CWrapEngine()
893{
894        CheckLanguage();
895}
896
897static wxString GetLocaleFile(const wxString& localesDir, wxString name)
898{
899        if (wxFileName::FileExists(localesDir + name + _T("/filezilla.mo")))
900                return name;
901        if (wxFileName::FileExists(localesDir + name + _T("/LC_MESSAGES/filezilla.mo")))
902                return name + _T("/LC_MESSAGES");
903
904        size_t pos = name.Find('@');
905        if (pos > 0)
906        {
907                name = name.Left(pos);
908                if (wxFileName::FileExists(localesDir + name + _T("/filezilla.mo")))
909                        return name;
910                if (wxFileName::FileExists(localesDir + name + _T("/LC_MESSAGES/filezilla.mo")))
911                        return name + _T("/LC_MESSAGES");
912        }
913
914        pos = name.Find('_');
915        if (pos > 0)
916        {
917                name = name.Left(pos);
918                if (wxFileName::FileExists(localesDir + name + _T("/filezilla.mo")))
919                        return name;
920                if (wxFileName::FileExists(localesDir + name + _T("/LC_MESSAGES/filezilla.mo")))
921                        return name + _T("/LC_MESSAGES");
922        }
923
924        return wxString();
925}
926
927bool CWrapEngine::LoadCache()
928{
929        // We have to synchronize access to layout.xml so that multiple processes don't write
930        // to the same file or one is reading while the other one writes.
931        CInterProcessMutex mutex(MUTEX_LAYOUT);
932
933        CXmlFile xml(wxGetApp().GetSettingsFile(_T("layout")));
934        auto document = xml.Load();
935
936        if (!document) {
937                m_use_cache = false;
938                wxMessageBoxEx(xml.GetError(), _("Error loading xml file"), wxICON_ERROR);
939
940                return false;
941        }
942
943        bool cacheValid = true;
944
945        auto layoutElement = document.child("Layout");
946        if (!layoutElement)
947                layoutElement = document.append_child("Layout");
948
949        const wxString buildDate = CBuildInfo::GetBuildDateString();
950        if (GetTextAttribute(layoutElement, "Builddate") != buildDate) {
951                cacheValid = false;
952                SetTextAttribute(layoutElement, "Builddate", buildDate);
953        }
954
955        const wxString buildTime = CBuildInfo::GetBuildTimeString();
956        if (GetTextAttribute(layoutElement, "Buildtime") != buildTime) {
957                cacheValid = false;
958                SetTextAttribute(layoutElement, "Buildtime", buildTime);
959        }
960
961        // Enumerate resource file names
962        // -----------------------------
963
964        auto resources = layoutElement.child("Resources");
965        if (!resources)
966                resources = layoutElement.append_child("Resources");
967
968        CLocalPath resourceDir = wxGetApp().GetResourceDir();
969        resourceDir.AddSegment(_T("xrc"));
970        wxDir dir(resourceDir.GetPath());
971
972        wxLogNull log;
973
974        wxString xrc;
975        for (bool found = dir.GetFirst(&xrc, _T("*.xrc")); found; found = dir.GetNext(&xrc)) {
976                if (!wxFileName::FileExists(resourceDir.GetPath() + xrc))
977                        continue;
978
979                CDateTime const date = CLocalFileSystem::GetModificationTime(resourceDir.GetPath() + xrc);
980                wxString const ticks = std::to_wstring(date.GetTimeT());
981
982                auto resourceElement = FindElementWithAttribute(resources, "xrc", "file", xrc.mb_str());
983                if (!resourceElement) {
984                        resourceElement = resources.append_child("xrc");
985                        SetTextAttribute(resourceElement, "file", xrc);
986                        SetTextAttribute(resourceElement, "date", ticks);
987                        cacheValid = false;
988                }
989                else {
990                        wxString xrcNodeDate = GetTextAttribute(resourceElement, "date");
991                        if (xrcNodeDate.empty() || xrcNodeDate != ticks) {
992                                cacheValid = false;
993                                SetTextAttribute(resourceElement, "date", ticks);
994                        }
995                }
996        }
997
998        if (!cacheValid) {
999                // Clear all languages
1000                auto language = layoutElement.child("Language");
1001                while (language) {
1002                        layoutElement.remove_child(language);
1003                        language = layoutElement.child("Language");
1004                }
1005        }
1006
1007        // Get current language
1008        wxString language = wxGetApp().GetCurrentLanguageCode();
1009        if (language.empty())
1010                language = _T("default");
1011
1012        auto languageElement = FindElementWithAttribute(layoutElement, "Language", "id", language.mb_str());
1013        if (!languageElement) {
1014                languageElement = layoutElement.append_child("Language");
1015                SetTextAttribute(languageElement, "id", language.mb_str());
1016        }
1017
1018        // Get static text font and measure sample text
1019        wxFrame* pFrame = new wxFrame;
1020        pFrame->Create(0, -1, _T("Title"), wxDefaultPosition, wxDefaultSize, wxFRAME_TOOL_WINDOW);
1021        wxStaticText* pText = new wxStaticText(pFrame, -1, _T("foo"));
1022
1023        wxFont font = pText->GetFont();
1024        wxString fontDesc = font.GetNativeFontInfoDesc();
1025
1026        auto fontElement = languageElement.child("Font");
1027        if (!fontElement)
1028                fontElement = languageElement.append_child("Font");
1029
1030        if (GetTextAttribute(fontElement, "font") != fontDesc) {
1031                SetTextAttribute(fontElement, "font", fontDesc);
1032                cacheValid = false;
1033        }
1034
1035        int width, height;
1036        pText->GetTextExtent(_T("Just some test string we are measuring. If width or heigh differ from the recorded values, invalidate cache. 1234567890MMWWII"), &width, &height);
1037
1038        if (GetAttributeInt(fontElement, "width") != width ||
1039                GetAttributeInt(fontElement, "height") != height)
1040        {
1041                cacheValid = false;
1042                SetAttributeInt(fontElement, "width", width);
1043                SetAttributeInt(fontElement, "height", height);
1044        }
1045
1046        pFrame->Destroy();
1047
1048        // Get language file
1049        CLocalPath const localesDir = wxGetApp().GetLocalesDir();
1050        wxString name = GetLocaleFile(localesDir.GetPath(), language);
1051
1052        if (!name.empty()) {
1053                CDateTime const date = CLocalFileSystem::GetModificationTime(localesDir.GetPath() + name + _T("/filezilla.mo"));
1054                wxString const ticks = std::to_wstring(date.GetTimeT());
1055
1056                wxString languageNodeDate = GetTextAttribute(languageElement, "date");
1057                if (languageNodeDate.empty() || languageNodeDate != ticks) {
1058                        SetTextAttribute(languageElement, "date", ticks);
1059                        cacheValid = false;
1060                }
1061        }
1062        else
1063                SetTextAttribute(languageElement, "date", "");
1064        if (!cacheValid) {
1065                pugi::xml_node dialog;
1066                while ((dialog = languageElement.child("Dialog")))
1067                        languageElement.remove_child(dialog);
1068        }
1069
1070        if (COptions::Get()->GetOptionVal(OPTION_DEFAULT_KIOSKMODE) == 2) {
1071                m_use_cache = cacheValid;
1072                return true;
1073        }
1074
1075        if (!xml.Save(true)) {
1076                m_use_cache = false;
1077        }
1078        return true;
1079}
1080
1081void CWrapEngine::ClearCache()
1082{
1083        // We have to synchronize access to layout.xml so that multiple processes don't write
1084        // to the same file or one is reading while the other one writes.
1085        CInterProcessMutex mutex(MUTEX_LAYOUT);
1086        wxFileName file(COptions::Get()->GetOption(OPTION_DEFAULT_SETTINGSDIR), _T("layout.xml"));
1087        if (file.FileExists())
1088                wxRemoveFile(file.GetFullPath());
1089}
1090
1091void CWrapEngine::CheckLanguage()
1092{
1093        int lang = wxGetApp().GetCurrentLanguage();
1094        if (lang == wxLANGUAGE_CHINESE || lang == wxLANGUAGE_CHINESE_SIMPLIFIED ||
1095                lang == wxLANGUAGE_CHINESE_TRADITIONAL || lang == wxLANGUAGE_CHINESE_HONGKONG ||
1096                lang == wxLANGUAGE_CHINESE_MACAU || lang == wxLANGUAGE_CHINESE_SINGAPORE ||
1097                lang == wxLANGUAGE_CHINESE_TAIWAN ||
1098                lang == wxLANGUAGE_JAPANESE)
1099        {
1100                m_wrapOnEveryChar = true;
1101                m_noWrapChars = noWrapChars_Chinese;
1102        }
1103        else {
1104                m_wrapOnEveryChar = false;
1105                m_noWrapChars = 0;
1106        }
1107}
Note: See TracBrowser for help on using the repository browser.