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

Last change on this file since 3185 was 3185, checked in by jrpelegrina, 3 years ago

Update new version: 3.15.02

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