67#include <wx/txtstrm.h>
68#include <wx/wfstream.h>
69#include <wx/mstream.h>
70#include <wx/settings.h>
98 auto strings = wxSplit(
id.GET(), L
'-');
100 for (
auto &
string : strings)
101 result +=
string.Capitalize();
126 return wxFileName( themeDir,
wxT(
"ThemeImageDefsAsCee.h") ).GetFullPath();
133 return wxFileName( dir, Str,
wxT(
"png") ).GetFullPath();
143#ifdef EXPERIMENTAL_EXTRA_THEME_RESOURCES
144 extern void RegisterExtraThemeResources();
145 RegisterExtraThemeResources();
194 std::map< EnumValueSymbol, const ThemeBase::RegisteredTheme& >;
204 const std::vector<unsigned char> &data )
206 , preferredSystemAppearance { preferredSystemAppearance }
221 auto &resources = *
mpSet;
224 const bool cbOkIfNotFound =
true;
240 LoadComponents( cbOkIfNotFound );
263 RotateImageInto( bmpRecordBesideDisabled, bmpRecordBelowDisabled,
false );
272 wxColor DesiredText = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
274 int TextColourDifference =
ColourDistance( CurrentText, DesiredText );
278 if ( TextColourDifference != 0 ) {
280 if ( ContrastLevel > 250 )
281 Colour( clrTrackPanelText ) = DesiredText;
309 abs( From.Red() - To.Red() )
310 + abs( From.Green() - To.Green() )
311 + abs( From.Blue() - To.Blue() );
319 wxColour From =
Colour( clrMedium );
320#if defined( __WXGTK__ )
321 wxColour To = wxSystemSettings::GetColour( wxSYS_COLOUR_BACKGROUND );
323 wxColour To = wxSystemSettings::GetColour( wxSYS_COLOUR_3DFACE );
346 Colour( clrTrackInfo ) = To;
352 wxBitmap Bmp1( pXpm );
353 wxBitmap Bmp2( pMask );
362 wxASSERT( Bmp1.GetDepth()==-1 || Bmp1.GetDepth()==24);
363 wxASSERT( Bmp1.GetDepth()==-1 || Bmp2.GetDepth()==24);
366 nBytes = Bmp1.GetWidth() * Bmp1.GetHeight();
367 wxImage Img1( Bmp1.ConvertToImage());
368 wxImage Img2( Bmp2.ConvertToImage());
371 unsigned char *mk = Img2.GetData();
374 static_cast<unsigned char*
>(malloc( nBytes )) };
377 for(i=0;i<nBytes;i++)
383 Img1.SetAlpha( alpha.release() );
397 int &flags,
int &iIndex,
char const ** pXpm,
const wxString & Name )
399 wxBitmap Bmp( pXpm );
400 wxImage Img( Bmp.ConvertToImage() );
402 Img.SetMaskColour(0xDE, 0xDE, 0xDE);
413 int &flags,
int &iIndex,
const wxImage &
Image,
const wxString & Name )
415 auto &resources = *
mpSet;
425 TempImage.ConvertAlphaToMask();
426 resources.mBitmaps.push_back( wxBitmap( TempImage ) );
428 resources.mBitmaps.push_back( wxBitmap(
Image ) );
431 flags &= ~resFlagSkip;
432 auto index = resources.mBitmaps.size() - 1;
438 wxASSERT(allNames.insert(Name).second);
443 wxASSERT(iIndex == index);
450 int &iIndex,
const wxColour &Clr,
const wxString & Name )
452 auto &resources = *
mpSet;
453 resources.
mColours.push_back( Clr );
454 auto index = resources.mColours.size() - 1;
459 wxASSERT(allNames.insert(Name).second);
464 wxASSERT(iIndex == index);
470 : mxCacheWidth{ width }
558 size_t OnSysWrite(
const void *buffer,
size_t bufsize)
override;
568 bOk =
File.Open( Filename, wxFile::write );
571 File.Write( wxString::Format(
572 wxT(
"/// @file %s\r\n"), wxFileName(Filename).GetFullName()));
573 File.Write(
wxT(
"/// @brief This file was Auto-Generated.\r\n") );
575 File.Write(
wxT(
"/// It is included by Theme.cpp.\r\n") );
576 File.Write(
wxT(
"/// Only check this into Git if you've read and understood the guidelines!\r\n\r\n ") );
586 for(
int i=0;i<(int)bufsize;i++)
589 Temp = wxString::Format(
wxT(
"%i,"),(
int)(((
unsigned char*)buffer)[i]) );
626 XO(
"Themes written to:\n %s/*/%s.")
633 auto &resources = *
mpSet;
639 if( !ImageCache.HasAlpha() )
641 ImageCache.InitAlpha();
648 wxLogDebug(
wxT(
"<img src=\"ImageCache.png\" usemap=\"#map1\">" ));
649 wxLogDebug(
wxT(
"<map name=\"map1\">") );
653 for (
size_t i = 0; i < resources.mImages.size() ; ++i)
655 wxImage &SrcImage = resources.mImages[i];
659 context.GetNextPosition( SrcImage.GetWidth(), SrcImage.GetHeight());
660 ImageCache.SetRGB( context.Rect(), 0xf2, 0xb0, 0x27 );
663 context.mxPos + context.mBorderWidth,
664 context.myPos + context.mBorderWidth);
666 ImageCache.SetRGB( context.RectInner(), 1,1,1);
669 wxRect R( context.Rect() );
670 wxLogDebug(
wxT(
"<area title=\"Bitmap:%s\" shape=rect coords=\"%i,%i,%i,%i\">"),
672 R.GetLeft(), R.GetTop(), R.GetRight(), R.GetBottom() );
680 context.SetColourGroup();
681 for (
size_t i = 0; i < resources.mColours.size(); ++i)
684 wxColour c = resources.mColours[i];
685 ImageCache.SetRGB( context.Rect() , 0xf2, 0xb0, 0x27 );
686 ImageCache.SetRGB( context.RectInner() , c.Red(), c.Green(), c.Blue() );
693 ImageCache.SetAlpha( context.mxPos + x, context.myPos+y, 255);
698 wxRect R( context.Rect() );
699 wxLogDebug(
wxT(
"<area title=\"Colour:%s\" shape=rect coords=\"%i,%i,%i,%i\">"),
701 R.GetLeft(), R.GetTop(), R.GetRight(), R.GetBottom() );
709 int b = (j >> 8) | ((i>>4)&0xf0);
710 wxRect R( i,j, 1, 1);
711 ImageCache.SetRGB( R, r, g, b );
712 ImageCache.SetAlpha( i,j, 255);
717 wxLogDebug(
"</map>" );
731 if( wxFileExists( FileName ))
746 ImageCache.GetWidth()*4,
747 ImageCache.GetHeight()*4,
748 wxIMAGE_QUALITY_NEAREST );
750 if( !ImageCache.SaveFile( FileName, wxBITMAP_TYPE_PNG ))
753 XO(
"Audacity could not write file:\n %s.")
767 auto FileName = wxFileName{ dir,
name }.GetFullPath();
768 if( !OutStream.
OpenFile( FileName ))
771 XO(
"Audacity could not open file:\n %s\nfor writing.")
775 if( !ImageCache.SaveFile(OutStream, wxBITMAP_TYPE_PNG ) )
778 XO(
"Audacity could not write images to file:\n %s.")
798 auto &resources = *
mpSet;
804 wxFFile File( FileName,
wxT(
"wb") );
805 if( !File.IsOpened() )
808 File.Write(
wxT(
"<html>\r\n"));
809 File.Write(
wxT(
"<body bgcolor=\"303030\">\r\n"));
810 wxString Temp = wxString::Format(
wxT(
"<img src=\"ImageCache.png\" width=\"%i\" usemap=\"#map1\">\r\n" ),
ImageCacheWidth );
812 File.Write(
wxT(
"<map name=\"map1\">\r\n") );
814 for (
size_t i = 0; i < resources.mImages.size(); ++i)
816 wxImage &SrcImage = resources.mImages[i];
820 context.GetNextPosition( SrcImage.GetWidth(), SrcImage.GetHeight());
822 wxRect R( context.RectInner() );
823 File.Write( wxString::Format(
824 wxT(
"<area title=\"Bitmap:%s\" shape=rect coords=\"%i,%i,%i,%i\">\r\n"),
826 R.GetLeft(), R.GetTop(), R.GetRight(), R.GetBottom()) );
830 context.SetColourGroup();
831 for (
size_t i = 0; i < resources.mColours.size(); ++i)
835 wxRect R( context.RectInner() );
836 File.Write( wxString::Format(
wxT(
"<area title=\"Colour:%s\" shape=rect coords=\"%i,%i,%i,%i\">\r\n"),
838 R.GetLeft(), R.GetTop(), R.GetRight(), R.GetBottom()) );
840 File.Write(
wxT(
"</map>\r\n") );
841 File.Write(
wxT(
"</body>\r\n"));
842 File.Write(
wxT(
"</html>\r\n"));
852 auto &resources = *
mpSet;
856 if( !File.IsOpened() )
859 for (
size_t i = 0; i < resources.mImages.size(); ++i)
861 wxImage &SrcImage = resources.mImages[i];
866 int t = (int)PrevFlags;
868 if( t==0 ) Temp =
wxT(
" resFlagNone ");
873 Temp.Replace(
wxT(
" "),
wxT(
" | ") );
875 File.Write( wxString::Format(
wxT(
"\r\n SET_THEME_FLAGS( %s );\r\n"),
878 File.Write( wxString::Format(
879 wxT(
" DEFINE_IMAGE( bmp%s, wxImage( %i, %i ), wxT(\"%s\"));\r\n"),
882 SrcImage.GetHeight(),
892#ifdef EXPERIMENTAL_DA
906 auto &resources = *
mpSet;
920 if( type.
empty() || type ==
"custom" )
926 const auto &FileName =
928 if( !wxFileExists( FileName ))
933 XO(
"Audacity could not find file:\n %s.\nTheme not loaded.")
937 if( !ImageCache.LoadFile( FileName, wxBITMAP_TYPE_PNG ))
941 XO(
"Audacity could not load file:\n %s.\nBad png format perhaps?")
950 const unsigned char * pImage =
nullptr;
952 auto iter = lookup.find({type, {}});
953 if (
const auto end = lookup.end(); iter ==
end) {
954 iter = lookup.find({
"classic", {}});
955 wxASSERT(iter !=
end);
965 pImage = iter->second.data.data();
967 wxMemoryInputStream InternalStream( pImage,
ImageSize );
969 if( !ImageCache.LoadFile( InternalStream, wxBITMAP_TYPE_PNG ))
977"Audacity could not read its default theme.\nPlease report the problem."));
985 int h = ImageCache.GetHeight() * ((1.0*
ImageCacheWidth)/ImageCache.GetWidth());
990 for (
size_t i = 0; i < resources.mImages.size(); ++i)
996 context.GetNextPosition(
Image.GetWidth(),
Image.GetHeight() );
997 wxRect R = context.RectInner();
1000 resources.mBitmaps[i] = wxBitmap(
Image);
1003 if( !ImageCache.HasAlpha() )
1004 ImageCache.InitAlpha();
1009 context.SetColourGroup();
1010 wxColour TempColour;
1011 for (
size_t i = 0; i < resources.mColours.size(); ++i)
1014 context.RectMid( x, y );
1015 wxRect R = context.RectInner();
1019 if( ImageCache.GetAlpha(x,y ) > 128 )
1021 TempColour = wxColour(
1022 ImageCache.GetRed( x,y),
1023 ImageCache.GetGreen( x,y),
1024 ImageCache.GetBlue(x,y));
1029 if( TempColour != wxColour(1,1,1) )
1030 resources.mColours[i] = TempColour;
1046 auto &resources = *
mpSet;
1049 if( !wxDirExists( dir ))
1056 for (
size_t i = 0; i < resources.mImages.size(); ++i)
1061 if( wxFileExists( FileName ))
1063 if( !resources.mImages[i].LoadFile( FileName, wxBITMAP_TYPE_PNG ))
1068"Audacity could not load file:\n %s.\nBad png format perhaps?")
1076 if( ! resources.mImages[i].HasAlpha() )
1079 resources.mImages[i].InitAlpha();
1081 resources.mBitmaps[i] = wxBitmap( resources.mImages[i] );
1089 const auto fName = wxFileName{ dir,
ColorFileName }.GetFullPath();
1090 wxTextFile file{ fName };
1092 if (!file.IsOpened())
1097 static const std::wregex expr{
1098 LR
"(^ *([_[:alnum:]]+).*#([0-9a-fA-F]{6});)" };
1101 std::unordered_set<wxString>
names;
1102 for (
auto str = file.GetFirstLine();
1103 !file.Eof();
str = file.GetNextLine()) {
1104 if (std::wsmatch match;
1105 regex_search(
str.ToStdWstring(), match, expr )) {
1106 const wxString
name{ match[1] };
1110 else if (
const auto iter = std::find(
begin,
end,
name);
1116 static_cast<unsigned>(stoi(match[2],
nullptr, 16));
1117 unsigned char rr = (rrggbb >> 16) & 0xffu;
1118 unsigned char gg = (rrggbb >> 8) & 0xffu;
1119 unsigned char bb = rrggbb & 0xffu;
1120 resources.mColours[iter -
begin] = { rr, gg, bb };
1133"None of the expected theme component files\n were found in:\n %s.")
1153 auto &resources = *
mpSet;
1156 if( !wxDirExists( dir ))
1164 wxMkDir( dir.fn_str() );
1166 wxMkDir( dir.fn_str(), 0700 );
1168 if( !wxDirExists( dir ))
1171 XO(
"Could not create directory:\n %s")
1179 for (
size_t i = 0; i < resources.mImages.size(); ++i)
1184 if( wxFileExists( FileName ))
1202"Some required files in:\n %s\nwere already present. Overwrite?")
1207 if (result == MessageBoxResult::No)
1211 for (
size_t i = 0; i < resources.mImages.size(); ++i)
1216 if( !resources.mImages[i].SaveFile( FileName, wxBITMAP_TYPE_PNG ))
1219 XO(
"Audacity could not save file:\n %s")
1228 const auto fName = wxFileName{ dir,
ColorFileName }.GetFullPath();
1229 wxFileOutputStream ffStream{ fName };
1230 if (!ffStream.IsOk()) {
1234 wxTextOutputStream ss{ffStream};
1236 ss <<
"." <<
id.GET() <<
"Theme {\n";
1237 for (
size_t i = 0; i < resources.mColours.size(); ++i) {
1238 const auto &colour = resources.mColours[i];
1243 << wxString::Format(
"%02x", colour.Red())
1244 << wxString::Format(
"%02x", colour.Green())
1245 << wxString::Format(
"%02x", colour.Blue())
1265 XO(
"Themes as Cee code written to:\n %s/*%s.")
1272 wxImage image( Bmp.ConvertToImage() );
1279 while (iter !=
end) {
1280 if (
mpSet == &iter->second)
1283 iter =
mSets.erase(iter);
1289 wxASSERT( iIndex >= 0 );
1290 auto &resources = *
mpSet;
1292 return resources.mColours[iIndex];
1297 wxASSERT( iIndex >= 0 );
1298 Brush.SetColour(
Colour( iIndex ));
1303 wxASSERT( iIndex >= 0 );
1304 Pen.SetColour(
Colour( iIndex ));
1309 wxASSERT( iIndex >= 0 );
1310 auto &resources = *
mpSet;
1312 return resources.mBitmaps[iIndex];
1317 wxASSERT( iIndex >= 0 );
1318 auto &resources = *
mpSet;
1320 return resources.mImages[iIndex];
1324 wxASSERT( iIndex >= 0 );
1325 auto &resources = *
mpSet;
1328 return wxSize(
Image.GetWidth(),
Image.GetHeight());
1334 Image( iIndex ) = *pImage;
1335 Bitmap( iIndex ) = wxBitmap( *pImage );
1341 wxImage img2 = img.Rotate90( bClockwise );
1348 std::vector<EnumValueSymbol> symbols;
1352 symbols.emplace_back(symbol);
1358 "classic",
"light",
"dark",
"high-contrast"
1364 std::stable_sort( symbols.begin(), symbols.end(),
1365 [](
auto &a,
auto &b){ return index(a) < index(b); } );
1368 symbols.emplace_back(
1370 "custom",
XO(
"Custom")
1376 constexpr int defaultTheme =
1377#ifdef EXPERIMENTAL_DA
1385 wxT(
"/GUI/Theme"), symbols(), defaultTheme
Toolkit-neutral facade for basic user interface services.
static ThemeBase::RegisteredTheme theme
static const AudacityProject::AttachedObjects::RegisteredFactory key
const TranslatableString name
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
gPrefs Read(wxT("/GUI/VerticalZooming"), &bVZoom, false)
const int ImageCacheWidth
std::map< EnumValueSymbol, const ThemeBase::RegisteredTheme & > ThemeCacheLookup
static ThemeCacheLookup & GetThemeCacheLookup()
const int ImageCacheHeight
THEME_API BoolSetting GUIBlendThemes
THEME_API ChoiceSetting & GUITheme()
PreferredSystemAppearance
A system theme, that matches selected theme best (only works on macOS with builtin themes).
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
This specialization of Setting for bool adds a Toggle method to negate the saved value.
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
A cursor for iterating the theme bitmap.
void SetNewGroup(int iGroupSize)
void RectMid(int &x, int &y)
void GetNextPosition(int xSize, int ySize)
An explicitly nonlocalized string, not meant for the user to see.
CallbackReturn Publish(const ThemeChangeMessage &message)
Send a message to connected callbacks.
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined */
Helper class based on wxOutputStream used to get a png file in text format.
size_t OnSysWrite(const void *buffer, size_t bufsize) override
int OpenFile(const FilePath &Filename)
Opens the file and also adds a standard comment at the start of it.
virtual ~SourceOutputStream()
Destructor. We close our text stream in here.
void SwitchTheme(teThemeType Theme)
bool SaveOneThemeComponents(teThemeType id)
void LoadTheme(teThemeType Theme)
void RegisterImage(NameSet &allNames, int &flags, int &iIndex, char const **pXpm, const wxString &Name)
wxColour & Colour(int iIndex)
void SetBrushColour(wxBrush &Brush, int iIndex)
void DeleteUnusedThemes()
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)
int ColourDistance(wxColour &From, wxColour &To)
bool CreateOneImageCache(teThemeType id, bool bBinarySave)
void WriteOneImageMap(teThemeType id)
teThemeType GetFallbackThemeType()
void LoadOneThemeComponents(teThemeType id, bool bOkIfNotFound=false)
wxArrayString mBitmapNames
void SetPenColour(wxPen &Pen, int iIndex)
std::map< Identifier, ThemeSet > mSets
std::unordered_set< wxString > NameSet
wxArrayString mColourNames
PreferredSystemAppearance mPreferredSystemAppearance
bool ReadImageCache(teThemeType type={}, bool bOkIfNotFound=false)
std::vector< int > mBitmapFlags
void SetFilePath(const FilePath &path)
virtual void EnsureInitialised()=0
wxSize ImageSize(int iIndex)
static bool LoadPreferredTheme()
void WriteImageDefs()
Writes a series of Macro definitions that can be used in the include file.
void RegisterColour(NameSet &allNames, int &iIndex, const wxColour &Clr, const wxString &Name)
void SaveThemeComponents()
void RecolourBitmap(int iIndex, wxColour From, wxColour To)
void LoadThemeComponents(bool bOkIfNotFound=false)
Based on ThemeBase, Theme manages image and icon resources.
void EnsureInitialised() override
void RegisterImagesAndColours()
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.
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
FILES_API wxString MkDir(const wxString &Str)
FILES_API FilePath DataDir()
Audacity user data directory.
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
FilePath ThemeComponent(const wxString &dir, const wxString &Str)
constexpr auto ThemeCacheFileName
FilePath ThemeSubdir(const FilePath &themeDir, Identifier id)
Has the side-effect of ensuring existence of the directory.
constexpr auto ImageMapFileName
FilePath ThemeImageDefsAsCee(const FilePath &themeDir)
constexpr auto ImageCacheFileName
constexpr auto ColorFileName
wxString ThemeFilePrefix(teThemeType id)
FilePath ThemeComponentsDir(const FilePath &themeDir, Identifier id)
Has the side-effect of ensuring existence of the directory.
MessageBoxOptions && ButtonStyle(Button style) &&
const EnumValueSymbol symbol
RegisteredTheme(EnumValueSymbol symbol, PreferredSystemAppearance preferredSystemAppearance, const std::vector< unsigned char > &data)
std::vector< wxImage > mImages
std::vector< wxColour > mColours