diff --git a/src/apps/haikudepot/HaikuDepotConstants.h b/src/apps/haikudepot/HaikuDepotConstants.h index d251f69c20..9671451c7e 100644 --- a/src/apps/haikudepot/HaikuDepotConstants.h +++ b/src/apps/haikudepot/HaikuDepotConstants.h @@ -1,5 +1,5 @@ /* - * Copyright 2018, Andrew Lindesay . + * Copyright 2018-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef HAIKU_DEPOT_CONSTANTS_H @@ -18,7 +18,8 @@ enum { MSG_SERVER_DATA_CHANGED = 'svdc', MSG_ALERT_SIMPLE_ERROR = 'nser', MSG_DID_ADD_USER_RATING = 'adur', - MSG_DID_UPDATE_USER_RATING = 'upur' + MSG_DID_UPDATE_USER_RATING = 'upur', + MSG_LANGUAGE_SELECTED = 'lngs' }; @@ -54,4 +55,9 @@ enum { RSRC_ARROW_RIGHT = 550, }; + +#define LANGUAGE_DEFAULT_CODE "en" +#define LANGUAGE_DEFAULT Language(LANGUAGE_DEFAULT_CODE, "English", true) + + #endif // HAIKU_DEPOT_CONSTANTS_H \ No newline at end of file diff --git a/src/apps/haikudepot/Jamfile b/src/apps/haikudepot/Jamfile index 2a04c7ba49..875f9fec7b 100644 --- a/src/apps/haikudepot/Jamfile +++ b/src/apps/haikudepot/Jamfile @@ -16,12 +16,19 @@ local dumpExportRepositoryBulkListerTargetDirectory = local dumpExportPkgBulkListenerTargetDirectory = [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportpkgbulklistener ] ; +local dumpExportReferenceListenerTargetDirectory = + [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) + dumpexportreferencelistener ] ; + local dumpExportPkgModelTargetDirectory = [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportpkgmodel ] ; local dumpExportRepositoryModelTargetDirectory = [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportrepositorymodel ] ; +local dumpExportReferenceModelTargetDirectory = + [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) + dumpexportreferencemodel ] ; # During the build process, some sources are generated into a directory. In # order to maintain a timestamp on that generation process, a dummy file is @@ -34,10 +41,15 @@ local dumpExportRepositoryBulkListerTargetFile = [ FDirName $(dumpExportRepositoryBulkListerTargetDirectory) $(dummyFile) ] ; local dumpExportPkgBulkListenerTargetFile = [ FDirName $(dumpExportPkgBulkListenerTargetDirectory) $(dummyFile) ] ; +local dumpExportReferenceListenerTargetFile = + [ FDirName $(dumpExportReferenceListenerTargetDirectory) $(dummyFile) ] ; + local dumpExportPkgModelTargetFile = [ FDirName $(dumpExportPkgModelTargetDirectory) $(dummyFile) ] ; local dumpExportRepositoryModelTargetFile = [ FDirName $(dumpExportRepositoryModelTargetDirectory) $(dummyFile) ] ; +local dumpExportReferenceModelTargetFile = + [ FDirName $(dumpExportReferenceModelTargetDirectory) $(dummyFile) ] ; # source directories local sourceDirs = @@ -57,10 +69,14 @@ for sourceDir in $(sourceDirs) { } SEARCH_SOURCE += [ FDirName $(HAIKU_TOP) src servers package ] ; + SEARCH_SOURCE += $(dumpExportRepositoryBulkListerTargetDirectory) ; SEARCH_SOURCE += $(dumpExportPkgBulkListenerTargetDirectory) ; +SEARCH_SOURCE += $(dumpExportReferenceListenerTargetDirectory) ; + SEARCH_SOURCE += $(dumpExportPkgModelTargetDirectory) ; SEARCH_SOURCE += $(dumpExportRepositoryModelTargetDirectory) ; +SEARCH_SOURCE += $(dumpExportReferenceModelTargetDirectory) ; local textDocumentSources = # edits_generic @@ -101,6 +117,7 @@ local applicationSources = FilterView.cpp LocalIconStore.cpp JobStateListener.cpp + LanguageModel.cpp LinkView.cpp LinkedBitmapView.cpp Logger.cpp @@ -138,6 +155,7 @@ local applicationSources = ServerHelper.cpp ServerSettings.cpp ServerPkgDataUpdateProcess.cpp + ServerReferenceDataUpdateProcess.cpp ServerRepositoryDataUpdateProcess.cpp ServerIconExportUpdateProcess.cpp StandardMetaDataJsonEventListener.cpp @@ -151,6 +169,7 @@ local applicationSources = # util AppUtils.cpp DataIOUtils.cpp + LanguageMenuUtils.cpp RepositoryUrlUtils.cpp StorageUtils.cpp ToFileUrlProtocolListener.cpp @@ -173,6 +192,14 @@ local generatedRepositoryModelSourceFiles = DumpExportRepositorySourceMirror.cpp ; +local generatedReferenceModelSourceFiles = + DumpExportReference.cpp + DumpExportReferenceNaturalLanguage.cpp + DumpExportReferencePkgCategory.cpp + DumpExportReferenceUserRatingStability.cpp + DumpExportReferenceCountry.cpp +; + local generatedPkgParserSourceFiles = DumpExportPkgJsonListener.cpp ; @@ -181,10 +208,19 @@ local generatedRepositoryParserSourceFiles = DumpExportRepositoryJsonListener.cpp ; +local generatedReferenceParserSourceFiles = + DumpExportReferenceJsonListener.cpp +; + Application HaikuDepot - : $(applicationSources) $(textDocumentSources) - $(generatedPkgModelSourceFiles) $(generatedRepositoryModelSourceFiles) - $(generatedPkgParserSourceFiles) $(generatedRepositoryParserSourceFiles) + : $(applicationSources) + $(textDocumentSources) + $(generatedPkgModelSourceFiles) + $(generatedRepositoryModelSourceFiles) + $(generatedReferenceModelSourceFiles) + $(generatedPkgParserSourceFiles) + $(generatedRepositoryParserSourceFiles) + $(generatedReferenceParserSourceFiles) : be package bnetapi translation libcolumnlistview.a shared [ TargetLibstdc++ ] [ TargetLibsupc++ ] localestub : HaikuDepot.rdef @@ -225,12 +261,18 @@ HdsSchemaGenModel $(dumpExportPkgModelTargetFile) HdsSchemaGenModel $(dumpExportRepositoryModelTargetFile) : dumpexportrepository.json : jsonschema2cppmodel.py ; +HdsSchemaGenModel $(dumpExportReferenceModelTargetFile) + : dumpexportreference.json : jsonschema2cppmodel.py ; + HdsSchemaGenBulkParser $(dumpExportRepositoryBulkListerTargetFile) : dumpexportrepository.json : jsonschema2cppparser.py ; HdsSchemaGenBulkParser $(dumpExportPkgBulkListenerTargetFile) : dumpexportpkg.json : jsonschema2cppparser.py ; +HdsSchemaGenParser $(dumpExportReferenceListenerTargetFile) + : dumpexportreference.json : jsonschema2cppparser.py ; + HdsSchemaGenAppSrcDependsOnGeneration [ FGristFiles $(generatedPkgParserSourceFiles) ] [ FGristFiles $(generatedPkgParserSourceFiles:S=.h) ] @@ -241,6 +283,11 @@ HdsSchemaGenAppSrcDependsOnGeneration [ FGristFiles $(generatedRepositoryParserSourceFiles:S=.h) ] : $(dumpExportRepositoryBulkListerTargetFile) ; +HdsSchemaGenAppSrcDependsOnGeneration + [ FGristFiles $(generatedReferenceParserSourceFiles) ] + [ FGristFiles $(generatedReferenceParserSourceFiles:S=.h) ] + : $(dumpExportReferenceListenerTargetFile) ; + HdsSchemaGenAppSrcDependsOnGeneration [ FGristFiles $(generatedRepositoryModelSourceFiles) ] [ FGristFiles $(generatedRepositoryModelSourceFiles:S=.h) ] @@ -251,13 +298,20 @@ HdsSchemaGenAppSrcDependsOnGeneration [ FGristFiles $(generatedPkgModelSourceFiles:S=.h) ] : $(dumpExportPkgModelTargetFile) ; +HdsSchemaGenAppSrcDependsOnGeneration + [ FGristFiles $(generatedReferenceModelSourceFiles) ] + [ FGristFiles $(generatedReferenceModelSourceFiles:S=.h) ] + : $(dumpExportReferenceModelTargetFile) ; + # This will ensure that if any of the generated files' header files change, then # the application should be re-built. Depends [ FGristFiles $(applicationSources:S=.o) ] : [ FGristFiles $(generatedPkgParserSourceFiles:S=.h) ] + [ FGristFiles $(generatedPkgModelSourceFiles:S=.h) ] [ FGristFiles $(generatedRepositoryParserSourceFiles:S=.h) ] [ FGristFiles $(generatedRepositoryModelSourceFiles:S=.h) ] - [ FGristFiles $(generatedPkgModelSourceFiles:S=.h) ] + [ FGristFiles $(generatedReferenceParserSourceFiles:S=.h) ] + [ FGristFiles $(generatedReferenceModelSourceFiles:S=.h) ] ; \ No newline at end of file diff --git a/src/apps/haikudepot/build/jam/HdsSchemaGenRules b/src/apps/haikudepot/build/jam/HdsSchemaGenRules index 8c3bf8c1d5..96872b4bb2 100644 --- a/src/apps/haikudepot/build/jam/HdsSchemaGenRules +++ b/src/apps/haikudepot/build/jam/HdsSchemaGenRules @@ -72,6 +72,29 @@ actions HdsSchemaGenBulkParser1 touch $(1) } +# pragma mark - Non-Bulk Parsing Class Generation + +# 1 : the dummy file in the class generation directory (target) +# 2 : the JSON schema file +# 3 : the Python script to use + +rule HdsSchemaGenParser +{ + SEARCH on $(2) = [ FDirName $(SUBDIR) server schema ] ; + SEARCH on $(3) = [ FDirName $(SUBDIR) build scripts ] ; + + Clean $(1:D) ; + Depends $(1) : $(2) $(3) ; + HdsSchemaGenParser1 $(1) : $(2) $(3) $(1:D) ; +} + +actions HdsSchemaGenParser1 +{ + mkdir -p $(2[3]) + $(HOST_PYTHON) $(2[2]) -i $(2[1]) --outputdirectory $(2[3]) + touch $(1) +} + # pragma mark - Registering Generated Classes # Because a number of .cpp and .h files will be generated from a single python diff --git a/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py b/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py index ce4b453a43..8eb54ad943 100644 --- a/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py +++ b/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py @@ -152,7 +152,27 @@ def writescalaraccessors(outputfile, cppclassname, cppname, cppmembername, cppty string.Template(""" ${cpptype} ${cppclassname}::${cppname}() -{ +{""").substitute(dict)) + + if cpptype == jscom.CPP_TYPE_BOOLEAN: + outputfile.write(string.Template(""" + if (${cppname}IsNull()) + return false; +""").substitute(dict)) + + if cpptype == jscom.CPP_TYPE_INTEGER: + outputfile.write(string.Template(""" + if (${cppname}IsNull()) + return 0; +""").substitute(dict)) + + if cpptype == jscom.CPP_TYPE_NUMBER: + outputfile.write(string.Template(""" + if (${cppname}IsNull()) + return 0.0; +""").substitute(dict)) + + outputfile.write(string.Template(""" return *${cppmembername}; } diff --git a/src/apps/haikudepot/model/LanguageModel.cpp b/src/apps/haikudepot/model/LanguageModel.cpp new file mode 100644 index 0000000000..30263cd2c6 --- /dev/null +++ b/src/apps/haikudepot/model/LanguageModel.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2019, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#include "LanguageModel.h" + +#include +#include +#include + +#include "HaikuDepotConstants.h" +#include "Logger.h" + + +static int32 +LanguagesCompareFn(const LanguageRef& l1, const LanguageRef& l2) +{ + const BLocale* locale = BLocaleRoster::Default()->GetDefaultLocale(); + BCollator collator; + + if (B_OK != locale->GetCollator(&collator)) { + debugger("unable to get the locale's collator"); + } + + BString name1; + BString name2; + + if (l1->GetName(name1) == B_OK && l2->GetName(name2) == B_OK) + return collator.Compare(name1.String(), name2.String()); + + return collator.Compare(l1->Code(), l2->Code()); +} + + +LanguageModel::LanguageModel() + : + fSupportedLanguages(LanguagesCompareFn, NULL), + fPreferredLanguage(LANGUAGE_DEFAULT) +{ + const Language defaultLanguage = _DeriveDefaultLanguage(); + fSupportedLanguages.Add(LanguageRef( + new Language(defaultLanguage))); + _SetPreferredLanguage(defaultLanguage); +} + + +LanguageModel::~LanguageModel() +{ +} + + +void +LanguageModel::AddSupportedLanguages(const LanguageList& languages) +{ + for (int32 i = 0; i < languages.CountItems(); i++) { + const LanguageRef language = languages.ItemAt(i); + int32 index = IndexOfSupportedLanguage(language->Code()); + + if (index == -1) + fSupportedLanguages.Add(language); + else + fSupportedLanguages.Replace(index, language); + } + + // it could be that the preferred language does not exist in the + // list. In this case it is necessary to choose one from the list. + _SetPreferredLanguage(_DeriveDefaultLanguage()); +} + + +void +LanguageModel::_SetPreferredLanguage(const Language& language) +{ + fPreferredLanguage = language; + if(Logger::IsDebugEnabled()) + printf("set preferred language [%s]\n", language.Code()); +} + + +int32 +LanguageModel::IndexOfSupportedLanguage(const BString& languageCode) const +{ + for (int32 i = 0; i < fSupportedLanguages.CountItems(); i++) { + if (fSupportedLanguages.ItemAt(i)->Code() == languageCode) + return i; + } + + return -1; +} + + +/*! This will derive the default language. If there are no other + possible languages configured then the default language will be + assumed to exist. Otherwise if there is a set of possible languages + then this method will ensure that the default language is in that + set. +*/ + +Language +LanguageModel::_DeriveDefaultLanguage() const +{ + Language defaultLanguage = _DeriveSystemDefaultLanguage(); + + if(Logger::IsDebugEnabled()) { + printf("derived system default language [%s]\n", + defaultLanguage.Code()); + } + + // if there are no supported languages; as is the case to start with as the + // application starts, the default language from the system is used anyway. + // The data queried in HDS will handle the case where the language is not + // 'known' at the HDS end so it doesn't matter if it is invalid. + + if (fSupportedLanguages.IsEmpty()) + return defaultLanguage; + + // if there are supported languages defined then the preferred language + // needs to be one of the supported ones. + + Language* foundSupportedLanguage = _FindSupportedLanguage( + defaultLanguage.Code()); + + if (foundSupportedLanguage == NULL) { + printf("unable to find the language [%s] - looking for app default", + defaultLanguage.Code()); + foundSupportedLanguage = _FindSupportedLanguage( + LANGUAGE_DEFAULT.Code()); + } + + if (foundSupportedLanguage == NULL) { + printf("unable to find the app default language - using the first " + "supported language"); + foundSupportedLanguage = fSupportedLanguages.ItemAt(0); + } + + return Language(*foundSupportedLanguage); +} + + +/*static*/ Language +LanguageModel::_DeriveSystemDefaultLanguage() +{ + BLocaleRoster* localeRoster = BLocaleRoster::Default(); + if (localeRoster != NULL) { + BMessage preferredLanguages; + if (localeRoster->GetPreferredLanguages(&preferredLanguages) == B_OK) { + BString language; + BString languageIso; + if (preferredLanguages.FindString( + "language", 0, &language) == B_OK) { + language.CopyInto(languageIso, 0, 2); + return Language(languageIso, languageIso, true); + } + } + } + + return LANGUAGE_DEFAULT; +} + + +Language* +LanguageModel::_FindSupportedLanguage(const BString& code) const +{ + int32 index = IndexOfSupportedLanguage(code); + if (-1 == index) + return NULL; + return fSupportedLanguages.ItemAt(index); +} \ No newline at end of file diff --git a/src/apps/haikudepot/model/LanguageModel.h b/src/apps/haikudepot/model/LanguageModel.h new file mode 100644 index 0000000000..5010332d32 --- /dev/null +++ b/src/apps/haikudepot/model/LanguageModel.h @@ -0,0 +1,47 @@ +/* + * Copyright 2019, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#ifndef LANGUAGE_MODEL_H +#define LANGUAGE_MODEL_H + + +#include + +#include "List.h" +#include "PackageInfo.h" + + +typedef BReference LanguageRef; +typedef List LanguageList; + + +class LanguageModel { +public: + LanguageModel(); + virtual ~LanguageModel(); + + const LanguageList& SupportedLanguages() const + { return fSupportedLanguages; } + void AddSupportedLanguages( + const LanguageList& languages); + int32 IndexOfSupportedLanguage( + const BString& languageCode) const; + + const Language& PreferredLanguage() const + { return fPreferredLanguage; } + +private: + static Language _DeriveSystemDefaultLanguage(); + Language _DeriveDefaultLanguage() const; + Language* _FindSupportedLanguage( + const BString& code) const; + void _SetPreferredLanguage(const Language& language); + +private: + LanguageList fSupportedLanguages; + Language fPreferredLanguage; +}; + + +#endif // LANGUAGE_MODEL_H \ No newline at end of file diff --git a/src/apps/haikudepot/model/Model.cpp b/src/apps/haikudepot/model/Model.cpp index f6ee6a675f..7f583f6348 100644 --- a/src/apps/haikudepot/model/Model.cpp +++ b/src/apps/haikudepot/model/Model.cpp @@ -1,7 +1,7 @@ /* * Copyright 2013-2014, Stephan Aßmus . * Copyright 2014, Axel Dörfler . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -353,7 +353,6 @@ Model::Model() fDepotFilter(""), fSearchTermsFilter(PackageFilterRef(new AnyFilter(), true)), fIsFeaturedFilter(), - fShowFeaturedPackages(true), fShowAvailablePackages(true), fShowInstalledPackages(true), @@ -378,38 +377,6 @@ Model::Model() // get the defined categories and their translated names. // This should then be used instead of hard-coded // categories and translations in the app. - - fPreferredLanguage = "en"; - BLocaleRoster* localeRoster = BLocaleRoster::Default(); - if (localeRoster != NULL) { - BMessage preferredLanguages; - if (localeRoster->GetPreferredLanguages(&preferredLanguages) == B_OK) { - BString language; - if (preferredLanguages.FindString("language", 0, &language) == B_OK) - language.CopyInto(fPreferredLanguage, 0, 2); - } - } - - // TODO: Fetch this from the web-app. - fSupportedLanguages.Add("en"); - fSupportedLanguages.Add("es"); - fSupportedLanguages.Add("de"); - fSupportedLanguages.Add("fr"); - fSupportedLanguages.Add("it"); - fSupportedLanguages.Add("ja"); - fSupportedLanguages.Add("pt"); - fSupportedLanguages.Add("ru"); - fSupportedLanguages.Add("sk"); - fSupportedLanguages.Add("zh"); - - if (!fSupportedLanguages.Contains(fPreferredLanguage)) { - // Force the preferred language to one of the currently supported - // ones, until the web application supports all ISO language codes. - printf("User preferred language '%s' not currently supported, " - "defaulting to 'en'.\n", fPreferredLanguage.String()); - fPreferredLanguage = "en"; - } - fWebAppInterface.SetPreferredLanguage(fPreferredLanguage); } @@ -418,6 +385,13 @@ Model::~Model() } +LanguageModel& +Model::Language() +{ + return fLanguageModel; +} + + bool Model::AddListener(const ModelListenerRef& listener) { @@ -903,6 +877,30 @@ Model::SetAuthorization(const BString& username, const BString& password, } +status_t +Model::_LocalDataPath(const BString leaf, BPath& path) const +{ + BString leafAssembled(leaf); + leafAssembled.ReplaceAll("%languageCode%", + LanguageModel().PreferredLanguage().Code()); + + BPath repoDataPath; + + if (find_directory(B_USER_CACHE_DIRECTORY, &repoDataPath) == B_OK + && repoDataPath.Append("HaikuDepot") == B_OK + && create_directory(repoDataPath.Path(), 0777) == B_OK + && repoDataPath.Append(leafAssembled) == B_OK) { + path.SetTo(repoDataPath.Path()); + return B_OK; + } + + path.Unset(); + fprintf(stdout, "unable to find the user cache file for [%s] data", + leaf.String()); + return B_ERROR; +} + + /*! When bulk repository data comes down from the server, it will arrive as a json.gz payload. This is stored locally as a cache and this method will provide the on-disk storage location for @@ -912,20 +910,19 @@ Model::SetAuthorization(const BString& username, const BString& password, status_t Model::DumpExportRepositoryDataPath(BPath& path) const { - BPath repoDataPath; + return _LocalDataPath("repository-all_%languageCode%.json.gz", path); +} - if (find_directory(B_USER_CACHE_DIRECTORY, &repoDataPath) == B_OK - && repoDataPath.Append("HaikuDepot") == B_OK - && create_directory(repoDataPath.Path(), 0777) == B_OK - && repoDataPath.Append("repository-all_en.json.gz") == B_OK) { - path.SetTo(repoDataPath.Path()); - return B_OK; - } - path.Unset(); - fprintf(stdout, "unable to find the user cache file for repositories'" - " data"); - return B_ERROR; +/*! When the system downloads reference data (eg; categories) from the server + then the downloaded data is stored and cached at the path defined by this + method. +*/ + +status_t +Model::DumpExportReferenceDataPath(BPath& path) const +{ + return _LocalDataPath("reference-all_%languageCode%.json.gz", path); } @@ -956,7 +953,7 @@ Model::DumpExportPkgDataPath(BPath& path, BString leafName; leafName.SetToFormat("pkg-all-%s-%s.json.gz", repositorySourceCode.String(), - fPreferredLanguage.String()); + LanguageModel().PreferredLanguage().Code()); if (find_directory(B_USER_CACHE_DIRECTORY, &repoDataPath) == B_OK && repoDataPath.Append("HaikuDepot") == B_OK diff --git a/src/apps/haikudepot/model/Model.h b/src/apps/haikudepot/model/Model.h index 3799c408f3..553f48614a 100644 --- a/src/apps/haikudepot/model/Model.h +++ b/src/apps/haikudepot/model/Model.h @@ -1,6 +1,6 @@ /* * Copyright 2013-2014, Stephan Aßmus . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef MODEL_H @@ -10,6 +10,7 @@ #include #include "AbstractProcess.h" +#include "LanguageModel.h" #include "LocalIconStore.h" #include "PackageInfo.h" #include "WebAppInterface.h" @@ -63,6 +64,8 @@ public: Model(); virtual ~Model(); + LanguageModel& Language(); + BLocker* Lock() { return &fLock; } @@ -150,12 +153,6 @@ public: void PopulatePackage(const PackageInfoRef& package, uint32 flags); - const StringList& SupportedLanguages() const - { return fSupportedLanguages; } - - const BString& PreferredLanguage() const - { return fPreferredLanguage; } - void SetUsername(BString username); const BString& Username() const; void SetAuthorization(const BString& username, @@ -171,6 +168,7 @@ public: void* context); status_t IconStoragePath(BPath& path) const; + status_t DumpExportReferenceDataPath(BPath& path) const; status_t DumpExportRepositoryDataPath(BPath& path) const; status_t DumpExportPkgDataPath(BPath& path, const BString& repositorySourceCode) const; @@ -178,6 +176,9 @@ public: void LogDepotsWithNoWebAppRepositoryCode() const; private: + status_t _LocalDataPath(const BString leaf, + BPath& path) const; + void _MaybeLogJsonRpcError( const BMessage &responsePayload, const char *sourceDescription) const; @@ -245,9 +246,7 @@ private: bool fShowSourcePackages; bool fShowDevelopPackages; - StringList fSupportedLanguages; - BString fPreferredLanguage; - + LanguageModel fLanguageModel; WebAppInterface fWebAppInterface; ModelListenerList fListeners; diff --git a/src/apps/haikudepot/model/PackageInfo.cpp b/src/apps/haikudepot/model/PackageInfo.cpp index e0ba889dde..e1cade50db 100644 --- a/src/apps/haikudepot/model/PackageInfo.cpp +++ b/src/apps/haikudepot/model/PackageInfo.cpp @@ -1,7 +1,7 @@ /* * Copyright 2013-2014, Stephan Aßmus . * Copyright 2013, Rene Gollent . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -15,6 +15,41 @@ #include +// #pragma mark - Language + + +Language::Language(const char* language, const BString& serverName, + bool isPopular) + : + BLanguage(language), + fServerName(serverName), + fIsPopular(isPopular) +{ +} + + +Language::Language(const Language& other) + : + BLanguage(other.Code()), + fServerName(other.fServerName), + fIsPopular(other.fIsPopular) +{ +} + + +status_t +Language::GetName(BString& name, + const BLanguage* displayLanguage) const +{ + status_t result = BLanguage::GetName(name, displayLanguage); + + if (result == B_OK && (name.IsEmpty() || name == Code())) + name.SetTo(fServerName); + + return result; +} + + // #pragma mark - UserInfo diff --git a/src/apps/haikudepot/model/PackageInfo.h b/src/apps/haikudepot/model/PackageInfo.h index 263d052e4a..c2c8ddb35b 100644 --- a/src/apps/haikudepot/model/PackageInfo.h +++ b/src/apps/haikudepot/model/PackageInfo.h @@ -1,6 +1,6 @@ /* * Copyright 2013-2014, Stephan Aßmus . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef PACKAGE_INFO_H @@ -9,6 +9,7 @@ #include +#include #include #include @@ -21,6 +22,30 @@ class BPath; +/*! This class represents a language that is supported by the Haiku + Depot Server system. This may differ from the set of languages + that are supported in the platform itself. +*/ + +class Language : public BReferenceable, public BLanguage { +public: + Language(const char* language, + const BString& serverName, + bool isPopular); + Language(const Language& other); + + status_t GetName(BString& name, + const BLanguage* displayLanguage = NULL + ) const; + bool IsPopular() const + { return fIsPopular; } + +private: + BString fServerName; + bool fIsPopular; +}; + + class UserInfo { public: UserInfo(); diff --git a/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp b/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp index f5a544c524..eeeec2f614 100644 --- a/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp +++ b/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018, Andrew Lindesay . + * Copyright 2018-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -21,6 +21,7 @@ #include "ServerHelper.h" #include "ServerIconExportUpdateProcess.h" #include "ServerPkgDataUpdateProcess.h" +#include "ServerReferenceDataUpdateProcess.h" #include "ServerRepositoryDataUpdateProcess.h" #include "ServerSettings.h" @@ -62,6 +63,11 @@ ProcessCoordinatorFactory::CreateBulkLoadCoordinator( serverRepositoryDataUpdate->AddPredecessor(localPkgDataLoad); processCoordinator->AddNode(serverRepositoryDataUpdate); + ProcessNode *serverReferenceDataUpdate = + new ProcessNode(new ServerReferenceDataUpdateProcess(model, + serverProcessOptions)); + processCoordinator->AddNode(serverReferenceDataUpdate); + // create a process for each of the repositories that are configured on the // local system. Later, only those that have a web-app repository server // code will be actually processed, but this means that the creation of the @@ -74,9 +80,11 @@ ProcessCoordinatorFactory::CreateBulkLoadCoordinator( if (repoNamesResult == B_OK) { for (int32 i = 0; i < repoNames.CountStrings(); i++) { ProcessNode* processNode = new ProcessNode( - new ServerPkgDataUpdateProcess(model->PreferredLanguage(), + new ServerPkgDataUpdateProcess( + model->Language().PreferredLanguage().Code(), repoNames.StringAt(i), model, serverProcessOptions)); processNode->AddPredecessor(serverRepositoryDataUpdate); + processNode->AddPredecessor(serverReferenceDataUpdate); processCoordinator->AddNode(processNode); } } else { diff --git a/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.cpp b/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.cpp new file mode 100644 index 0000000000..ceaa4849ea --- /dev/null +++ b/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.cpp @@ -0,0 +1,152 @@ +/* + * Copyright 2019, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ + + +#include "ServerReferenceDataUpdateProcess.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "ServerSettings.h" +#include "StorageUtils.h" +#include "Logger.h" +#include "DumpExportReference.h" +#include "DumpExportReferenceNaturalLanguage.h" +#include "DumpExportReferencePkgCategory.h" +#include "DumpExportReferenceUserRatingStability.h" +#include "DumpExportReferenceCountry.h" +#include "DumpExportReferenceJsonListener.h" + + +#undef B_TRANSLATION_CONTEXT +#define B_TRANSLATION_CONTEXT "ServerReferenceDataUpdateProcess" + + +ServerReferenceDataUpdateProcess::ServerReferenceDataUpdateProcess( + Model* model, + uint32 serverProcessOptions) + : + AbstractSingleFileServerProcess(serverProcessOptions), + fModel(model) +{ +} + + +ServerReferenceDataUpdateProcess::~ServerReferenceDataUpdateProcess() +{ +} + + +const char* +ServerReferenceDataUpdateProcess::Name() const +{ + return "ServerReferenceDataUpdateProcess"; +} + + +const char* +ServerReferenceDataUpdateProcess::Description() const +{ + return B_TRANSLATE("Synchronizing reference data from server"); +} + + +BString +ServerReferenceDataUpdateProcess::UrlPathComponent() +{ + BString result; + result.SetToFormat("/__reference/all-%s.json.gz", + fModel->Language().PreferredLanguage().Code()); + return result; +} + + +status_t +ServerReferenceDataUpdateProcess::GetLocalPath(BPath& path) const +{ + return fModel->DumpExportReferenceDataPath(path); +} + + +status_t +ServerReferenceDataUpdateProcess::ProcessLocalData() +{ + SingleDumpExportReferenceJsonListener* listener = + new SingleDumpExportReferenceJsonListener(); + + BPath localPath; + status_t result = GetLocalPath(localPath); + + if (result != B_OK) + return result; + + result = ParseJsonFromFileWithListener(listener, localPath); + + if (result != B_OK) + return result; + + result = listener->ErrorStatus(); + + if (result != B_OK) + return result; + + return _ProcessData(listener->Target()); +} + + +status_t +ServerReferenceDataUpdateProcess::_ProcessData(DumpExportReference* data) +{ + // more to come here later... + return _ProcessNaturalLanguages(data); +} + + +status_t +ServerReferenceDataUpdateProcess::_ProcessNaturalLanguages( + DumpExportReference* data) +{ + printf("[%s] will populate %" B_PRId32 " natural languages\n", + Name(), data->CountNaturalLanguages()); + + LanguageList result; + + for (int32 i = 0; i < data->CountNaturalLanguages(); i++) { + DumpExportReferenceNaturalLanguage* naturalLanguage = + data->NaturalLanguagesItemAt(i); + result.Add(LanguageRef(new Language( + *(naturalLanguage->Code()), + *(naturalLanguage->Name()), + naturalLanguage->IsPopular()))); + } + + fModel->Language().AddSupportedLanguages(result); + + printf("[%s] did add %" B_PRId32 " supported languages\n", + Name(), result.CountItems()); + + return B_OK; +} + + +status_t +ServerReferenceDataUpdateProcess::GetStandardMetaDataPath(BPath& path) const +{ + return GetLocalPath(path); +} + + +void +ServerReferenceDataUpdateProcess::GetStandardMetaDataJsonPath( + BString& jsonPath) const +{ + jsonPath.SetTo("$.info"); +} diff --git a/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.h b/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.h new file mode 100644 index 0000000000..fe051b6efa --- /dev/null +++ b/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.h @@ -0,0 +1,55 @@ +/* + * Copyright 2019, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#ifndef SERVER_REFERENCE_DATA_UPDATE_PROCESS_H +#define SERVER_REFERENCE_DATA_UPDATE_PROCESS_H + + +#include "AbstractSingleFileServerProcess.h" + +#include +#include +#include +#include + +#include "Model.h" +#include "PackageInfo.h" + + +#undef B_TRANSLATION_CONTEXT +#define B_TRANSLATION_CONTEXT "ServerReferenceDataUpdateProcess" + +class DumpExportReference; + +class ServerReferenceDataUpdateProcess : public AbstractSingleFileServerProcess +{ +public: + + ServerReferenceDataUpdateProcess( + Model* model, uint32 serverProcessOptions); + virtual ~ServerReferenceDataUpdateProcess(); + + const char* Name() const; + const char* Description() const; + +protected: + status_t GetStandardMetaDataPath(BPath& path) const; + void GetStandardMetaDataJsonPath( + BString& jsonPath) const; + + BString UrlPathComponent(); + status_t ProcessLocalData(); + status_t GetLocalPath(BPath& path) const; + +private: + status_t _ProcessData(DumpExportReference* data); + status_t _ProcessNaturalLanguages( + DumpExportReference* data); + +private: + Model* fModel; + +}; + +#endif // SERVER_REFERENCE_DATA_UPDATE_PROCESS_H diff --git a/src/apps/haikudepot/server/ServerRepositoryDataUpdateProcess.cpp b/src/apps/haikudepot/server/ServerRepositoryDataUpdateProcess.cpp index cc4b78e109..ebc6ffa9f3 100644 --- a/src/apps/haikudepot/server/ServerRepositoryDataUpdateProcess.cpp +++ b/src/apps/haikudepot/server/ServerRepositoryDataUpdateProcess.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018, Andrew Lindesay . + * Copyright 2017-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -177,7 +177,10 @@ ServerRepositoryDataUpdateProcess::Description() const BString ServerRepositoryDataUpdateProcess::UrlPathComponent() { - return BString("/__repository/all-en.json.gz"); + BString result; + result.SetToFormat("/__repository/all-%s.json.gz", + fModel->Language().PreferredLanguage().Code()); + return result; } diff --git a/src/apps/haikudepot/server/WebAppInterface.cpp b/src/apps/haikudepot/server/WebAppInterface.cpp index c03c8d8293..30231264a3 100644 --- a/src/apps/haikudepot/server/WebAppInterface.cpp +++ b/src/apps/haikudepot/server/WebAppInterface.cpp @@ -1,6 +1,6 @@ /* * Copyright 2014, Stephan Aßmus . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -271,8 +271,6 @@ enum { WebAppInterface::WebAppInterface() - : - fLanguage("en") { } @@ -280,8 +278,7 @@ WebAppInterface::WebAppInterface() WebAppInterface::WebAppInterface(const WebAppInterface& other) : fUsername(other.fUsername), - fPassword(other.fPassword), - fLanguage(other.fLanguage) + fPassword(other.fPassword) { } @@ -296,11 +293,8 @@ WebAppInterface::operator=(const WebAppInterface& other) { if (this == &other) return *this; - fUsername = other.fUsername; fPassword = other.fPassword; - fLanguage = other.fLanguage; - return *this; } @@ -314,13 +308,6 @@ WebAppInterface::SetAuthorization(const BString& username, } -void -WebAppInterface::SetPreferredLanguage(const BString& language) -{ - fLanguage = language; -} - - status_t WebAppInterface::GetChangelog(const BString& packageName, BMessage& message) { diff --git a/src/apps/haikudepot/server/WebAppInterface.h b/src/apps/haikudepot/server/WebAppInterface.h index 543aac1557..4b99fa231f 100644 --- a/src/apps/haikudepot/server/WebAppInterface.h +++ b/src/apps/haikudepot/server/WebAppInterface.h @@ -1,6 +1,6 @@ /* * Copyright 2014, Stephan Aßmus . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef WEB_APP_INTERFACE_H @@ -51,9 +51,6 @@ public: const BString& Username() const { return fUsername; } - void SetPreferredLanguage(const BString& language); - void SetArchitecture(const BString& architecture); - status_t GetChangelog( const BString& packageName, BMessage& message); @@ -130,7 +127,6 @@ private: private: BString fUsername; BString fPassword; - BString fLanguage; static int fRequestIndex; }; diff --git a/src/apps/haikudepot/server/schema/dumpexportreference.json b/src/apps/haikudepot/server/schema/dumpexportreference.json new file mode 100644 index 0000000000..08eb684863 --- /dev/null +++ b/src/apps/haikudepot/server/schema/dumpexportreference.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/schema#", + "id": "http://depot.haiku-os.org/schema/reference/dumpreference.json", + "title": "Reference", + "javaType": "org.haiku.haikudepotserver.reference.model.dumpexport.DumpExportReference", + "type": "object", + "properties": { + "naturalLanguages": { + "type": "array", + "items": { + "type": "object", + "javaType": "org.haiku.haikudepotserver.reference.model.dumpexport.DumpExportReferenceNaturalLanguage", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "isPopular": { + "type": "boolean" + } + } + } + }, + "pkgCategories": { + "type": "array", + "items": { + "type": "object", + "javaType": "org.haiku.haikudepotserver.reference.model.dumpexport.DumpExportReferencePkgCategory", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "userRatingStabilities": { + "type": "array", + "items": { + "type": "object", + "javaType": "org.haiku.haikudepotserver.reference.model.dumpexport.DumpExportReferenceUserRatingStability", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "countries": { + "type": "array", + "items": { + "type": "object", + "javaType": "org.haiku.haikudepotserver.reference.model.dumpexport.DumpExportReferenceCountry", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/src/apps/haikudepot/ui/PackageInfoView.cpp b/src/apps/haikudepot/ui/PackageInfoView.cpp index b93bc82648..d7733bd371 100644 --- a/src/apps/haikudepot/ui/PackageInfoView.cpp +++ b/src/apps/haikudepot/ui/PackageInfoView.cpp @@ -1,6 +1,6 @@ /* * Copyright 2013-2014, Stephan Aßmus . - * Copyright 2018, Andrew Lindesay . + * Copyright 2018-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -1050,8 +1050,6 @@ public: .Add(scrollView, 1.0f) .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f) ; - - _InitPreferredLanguages(); } virtual ~UserRatingsView() @@ -1113,33 +1111,9 @@ public: } } -private: - void _InitPreferredLanguages() - { - fPreferredLanguages.Clear(); - - BLocaleRoster* localeRoster = BLocaleRoster::Default(); - if (localeRoster == NULL) - return; - - BMessage preferredLanguages; - if (localeRoster->GetPreferredLanguages(&preferredLanguages) != B_OK) - return; - - BString language; - int32 index = 0; - while (preferredLanguages.FindString("language", index++, - &language) == B_OK) { - BString languageCode; - language.CopyInto(languageCode, 0, 2); - fPreferredLanguages.Add(languageCode); - } - } - private: BGroupLayout* fRatingContainerLayout; RatingSummaryView* fRatingSummaryView; - StringList fPreferredLanguages; }; diff --git a/src/apps/haikudepot/ui/RatePackageWindow.cpp b/src/apps/haikudepot/ui/RatePackageWindow.cpp index 975a82cf3d..aff723798c 100644 --- a/src/apps/haikudepot/ui/RatePackageWindow.cpp +++ b/src/apps/haikudepot/ui/RatePackageWindow.cpp @@ -1,6 +1,6 @@ /* * Copyright 2014, Stephan Aßmus . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -22,6 +22,7 @@ #include #include "HaikuDepotConstants.h" +#include "LanguageMenuUtils.h" #include "MarkupParser.h" #include "RatingView.h" #include "ServerHelper.h" @@ -37,7 +38,6 @@ enum { MSG_SEND = 'send', MSG_PACKAGE_RATED = 'rpkg', MSG_STABILITY_SELECTED = 'stbl', - MSG_LANGUAGE_SELECTED = 'lngs', MSG_RATING_ACTIVE_CHANGED = 'rtac', MSG_RATING_DETERMINATE_CHANGED = 'rdch' }; @@ -180,19 +180,6 @@ add_stabilities_to_menu(const StabilityRatingList& stabilities, BMenu* menu) } -static void -add_languages_to_menu(const StringList& languages, BMenu* menu) -{ - for (int i = 0; i < languages.CountItems(); i++) { - const BString& language = languages.ItemAtFast(i); - BMessage* message = new BMessage(MSG_LANGUAGE_SELECTED); - message->AddString("code", language); - BMenuItem* item = new BMenuItem(language, message); - menu->AddItem(item); - } -} - - RatePackageWindow::RatePackageWindow(BWindow* parent, BRect frame, Model& model) : @@ -204,7 +191,7 @@ RatePackageWindow::RatePackageWindow(BWindow* parent, BRect frame, fTextEditor(new TextEditor(), true), fRating(RATING_NONE), fRatingDeterminate(false), - fCommentLanguage(fModel.PreferredLanguage()), + fCommentLanguageCode(fModel.Language().PreferredLanguage().Code()), fWorkerThread(-1) { AddToSubset(parent); @@ -260,13 +247,12 @@ RatePackageWindow::RatePackageWindow(BWindow* parent, BRect frame, fCommentLanguageField = new BMenuField("language", B_TRANSLATE("Comment language:"), languagesMenu); - add_languages_to_menu(fModel.SupportedLanguages(), languagesMenu); + LanguageMenuUtils::AddLanguagesToMenu( + fModel.Language().SupportedLanguages(), + languagesMenu); languagesMenu->SetTargetForItems(this); - - BMenuItem* defaultItem = languagesMenu->ItemAt( - fModel.SupportedLanguages().IndexOf(fCommentLanguage)); - if (defaultItem != NULL) - defaultItem->SetMarked(true); + LanguageMenuUtils::MarkLanguageInMenu(fCommentLanguageCode, + languagesMenu); fRatingActiveCheckBox = new BCheckBox("rating active", B_TRANSLATE("Other users can see this rating"), @@ -348,7 +334,7 @@ RatePackageWindow::MessageReceived(BMessage* message) break; case MSG_LANGUAGE_SELECTED: - message->FindString("code", &fCommentLanguage); + message->FindString("code", &fCommentLanguageCode); break; case MSG_RATING_DETERMINATE_CHANGED: @@ -514,11 +500,9 @@ RatePackageWindow::_RelayServerDataToUI(BMessage& response) item->SetMarked(true); } if (response.FindString("naturalLanguageCode", - &fCommentLanguage) == B_OK) { - BMenuItem* item = fCommentLanguageField->Menu()->ItemAt( - fModel.SupportedLanguages().IndexOf(fCommentLanguage)); - if (item != NULL) - item->SetMarked(true); + &fCommentLanguageCode) == B_OK) { + LanguageMenuUtils::MarkLanguageInMenu( + fCommentLanguageCode, fCommentLanguageField->Menu()); } double rating; if (response.FindDouble("rating", &rating) == B_OK) { @@ -649,7 +633,7 @@ RatePackageWindow::_SendRatingThread() int rating = (int)fRating; BString stability = fStability; BString comment = fRatingText->Text(); - BString languageCode = fCommentLanguage; + BString languageCode = fCommentLanguageCode; BString ratingID = fRatingID; bool active = fRatingActive; diff --git a/src/apps/haikudepot/ui/RatePackageWindow.h b/src/apps/haikudepot/ui/RatePackageWindow.h index 04ca5f0183..bd6e2c5393 100644 --- a/src/apps/haikudepot/ui/RatePackageWindow.h +++ b/src/apps/haikudepot/ui/RatePackageWindow.h @@ -1,6 +1,6 @@ /* * Copyright 2014, Stephan Aßmus . - * Copyright 2018, Andrew Lindesay . + * Copyright 2018-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef RATE_PACKAGE_WINDOW_H @@ -56,7 +56,7 @@ private: bool fRatingDeterminate; BString fStability; StabilityRatingList fStabilityCodes; - BString fCommentLanguage; + BString fCommentLanguageCode; BString fRatingID; bool fRatingActive; PackageInfoRef fPackage; diff --git a/src/apps/haikudepot/ui/UserLoginWindow.cpp b/src/apps/haikudepot/ui/UserLoginWindow.cpp index 29263a1d05..a225540bc5 100644 --- a/src/apps/haikudepot/ui/UserLoginWindow.cpp +++ b/src/apps/haikudepot/ui/UserLoginWindow.cpp @@ -1,5 +1,6 @@ /* * Copyright 2014, Stephan Aßmus . + * Copyright 2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -21,6 +22,8 @@ #include #include "BitmapView.h" +#include "HaikuDepotConstants.h" +#include "LanguageMenuUtils.h" #include "Model.h" #include "TabView.h" #include "WebAppInterface.h" @@ -34,31 +37,17 @@ enum { MSG_SEND = 'send', MSG_TAB_SELECTED = 'tbsl', MSG_CAPTCHA_OBTAINED = 'cpob', - MSG_VALIDATE_FIELDS = 'vldt', - MSG_LANGUAGE_SELECTED = 'lngs', + MSG_VALIDATE_FIELDS = 'vldt' }; -static void -add_languages_to_menu(const StringList& languages, BMenu* menu) -{ - for (int i = 0; i < languages.CountItems(); i++) { - const BString& language = languages.ItemAtFast(i); - BMessage* message = new BMessage(MSG_LANGUAGE_SELECTED); - message->AddString("code", language); - BMenuItem* item = new BMenuItem(language, message); - menu->AddItem(item); - } -} - - UserLoginWindow::UserLoginWindow(BWindow* parent, BRect frame, Model& model) : BWindow(frame, B_TRANSLATE("Log in"), B_FLOATING_WINDOW_LOOK, B_FLOATING_SUBSET_WINDOW_FEEL, B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_RESIZABLE | B_NOT_ZOOMABLE), - fPreferredLanguage(model.PreferredLanguage()), + fPreferredLanguageCode(model.Language().PreferredLanguage().Code()), fModel(model), fMode(NONE), fWorkerThread(-1) @@ -83,14 +72,15 @@ UserLoginWindow::UserLoginWindow(BWindow* parent, BRect frame, Model& model) fLanguageCodeField = new BMenuField("language", B_TRANSLATE("Preferred language:"), languagesMenu); - add_languages_to_menu(fModel.SupportedLanguages(), languagesMenu); + LanguageMenuUtils::AddLanguagesToMenu( + fModel.Language().SupportedLanguages(), + languagesMenu); languagesMenu->SetTargetForItems(this); - BMenuItem* defaultItem = languagesMenu->ItemAt( - fModel.SupportedLanguages().IndexOf(fPreferredLanguage)); - if (defaultItem != NULL) - defaultItem->SetMarked(true); - + printf("using preferred language code [%s]\n", + fPreferredLanguageCode.String()); + LanguageMenuUtils::MarkLanguageInMenu(fPreferredLanguageCode, + languagesMenu); fEmailField = new BTextControl(B_TRANSLATE("Email address:"), "", NULL); fCaptchaView = new BitmapView("captcha view"); @@ -218,7 +208,7 @@ UserLoginWindow::MessageReceived(BMessage* message) break; case MSG_LANGUAGE_SELECTED: - message->FindString("code", &fPreferredLanguage); + message->FindString("code", &fPreferredLanguageCode); break; default: @@ -623,7 +613,7 @@ UserLoginWindow::_CreateAccountThread() BString email(fEmailField->Text()); BString captchaToken(fCaptchaToken); BString captchaResponse(fCaptchaResultField->Text()); - BString languageCode(fPreferredLanguage); + BString languageCode(fPreferredLanguageCode); Unlock(); diff --git a/src/apps/haikudepot/ui/UserLoginWindow.h b/src/apps/haikudepot/ui/UserLoginWindow.h index 6d529e717e..ee52d452ca 100644 --- a/src/apps/haikudepot/ui/UserLoginWindow.h +++ b/src/apps/haikudepot/ui/UserLoginWindow.h @@ -1,5 +1,6 @@ /* * Copyright 2014, Stephan Aßmus . + * Copyright 2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef USER_LOGIN_WINDOW_H @@ -84,7 +85,7 @@ private: BString fCaptchaToken; BitmapRef fCaptchaImage; - BString fPreferredLanguage; + BString fPreferredLanguageCode; Model& fModel; diff --git a/src/apps/haikudepot/util/AppUtils.cpp b/src/apps/haikudepot/util/AppUtils.cpp index 54933eff1c..79bdfff77a 100644 --- a/src/apps/haikudepot/util/AppUtils.cpp +++ b/src/apps/haikudepot/util/AppUtils.cpp @@ -9,10 +9,10 @@ #include #include -#include #include "HaikuDepotConstants.h" + /*! This method can be called to pop up an error in the user interface; typically in a background thread. */ diff --git a/src/apps/haikudepot/util/LanguageMenuUtils.cpp b/src/apps/haikudepot/util/LanguageMenuUtils.cpp new file mode 100644 index 0000000000..f22e468aeb --- /dev/null +++ b/src/apps/haikudepot/util/LanguageMenuUtils.cpp @@ -0,0 +1,137 @@ +/* + * Copyright 2019, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#include "LanguageMenuUtils.h" + +#include + +#include +#include +#include + +#include "HaikuDepotConstants.h" +#include "Logger.h" + + +/*! This method will add the supplied languages to the menu. It will + add first the popular languages, followed by a separator and then + other less popular languages. +*/ + +/* static */ void +LanguageMenuUtils::AddLanguagesToMenu( + const LanguageList& languages, BMenu* menu) +{ + if (languages.IsEmpty()) + printf("there are no languages defined\n"); + + int32 addedPopular = LanguageMenuUtils::_AddLanguagesToMenu( + languages, menu, true); + + if (addedPopular > 0) + menu->AddSeparatorItem(); + + int32 addedNonPopular = LanguageMenuUtils::_AddLanguagesToMenu( + languages, menu, false); + + if (Logger::IsDebugEnabled()) { + printf("did add %" B_PRId32 " popular languages and %" B_PRId32 + " non-popular languages to a menu\n", addedPopular, + addedNonPopular); + } +} + + +/* static */ void +LanguageMenuUtils::MarkLanguageInMenu( + const BString& languageCode, BMenu* menu) { + if (menu->CountItems() == 0) { + debugger("menu contains no items; not able to set the " + "language"); + return; + } + + int32 index = LanguageMenuUtils::_IndexOfLanguageInMenu( + languageCode, menu); + + if (index == -1) { + printf("unable to find the language [%s] in the menu\n", + languageCode.String()); + menu->ItemAt(0)->SetMarked(true); + } + else + menu->ItemAt(index)->SetMarked(true); +} + + +/* static */ void +LanguageMenuUtils::_AddLanguageToMenu( + const BString& code, const BString& name, BMenu* menu) +{ + BMessage* message = new BMessage(MSG_LANGUAGE_SELECTED); + message->AddString("code", code); + BMenuItem* item = new BMenuItem(name, message); + menu->AddItem(item); +} + + +/* static */ void +LanguageMenuUtils::_AddLanguageToMenu( + const LanguageRef& language, BMenu* menu) +{ + BString name; + if (language->GetName(name) != B_OK || name.IsEmpty()) + name.SetTo("???"); + LanguageMenuUtils::_AddLanguageToMenu(language->Code(), name, menu); +} + + +/* static */ int32 +LanguageMenuUtils::_AddLanguagesToMenu(const LanguageList& languages, + BMenu* menu, bool isPopular) +{ + int32 count = 0; + + for (int32 i = 0; i < languages.CountItems(); i++) { + const LanguageRef language = languages.ItemAtFast(i); + + if (language->IsPopular() == isPopular) { + LanguageMenuUtils::_AddLanguageToMenu(language, menu); + count++; + } + } + + return count; +} + + +/* static */ status_t +LanguageMenuUtils::_GetLanguageAtIndexInMenu(BMenu* menu, int32 index, + BString* result) +{ + BMessage *itemMessage = menu->ItemAt(index)->Message(); + + if (itemMessage == NULL) + return B_ERROR; + + return itemMessage->FindString("code", result); +} + + +/* static */ int32 +LanguageMenuUtils::_IndexOfLanguageInMenu( + const BString& languageCode, BMenu* menu) +{ + BString itemLanguageCode; + for (int32 i = 0; i < menu->CountItems(); i++) { + if (_GetLanguageAtIndexInMenu( + menu, i, &itemLanguageCode) == B_OK) { + if (itemLanguageCode == languageCode) { + return i; + } + } + } + + return -1; +} \ No newline at end of file diff --git a/src/apps/haikudepot/util/LanguageMenuUtils.h b/src/apps/haikudepot/util/LanguageMenuUtils.h new file mode 100644 index 0000000000..420d711af8 --- /dev/null +++ b/src/apps/haikudepot/util/LanguageMenuUtils.h @@ -0,0 +1,43 @@ +/* + * Copyright 2019, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#ifndef LANGUAGE_MENU_UTILS_H +#define LANGUAGE_MENU_UTILS_H + + +#include + +#include "Model.h" +#include "PackageInfo.h" + + +class LanguageMenuUtils { + +public: + static void AddLanguagesToMenu( + const LanguageList& languages, + BMenu* menu); + static void MarkLanguageInMenu( + const BString& languageCode, + BMenu* menu); + +private: + static int32 _IndexOfLanguageInMenu( + const BString& languageCode, + BMenu* menu); + static status_t _GetLanguageAtIndexInMenu(BMenu* menu, + int32 index, BString* result); + static int32 _AddLanguagesToMenu( + const LanguageList& languages, + BMenu* menu, bool isPopular); + static void _AddLanguageToMenu( + const LanguageRef& language, + BMenu* menu); + static void _AddLanguageToMenu( + const BString& code, + const BString& name, BMenu* menu); +}; + + +#endif // LANGUAGE_MENU_UTILS_H