1 /***************************************************************************
2  *
3  * Project:  OpenCPN
4  *
5  ***************************************************************************
6  *   Copyright (C) 2013 by David S. Register                               *
7  *                                                                         *
8  *   This program is free software; you can redistribute it and/or modify  *
9  *   it under the terms of the GNU General Public License as published by  *
10  *   the Free Software Foundation; either version 2 of the License, or     *
11  *   (at your option) any later version.                                   *
12  *                                                                         *
13  *   This program is distributed in the hope that it will be useful,       *
14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
16  *   GNU General Public License for more details.                          *
17  *                                                                         *
18  *   You should have received a copy of the GNU General Public License     *
19  *   along with this program; if not, write to the                         *
20  *   Free Software Foundation, Inc.,                                       *
21  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.         *
22  **************************************************************************/
23 
24 #include <locale>
25 
26 #include <wx/gdicmn.h>
27 #include <wx/tokenzr.h>
28 
29 #include "FontMgr.h"
30 #include "OCPNPlatform.h"
31 
32 class  OCPNwxFontList: public wxGDIObjListBase
33 {
34 public:
35     wxFont *FindOrCreateFont(int pointSize,
36                              wxFontFamily family,
37                              wxFontStyle style,
38                              wxFontWeight weight,
39                              bool underline = false,
40                              const wxString& face = wxEmptyString,
41                              wxFontEncoding encoding = wxFONTENCODING_DEFAULT);
42     void FreeAll( void );
43 
44 private:
45     bool isSame(wxFont *font, int pointSize, wxFontFamily family,
46                 wxFontStyle style,
47                 wxFontWeight weight,
48                 bool underline,
49                 const wxString& facename,
50                 wxFontEncoding encoding);
51 };
52 
53 
54 extern wxString g_locale;
55 
56 wxString s_locale;
57 int g_default_font_size;
58 wxString g_default_font_facename;
59 
60 
61 FontMgr * FontMgr::instance = NULL;
62 
Get()63 FontMgr & FontMgr::Get()
64 {
65     if (!instance)
66         instance = new FontMgr;
67     return *instance;
68 }
69 
Shutdown()70 void FontMgr::Shutdown()
71 {
72     if (instance)
73     {
74         delete instance;
75         instance = NULL;
76     }
77 }
78 
FontMgr()79 FontMgr::FontMgr()
80     : m_wxFontCache(NULL)
81     ,m_fontlist(NULL)
82     , pDefFont(NULL)
83 {
84     //    Create the list of fonts
85     m_fontlist = new FontList;
86     m_fontlist->DeleteContents( true );
87 
88     s_locale = g_locale;
89 
90     //    Get a nice generic font as default
91     pDefFont = FindOrCreateFont( 12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, FALSE,
92             wxString( _T ( "" ) ), wxFONTENCODING_SYSTEM );
93 
94 }
95 
~FontMgr()96 FontMgr::~FontMgr()
97 {
98     m_fontlist->Clear();
99     delete m_fontlist;
100 
101     delete m_wxFontCache;
102 }
103 
SetLocale(wxString & newLocale)104 void FontMgr::SetLocale( wxString& newLocale)
105 {
106     s_locale = newLocale;
107 }
108 
GetFontColor(const wxString & TextElement) const109 wxColour FontMgr::GetFontColor( const wxString &TextElement ) const
110 {
111     //    Look thru the font list for a match
112     MyFontDesc *pmfd;
113     auto node = m_fontlist->GetFirst();
114     while( node ) {
115         pmfd = node->GetData();
116         if( pmfd->m_dialogstring == TextElement ) {
117             if(pmfd->m_configstring.BeforeFirst('-') == s_locale)
118                 return pmfd->m_color;
119         }
120         node = node->GetNext();
121     }
122 
123     return wxColour( 0, 0, 0 );
124 }
125 
SetFontColor(const wxString & TextElement,const wxColour color) const126 bool FontMgr::SetFontColor( const wxString &TextElement, const wxColour color ) const
127 {
128   //    Look thru the font list for a match
129   MyFontDesc *pmfd;
130   auto node = m_fontlist->GetFirst();
131   while( node ) {
132     pmfd = node->GetData();
133     if( pmfd->m_dialogstring == TextElement ) {
134       if(pmfd->m_configstring.BeforeFirst('-') == s_locale) {
135         pmfd->m_color = color;
136         return true;
137       }
138     }
139     node = node->GetNext();
140   }
141 
142   return false;
143 }
144 
GetFontConfigKey(const wxString & description)145 wxString FontMgr::GetFontConfigKey( const wxString &description )
146 {
147     // Create the configstring by combining the locale with
148     // a hash of the font description. Hash is used because the i18n
149     // description can contain characters that mess up the config file.
150 
151     wxString configkey;
152     configkey = s_locale;
153     configkey.Append( _T("-") );
154 
155     using namespace std;
156     locale loc;
157     const collate<char>& coll = use_facet<collate<char> >( loc );
158 //    char cFontDesc[101];
159 //    wcstombs( cFontDesc, description.c_str(), 100 );
160 //    cFontDesc[100] = 0;
161 
162     wxCharBuffer abuf = description.ToUTF8();
163 
164     int fdLen = strlen( abuf );
165 
166     configkey.Append(
167             wxString::Format( _T("%08lx"),
168                               coll.hash( abuf.data(), abuf.data() + fdLen ) ) );
169     return configkey;
170 }
171 
GetFont(const wxString & TextElement,int user_default_size)172 wxFont *FontMgr::GetFont( const wxString &TextElement, int user_default_size )
173 {
174     //    Look thru the font list for a match
175     MyFontDesc *pmfd;
176     auto node = m_fontlist->GetFirst();
177     while( node ) {
178         pmfd = node->GetData();
179         if( pmfd->m_dialogstring == TextElement ) {
180             if(pmfd->m_configstring.BeforeFirst('-') == s_locale)
181                 return pmfd->m_font;
182         }
183         node = node->GetNext();
184     }
185 
186     // Found no font, so create a nice one and add to the list
187     wxString configkey = GetFontConfigKey( TextElement );
188 
189     //    Now create a benign, always present native font
190     //    with optional user requested default size
191 
192     //    Get the system default font.
193     wxFont sys_font = *wxNORMAL_FONT;
194     int sys_font_size = sys_font.GetPointSize();
195     wxString FaceName = sys_font.GetFaceName();
196 
197     int new_size;
198     if( 0 == user_default_size ){
199         if(g_default_font_size)
200             new_size = g_default_font_size;
201         else
202         new_size = sys_font_size;
203     }
204     else
205         new_size = user_default_size;
206 
207     if(g_default_font_facename.Length())
208         FaceName = g_default_font_facename;
209 
210     wxString nativefont = GetSimpleNativeFont( new_size, FaceName );
211     wxFont *nf = wxFont::New( nativefont );
212 
213     wxColor color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
214 
215     MyFontDesc *pnewfd = new MyFontDesc( TextElement, configkey, nf, color );
216     m_fontlist->Append( pnewfd );
217 
218     return pnewfd->m_font;
219 
220 }
221 
GetSimpleNativeFont(int size,wxString face)222 wxString FontMgr::GetSimpleNativeFont( int size, wxString face )
223 {
224     //    Now create a benign, always present native string
225     wxString nativefont;
226 
227     // this should work for all platforms
228     nativefont = wxFont(size, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, face)
229     .GetNativeFontInfoDesc();
230 
231     return nativefont;
232 }
233 
SetFont(const wxString & TextElement,wxFont * pFont,wxColour color)234 bool FontMgr::SetFont(const wxString &TextElement, wxFont *pFont, wxColour color)
235 {
236     //    Look thru the font list for a match
237     MyFontDesc *pmfd;
238     auto node = m_fontlist->GetFirst();
239     while( node ) {
240         pmfd = node->GetData();
241         if( pmfd->m_dialogstring == TextElement ) {
242             if(pmfd->m_configstring.BeforeFirst('-') == s_locale) {
243 
244             // Todo Think about this
245             //
246 
247 //      Cannot delete the present font, since it may be in use elsewhere
248 //      This WILL leak....but only on font changes
249 
250 //              delete pmfd->m_font;                            // purge any old value
251 
252                 pmfd->m_font = pFont;
253                 pmfd->m_nativeInfo = pFont->GetNativeFontInfoDesc();
254                 pmfd->m_color = color;
255 
256                 return true;
257             }
258         }
259         node = node->GetNext();
260     }
261 
262     return false;
263 }
264 
GetNumFonts(void) const265 int FontMgr::GetNumFonts( void ) const
266 {
267     return m_fontlist->GetCount();
268 }
269 
GetConfigString(int i) const270 const wxString & FontMgr::GetConfigString( int i ) const
271 {
272     MyFontDesc * pfd = m_fontlist->Item( i )->GetData();
273     return pfd->m_configstring;
274 }
275 
GetDialogString(int i) const276 const wxString & FontMgr::GetDialogString( int i ) const
277 {
278     MyFontDesc *pfd = m_fontlist->Item( i )->GetData();
279     return pfd->m_dialogstring;
280 }
281 
GetNativeDesc(int i) const282 const wxString & FontMgr::GetNativeDesc( int i ) const
283 {
284     MyFontDesc *pfd = m_fontlist->Item( i )->GetData();
285     return pfd->m_nativeInfo;
286 }
287 
GetFullConfigDesc(int i) const288 wxString FontMgr::GetFullConfigDesc( int i ) const
289 {
290     MyFontDesc *pfd = m_fontlist->Item( i )->GetData();
291     wxString ret = pfd->m_dialogstring;
292     ret.Append( _T ( ":" ) );
293     ret.Append( pfd->m_nativeInfo );
294     ret.Append( _T ( ":" ) );
295 
296     wxString cols( _T("rgb(0,0,0)") );
297     if( pfd->m_color.IsOk() ) cols = pfd->m_color.GetAsString( wxC2S_CSS_SYNTAX );
298 
299     ret.Append( cols );
300     return ret;
301 }
302 
FindFontByConfigString(wxString pConfigString)303 MyFontDesc *FontMgr::FindFontByConfigString( wxString pConfigString )
304 {
305     //    Search for a match in the list
306     MyFontDesc *pmfd;
307     auto node = m_fontlist->GetFirst();
308 
309     while( node ) {
310         pmfd = node->GetData();
311         if( pmfd->m_configstring == pConfigString ) {
312             return pmfd;
313         }
314         node = node->GetNext();
315     }
316 
317     return NULL;
318 }
319 
320 
LoadFontNative(wxString * pConfigString,wxString * pNativeDesc)321 void FontMgr::LoadFontNative( wxString *pConfigString, wxString *pNativeDesc )
322 {
323     //    Parse the descriptor string
324 
325     wxStringTokenizer tk( *pNativeDesc, _T ( ":" ) );
326     wxString dialogstring = tk.GetNextToken();
327     wxString nativefont = tk.GetNextToken();
328 
329     wxString c = tk.GetNextToken();
330     wxColour color( c );            // from string description
331 
332     //    Search for a match in the list
333     MyFontDesc *pmfd;
334     auto node = m_fontlist->GetFirst();
335 
336     while( node ) {
337         pmfd = node->GetData();
338         if( pmfd->m_configstring == *pConfigString ) {
339             if(pmfd->m_configstring.BeforeFirst('-') == g_locale) {
340                 pmfd->m_nativeInfo = nativefont;
341                 wxFont *nf = pmfd->m_font->New( pmfd->m_nativeInfo );
342                 pmfd->m_font = nf;
343                 break;
344             }
345         }
346         node = node->GetNext();
347     }
348 
349     //    Create and add the font to the list
350     if( !node ) {
351 
352         wxFont *nf0 = new wxFont();
353 
354 #ifdef __OCPN__ANDROID__
355         wxFont *nf = new wxFont( nativefont );
356 #else
357         wxFont *nf = nf0->New( nativefont );
358 #endif
359 
360         double font_size = nf->GetPointSize();
361         wxString s = nf->GetNativeFontInfoDesc();
362 
363         //    Scrub the native font string for bad unicode conversion
364 #ifdef __WXMSW__
365         wxString face = nf->GetFaceName();
366         const wxChar *t = face.c_str();
367         if( *t > 255 ) {
368             delete nf;
369             wxString substitute_native = GetSimpleNativeFont( 12, _T("") );
370             nf = nf0->New( substitute_native );
371         }
372 #endif
373         delete nf0;
374 
375         MyFontDesc *pnewfd = new MyFontDesc( dialogstring, *pConfigString, nf, color );
376         m_fontlist->Append( pnewfd );
377 
378     }
379 }
380 
FindOrCreateFont(int point_size,wxFontFamily family,wxFontStyle style,wxFontWeight weight,bool underline,const wxString & facename,wxFontEncoding encoding)381 wxFont* FontMgr::FindOrCreateFont( int point_size, wxFontFamily family,
382                     wxFontStyle style, wxFontWeight weight, bool underline,
383                     const wxString &facename,
384                     wxFontEncoding encoding)
385 {
386     if (m_wxFontCache == 0)
387         m_wxFontCache = new OCPNwxFontList;
388     return m_wxFontCache->FindOrCreateFont( point_size, family, style, weight,
389         underline, facename, encoding);
390 }
391 
isSame(wxFont * font,int pointSize,wxFontFamily family,wxFontStyle style,wxFontWeight weight,bool underline,const wxString & facename,wxFontEncoding encoding)392 bool OCPNwxFontList::isSame(wxFont *font, int pointSize, wxFontFamily family,
393                              wxFontStyle style,
394                              wxFontWeight weight,
395                              bool underline,
396                              const wxString& facename,
397                              wxFontEncoding encoding)
398 {
399     if (
400          font->GetPointSize () == pointSize &&
401          font->GetStyle () == style &&
402          font->GetWeight () == weight &&
403          font->GetUnderlined () == underline )
404     {
405         bool same;
406 
407         // empty facename matches anything at all: this is bad because
408         // depending on which fonts are already created, we might get back
409         // a different font if we create it with empty facename, but it is
410         // still better than never matching anything in the cache at all
411         // in this case
412         if ( !facename.empty() )
413         {
414             const wxString& fontFace = font->GetFaceName();
415 
416             // empty facename matches everything
417             same = !fontFace || fontFace == facename;
418         }
419         else
420         {
421             same = font->GetFamily() == family;
422         }
423         if ( same && (encoding != wxFONTENCODING_DEFAULT) )
424         {
425             // have to match the encoding too
426             same = font->GetEncoding() == encoding;
427         }
428         return same;
429     }
430     return false;
431 }
432 
FindOrCreateFont(int pointSize,wxFontFamily family,wxFontStyle style,wxFontWeight weight,bool underline,const wxString & facename,wxFontEncoding encoding)433 wxFont *OCPNwxFontList::FindOrCreateFont(int pointSize,
434                                      wxFontFamily family,
435                                      wxFontStyle style,
436                                      wxFontWeight weight,
437                                      bool underline,
438                                      const wxString& facename,
439                                      wxFontEncoding encoding)
440 {
441     // from wx source code
442     // In all ports but wxOSX, the effective family of a font created using
443     // wxFONTFAMILY_DEFAULT is wxFONTFAMILY_SWISS so this is what we need to
444     // use for comparison.
445     //
446     // In wxOSX the original wxFONTFAMILY_DEFAULT seems to be kept and it uses
447     // a different font than wxFONTFAMILY_SWISS anyhow so we just preserve it.
448 #ifndef __WXOSX__
449     if ( family == wxFONTFAMILY_DEFAULT )
450         family = wxFONTFAMILY_SWISS;
451 #endif // !__WXOSX__
452 
453     wxFont *font;
454     wxList::compatibility_iterator node;
455     for (node = list.GetFirst(); node; node = node->GetNext())
456     {
457         font = (wxFont *)node->GetData();
458         if (isSame(font, pointSize, family, style, weight, underline, facename, encoding))
459             return font;
460     }
461 
462     // font not found, create the new one
463     font = NULL;
464     wxFont fontTmp(pointSize, family, style, weight, underline, facename, encoding);
465     if (fontTmp.IsOk())
466     {
467         font = new wxFont(fontTmp);
468         list.Append(font);
469 
470         // double check the font really roundtrip
471         //  Removed after verification.
472         //wxASSERT(isSame(font, pointSize, family, style, weight, underline, facename, encoding));
473     }
474 
475     return font;
476 }
477 
FreeAll(void)478 void OCPNwxFontList::FreeAll( void )
479 {
480     wxFont *font;
481     wxList::compatibility_iterator node;
482     for (node = list.GetFirst(); node; node = node->GetNext())
483     {
484         font = (wxFont *)node->GetData();
485         delete font;
486     }
487 }
488 
489 static wxString FontCandidates[] = {
490     _T("AISTargetAlert"),
491     _T("AISTargetQuery"),
492     _T("StatusBar"),
493     _T("AIS Target Name" ),
494     _T("ObjectQuery"),
495     _T("RouteLegInfoRollover"),
496     _T("ExtendedTideIcon"),
497     _T("CurrentValue"),
498     _T("Console Legend"),
499     _T("Console Value"),
500     _T("AISRollover"),
501     _T("TideCurrentGraphRollover"),
502     _T("Marks"),
503     _T("ChartTexts"),
504     _T("ToolTips"),
505     _T("Dialog"),
506     _T("Menu"),
507     _T("END_OF_LIST")
508 };
509 
510 
ScrubList()511 void FontMgr::ScrubList( )
512 {
513     wxString now_locale = g_locale;
514     wxArrayString string_array;
515 
516     //  Build the composite candidate array
517     wxArrayString candidateArray;
518     unsigned int i = 0;
519 
520     // The fixed, static list
521     while( true ){
522         wxString candidate = FontCandidates[i];
523         if(candidate == _T("END_OF_LIST") ) {
524             break;
525         }
526 
527         candidateArray.Add(candidate);
528         i++;
529     }
530 
531     //  The Aux Key array
532     for(unsigned int i=0 ; i <  m_AuxKeyArray.GetCount() ; i++){
533         candidateArray.Add(m_AuxKeyArray[i]);
534     }
535 
536 
537     for(unsigned int i = 0; i < candidateArray.GetCount() ; i++ ){
538         wxString candidate = candidateArray[i];
539 
540         //  For each font identifier string in the FontCandidate array...
541 
542         //  In the current locale, walk the loaded list looking for a translation
543         //  that is correct, according to the currently load .mo file.
544         //  If found, add to a temporary array
545 
546         wxString trans = wxGetTranslation(candidate);
547 
548         MyFontDesc *pmfd;
549         auto node = m_fontlist->GetFirst();
550         while( node ) {
551             pmfd = node->GetData();
552             wxString tlocale = pmfd->m_configstring.BeforeFirst('-');
553             if( tlocale == now_locale) {
554                 if(trans == pmfd->m_dialogstring){
555                     string_array.Add(pmfd->m_dialogstring);
556                 }
557             }
558 
559             node = node->GetNext();
560         }
561     }
562 
563     // now we have an array of correct translations
564     // Walk the loaded list again.
565     // If a list item's translation is not in the "good" array, mark it for removal
566 
567     MyFontDesc *pmfd;
568     auto node = m_fontlist->GetFirst();
569     while( node ) {
570         pmfd = node->GetData();
571         wxString tlocale = pmfd->m_configstring.BeforeFirst('-');
572         if( tlocale == now_locale) {
573             bool bfound = false;
574             for(unsigned int i=0 ; i < string_array.GetCount() ; i++){
575                 if( string_array[i] == pmfd->m_dialogstring){
576                     bfound = true;
577                     break;
578                 }
579             }
580             if(!bfound){        // mark for removal
581                 pmfd->m_dialogstring = _T("");
582                 pmfd->m_configstring = _T("");
583             }
584         }
585 
586         node = node->GetNext();
587     }
588 
589     //  Remove the marked list items
590     node = m_fontlist->GetFirst();
591     while( node ) {
592         pmfd = node->GetData();
593         if( pmfd->m_dialogstring == _T("") ) {
594             bool bd = m_fontlist->DeleteObject(pmfd);
595             if(bd)
596                 node = m_fontlist->GetFirst();
597         }
598         else
599             node = node->GetNext();
600 
601     }
602 
603     //  And finally, for good measure, make sure that everything in the candidate array has a valid entry in the list
604     i = 0;
605     while( true ){
606         wxString candidate = FontCandidates[i];
607         if(candidate == _T("END_OF_LIST") ) {
608             break;
609         }
610 
611         GetFont( wxGetTranslation(candidate), g_default_font_size );
612 
613         i++;
614     }
615 
616 
617 }
618 
AddAuxKey(wxString key)619 bool FontMgr::AddAuxKey( wxString key )
620 {
621     for(unsigned int i=0 ; i <  m_AuxKeyArray.GetCount() ; i++){
622         if(m_AuxKeyArray[i] == key)
623             return false;
624     }
625     m_AuxKeyArray.Add(key);
626     return true;
627 }
628 
629