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