Improve kickoff's group for non-latin language.

Use icu transliterator to convert i18n'ed name into ascii for initial
character grouping. Since AppsModel does not use group(), just use group
to store this value for convenience.

Relevant qt feature request for having a new mode for section:
https://bugreports.qt.io/browse/QTBUG-91258

Strategy used by this code mainly focused on CJK language.
1. Japanese locale will group all Han script together.
   Katakana will be converted to hiragana.
2. Hangul will decompose and use consonant as group name.
3. Han will use icu "Han-Latin" transliteration to convert to pinyin.

BUG: 433297
wilder-5.25
Xuetian Weng 4 years ago committed by Fushan Wen
parent 127cd63133
commit c0cb9f9929
  1. 7
      applets/kicker/CMakeLists.txt
  2. 56
      applets/kicker/plugin/appentry.cpp
  3. 3
      applets/kicker/plugin/appentry.h
  4. 8
      applets/kicker/plugin/appsmodel.cpp
  5. 10
      applets/kicker/plugin/rootmodel.cpp

@ -1,3 +1,5 @@
find_package(ICU COMPONENTS i18n uc)
add_definitions(
-DQT_USE_QSTRINGBUILDER
-DQT_NO_CAST_TO_ASCII
@ -87,6 +89,11 @@ if (${HAVE_APPSTREAMQT})
target_link_libraries(kickerplugin AppStreamQt)
endif()
if (ICU_FOUND)
target_link_libraries(kickerplugin ICU::i18n ICU::uc)
target_compile_definitions(kickerplugin PRIVATE "-DHAVE_ICU")
endif()
add_subdirectory(plugin/autotests)
install(TARGETS kickerplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/kicker)

@ -40,6 +40,56 @@
#include <Plasma/Plasma>
#ifdef HAVE_ICU
#include <unicode/translit.h>
#endif
namespace
{
QString groupName(const QString &name)
{
if (name.isEmpty()) {
return QString();
}
// Here we will apply a locale based strategy for the first character.
// If first character is hangul, run decomposition and return the choseong (consonants).
if (name[0].script() == QChar::Script_Hangul) {
auto decomposed = name[0].decomposition();
if (decomposed.isEmpty()) {
return name.left(1);
}
return decomposed.left(1);
}
if (QLocale::system().language() == QLocale::Japanese) {
// We do this here for Japanese locale because:
// 1. it does not make much sense to have every different Kanji to have a different group.
// 2. ICU transliterator can't yet convert Kanji to Hiragana.
// https://unicode-org.atlassian.net/browse/ICU-5874
if (name[0].script() == QChar::Script_Han) {
// Unicode Han
return QString::fromUtf8("\xe6\xbc\xa2");
}
}
#ifdef HAVE_ICU
static auto ue = UErrorCode::U_ZERO_ERROR;
static auto transliterator =
std::unique_ptr<icu::Transliterator>(icu::Transliterator::createInstance("Han-Latin; "
"Katakana-Hiragana; "
"Latin-ASCII",
UTRANS_FORWARD,
ue));
if (ue == UErrorCode::U_ZERO_ERROR) {
icu::UnicodeString icuText(reinterpret_cast<const char16_t *>(name.data()), name.size());
transliterator->transliterate(icuText);
return QString::fromUtf16(icuText.getBuffer(), static_cast<int>(icuText.length())).left(1);
}
#endif
return name.left(1);
}
}
AppEntry::AppEntry(AbstractModel *owner, KService::Ptr service, NameFormat nameFormat)
: AbstractEntry(owner)
, m_service(service)
@ -78,6 +128,7 @@ AppEntry::AppEntry(AbstractModel *owner, const QString &id)
void AppEntry::init(NameFormat nameFormat)
{
m_name = nameFromService(m_service, nameFormat);
m_group = groupName(m_name);
if (nameFormat == GenericNameOnly) {
m_description = nameFromService(m_service, NameOnly);
@ -118,6 +169,11 @@ KService::Ptr AppEntry::service() const
return m_service;
}
QString AppEntry::group() const
{
return m_group;
}
QString AppEntry::id() const
{
if (!m_id.isEmpty()) {

@ -39,6 +39,7 @@ public:
QString name() const override;
QString description() const override;
KService::Ptr service() const;
QString group() const override;
QString id() const override;
QUrl url() const override;
@ -59,6 +60,8 @@ private:
QString m_id;
QString m_name;
QString m_description;
// Not an actual group name, but the first character for transliterated name.
QString m_group;
mutable QIcon m_icon;
KService::Ptr m_service;
static MenuEntryEditor *m_menuEntryEditor;

@ -163,6 +163,8 @@ QVariant AppsModel::data(const QModelIndex &index, int role) const
}
return actionList;
} else if (role == Kicker::GroupRole) {
return entry->group();
}
return QVariant();
@ -691,7 +693,11 @@ void AppsModel::sortEntries()
if (a->type() != b->type()) {
return a->type() > b->type();
} else {
return c.compare(a->name(), b->name()) < 0;
if (a->group() != b->group()) {
return c.compare(a->group(), b->group()) < 0;
} else {
return c.compare(a->name(), b->name()) < 0;
}
}
});
}

@ -370,11 +370,17 @@ void RootModel::refresh()
for (int i = 0; i < model->count(); ++i) {
AbstractEntry *appEntry = static_cast<AbstractEntry *>(model->index(i, 0).internalPointer());
if (appEntry->name().isEmpty()) {
// App entry's group stores a transliterated first character of the name. Prefer to use that.
QString name = appEntry->group();
if (name.isEmpty()) {
name = appEntry->name();
}
if (name.isEmpty()) {
continue;
}
const QChar &first = appEntry->name().at(0).toUpper();
const QChar &first = name.at(0).toUpper();
m_categoryHash[first.isDigit() ? QStringLiteral("0-9") : first].append(appEntry);
}
}

Loading…
Cancel
Save