diff --git a/bin/assets/i18n/de.xml b/bin/assets/i18n/de.xml index 21f09c73a..24ac28578 100644 --- a/bin/assets/i18n/de.xml +++ b/bin/assets/i18n/de.xml @@ -323,6 +323,8 @@ ecode versucht, externes Terminal zu öffnen. Ordner/Projekteinstellungen Ordner/Projekteinstellungen Blockfaltungsaktualisierungsfrequenz + Extern + Extern: %s Schriftgröße erhöhen Schriftgröße zurücksetzen Schriftgröße verringern diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml index 58d69c73f..cbe232461 100644 --- a/bin/assets/i18n/en.xml +++ b/bin/assets/i18n/en.xml @@ -307,6 +307,8 @@ ecode will try to open an external terminal. Folder/Project Settings Folder/Project Settings Folds Refresh Frequency + External + External: %s Font Size Grow Font Size Reset Font Size Shrink diff --git a/bin/assets/i18n/fr.xml b/bin/assets/i18n/fr.xml index dfb659e9f..e241f506c 100644 --- a/bin/assets/i18n/fr.xml +++ b/bin/assets/i18n/fr.xml @@ -306,6 +306,8 @@ ecode tentera d'ouvrir un terminal externe. Paramètres du dossier/projet Paramètres du dossier/projet Fréquence de rafraîchissement des replis + Externe + Externe : %s Augmenter la taille de la police Réinitialiser la taille de la police Réduire la taille de la police diff --git a/bin/assets/i18n/zh.xml b/bin/assets/i18n/zh.xml index 003b4a15f..6bef49895 100644 --- a/bin/assets/i18n/zh.xml +++ b/bin/assets/i18n/zh.xml @@ -231,6 +231,8 @@ 查找: 文件夹/项目设置 文件夹设置 + 外部 + 外部:%s 字号+ 字号重置 字号- diff --git a/include/eepp/ui/tools/uifontpickerdialog.hpp b/include/eepp/ui/tools/uifontpickerdialog.hpp index a7f24a8e8..1acbbf087 100644 --- a/include/eepp/ui/tools/uifontpickerdialog.hpp +++ b/include/eepp/ui/tools/uifontpickerdialog.hpp @@ -220,6 +220,9 @@ class EE_API UIFontPickerDialog : public UIWindow { void browseFont(); bool addExternalFont( const std::string& path, Uint32 faceIndex = 0 ); + bool isExternalFont( const FontDesc& font ) const; + std::string externalTag( const FontDesc& font, bool includeFileName ); + bool familyEntryMatchesFont( const FontFamilyEntry& family, const FontDesc& font ) const; void clearBrowseDialog(); diff --git a/src/eepp/ui/tools/uifontpickerdialog.cpp b/src/eepp/ui/tools/uifontpickerdialog.cpp index e0ec8fe7b..1a7032f79 100644 --- a/src/eepp/ui/tools/uifontpickerdialog.cpp +++ b/src/eepp/ui/tools/uifontpickerdialog.cpp @@ -472,6 +472,8 @@ void UIFontPickerDialog::loadFonts() { void UIFontPickerDialog::setFonts( std::vector fonts ) { FontDesc selectedFont = mSelection.font; + for ( const auto& font : fonts ) + mExternalFontKeys.erase( font.getFileKey() ); mergeLoadedFonts( fonts ); for ( const auto& font : mFonts ) { auto found = std::find_if( fonts.begin(), fonts.end(), @@ -575,21 +577,32 @@ void UIFontPickerDialog::updateFamilies() { FontDesc previousFont = mSelection.font; const std::string query = String::toLower( mSearchInput->getText().toUtf8() ); + UnorderedSet systemFamilies; + for ( const auto& font : mFonts ) { + if ( !isExternalFont( font ) ) + systemFamilies.insert( font.family ); + } UnorderedSet familyKeys; mFamilies.clear(); for ( const auto& font : mFonts ) { - const bool external = - mExternalFontKeys.find( font.getFileKey() ) != mExternalFontKeys.end(); + const bool external = isExternalFont( font ); if ( wantsMonospaceOnly() && !font.monospace && !external ) continue; - const std::string label = font.family + ( external ? " [External]" : "" ); + const bool separateExternal = external && ( mFlags & ShowStyle ) == 0; + const bool externalOnly = systemFamilies.find( font.family ) == systemFamilies.end(); + std::string label( font.family ); + if ( separateExternal ) { + label += " " + externalTag( font, true ); + } else if ( externalOnly ) { + label += " " + externalTag( font, false ); + } if ( !query.empty() && String::toLower( label ).find( query ) == std::string::npos ) continue; const std::string familyKey = - font.family + ( external ? "\n" + font.getFileKey() : std::string{} ); + font.family + ( separateExternal ? "\n" + font.getFileKey() : std::string{} ); if ( familyKeys.insert( familyKey ).second ) mFamilies.push_back( - { label, font.family, external ? font.getFileKey() : std::string{} } ); + { label, font.family, separateExternal ? font.getFileKey() : std::string{} } ); } mFamilyModel = std::make_shared( &mFamilies ); @@ -609,22 +622,23 @@ void UIFontPickerDialog::updateFamilies() { void UIFontPickerDialog::updateStyles() { mStyles.clear(); + bool selectionMatchesFamily = false; if ( !mFamilyList->getSelection().isEmpty() ) { const Int64 row = mFamilyList->getSelection().first().row(); if ( row >= 0 && row < static_cast( mFamilies.size() ) ) { const FontFamilyEntry& family = mFamilies[row]; + selectionMatchesFamily = familyEntryMatchesFont( family, mSelection.font ); for ( const auto& font : mFonts ) { - const bool external = - mExternalFontKeys.find( font.getFileKey() ) != mExternalFontKeys.end(); - const bool matchesSource = family.externalFontKey.empty() - ? !external - : family.externalFontKey == font.getFileKey(); - if ( font.family == family.family && matchesSource && + const bool external = isExternalFont( font ); + if ( familyEntryMatchesFont( family, font ) && ( !wantsMonospaceOnly() || font.monospace || external ) ) { std::string label( styleLabel( font ) ); auto tagIt = mFontTags.find( font.getFileKey() ); - if ( tagIt != mFontTags.end() ) + if ( external ) { + label += " " + externalTag( font, true ); + } else if ( tagIt != mFontTags.end() ) { label += " [" + tagIt->second + "]"; + } mStyles.push_back( { label, font } ); } } @@ -637,8 +651,7 @@ void UIFontPickerDialog::updateStyles() { mUpdating = false; if ( !mStyles.empty() ) { - const std::string selectedFamily( mStyles.front().desc.family ); - if ( selectedFamily == mSelection.font.family ) + if ( selectionMatchesFamily && ( mFlags & ShowStyle ) != 0 ) selectStyle( mSelection.font ); else selectRegularStyle(); @@ -747,12 +760,12 @@ void UIFontPickerDialog::selectInitialRows() { void UIFontPickerDialog::selectFamily( const FontDesc& font ) { if ( font.family.empty() || !mFamilyModel ) return; - const bool external = mExternalFontKeys.find( font.getFileKey() ) != mExternalFontKeys.end(); + const bool separateExternal = isExternalFont( font ) && ( mFlags & ShowStyle ) == 0; for ( size_t i = 0; i < mFamilies.size(); i++ ) { const FontFamilyEntry& family = mFamilies[i]; if ( family.family == font.family && - ( external ? family.externalFontKey == font.getFileKey() - : family.externalFontKey.empty() ) ) { + ( separateExternal ? family.externalFontKey == font.getFileKey() + : family.externalFontKey.empty() ) ) { mFamilyList->setSelection( mFamilyModel->index( i ) ); return; } @@ -899,6 +912,30 @@ bool UIFontPickerDialog::addExternalFont( const std::string& path, Uint32 faceIn return true; } +bool UIFontPickerDialog::isExternalFont( const FontDesc& font ) const { + return mExternalFontKeys.find( font.getFileKey() ) != mExternalFontKeys.end(); +} + +std::string UIFontPickerDialog::externalTag( const FontDesc& font, bool includeFileName ) { + const std::string fileName( FileSystem::fileNameFromPath( font.path ) ); + if ( includeFileName && !fileName.empty() ) { + return "[" + + String::format( i18n( "font_picker_external_file", "External: %s" ).toUtf8(), + fileName ) + + "]"; + } + return "[" + i18n( "font_picker_external", "External" ).toUtf8() + "]"; +} + +bool UIFontPickerDialog::familyEntryMatchesFont( const FontFamilyEntry& family, + const FontDesc& font ) const { + if ( family.family != font.family ) + return false; + if ( family.externalFontKey.empty() ) + return ( mFlags & ShowStyle ) != 0 || !isExternalFont( font ); + return family.externalFontKey == font.getFileKey(); +} + void UIFontPickerDialog::clearBrowseDialog() { if ( !mBrowseDialog ) return; @@ -930,7 +967,6 @@ void UIFontPickerDialog::setSelectedFont( const FontDesc& desc ) { mMonospaceOnly->setChecked( true ); selectFamily( selection ); updateStyles(); - selectStyle( selection ); updateSelectionFromLists( false ); if ( previousSelection != mSelection ) emitSelectionChanged(); diff --git a/src/tests/unit_tests/uifontpickerdialog_tests.cpp b/src/tests/unit_tests/uifontpickerdialog_tests.cpp index ecce785fb..17f953615 100644 --- a/src/tests/unit_tests/uifontpickerdialog_tests.cpp +++ b/src/tests/unit_tests/uifontpickerdialog_tests.cpp @@ -65,6 +65,13 @@ class TestFontPickerDialog : public UIFontPickerDialog { Uint32 getPreviewTextSize() const { return mPreviewText->getFontSize(); } Uint32 getPreviewInputSize() const { return mPreviewInput->getFontSize(); } String getDetails() const { return mDetailsText->getText(); } + void setFontsForTest( std::vector fonts ) { setFonts( std::move( fonts ) ); } + void markSelectedFontExternalForTest() { + FontDesc selectedFont = mSelection.font; + mExternalFontKeys.insert( selectedFont.getFileKey() ); + updateFamilies(); + setSelectedFont( selectedFont ); + } protected: TestFontPickerDialog( Uint32 flags ) : UIFontPickerDialog( flags ) {} @@ -97,7 +104,7 @@ UTEST( UIFontPickerDialog, PreselectsExternalFontPath ) { EXPECT_FALSE( selectionDialog->getSelection().font.family.empty() ); } -UTEST( UIFontPickerDialog, ExternalFontKeepsSeparateFamilyEntryOnNameCollision ) { +UTEST( UIFontPickerDialog, ExternalFontBecomesDistinctStyleOnFamilyNameCollision ) { const std::string managedFontPath = Sys::getProcessPath() + "assets/fonts/NotoSansKR-Regular.ttf"; ASSERT_TRUE( FileSystem::fileExists( managedFontPath ) ); @@ -126,23 +133,44 @@ UTEST( UIFontPickerDialog, ExternalFontKeepsSeparateFamilyEntryOnNameCollision ) EXPECT_STDSTREQ( externalPath, dialog->getSelection().font.path ); EXPECT_FALSE( dialog->getFamilyList()->getSelection().isEmpty() ); - EXPECT_STDSTREQ( family + " [External]", - dialog->getFamilyList()->getSelection().first().data().toString() ); + EXPECT_STDSTREQ( family, dialog->getFamilyList()->getSelection().first().data().toString() ); + EXPECT_TRUE( dialog->getStyleList()->getSelection().first().data().toString().find( + "[External:" ) != std::string::npos ); - bool foundSystemFamily = false; - bool foundExternalFamily = false; + size_t matchingFamilies = 0; Model* familyModel = dialog->getFamilyList()->getModel(); ASSERT_TRUE( familyModel != nullptr ); for ( size_t row = 0; row < familyModel->rowCount(); row++ ) { const std::string label = familyModel->index( row ).data().toString(); - foundSystemFamily |= label == family; - foundExternalFamily |= label == family + " [External]"; + matchingFamilies += label == family; } - EXPECT_TRUE( foundSystemFamily ); - EXPECT_TRUE( foundExternalFamily ); + EXPECT_EQ( 1u, matchingFamilies ); + + bool foundSystemStyle = false; + bool foundExternalStyle = false; + Model* styleModel = dialog->getStyleList()->getModel(); + ASSERT_TRUE( styleModel != nullptr ); + for ( size_t row = 0; row < styleModel->rowCount(); row++ ) { + const std::string label = styleModel->index( row ).data().toString(); + foundSystemStyle |= label.find( "[External:" ) == std::string::npos; + foundExternalStyle |= label.find( "[External:" ) != std::string::npos; + } + EXPECT_TRUE( foundSystemStyle ); + EXPECT_TRUE( foundExternalStyle ); EXPECT_EQ( managedFont.get(), resourceScope.findFont( externalName ).get() ); + TestFontPickerDialog* hiddenStyleDialog = + TestFontPickerDialog::New( UIFontPickerDialog::ShowSize ); + hiddenStyleDialog->setSelectedFont( externalPath ); + pumpUntil( app.getUI(), + [hiddenStyleDialog] { return hiddenStyleDialog->getButtonOK()->isEnabled(); } ); + EXPECT_STDSTREQ( externalPath, hiddenStyleDialog->getSelection().font.path ); + EXPECT_FALSE( hiddenStyleDialog->getStyleList()->getParent()->isVisible() ); + EXPECT_TRUE( hiddenStyleDialog->getFamilyList()->getSelection().first().data().toString().find( + "[External:" ) != std::string::npos ); + dialog->releasePreviewFont(); + hiddenStyleDialog->releasePreviewFont(); FileSystem::fileRemove( externalPath ); } @@ -324,7 +352,44 @@ UTEST( UIFontPickerDialog, AsyncLoadPreservesExternalFontPreselection ) { EXPECT_FALSE( dialog->getFamilyList()->getSelection().isEmpty() ); EXPECT_FALSE( dialog->getStyleList()->getSelection().isEmpty() ); EXPECT_TRUE( dialog->getFamilyList()->getSelection().first().data().toString().find( - "[External]" ) != std::string::npos ); + "[External" ) != std::string::npos ); +} + +UTEST( UIFontPickerDialog, AsyncLoadPromotesEnumeratedExternalFontToSystemFamily ) { + std::vector fonts = SystemFontResolver::instance()->enumerate(); + auto regular = std::find_if( fonts.begin(), fonts.end(), []( const FontDesc& font ) { + return font.weight == FontWeight::Normal && !font.italic && !font.family.empty() && + !font.path.empty(); + } ); + if ( regular == fonts.end() ) + UTEST_SKIP( "no regular system font available" ); + const FontDesc regularFont = *regular; + + UIApplication app( + WindowSettings( 320, 240, "eepp - UIFontPickerDialog Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + + TestFontPickerDialog* dialog = TestFontPickerDialog::New(); + dialog->setFontsForTest( fonts ); + dialog->setSelectedFont( regularFont ); + dialog->markSelectedFontExternalForTest(); + EXPECT_STDSTREQ( regularFont.family + " [External]", + dialog->getFamilyList()->getSelection().first().data().toString() ); + EXPECT_TRUE( dialog->getStyleList()->getSelection().first().data().toString().find( + "[External:" ) != std::string::npos ); + + dialog->setFontsForTest( std::move( fonts ) ); + EXPECT_STDSTREQ( regularFont.path, dialog->getSelection().font.path ); + EXPECT_EQ( regularFont.faceIndex, dialog->getSelection().font.faceIndex ); + EXPECT_EQ( FontWeight::Normal, dialog->getSelection().font.weight ); + EXPECT_FALSE( dialog->getSelection().font.italic ); + EXPECT_STDSTREQ( regularFont.family, + dialog->getFamilyList()->getSelection().first().data().toString() ); + EXPECT_TRUE( dialog->getStyleList()->getSelection().first().data().toString().find( + "[External:" ) == std::string::npos ); + + dialog->releasePreviewFont(); } UTEST( UIFontPickerDialog, DefaultColorComesFromTheme ) { @@ -415,6 +480,43 @@ UTEST( UIFontPickerDialog, StyleLabelsIncludeFontStretch ) { EXPECT_TRUE( foundStyle ); } +UTEST( UIFontPickerDialog, PreservesVisibleStyleAndPrefersRegularWhenHidden ) { + std::vector fonts = SystemFontResolver::instance()->enumerate(); + auto italic = std::find_if( fonts.begin(), fonts.end(), [&]( const FontDesc& candidate ) { + if ( !candidate.italic ) + return false; + return std::find_if( fonts.begin(), fonts.end(), [&]( const FontDesc& regular ) { + return regular.family == candidate.family && + regular.weight == FontWeight::Normal && !regular.italic; + } ) != fonts.end(); + } ); + if ( italic == fonts.end() ) + UTEST_SKIP( "no family with regular and italic styles available" ); + const FontDesc italicFont = *italic; + + UIApplication app( + WindowSettings( 320, 240, "eepp - UIFontPickerDialog Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + + TestFontPickerDialog* visible = TestFontPickerDialog::New( UIFontPickerDialog::ShowStyle ); + visible->setFontsForTest( fonts ); + visible->setSelectedFont( italicFont ); + EXPECT_TRUE( visible->getStyleList()->getParent()->isVisible() ); + EXPECT_EQ( italicFont.weight, visible->getSelection().font.weight ); + EXPECT_TRUE( visible->getSelection().font.italic ); + + TestFontPickerDialog* hidden = TestFontPickerDialog::New( UIFontPickerDialog::ShowSize ); + hidden->setFontsForTest( std::move( fonts ) ); + hidden->setSelectedFont( italicFont ); + EXPECT_FALSE( hidden->getStyleList()->getParent()->isVisible() ); + EXPECT_EQ( FontWeight::Normal, hidden->getSelection().font.weight ); + EXPECT_FALSE( hidden->getSelection().font.italic ); + + visible->releasePreviewFont(); + hidden->releasePreviewFont(); +} + UTEST( UIFontPickerDialog, ApplyButtonEmitsOnApply ) { UIApplication app( WindowSettings( 320, 240, "eepp - UIFontPickerDialog Test", WindowStyle::Default, diff --git a/src/tools/ecode/fontpickercontroller.cpp b/src/tools/ecode/fontpickercontroller.cpp index be8745f23..124342b82 100644 --- a/src/tools/ecode/fontpickercontroller.cpp +++ b/src/tools/ecode/fontpickercontroller.cpp @@ -105,7 +105,8 @@ void FontPickerController::openFontDialog( std::string& fontPath, bool loadingMo defaultResourceScope().publishLocalFont( std::move( fontName ), font ); }; - const Uint32 flags = ( pickFontSize ? UIFontPickerDialog::ShowSize : 0 ) | + const Uint32 flags = UIFontPickerDialog::ShowStyle | + ( pickFontSize ? UIFontPickerDialog::ShowSize : 0 ) | ( loadingMonoFont ? UIFontPickerDialog::MonospaceOnly : 0 ); UIFontPickerDialog* dialog = UIFontPickerDialog::New( flags ); dialog->setTitle( mApp->i18n( "select_font", "Select Font" ) );