Audacity 3.2.0
Theme.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Theme.cpp
6
7 James Crook
8
9 Audacity is free software.
10 This file is licensed under the wxWidgets license, see License.txt
11
12********************************************************************//*****************************************************************//*****************************************************************//*****************************************************************//*****************************************************************/
55
56#include "Theme.h"
57
58
59
60#include <map>
61
62#include <wx/wxprec.h>
63#include <wx/brush.h>
64#include <wx/pen.h>
65#include <wx/file.h>
66#include <wx/ffile.h>
67#include <wx/txtstrm.h>
68#include <wx/wfstream.h>
69#include <wx/mstream.h>
70#include <wx/settings.h>
71#include <regex>
72
73#include "AllThemeResources.h"
74#include "BasicUI.h"
75#include "FileNames.h"
76#include "Prefs.h"
77#include "ImageManipulation.h"
78#include "Internat.h"
79#include "MemoryX.h"
80
81// theTheme is a global variable.
82THEME_API Theme theTheme;
83
85{
86}
87
89{
90}
91
92namespace {
93// Side of a square painted in one of the theme colours in image caches
94constexpr int iColSize = 10;
95
97{
98 auto strings = wxSplit(id.GET(), L'-');
99 wxString result;
100 for (auto &string : strings)
101 result += string.Capitalize();
102 return result;
103}
104
107{
108 return FileNames::MkDir(
109 wxFileName( FileNames::MkDir(themeDir), id.GET() ).GetFullPath() );
110}
111
114{
115 return FileNames::MkDir( wxFileName( ThemeSubdir(themeDir, id), wxT("Components") ).GetFullPath() );
116}
117
118constexpr auto ImageCacheFileName = L"ImageCache.png";
119
120constexpr auto ImageMapFileName = L"ImageCache.htm";
121
122constexpr auto ColorFileName = L"Colors.txt";
123
125{
126 return wxFileName( themeDir, wxT("ThemeImageDefsAsCee.h") ).GetFullPath();
127}
128
129constexpr auto ThemeCacheFileName = L"ThemeAsCeeCode.h";
130
131FilePath ThemeComponent(const wxString &dir, const wxString &Str)
132{
133 return wxFileName( dir, Str, wxT("png") ).GetFullPath();
134}
135}
136
138{
139 if ( !mpSet || mpSet->bInitialised )
140 return;
142
143#ifdef EXPERIMENTAL_EXTRA_THEME_RESOURCES
144 extern void RegisterExtraThemeResources();
145 RegisterExtraThemeResources();
146#endif
147}
148
150{
151 if (mThemeDir.empty())
153 wxFileName(FileNames::DataDir(), wxT("Theme")).GetFullPath());
154 return mThemeDir;
155}
156
158{
159 mThemeDir = path;
160}
161
163{
166 return true;
167}
168
170{
171 if ( !mpSet || mpSet->bInitialised )
172 return;
173 mpSet->bInitialised = true;
174
175// This initialises the variables e.g
176// RegisterImage( myFlags, bmpRecordButton, some image, wxT("RecordButton"));
177 int myFlags = resFlagPaired;
178 NameSet allNames;
179#define THEME_INITS
180#include "AllThemeResources.h"
181
182
183}
184
186{
187}
188
190{
191}
192
194 std::map< EnumValueSymbol, const ThemeBase::RegisteredTheme& >;
195
197{
198 static ThemeCacheLookup theMap;
199 return theMap;
200}
201
203 EnumValueSymbol symbol, PreferredSystemAppearance preferredSystemAppearance,
204 const std::vector<unsigned char> &data )
205 : symbol{ symbol }
206 , preferredSystemAppearance { preferredSystemAppearance }
207 , data { data }
208{
209 GetThemeCacheLookup().emplace(symbol, *this);
210}
211
213{
214 GetThemeCacheLookup().erase(symbol);
215}
216
218{
219 // Switch the active theme set
220 mpSet = &mSets[Theme.empty() ? GUITheme().Read() : Theme];
221 auto &resources = *mpSet;
223
224 const bool cbOkIfNotFound = true;
225
226 if( !ReadImageCache( Theme, cbOkIfNotFound ) )
227 {
228 // THEN get the default set.
229 ReadImageCache( GetFallbackThemeType(), !cbOkIfNotFound );
230
231 // JKC: Now we could go on and load the individual images
232 // on top of the default images using the commented out
233 // code that follows...
234 //
235 // However, I think it is better to get the user to
236 // build a NEW image cache, which they can do easily
237 // from the Theme preferences tab.
238#if 0
239 // and now add any available component images.
240 LoadComponents( cbOkIfNotFound );
241
242 // JKC: I'm usure about doing this next step automatically.
243 // Suppose the disk is write protected?
244 // Is having the image cache created automatically
245 // going to confuse users? Do we need version specific names?
246 // and now save the combined image as a cache for later use.
247 // We should load the images a little faster in future as a result.
249#endif
250 }
251}
252
256{
258
259 // Post-processing steps after loading of the cache
260
261 // Two always overwritten images
262 RotateImageInto( bmpRecordBeside, bmpRecordBelow, false );
263 RotateImageInto( bmpRecordBesideDisabled, bmpRecordBelowDisabled, false );
264
265 // Next line is not required as we haven't yet built the GUI
266 // when this function is (or should be) called.
267 // AColor::ApplyUpdatedImages();
268
269 // The next step doesn't post-process the theme data. It only modifies
270 // system settings. So it is not necessary to make it conditional on
271 // preferences, for avoidance of errors in the use of Theme preferences.
272 // (See commit 2020217)
274}
275
276void ThemeBase::RecolourBitmap( int iIndex, wxColour From, wxColour To )
277{
278 wxImage Image( Bitmap( iIndex ).ConvertToImage() );
279
280 std::unique_ptr<wxImage> pResult = ChangeImageColour(
281 &Image, From, To );
282 ReplaceImage( iIndex, pResult.get() );
283}
284
285int ThemeBase::ColourDistance( wxColour & From, wxColour & To ){
286 return
287 abs( From.Red() - To.Red() )
288 + abs( From.Green() - To.Green() )
289 + abs( From.Blue() - To.Blue() );
290}
291
292wxImage ThemeBase::MaskedImage( char const ** pXpm, char const ** pMask )
293{
294 wxBitmap Bmp1( pXpm );
295 wxBitmap Bmp2( pMask );
296
297// wxLogDebug( wxT("Image 1: %i Image 2: %i"),
298// Bmp1.GetDepth(), Bmp2.GetDepth() );
299
300 // We want a 24-bit-depth bitmap if all is working, but on some
301 // platforms it might just return -1 (which means best available
302 // or not relevant).
303 // JKC: \todo check that we're not relying on 24 bit elsewhere.
304 wxASSERT( Bmp1.GetDepth()==-1 || Bmp1.GetDepth()==24);
305 wxASSERT( Bmp1.GetDepth()==-1 || Bmp2.GetDepth()==24);
306
307 int i,nBytes;
308 nBytes = Bmp1.GetWidth() * Bmp1.GetHeight();
309 wxImage Img1( Bmp1.ConvertToImage());
310 wxImage Img2( Bmp2.ConvertToImage());
311
312// unsigned char *src = Img1.GetData();
313 unsigned char *mk = Img2.GetData();
314 //wxImage::setAlpha requires memory allocated with malloc, not NEW
316 static_cast<unsigned char*>(malloc( nBytes )) };
317
318 // Extract alpha channel from second XPM.
319 for(i=0;i<nBytes;i++)
320 {
321 alpha[i] = mk[0];
322 mk+=3;
323 }
324
325 Img1.SetAlpha( alpha.release() );
326
327 //dmazzoni: the top line does not work on wxGTK
328 //wxBitmap Result( Img1, 32 );
329 //wxBitmap Result( Img1 );
330
331 return Img1;
332}
333
334// Legacy function to allow use of an XPM where no theme image was defined.
335// Bit depth and mask needs review.
336// Note that XPMs don't offer translucency, so unsuitable for a round shape overlay,
337// for example.
339 int &flags, int &iIndex, char const ** pXpm, const wxString & Name )
340{
341 wxBitmap Bmp( pXpm );
342 wxImage Img( Bmp.ConvertToImage() );
343 // The next line recommended by http://forum.audacityteam.org/viewtopic.php?f=50&t=96765
344 Img.SetMaskColour(0xDE, 0xDE, 0xDE);
345 Img.InitAlpha();
346
347 //dmazzoni: the top line does not work on wxGTK
348 //wxBitmap Bmp2( Img, 32 );
349 //wxBitmap Bmp2( Img );
350
351 RegisterImage( allNames, flags, iIndex, Img, Name );
352}
353
355 int &flags, int &iIndex, const wxImage &Image, const wxString & Name )
356{
357 auto &resources = *mpSet;
358 resources.mImages.push_back( Image );
359
360#ifdef __APPLE__
361 // On Mac, bitmaps with alpha don't work.
362 // So we convert to a mask and use that.
363 // It isn't quite as good, as alpha gives smoother edges.
364 //[Does not affect the large control buttons, as for those we do
365 // the blending ourselves anyway.]
366 wxImage TempImage( Image );
367 TempImage.ConvertAlphaToMask();
368 resources.mBitmaps.push_back( wxBitmap( TempImage ) );
369#else
370 resources.mBitmaps.push_back( wxBitmap( Image ) );
371#endif
372
373 flags &= ~resFlagSkip;
374 auto index = resources.mBitmaps.size() - 1;
375 if (iIndex == -1) {
376 // First time assignment of global variable identifying an image
377 iIndex = index;
378 mBitmapNames.push_back( Name );
379 mBitmapFlags.push_back( flags );
380 wxASSERT(allNames.insert(Name).second);
381 }
382 else {
383 // If revisiting for another theme set,
384 // images should be re-done in the same sequence
385 wxASSERT(iIndex == index);
386 wxASSERT(mBitmapNames[index] == Name);
387 wxASSERT(mBitmapFlags[index] == flags);
388 }
389}
390
392 int &iIndex, const wxColour &Clr, const wxString & Name )
393{
394 auto &resources = *mpSet;
395 resources.mColours.push_back( Clr );
396 auto index = resources.mColours.size() - 1;
397 if (iIndex == -1) {
398 // First time assignment of global variable identifying a colour
399 iIndex = index;
400 mColourNames.push_back( Name );
401 wxASSERT(allNames.insert(Name).second);
402 }
403 else {
404 // If revisiting for another theme set,
405 // colours should be re-done in the same sequence
406 wxASSERT(iIndex == index);
407 wxASSERT(mColourNames[index] == Name);
408 }
409}
410
412 : mxCacheWidth{ width }
413{
414 SetNewGroup(1);
415}
416
417void FlowPacker::SetNewGroup( int iGroupSize )
418{
420 mxPos =0;
422 iImageGroupSize = iGroupSize;
423 iImageGroupIndex = -1;
425}
426
428{
429 myPosBase = 750;
430 mxPos =0;
432 iImageGroupSize = 1;
433 iImageGroupIndex = -1;
435 myHeight = 11;
436}
437
438void FlowPacker::GetNextPosition( int xSize, int ySize )
439{
440 xSize += 2*mBorderWidth;
441 ySize += 2*mBorderWidth;
442 // if the height has increased, then we are on a NEW group.
443 if(( ySize > myHeight )||(((mFlags ^ mOldFlags )& ~resFlagSkip)!=0))
444 {
445 SetNewGroup( ((mFlags & resFlagPaired)!=0) ? 2 : 1 );
446 myHeight = ySize;
447// mFlags &= ~resFlagNewLine;
448// mOldFlags = mFlags;
449 }
450
453 {
456 }
457
458 if(mxPos > (mxCacheWidth - xSize ))
459 {
462 myHeight = ySize;
463 }
465
466 mComponentWidth = xSize;
467 mComponentHeight = ySize;
468}
469
471{
472 return wxRect( mxPos, myPos, mComponentWidth, mComponentHeight);
473}
474
476{
477 return Rect().Deflate( mBorderWidth, mBorderWidth );
478}
479
480void FlowPacker::RectMid( int &x, int &y )
481{
482 x = mxPos + mComponentWidth/2;
483 y = myPos + mComponentHeight/2;
484}
485
486
492class SourceOutputStream final : public wxOutputStream
493{
494public:
496 int OpenFile(const FilePath & Filename);
497 virtual ~SourceOutputStream();
498
499protected:
500 size_t OnSysWrite(const void *buffer, size_t bufsize) override;
501 wxFile File;
503};
504
507{
508 nBytes = 0;
509 bool bOk;
510 bOk = File.Open( Filename, wxFile::write );
511 if( bOk )
512 {
513 File.Write( wxString::Format(
514 wxT("/// @file %s\r\n"), wxFileName(Filename).GetFullName()));
515 File.Write( wxT("/// @brief This file was Auto-Generated.\r\n") );
516 File.Write( wxT("///\r\n") );
517 File.Write( wxT("/// It is included by Theme.cpp.\r\n") );
518 File.Write( wxT("/// Only check this into Git if you've read and understood the guidelines!\r\n\r\n ") );
519 }
520 return bOk;
521}
522
525size_t SourceOutputStream::OnSysWrite(const void *buffer, size_t bufsize)
526{
527 wxString Temp;
528 for(int i=0;i<(int)bufsize;i++)
529 {
530 // Write one byte with a comma
531 Temp = wxString::Format( wxT("%i,"),(int)(((unsigned char*)buffer)[i]) );
532 File.Write( Temp );
533 nBytes++;
534 // New line if more than 20 bytes written since last time.
535 if( (nBytes %20)==0 )
536 {
537 File.Write( wxT("\r\n "));
538 }
539 }
540 return bufsize;
541}
542
545{
546 File.Write( wxT("\r\n") );
547 File.Close();
548}
549
550
551// Must be wide enough for bmpAudacityLogo. Use double width + 10.
552const int ImageCacheWidth = 440;
553
554const int ImageCacheHeight = 836;
555
557{
558 ValueRestorer cleanup{ mpSet };
559 for (auto &[key, data] : GetThemeCacheLookup())
560 if (!CreateOneImageCache(key.Internal(), true))
561 // Some file failed to save, message was given
562 return;
564/* i18n-hint: A theme is a consistent visual style across an application's
565graphical user interface, including choices of colors, and similarity of images
566such as those on button controls. Audacity can load and save alternative
567themes. */
568 XO("Themes written to:\n %s/*/%s.")
570}
571
572bool ThemeBase::CreateOneImageCache( teThemeType id, bool bBinarySave )
573{
574 SwitchTheme( id );
575 auto &resources = *mpSet;
576
578 ImageCache.SetRGB( wxRect( 0,0,ImageCacheWidth, ImageCacheHeight), 1,1,1);//Not-quite black.
579
580 // Ensure we have an alpha channel...
581 if( !ImageCache.HasAlpha() )
582 {
583 ImageCache.InitAlpha();
584 }
585
586 FlowPacker context{ ImageCacheWidth };
587
588//#define IMAGE_MAP
589#ifdef IMAGE_MAP
590 wxLogDebug( wxT("<img src=\"ImageCache.png\" usemap=\"#map1\">" ));
591 wxLogDebug( wxT("<map name=\"map1\">") );
592#endif
593
594 // Save the bitmaps
595 for (size_t i = 0; i < resources.mImages.size() ; ++i)
596 {
597 wxImage &SrcImage = resources.mImages[i];
598 context.mFlags = mBitmapFlags[i];
599 if( !(mBitmapFlags[i] & resFlagInternal) )
600 {
601 context.GetNextPosition( SrcImage.GetWidth(), SrcImage.GetHeight());
602 ImageCache.SetRGB( context.Rect(), 0xf2, 0xb0, 0x27 );
603 if( !(context.mFlags & resFlagSkip) )
604 PasteSubImage( &ImageCache, &SrcImage,
605 context.mxPos + context.mBorderWidth,
606 context.myPos + context.mBorderWidth);
607 else
608 ImageCache.SetRGB( context.RectInner(), 1,1,1);
609#ifdef IMAGE_MAP
610 // No href in html. Uses title not alt.
611 wxRect R( context.Rect() );
612 wxLogDebug( wxT("<area title=\"Bitmap:%s\" shape=rect coords=\"%i,%i,%i,%i\">"),
613 mBitmapNames[i],
614 R.GetLeft(), R.GetTop(), R.GetRight(), R.GetBottom() );
615#endif
616 }
617 }
618
619 // Now save the colours.
620 int x,y;
621
622 context.SetColourGroup();
623 for (size_t i = 0; i < resources.mColours.size(); ++i)
624 {
625 context.GetNextPosition( iColSize, iColSize );
626 wxColour c = resources.mColours[i];
627 ImageCache.SetRGB( context.Rect() , 0xf2, 0xb0, 0x27 );
628 ImageCache.SetRGB( context.RectInner() , c.Red(), c.Green(), c.Blue() );
629
630 // YUCK! No function in wxWidgets to set a rectangle of alpha...
631 for(x=0;x<iColSize;x++)
632 {
633 for(y=0;y<iColSize;y++)
634 {
635 ImageCache.SetAlpha( context.mxPos + x, context.myPos+y, 255);
636 }
637 }
638#ifdef IMAGE_MAP
639 // No href in html. Uses title not alt.
640 wxRect R( context.Rect() );
641 wxLogDebug( wxT("<area title=\"Colour:%s\" shape=rect coords=\"%i,%i,%i,%i\">"),
642 mColourNames[i],
643 R.GetLeft(), R.GetTop(), R.GetRight(), R.GetBottom() );
644#endif
645 }
646#if TEST_CARD
647 for ( unsigned i = 0; i < ImageCacheWidth; ++i)
648 for ( unsigned j = 0; j < ImageCacheHeight; ++j) {
649 int r = j &0xff;
650 int g = i &0xff;
651 int b = (j >> 8) | ((i>>4)&0xf0);
652 wxRect R( i,j, 1, 1);
653 ImageCache.SetRGB( R, r, g, b );
654 ImageCache.SetAlpha( i,j, 255);
655 }
656#endif
657
658#ifdef IMAGE_MAP
659 wxLogDebug( "</map>" );
660#endif
661
662 using namespace BasicUI;
663
664 // IF bBinarySave, THEN saving to a normal PNG file.
665 if( bBinarySave )
666 {
667 auto dir = ThemeSubdir(GetFilePath(), id);
668 auto FileName = wxFileName{ dir, ImageCacheFileName }.GetFullPath();
669
670 // Perhaps we should prompt the user if they are overwriting
671 // an existing theme cache?
672#if 0
673 if( wxFileExists( FileName ))
674 {
675 auto message =
676// XO(
677//"Theme cache file:\n %s\nalready exists.\nAre you sure you want to replace it?")
678// .Format( FileName );
679 TranslatableString{ FileName };
680 ShowMessageBox( message );
681 return false;
682 }
683#endif
684#if 0
685 // Deliberate policy to use the fast/cheap blocky pixel-multiplication
686 // algorithm, as this introduces no artifacts on repeated scale up/down.
687 ImageCache.Rescale(
688 ImageCache.GetWidth()*4,
689 ImageCache.GetHeight()*4,
690 wxIMAGE_QUALITY_NEAREST );
691#endif
692 if( !ImageCache.SaveFile( FileName, wxBITMAP_TYPE_PNG ))
693 {
695 XO("Audacity could not write file:\n %s.")
696 .Format( FileName ));
697 return false;
698 }
699 }
700 // ELSE saving to a C code textual version.
701 else
702 {
703 // Generated header file is not put in the sub-directory for
704 // the theme, but is instead distinguished by a prefix on the file name.
705 // So the header can simply be copied into the source code tree.
706 auto dir = GetFilePath();
707 SourceOutputStream OutStream;
709 auto FileName = wxFileName{ dir, name }.GetFullPath();
710 if( !OutStream.OpenFile( FileName ))
711 {
713 XO("Audacity could not open file:\n %s\nfor writing.")
714 .Format( FileName ));
715 return false;
716 }
717 if( !ImageCache.SaveFile(OutStream, wxBITMAP_TYPE_PNG ) )
718 {
720 XO("Audacity could not write images to file:\n %s.")
721 .Format( FileName ));
722 return false;
723 }
724 }
725 return true;
726}
727
729{
730 ValueRestorer cleanup{ mpSet };
731 for (auto &[key, data] : GetThemeCacheLookup())
732 WriteOneImageMap(key.Internal());
733}
734
738{
739 SwitchTheme( id );
740 auto &resources = *mpSet;
741
742 FlowPacker context{ ImageCacheWidth };
743
744 auto dir = ThemeSubdir(GetFilePath(), id);
745 auto FileName = wxFileName{ dir, ImageMapFileName }.GetFullPath();
746 wxFFile File( FileName, wxT("wb") );// I'll put in NEW lines explicitly.
747 if( !File.IsOpened() )
748 return;
749
750 File.Write( wxT("<html>\r\n"));
751 File.Write( wxT("<body bgcolor=\"303030\">\r\n"));
752 wxString Temp = wxString::Format( wxT("<img src=\"ImageCache.png\" width=\"%i\" usemap=\"#map1\">\r\n" ), ImageCacheWidth );
753 File.Write( Temp );
754 File.Write( wxT("<map name=\"map1\">\r\n") );
755
756 for (size_t i = 0; i < resources.mImages.size(); ++i)
757 {
758 wxImage &SrcImage = resources.mImages[i];
759 context.mFlags = mBitmapFlags[i];
760 if( !(mBitmapFlags[i] & resFlagInternal) )
761 {
762 context.GetNextPosition( SrcImage.GetWidth(), SrcImage.GetHeight());
763 // No href in html. Uses title not alt.
764 wxRect R( context.RectInner() );
765 File.Write( wxString::Format(
766 wxT("<area title=\"Bitmap:%s\" shape=rect coords=\"%i,%i,%i,%i\">\r\n"),
767 mBitmapNames[i],
768 R.GetLeft(), R.GetTop(), R.GetRight(), R.GetBottom()) );
769 }
770 }
771 // Now save the colours.
772 context.SetColourGroup();
773 for (size_t i = 0; i < resources.mColours.size(); ++i)
774 {
775 context.GetNextPosition( iColSize, iColSize );
776 // No href in html. Uses title not alt.
777 wxRect R( context.RectInner() );
778 File.Write( wxString::Format( wxT("<area title=\"Colour:%s\" shape=rect coords=\"%i,%i,%i,%i\">\r\n"),
779 mColourNames[i],
780 R.GetLeft(), R.GetTop(), R.GetRight(), R.GetBottom()) );
781 }
782 File.Write( wxT("</map>\r\n") );
783 File.Write( wxT("</body>\r\n"));
784 File.Write( wxT("</html>\r\n"));
785 // File will be closed automatically.
786}
787
790{
791 // The generated image defs macro calls depend only on sizes of images,
792 // not contents, and so there is only one file good across all themes.
793
794 auto &resources = *mpSet;
796
797 wxFFile File( ThemeImageDefsAsCee(GetFilePath()), wxT("wb") );
798 if( !File.IsOpened() )
799 return;
800 teResourceFlags PrevFlags = (teResourceFlags)-1;
801 for (size_t i = 0; i < resources.mImages.size(); ++i)
802 {
803 wxImage &SrcImage = resources.mImages[i];
804 // No href in html. Uses title not alt.
805 if( PrevFlags != mBitmapFlags[i] )
806 {
807 PrevFlags = (teResourceFlags)mBitmapFlags[i];
808 int t = (int)PrevFlags;
809 wxString Temp;
810 if( t==0 ) Temp = wxT(" resFlagNone ");
811 if( t & resFlagPaired ) Temp += wxT(" resFlagPaired ");
812 if( t & resFlagCursor ) Temp += wxT(" resFlagCursor ");
813 if( t & resFlagNewLine ) Temp += wxT(" resFlagNewLine ");
814 if( t & resFlagInternal ) Temp += wxT(" resFlagInternal ");
815 Temp.Replace( wxT(" "), wxT(" | ") );
816
817 File.Write( wxString::Format( wxT("\r\n SET_THEME_FLAGS( %s );\r\n"),
818 Temp ));
819 }
820 File.Write( wxString::Format(
821 wxT(" DEFINE_IMAGE( bmp%s, wxImage( %i, %i ), wxT(\"%s\"));\r\n"),
822 mBitmapNames[i],
823 SrcImage.GetWidth(),
824 SrcImage.GetHeight(),
825 mBitmapNames[i]
826 ));
827 }
828}
829
830
832// Fallback must be an internally supported type,
833// to guarantee it is found.
834 return "light";
835}
836
842bool ThemeBase::ReadImageCache( teThemeType type, bool bOkIfNotFound)
843{
844 auto &resources = *mpSet;
846 wxImage ImageCache;
847
848 // Ensure we have an alpha channel...
849// if( !ImageCache.HasAlpha() )
850// {
851// ImageCache.InitAlpha();
852// }
853
854 using namespace BasicUI;
855
856 if( type.empty() || type == "custom" )
857 {
859
860 // Take the image cache file for the theme chosen in preferences
861 auto dir = ThemeSubdir(GetFilePath(), GUITheme().Read());
862 const auto &FileName =
863 wxFileName{ dir, ImageCacheFileName }.GetFullPath();
864 if( !wxFileExists( FileName ))
865 {
866 if( bOkIfNotFound )
867 return false; // did not load the images, so return false.
869 XO("Audacity could not find file:\n %s.\nTheme not loaded.")
870 .Format( FileName ));
871 return false;
872 }
873 if( !ImageCache.LoadFile( FileName, wxBITMAP_TYPE_PNG ))
874 {
876 /* i18n-hint: Do not translate png. It is the name of a file format.*/
877 XO("Audacity could not load file:\n %s.\nBad png format perhaps?")
878 .Format( FileName ));
879 return false;
880 }
881 }
882 // ELSE we are reading from internal storage.
883 else
884 {
885 size_t ImageSize = 0;
886 const unsigned char * pImage = nullptr;
887 auto &lookup = GetThemeCacheLookup();
888 auto iter = lookup.find({type, {}});
889 if (const auto end = lookup.end(); iter == end) {
890 iter = lookup.find({"classic", {}});
891 wxASSERT(iter != end);
892 }
893
894 mPreferredSystemAppearance = iter->second.preferredSystemAppearance;
895
896 ImageSize = iter->second.data.size();
897 if (ImageSize == 0)
898 // This must be the image compiler
899 return true;
900
901 pImage = iter->second.data.data();
902 //wxLogDebug("Reading ImageCache %p size %i", pImage, ImageSize );
903 wxMemoryInputStream InternalStream( pImage, ImageSize );
904
905 if( !ImageCache.LoadFile( InternalStream, wxBITMAP_TYPE_PNG ))
906 {
907 // If we get this message, it means that the data in file
908 // was not a valid png image.
909 // Most likely someone edited it by mistake,
910 // Or some experiment is being tried with NEW formats for it.
912 XO(
913"Audacity could not read its default theme.\nPlease report the problem."));
914 return false;
915 }
916 //wxLogDebug("Read %i by %i", ImageCache.GetWidth(), ImageCache.GetHeight() );
917 }
918
919 // Resize a large image down.
920 if( ImageCache.GetWidth() > ImageCacheWidth ){
921 int h = ImageCache.GetHeight() * ((1.0*ImageCacheWidth)/ImageCache.GetWidth());
922 ImageCache.Rescale( ImageCacheWidth, h );
923 }
924 FlowPacker context{ ImageCacheWidth };
925 // Load the bitmaps
926 for (size_t i = 0; i < resources.mImages.size(); ++i)
927 {
928 wxImage &Image = resources.mImages[i];
929 context.mFlags = mBitmapFlags[i];
930 if( !(mBitmapFlags[i] & resFlagInternal) )
931 {
932 context.GetNextPosition( Image.GetWidth(),Image.GetHeight() );
933 wxRect R = context.RectInner();
934 //wxLogDebug( "[%i, %i, %i, %i, \"%s\"], ", R.x, R.y, R.width, R.height, mBitmapNames[i].c_str() );
935 Image = GetSubImageWithAlpha( ImageCache, context.RectInner() );
936 resources.mBitmaps[i] = wxBitmap(Image);
937 }
938 }
939 if( !ImageCache.HasAlpha() )
940 ImageCache.InitAlpha();
941
942// return true; //To not load colours..
943 // Now load the colours.
944 int x,y;
945 context.SetColourGroup();
946 wxColour TempColour;
947 for (size_t i = 0; i < resources.mColours.size(); ++i)
948 {
949 context.GetNextPosition( iColSize, iColSize );
950 context.RectMid( x, y );
951 wxRect R = context.RectInner();
952 //wxLogDebug( "[%i, %i, %i, %i, \"%s\"], ", R.x, R.y, R.width, R.height, mColourNames[i].c_str() );
953 // Only change the colour if the alpha is opaque.
954 // This allows us to add NEW colours more easily.
955 if( ImageCache.GetAlpha(x,y ) > 128 )
956 {
957 TempColour = wxColour(
958 ImageCache.GetRed( x,y),
959 ImageCache.GetGreen( x,y),
960 ImageCache.GetBlue(x,y));
965 if( TempColour != wxColour(1,1,1) )
966 resources.mColours[i] = TempColour;
967 }
968 }
969 return true;
970}
971
972void ThemeBase::LoadThemeComponents( bool bOkIfNotFound )
973{
974 ValueRestorer cleanup{ mpSet };
975 for (auto &[key, data] : GetThemeCacheLookup())
976 LoadOneThemeComponents( key.Internal(), bOkIfNotFound );
977}
978
979void ThemeBase::LoadOneThemeComponents( teThemeType id, bool bOkIfNotFound )
980{
981 SwitchTheme( id );
982 auto &resources = *mpSet;
983 // IF directory doesn't exist THEN return early.
984 const auto dir = ThemeComponentsDir(GetFilePath(), id);
985 if( !wxDirExists( dir ))
986 return;
987
988 using namespace BasicUI;
989
990 int n=0;
991 FilePath FileName;
992 for (size_t i = 0; i < resources.mImages.size(); ++i)
993 {
994 if( !(mBitmapFlags[i] & resFlagInternal) )
995 {
996 FileName = ThemeComponent( dir, mBitmapNames[i] );
997 if( wxFileExists( FileName ))
998 {
999 if( !resources.mImages[i].LoadFile( FileName, wxBITMAP_TYPE_PNG ))
1000 {
1002 XO(
1003 /* i18n-hint: Do not translate png. It is the name of a file format.*/
1004"Audacity could not load file:\n %s.\nBad png format perhaps?")
1005 .Format( FileName ));
1006 return;
1007 }
1012 if( ! resources.mImages[i].HasAlpha() )
1013 {
1014 // wxLogDebug( wxT("File %s lacked alpha"), mBitmapNames[i] );
1015 resources.mImages[i].InitAlpha();
1016 }
1017 resources.mBitmaps[i] = wxBitmap( resources.mImages[i] );
1018 n++;
1019 }
1020 }
1021 }
1022
1023 // Now read complete information about the colors from one text file
1024 {
1025 const auto fName = wxFileName{ dir, ColorFileName }.GetFullPath();
1026 wxTextFile file{ fName };
1027 file.Open();
1028 if (!file.IsOpened())
1029 ShowMessageBox( XO("Couldn't read from file: %s").Format( fName ) );
1030 else {
1031 ++n;
1032 // Scan the line for name and #xxxxxx;
1033 static const std::wregex expr{
1034 LR"(^ *([_[:alnum:]]+).*#([0-9a-fA-F]{6});)" };
1035 const auto begin = mColourNames.begin(),
1036 end = mColourNames.end();
1037 std::unordered_set<wxString> names;
1038 for (auto str = file.GetFirstLine();
1039 !file.Eof(); str = file.GetNextLine()) {
1040 if (std::wsmatch match;
1041 regex_search( str.ToStdWstring(), match, expr )) {
1042 const wxString name{ match[1] };
1043 if (!names.insert(name).second)
1044 ShowMessageBox( Verbatim("Ignoring duplicate color name: %s")
1045 .Format( name ) );
1046 else if (const auto iter = std::find(begin, end, name);
1047 iter == end)
1049 Verbatim("Unrecognized color name: %s").Format( name ) );
1050 else {
1051 auto rrggbb =
1052 static_cast<unsigned>(stoi(match[2], nullptr, 16));
1053 unsigned char rr = (rrggbb >> 16) & 0xffu;
1054 unsigned char gg = (rrggbb >> 8) & 0xffu;
1055 unsigned char bb = rrggbb & 0xffu;
1056 resources.mColours[iter - begin] = { rr, gg, bb };
1057 }
1058 }
1059 }
1060 }
1061 }
1062
1063 if( n==0 )
1064 {
1065 if( bOkIfNotFound )
1066 return;
1068 XO(
1069"None of the expected theme component files\n were found in:\n %s.")
1070 .Format( dir ));
1071 }
1072}
1073
1075{
1076 ValueRestorer cleanup{ mpSet };
1077 for (auto &[key, data] : GetThemeCacheLookup())
1078 if (!SaveOneThemeComponents( key.Internal() ))
1079 // Some file failed to save, message was given
1080 return;
1082 XO("Themes written to:\n %s/*/Components/.").Format(GetFilePath()));
1083}
1084
1086{
1087 using namespace BasicUI;
1088 SwitchTheme( id );
1089 auto &resources = *mpSet;
1090 // IF directory doesn't exist THEN create it
1091 const auto dir = ThemeComponentsDir(GetFilePath(), id);
1092 if( !wxDirExists( dir ))
1093 {
1099#ifdef __WXMSW__
1100 wxMkDir( dir.fn_str() );
1101#else
1102 wxMkDir( dir.fn_str(), 0700 );
1103#endif
1104 if( !wxDirExists( dir ))
1105 {
1107 XO("Could not create directory:\n %s")
1108 .Format( dir ) );
1109 return false;
1110 }
1111 }
1112
1113 int n=0;
1114 FilePath FileName;
1115 for (size_t i = 0; i < resources.mImages.size(); ++i)
1116 {
1117 if( !(mBitmapFlags[i] & resFlagInternal) )
1118 {
1119 FileName = ThemeComponent( dir, mBitmapNames[i] );
1120 if( wxFileExists( FileName ))
1121 {
1122 ++n;
1123 break;
1124 }
1125 }
1126 }
1127
1128 if( wxFileExists( ThemeComponent( dir, ColorFileName ) ) )
1129 ++n;
1130
1131 using namespace BasicUI;
1132
1133 if (n > 0)
1134 {
1135 auto result =
1137 XO(
1138"Some required files in:\n %s\nwere already present. Overwrite?")
1139 .Format( dir ),
1141 .ButtonStyle(Button::YesNo)
1142 .DefaultIsNo());
1143 if (result == MessageBoxResult::No)
1144 return false;
1145 }
1146
1147 for (size_t i = 0; i < resources.mImages.size(); ++i)
1148 {
1149 if( !(mBitmapFlags[i] & resFlagInternal) )
1150 {
1151 FileName = ThemeComponent( dir, mBitmapNames[i] );
1152 if( !resources.mImages[i].SaveFile( FileName, wxBITMAP_TYPE_PNG ))
1153 {
1155 XO("Audacity could not save file:\n %s")
1156 .Format( FileName ));
1157 return false;
1158 }
1159 }
1160 }
1161
1162 // Now write complete information about the colors in one text file
1163 {
1164 const auto fName = wxFileName{ dir, ColorFileName }.GetFullPath();
1165 wxFileOutputStream ffStream{ fName };
1166 if (!ffStream.IsOk()) {
1167 ShowMessageBox( XO("Couldn't write to file: %s").Format( fName ) );
1168 return false;
1169 }
1170 wxTextOutputStream ss{ffStream};
1171 // Open with, for instance, ".darkTheme {"
1172 ss << "." << id.GET() << "Theme {\n";
1173 for (size_t i = 0; i < resources.mColours.size(); ++i) {
1174 const auto &colour = resources.mColours[i];
1175 ss
1176 // Write names, aligning the colons in a column,
1177 // followed by #rrggbb;
1178 << wxString::Format("%30s: #", mColourNames[i])
1179 << wxString::Format("%02x", colour.Red())
1180 << wxString::Format("%02x", colour.Green())
1181 << wxString::Format("%02x", colour.Blue())
1182 << ";\n";
1183 }
1184 ss << "}";
1185 }
1186
1187 return true;
1188}
1189
1191{
1192 ValueRestorer cleanup{ mpSet };
1193 for (auto &[key, data] : GetThemeCacheLookup()) {
1194 // false indicates not using standard binary method.
1195 if (!CreateOneImageCache(key.Internal(), false))
1196 // Some file failed to save, message was given
1197 return;
1198 }
1200 /* i18n-hint "Cee" means the C computer programming language */
1201 XO("Themes as Cee code written to:\n %s/*%s.")
1203}
1204
1205wxImage ThemeBase::MakeImageWithAlpha( wxBitmap & Bmp )
1206{
1207 // BUG in wxWidgets. Conversion from BMP to image does not preserve alpha.
1208 wxImage image( Bmp.ConvertToImage() );
1209 return image;
1210}
1211
1213{
1214 auto iter = mSets.begin(), end = mSets.end();
1215 while (iter != end) {
1216 if (mpSet == &iter->second)
1217 ++iter;
1218 else
1219 iter = mSets.erase(iter);
1220 }
1221}
1222
1223wxColour & ThemeBase::Colour( int iIndex )
1224{
1225 wxASSERT( iIndex >= 0 );
1226 auto &resources = *mpSet;
1228 return resources.mColours[iIndex];
1229}
1230
1231void ThemeBase::SetBrushColour( wxBrush & Brush, int iIndex )
1232{
1233 wxASSERT( iIndex >= 0 );
1234 Brush.SetColour( Colour( iIndex ));
1235}
1236
1237void ThemeBase::SetPenColour( wxPen & Pen, int iIndex )
1238{
1239 wxASSERT( iIndex >= 0 );
1240 Pen.SetColour( Colour( iIndex ));
1241}
1242
1243wxBitmap & ThemeBase::Bitmap( int iIndex )
1244{
1245 wxASSERT( iIndex >= 0 );
1246 auto &resources = *mpSet;
1248 return resources.mBitmaps[iIndex];
1249}
1250
1251wxImage & ThemeBase::Image( int iIndex )
1252{
1253 wxASSERT( iIndex >= 0 );
1254 auto &resources = *mpSet;
1256 return resources.mImages[iIndex];
1257}
1258wxSize ThemeBase::ImageSize( int iIndex )
1259{
1260 wxASSERT( iIndex >= 0 );
1261 auto &resources = *mpSet;
1263 wxImage & Image = resources.mImages[iIndex];
1264 return wxSize( Image.GetWidth(), Image.GetHeight());
1265}
1266
1268void ThemeBase::ReplaceImage( int iIndex, wxImage * pImage )
1269{
1270 Image( iIndex ) = *pImage;
1271 Bitmap( iIndex ) = wxBitmap( *pImage );
1272}
1273
1274void ThemeBase::RotateImageInto( int iTo, int iFrom, bool bClockwise )
1275{
1276 wxImage img(theTheme.Bitmap( iFrom ).ConvertToImage() );
1277 wxImage img2 = img.Rotate90( bClockwise );
1278 ReplaceImage( iTo, &img2 );
1279}
1280
1282{
1283 auto symbols = []{
1284 std::vector<EnumValueSymbol> symbols;
1285
1286 // Gather registered themes
1287 for (const auto &[symbol, data] : GetThemeCacheLookup())
1288 symbols.emplace_back(symbol);
1289
1290 // Sort the names, with built-in themes to the front,
1291 // conserving the ordering that was used in 3.1.0; otherwise
1292 // sorting other registered themes alphabetically by identifier
1293 static const Identifier names[] = {
1294 "classic", "light", "dark", "high-contrast"
1295 };
1296 static auto index = [](const EnumValueSymbol &symbol){
1297 auto begin = std::begin(names), end = std::end(names);
1298 return std::find(begin, end, symbol.Internal()) - begin;
1299 };
1300 std::stable_sort( symbols.begin(), symbols.end(),
1301 [](auto &a, auto &b){ return index(a) < index(b); } );
1302
1303 // Last, custom
1304 symbols.emplace_back(
1305 /* i18n-hint: user defined */
1306 "custom", XO("Custom")
1307 );
1308
1309 return symbols;
1310 };
1311
1312 constexpr int defaultTheme = 1; // "light"
1313
1314 static ChoiceSetting setting {
1315 wxT("/GUI/Theme"), symbols(), defaultTheme
1316 };
1317
1318 return setting;
1319}
wxImage(22, 22)
wxT("CloseDown"))
Toolkit-neutral facade for basic user interface services.
static ThemeBase::RegisteredTheme theme
#define str(a)
const TranslatableString name
Definition: Distortion.cpp:76
XO("Cut/Copy/Paste")
wxImage GetSubImageWithAlpha(const wxImage &Src, const wxRect &rect)
void PasteSubImage(wxImage *background, wxImage *foreground, int xoff, int yoff)
std::unique_ptr< wxImage > ChangeImageColour(wxImage *srcImage, wxColour &dstColour)
std::unique_ptr< Character[], freer > MallocString
Definition: MemoryX.h:147
static const AudacityProject::AttachedObjects::RegisteredFactory key
wxString FilePath
Definition: Project.h:21
static TranslatableStrings names
Definition: TagsEditor.cpp:153
const int ImageCacheWidth
Definition: Theme.cpp:552
std::map< EnumValueSymbol, const ThemeBase::RegisteredTheme & > ThemeCacheLookup
Definition: Theme.cpp:194
static ThemeCacheLookup & GetThemeCacheLookup()
Definition: Theme.cpp:196
THEME_API Theme theTheme
Definition: Theme.cpp:82
const int ImageCacheHeight
Definition: Theme.cpp:554
THEME_API ChoiceSetting & GUITheme()
teResourceFlags
Definition: Theme.h:59
@ resFlagNewLine
Definition: Theme.h:63
@ resFlagSkip
Definition: Theme.h:65
@ resFlagInternal
Definition: Theme.h:64
@ resFlagPaired
Definition: Theme.h:61
@ resFlagCursor
Definition: Theme.h:62
PreferredSystemAppearance
A system theme, that matches selected theme best (only works on macOS with builtin themes).
Definition: Theme.h:34
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
wxString Read() const
Definition: Prefs.cpp:388
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
A cursor for iterating the theme bitmap.
Definition: Theme.h:70
int myHeight
Definition: Theme.h:85
void SetColourGroup()
Definition: Theme.cpp:427
void SetNewGroup(int iGroupSize)
Definition: Theme.cpp:417
int myPosBase
Definition: Theme.h:92
void RectMid(int &x, int &y)
Definition: Theme.cpp:480
int iImageGroupIndex
Definition: Theme.h:90
wxRect RectInner()
Definition: Theme.cpp:475
int mxCacheWidth
Definition: Theme.h:93
int mComponentHeight
Definition: Theme.h:96
int mFlags
Definition: Theme.h:82
void GetNextPosition(int xSize, int ySize)
Definition: Theme.cpp:438
int mBorderWidth
Definition: Theme.h:86
int iImageGroupSize
Definition: Theme.h:89
FlowPacker(int width)
Definition: Theme.cpp:411
wxRect Rect()
Definition: Theme.cpp:470
int mComponentWidth
Definition: Theme.h:95
int mxPos
Definition: Theme.h:83
int mOldFlags
Definition: Theme.h:91
int myPos
Definition: Theme.h:84
Abstract base class used in importing a file.
An explicitly nonlocalized string, not meant for the user to see.
Definition: Identifier.h:22
bool empty() const
Definition: Identifier.h:61
CallbackReturn Publish(const ThemeChangeMessage &message)
Send a message to connected callbacks.
Definition: Observer.h:207
Helper class based on wxOutputStream used to get a png file in text format.
Definition: Theme.cpp:493
size_t OnSysWrite(const void *buffer, size_t bufsize) override
Definition: Theme.cpp:525
int OpenFile(const FilePath &Filename)
Opens the file and also adds a standard comment at the start of it.
Definition: Theme.cpp:506
virtual ~SourceOutputStream()
Destructor. We close our text stream in here.
Definition: Theme.cpp:544
void SwitchTheme(teThemeType Theme)
Definition: Theme.cpp:217
bool SaveOneThemeComponents(teThemeType id)
void LoadTheme(teThemeType Theme)
Definition: Theme.cpp:255
void RegisterImage(NameSet &allNames, int &flags, int &iIndex, char const **pXpm, const wxString &Name)
Definition: Theme.cpp:338
wxColour & Colour(int iIndex)
ThemeSet * mpSet
Definition: Theme.h:205
void SetBrushColour(wxBrush &Brush, int iIndex)
void DeleteUnusedThemes()
void SaveThemeAsCode()
wxImage MakeImageWithAlpha(wxBitmap &Bmp)
wxImage & Image(int iIndex)
wxBitmap & Bitmap(int iIndex)
void ReplaceImage(int iIndex, wxImage *pImage)
void RotateImageInto(int iTo, int iFrom, bool bClockwise)
wxImage MaskedImage(char const **pXpm, char const **pMask)
Definition: Theme.cpp:292
int ColourDistance(wxColour &From, wxColour &To)
Definition: Theme.cpp:285
bool CreateOneImageCache(teThemeType id, bool bBinarySave)
Definition: Theme.cpp:572
void WriteOneImageMap(teThemeType id)
Definition: Theme.cpp:737
ThemeBase(void)
Definition: Theme.cpp:185
FilePath mThemeDir
Definition: Theme.h:196
teThemeType GetFallbackThemeType()
Definition: Theme.cpp:831
void LoadOneThemeComponents(teThemeType id, bool bOkIfNotFound=false)
Definition: Theme.cpp:979
wxArrayString mBitmapNames
Definition: Theme.h:198
void SetPenColour(wxPen &Pen, int iIndex)
void WriteImageMap()
Definition: Theme.cpp:728
std::map< Identifier, ThemeSet > mSets
Definition: Theme.h:204
std::unordered_set< wxString > NameSet
Definition: Theme.h:151
FilePath GetFilePath()
Definition: Theme.cpp:149
wxArrayString mColourNames
Definition: Theme.h:200
PreferredSystemAppearance mPreferredSystemAppearance
Definition: Theme.h:202
bool ReadImageCache(teThemeType type={}, bool bOkIfNotFound=false)
Definition: Theme.cpp:842
std::vector< int > mBitmapFlags
Definition: Theme.h:199
void SetFilePath(const FilePath &path)
Definition: Theme.cpp:157
virtual void EnsureInitialised()=0
wxSize ImageSize(int iIndex)
void CreateImageCache()
Definition: Theme.cpp:556
static bool LoadPreferredTheme()
Definition: Theme.cpp:162
void WriteImageDefs()
Writes a series of Macro definitions that can be used in the include file.
Definition: Theme.cpp:789
void RegisterColour(NameSet &allNames, int &iIndex, const wxColour &Clr, const wxString &Name)
Definition: Theme.cpp:391
void SaveThemeComponents()
void RecolourBitmap(int iIndex, wxColour From, wxColour To)
Definition: Theme.cpp:276
virtual ~ThemeBase(void)
Definition: Theme.cpp:189
void LoadThemeComponents(bool bOkIfNotFound=false)
Definition: Theme.cpp:972
Based on ThemeBase, Theme manages image and icon resources.
Definition: Theme.h:209
void EnsureInitialised() override
Definition: Theme.cpp:137
void RegisterImagesAndColours()
Definition: Theme.cpp:169
Theme(void)
Definition: Theme.cpp:84
~Theme(void)
Definition: Theme.cpp:88
Holds a msgid for the translation catalog; may also bind format arguments.
TranslatableString & Format(Args &&...args) &
Capture variadic format arguments (by copy) when there is no plural.
Set a variable temporarily in a scope.
Definition: MemoryX.h:213
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:279
FILES_API wxString MkDir(const wxString &Str)
FILES_API FilePath DataDir()
Audacity user data directory.
FilePath ThemeComponent(const wxString &dir, const wxString &Str)
Definition: Theme.cpp:131
constexpr auto ThemeCacheFileName
Definition: Theme.cpp:129
FilePath ThemeSubdir(const FilePath &themeDir, Identifier id)
Has the side-effect of ensuring existence of the directory.
Definition: Theme.cpp:106
constexpr auto ImageMapFileName
Definition: Theme.cpp:120
FilePath ThemeImageDefsAsCee(const FilePath &themeDir)
Definition: Theme.cpp:124
constexpr auto ImageCacheFileName
Definition: Theme.cpp:118
constexpr auto ColorFileName
Definition: Theme.cpp:122
wxString ThemeFilePrefix(teThemeType id)
Definition: Theme.cpp:96
FilePath ThemeComponentsDir(const FilePath &themeDir, Identifier id)
Has the side-effect of ensuring existence of the directory.
Definition: Theme.cpp:113
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const char * begin(const char *str) noexcept
Definition: StringUtils.h:101
MessageBoxOptions && ButtonStyle(Button style) &&
Definition: BasicUI.h:107
const EnumValueSymbol symbol
Definition: Theme.h:142
RegisteredTheme(EnumValueSymbol symbol, PreferredSystemAppearance preferredSystemAppearance, const std::vector< unsigned char > &data)
Definition: Theme.cpp:202
std::vector< wxImage > mImages
Definition: Theme.h:103
std::vector< wxColour > mColours
Definition: Theme.h:105
bool bInitialised
Definition: Theme.h:107