diff --git a/android/jni/com/mapswithme/maps/editor/Editor.cpp b/android/jni/com/mapswithme/maps/editor/Editor.cpp index 05361d78a3..7a6b075c1e 100644 --- a/android/jni/com/mapswithme/maps/editor/Editor.cpp +++ b/android/jni/com/mapswithme/maps/editor/Editor.cpp @@ -247,6 +247,8 @@ Java_com_mapswithme_maps_editor_Editor_nativeSaveEditedFeature(JNIEnv *, jclass) case osm::Editor::SavedSuccessfully: return true; case osm::Editor::NoFreeSpaceError: + case osm::Editor::NoUnderlyingMapError: + case osm::Editor::SavingError: return false; } } @@ -312,6 +314,18 @@ Java_com_mapswithme_maps_editor_Editor_nativeGetNamesDataSource(JNIEnv * env, jc return env->NewObject(g_namesDataSourceClassID, g_namesDataSourceConstructorID, names, mandatoryNamesCount); } +JNIEXPORT jstring JNICALL +Java_com_mapswithme_maps_editor_Editor_nativeGetDefaultName(JNIEnv * env, jclass) +{ + return jni::ToJavaString(env, g_editableMapObject.GetDefaultName()); +} + +JNIEXPORT void JNICALL +Java_com_mapswithme_maps_editor_Editor_nativeEnableNamesAdvancedMode(JNIEnv *, jclass) +{ + g_editableMapObject.EnableNamesAdvancedMode(); +} + JNIEXPORT void JNICALL Java_com_mapswithme_maps_editor_Editor_nativeSetNames(JNIEnv * env, jclass, jobjectArray names) { diff --git a/android/src/com/mapswithme/maps/editor/Editor.java b/android/src/com/mapswithme/maps/editor/Editor.java index ac879ad9e3..274895485c 100644 --- a/android/src/com/mapswithme/maps/editor/Editor.java +++ b/android/src/com/mapswithme/maps/editor/Editor.java @@ -103,6 +103,8 @@ public final class Editor public static native boolean nativeIsBuilding(); public static native NamesDataSource nativeGetNamesDataSource(); + public static native String nativeGetDefaultName(); + public static native void nativeEnableNamesAdvancedMode(); public static native void nativeSetNames(@NonNull LocalizedName[] names); public static native LocalizedName nativeMakeLocalizedName(String langCode, String name); public static native Language[] nativeGetSupportedLanguages(); diff --git a/android/src/com/mapswithme/maps/editor/EditorHostFragment.java b/android/src/com/mapswithme/maps/editor/EditorHostFragment.java index 3cfbb4da4b..ef45818031 100644 --- a/android/src/com/mapswithme/maps/editor/EditorHostFragment.java +++ b/android/src/com/mapswithme/maps/editor/EditorHostFragment.java @@ -210,7 +210,6 @@ public class EditorHostFragment extends BaseMwmToolbarFragment ArrayList languages = new ArrayList<>(sNames.size()); for (LocalizedName name : sNames) languages.add(name.lang); - languages.add(LocalizedName.DEFAULT_LANG_NAME); args.putStringArrayList(LanguagesFragment.EXISTING_LOCALIZED_NAMES, languages); UiUtils.hide(mToolbarController.findViewById(R.id.save)); editWithFragment(Mode.LANGUAGE, R.string.choose_language, args, LanguagesFragment.class, false); @@ -333,7 +332,14 @@ public class EditorHostFragment extends BaseMwmToolbarFragment @Override public void onLanguageSelected(Language lang) { - addName(Editor.nativeMakeLocalizedName(lang.code, "")); + String name = ""; + if (lang.code.equals(Language.DEFAULT_LANG_CODE)) + { + name = Editor.nativeGetDefaultName(); + Editor.nativeEnableNamesAdvancedMode(); + } + + addName(Editor.nativeMakeLocalizedName(lang.code, name)); editMapObject(true /* focusToLastName */); } } diff --git a/android/src/com/mapswithme/maps/editor/LanguagesFragment.java b/android/src/com/mapswithme/maps/editor/LanguagesFragment.java index d1e8952a23..aec201453c 100644 --- a/android/src/com/mapswithme/maps/editor/LanguagesFragment.java +++ b/android/src/com/mapswithme/maps/editor/LanguagesFragment.java @@ -41,6 +41,12 @@ public class LanguagesFragment extends BaseMwmRecyclerFragment { @Override public int compare(Language lhs, Language rhs) { + // Default name can be changed, but it should be last in list of names. + if (lhs.isDefaultLang() && !rhs.isDefaultLang()) + return 1; + if (!lhs.isDefaultLang() && rhs.isDefaultLang()) + return -1; + return lhs.name.compareTo(rhs.name); } }); diff --git a/android/src/com/mapswithme/maps/editor/data/Language.java b/android/src/com/mapswithme/maps/editor/data/Language.java index 0a11b5f6d5..f2dca4d4be 100644 --- a/android/src/com/mapswithme/maps/editor/data/Language.java +++ b/android/src/com/mapswithme/maps/editor/data/Language.java @@ -5,6 +5,9 @@ import android.support.annotation.NonNull; // Corresponds to StringUtf8Multilang::Lang in core. public class Language { + // StringUtf8Multilang::GetLangByCode(StringUtf8Multilang::kDefaultCode). + public static final String DEFAULT_LANG_CODE = "default"; + public final String code; public final String name; @@ -13,4 +16,9 @@ public class Language this.code = code; this.name = name; } + + public boolean isDefaultLang() + { + return code.equals(DEFAULT_LANG_CODE); + } } diff --git a/android/src/com/mapswithme/maps/editor/data/LocalizedName.java b/android/src/com/mapswithme/maps/editor/data/LocalizedName.java index 11783c7f84..3ff3ed4c17 100644 --- a/android/src/com/mapswithme/maps/editor/data/LocalizedName.java +++ b/android/src/com/mapswithme/maps/editor/data/LocalizedName.java @@ -4,9 +4,6 @@ import android.support.annotation.NonNull; public class LocalizedName { - // StringUtf8Multilang::GetLangByCode(StringUtf8Multilang::kDefaultCode) - public static final String DEFAULT_LANG_NAME = "default"; - public int code; @NonNull public String name; @NonNull public String lang; diff --git a/indexer/editable_map_object.cpp b/indexer/editable_map_object.cpp index e7866b6a12..c7121920a9 100644 --- a/indexer/editable_map_object.cpp +++ b/indexer/editable_map_object.cpp @@ -13,6 +13,8 @@ namespace { +size_t const kFakeNamesCount = 2; + bool ExtractName(StringUtf8Multilang const & names, int8_t const langCode, vector & result) { @@ -70,6 +72,103 @@ bool IsProtocolSpecified(string const & website) { return GetProtocolNameLength(website) > 0; } +osm::FakeNames MakeFakeSource(StringUtf8Multilang const & source, + vector const & mwmLanguages, StringUtf8Multilang & fakeSource) +{ + string defaultName; + // Fake names works for mono language (official) speaking countries-only. + if (mwmLanguages.size() != 1 || !source.GetString(StringUtf8Multilang::kDefaultCode, defaultName)) + { + return {}; + } + + osm::FakeNames fakeNames; + // Mwm name has higher priority then English name. + array fillCandidates = {{mwmLanguages.front(), StringUtf8Multilang::kEnglishCode}}; + fakeSource = source; + + string tempName; + for (auto const code : fillCandidates) + { + if (!source.GetString(code, tempName)) + { + tempName = defaultName; + fakeSource.AddString(code, defaultName); + } + + fakeNames.names.push_back({code, tempName}); + } + + fakeNames.defaultName = defaultName; + + return fakeNames; +} + +// Tries to set default name from the localized name. Returns false when there's no such localized name. +bool TryToFillDefaultNameFromCode(int8_t const code, StringUtf8Multilang & names) +{ + string newDefaultName; + if (code != StringUtf8Multilang::kUnsupportedLanguageCode) + names.GetString(code, newDefaultName); + + // Default name can not be empty. + if (!newDefaultName.empty()) + { + names.AddString(StringUtf8Multilang::kDefaultCode, newDefaultName); + return true; + } + + return false; +} + +// Tries to set default name to any non-empty localized name. +// This is the case when fake names were cleared. +void TryToFillDefaultNameFromAnyLanguage(StringUtf8Multilang & names) +{ + names.ForEach([&names](int8_t langCode, string const & name) + { + if (name.empty() || langCode == StringUtf8Multilang::kDefaultCode) + return true; + + names.AddString(StringUtf8Multilang::kDefaultCode, name); + return false; + }); +} + +void RemoveFakesFromName(osm::FakeNames const & fakeNames, StringUtf8Multilang & name) +{ + vector codesToExclude; + string defaultName; + name.GetString(StringUtf8Multilang::kDefaultCode, defaultName); + + for (auto const & item : fakeNames.names) + { + string tempName; + if (!name.GetString(item.m_code, tempName)) + continue; + // No need to save in case when name is empty, duplicate of default name or was not changed. + if (tempName.empty() || tempName == defaultName || + (tempName == item.m_filledName && tempName == fakeNames.defaultName)) + { + codesToExclude.push_back(item.m_code); + } + } + + if (codesToExclude.empty()) + return; + + StringUtf8Multilang nameWithoutFakes; + name.ForEach([&codesToExclude, &nameWithoutFakes](int8_t langCode, string const & value) + { + auto const it = find(codesToExclude.begin(), codesToExclude.end(), langCode); + if (it == codesToExclude.end()) + nameWithoutFakes.AddString(langCode, value); + + return true; + }); + + name = nameWithoutFakes; +} } // namespace namespace osm @@ -112,7 +211,7 @@ vector const & EditableMapObject::GetEditableFields() StringUtf8Multilang const & EditableMapObject::GetName() const { return m_name; } -NamesDataSource EditableMapObject::GetNamesDataSource() const +NamesDataSource EditableMapObject::GetNamesDataSource() { auto const mwmInfo = GetID().m_mwmId.GetInfo(); @@ -124,7 +223,10 @@ NamesDataSource EditableMapObject::GetNamesDataSource() const auto const userLangCode = StringUtf8Multilang::GetLangIndex(languages::GetCurrentNorm()); - return GetNamesDataSource(m_name, mwmLanguages, userLangCode); + StringUtf8Multilang fakeSource; + m_fakeNames = MakeFakeSource(m_name, mwmLanguages, fakeSource); + + return GetNamesDataSource(fakeSource, mwmLanguages, userLangCode); } // static @@ -190,13 +292,14 @@ void EditableMapObject::SetName(StringUtf8Multilang const & name) { m_name = nam void EditableMapObject::SetName(string name, int8_t langCode) { strings::Trim(name); - if (name.empty()) + + if (m_namesAdvancedMode) + { + m_name.AddString(langCode, name); return; + } - ASSERT_NOT_EQUAL(StringUtf8Multilang::kDefaultCode, langCode, - ("Direct editing of default name is deprecated.")); - - if (!Editor::Instance().OriginalFeatureHasDefaultName(GetID())) + if (!name.empty() && !Editor::Instance().OriginalFeatureHasDefaultName(GetID())) { const auto mwmInfo = GetID().m_mwmId.GetInfo(); @@ -228,6 +331,43 @@ bool EditableMapObject::CanUseAsDefaultName(int8_t const lang, vector co return false; } +// static +void EditableMapObject::RemoveFakeNames(FakeNames const & fakeNames, StringUtf8Multilang & name) +{ + if (fakeNames.names.empty()) + return; + + int8_t newDefaultNameCode = StringUtf8Multilang::kUnsupportedLanguageCode; + size_t changedCount = 0; + string defaultName; + name.GetString(StringUtf8Multilang::kDefaultCode, defaultName); + + // New default name calculation priority: 1. name on mwm language, 2. english name. + for (auto it = fakeNames.names.rbegin(); it != fakeNames.names.rend(); ++it) + { + string tempName; + if (!name.GetString(it->m_code, tempName)) + continue; + + if (tempName != it->m_filledName) + { + if (!tempName.empty()) + newDefaultNameCode = it->m_code; + + ++changedCount; + } + } + + // If all previously filled fake names were changed - try to change the default name. + if (changedCount == fakeNames.names.size()) + { + if (!TryToFillDefaultNameFromCode(newDefaultNameCode, name)) + TryToFillDefaultNameFromAnyLanguage(name); + } + + RemoveFakesFromName(fakeNames, name); +} + void EditableMapObject::SetMercator(m2::PointD const & center) { m_mercator = center; } void EditableMapObject::SetType(uint32_t featureType) @@ -357,6 +497,29 @@ void EditableMapObject::SetOpeningHours(string const & openingHours) void EditableMapObject::SetPointType() { m_geomType = feature::EGeomType::GEOM_POINT; } + +void EditableMapObject::RemoveBlankNames() +{ + StringUtf8Multilang nameWithoutBlanks; + m_name.ForEach([&nameWithoutBlanks](int8_t langCode, string const & name) + { + if (!name.empty()) + nameWithoutBlanks.AddString(langCode, name); + + return true; + }); + + m_name = nameWithoutBlanks; +} + +void EditableMapObject::RemoveNeedlessNames() +{ + if (!IsNamesAdvancedModeEnabled()) + RemoveFakeNames(m_fakeNames, m_name); + + RemoveBlankNames(); +} + // static bool EditableMapObject::ValidateBuildingLevels(string const & buildingLevels) { diff --git a/indexer/editable_map_object.hpp b/indexer/editable_map_object.hpp index d033bc3d04..c4c16509d1 100644 --- a/indexer/editable_map_object.hpp +++ b/indexer/editable_map_object.hpp @@ -38,25 +38,43 @@ struct LocalizedName LocalizedName(int8_t code, string const & name); LocalizedName(string const & langCode, string const & name); - // m_code, m_lang and m_langName are defined in StringUtf8Multilang. + /// m_code, m_lang and m_langName are defined in StringUtf8Multilang. int8_t const m_code; - // Non-owning pointers to internal static char const * array. + /// Non-owning pointers to internal static char const * array. char const * const m_lang; char const * const m_langName; string const m_name; }; -// Class which contains vector of localized names with following priority: -// 1. Names for Mwm languages -// 2. User`s language name -// 3. Other names -// and mandatoryNamesCount - count of names which should be always shown. +/// Class which contains vector of localized names with following priority: +/// 1. Names for Mwm languages +/// 2. User`s language name +/// 3. Other names +/// and mandatoryNamesCount - count of names which should be always shown. struct NamesDataSource { vector names; size_t mandatoryNamesCount = 0; }; - + +struct FakeName +{ + int8_t m_code; + string m_filledName; +}; +/// Contains information about fake names which were added for user convenience. +struct FakeNames +{ + void Clear() + { + names.clear(); + defaultName.clear(); + } + + vector names; + string defaultName; +}; + struct LocalizedStreet { string m_defaultName; @@ -78,8 +96,8 @@ public: vector const & GetEditableFields() const; StringUtf8Multilang const & GetName() const; - // See comment for NamesDataSource class. - NamesDataSource GetNamesDataSource() const; + /// See comment for NamesDataSource class. + NamesDataSource GetNamesDataSource(); LocalizedStreet const & GetStreet() const; vector const & GetNearbyStreets() const; string const & GetHouseNumber() const; @@ -120,6 +138,13 @@ public: /// Special mark that it's a point feature, not area or line. void SetPointType(); + /// Enables advanced mode with direct access to default name and disables any recalculations. + void EnableNamesAdvancedMode() { m_namesAdvancedMode = true; } + bool IsNamesAdvancedModeEnabled() const { return m_namesAdvancedMode; } + /// Remove blank names for advanced mode. + void RemoveBlankNames(); + /// Calls RemoveBlankNames or RemoveFakeNames depending on mode. + void RemoveNeedlessNames(); static bool ValidateBuildingLevels(string const & buildingLevels); static bool ValidateHouseNumber(string const & houseNumber); @@ -129,18 +154,22 @@ public: static bool ValidateWebsite(string const & site); static bool ValidateEmail(string const & email); - // Check whether langCode can be used as default name. + /// Check whether langCode can be used as default name. static bool CanUseAsDefaultName(int8_t const langCode, vector const & nativeMwmLanguages); - // See comment for NamesDataSource class. + /// See comment for NamesDataSource class. static NamesDataSource GetNamesDataSource(StringUtf8Multilang const & source, vector const & nativeMwmLanguages, int8_t const userLanguage); + /// Removes fake names (which were added for user convenience) from name. + static void RemoveFakeNames(FakeNames const & fakeNames, StringUtf8Multilang & name); private: string m_houseNumber; LocalizedStreet m_street; vector m_nearbyStreets; EditableProperties m_editableProperties; + FakeNames m_fakeNames; + bool m_namesAdvancedMode = false; }; } // namespace osm diff --git a/indexer/indexer_tests/editable_map_object_test.cpp b/indexer/indexer_tests/editable_map_object_test.cpp index 7f0dae3af6..f9bdab1f00 100644 --- a/indexer/indexer_tests/editable_map_object_test.cpp +++ b/indexer/indexer_tests/editable_map_object_test.cpp @@ -12,6 +12,32 @@ int8_t GetLangCode(char const * ch) { return StringUtf8Multilang::GetLangIndex(ch); } + +struct ExpectedName +{ + string m_lang; + string m_value; +}; + +void CheckExpectations(StringUtf8Multilang const s, vector const expectations) +{ + size_t counter = 0; + s.ForEach([&expectations, &counter](int8_t const code, string const & name) { + auto const it = find_if(expectations.begin(), expectations.end(), [&code](ExpectedName const & item) + { + return GetLangCode(item.m_lang.c_str()) == code; + }); + + if (it == expectations.end()) + TEST(false, ("Unexpected language code:", code)); + + TEST_EQUAL(name, it->m_value, ()); + ++counter; + return true; + }); + + TEST_EQUAL(counter, expectations.size(), ()); +} UNIT_TEST(EditableMapObject_SetWebsite) { @@ -289,4 +315,281 @@ UNIT_TEST(EditableMapObject_SetInternet) setInternetAndCheck(bunkerEmo, osm::Internet::Wlan, true); setInternetAndCheck(bunkerEmo, osm::Internet::Wlan, true); } + +UNIT_TEST(EditableMapObject_RemoveFakeNames) +{ + EditableMapObject emo; + StringUtf8Multilang name; + osm::FakeNames fakeNames; + + name.AddString(GetLangCode("default"), "Default name"); + name.AddString(GetLangCode("ru"), "Default name"); + name.AddString(GetLangCode("en"), "Default name"); + fakeNames.names.push_back({GetLangCode("ru"), "Default name"}); + fakeNames.names.push_back({GetLangCode("en"), "Default name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Default name"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Default name"); + name.AddString(GetLangCode("ru"), "Changed name"); + name.AddString(GetLangCode("en"), "Default name"); + fakeNames.names.push_back({GetLangCode("ru"), "Default name"}); + fakeNames.names.push_back({GetLangCode("en"), "Default name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Default name"}, {"ru", "Changed name"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Default name"); + name.AddString(GetLangCode("ru"), "Default name"); + name.AddString(GetLangCode("en"), "Changed name"); + fakeNames.names.push_back({GetLangCode("ru"), "Default name"}); + fakeNames.names.push_back({GetLangCode("en"), "Default name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Default name"}, {"en", "Changed name"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Default name"); + name.AddString(GetLangCode("ru"), "Changed name"); + name.AddString(GetLangCode("en"), "Changed name"); + fakeNames.names.push_back({GetLangCode("ru"), "Default name"}); + fakeNames.names.push_back({GetLangCode("en"), "Default name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Changed name"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Default name"); + name.AddString(GetLangCode("ru"), "Changed name ru"); + name.AddString(GetLangCode("en"), "Changed name en"); + fakeNames.names.push_back({GetLangCode("ru"), "Default name"}); + fakeNames.names.push_back({GetLangCode("en"), "Default name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Changed name ru"}, {"en", "Changed name en"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Changed by other logic"); + name.AddString(GetLangCode("ru"), "Default name"); + name.AddString(GetLangCode("en"), "Changed name en"); + fakeNames.names.push_back({GetLangCode("ru"), "Default name"}); + fakeNames.names.push_back({GetLangCode("en"), "Default name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Changed by other logic"}, {"en", "Changed name en"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Changed by other logic"); + name.AddString(GetLangCode("ru"), "Changed name ru"); + name.AddString(GetLangCode("en"), "Changed name en"); + fakeNames.names.push_back({GetLangCode("ru"), "Default name"}); + fakeNames.names.push_back({GetLangCode("en"), "Default name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Changed name ru"}, {"en", "Changed name en"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Changed by other logic"); + name.AddString(GetLangCode("ru"), "Default name"); + name.AddString(GetLangCode("en"), "Default name"); + fakeNames.names.push_back({GetLangCode("ru"), "Default name"}); + fakeNames.names.push_back({GetLangCode("en"), "Default name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Changed by other logic"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Default name"); + name.AddString(GetLangCode("ru"), ""); + name.AddString(GetLangCode("en"), "Changed name en"); + fakeNames.names.push_back({GetLangCode("ru"), "Default name"}); + fakeNames.names.push_back({GetLangCode("en"), "Default name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Changed name en"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Default name"); + name.AddString(GetLangCode("ru"), ""); + name.AddString(GetLangCode("en"), ""); + fakeNames.names.push_back({GetLangCode("ru"), "Default name"}); + fakeNames.names.push_back({GetLangCode("en"), "Default name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Default name"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Changed by other logic"); + name.AddString(GetLangCode("ru"), ""); + name.AddString(GetLangCode("en"), ""); + name.AddString(GetLangCode("de"), "Deutch name"); + fakeNames.names.push_back({GetLangCode("ru"), "Default name"}); + fakeNames.names.push_back({GetLangCode("en"), "Default name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Deutch name"}, {"de", "Deutch name"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Default name"); + name.AddString(GetLangCode("ru"), "Test name"); + name.AddString(GetLangCode("en"), "Default name"); + fakeNames.names.push_back({GetLangCode("ru"), "Test name"}); + fakeNames.names.push_back({GetLangCode("en"), "Default name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Default name"}, {"ru", "Test name"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Default name"); + name.AddString(GetLangCode("ru"), "Test name changed"); + name.AddString(GetLangCode("en"), "Default name changed"); + fakeNames.names.push_back({GetLangCode("ru"), "Test name"}); + fakeNames.names.push_back({GetLangCode("en"), "Default name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Test name changed"}, {"en", "Default name changed"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Default name"); + name.AddString(GetLangCode("ru"), "Test name"); + name.AddString(GetLangCode("en"), "Second test name changed"); + fakeNames.names.push_back({GetLangCode("ru"), "Test name"}); + fakeNames.names.push_back({GetLangCode("en"), "Second test name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Default name"}, {"ru", "Test name"}, {"en", "Second test name changed"}}); + + name.Clear(); + fakeNames.Clear(); + + name.AddString(GetLangCode("default"), "Default name"); + name.AddString(GetLangCode("ru"), ""); + name.AddString(GetLangCode("en"), "Second test name changed"); + fakeNames.names.push_back({GetLangCode("ru"), "Test name"}); + fakeNames.names.push_back({GetLangCode("en"), "Second test name"}); + fakeNames.defaultName = "Default name"; + + EditableMapObject::RemoveFakeNames(fakeNames, name); + + CheckExpectations(name, {{"default", "Second test name changed"}}); +} + +UNIT_TEST(EditableMapObject_RemoveBlankNames) +{ + auto const getCountOfNames = [](StringUtf8Multilang const & names) { + size_t counter = 0; + names.ForEach([&counter](int8_t const, string const &) { + ++counter; + return true; + }); + + return counter; + }; + + StringUtf8Multilang name; + + name.AddString(GetLangCode("default"), "Default name"); + name.AddString(GetLangCode("ru"), "Ru name"); + name.AddString(GetLangCode("en"), "En name"); + name.AddString(GetLangCode("de"), "De name"); + + EditableMapObject emo; + emo.SetName(name); + emo.RemoveBlankNames(); + + TEST_EQUAL(getCountOfNames(emo.GetName()), 4, ()); + + name.Clear(); + + name.AddString(GetLangCode("default"), ""); + name.AddString(GetLangCode("ru"), "Ru name"); + name.AddString(GetLangCode("en"), "En name"); + name.AddString(GetLangCode("de"), ""); + + emo.SetName(name); + emo.RemoveBlankNames(); + + TEST_EQUAL(getCountOfNames(emo.GetName()), 2, ()); + + name.Clear(); + + name.AddString(GetLangCode("default"), "Default name"); + name.AddString(GetLangCode("ru"), ""); + name.AddString(GetLangCode("en"), ""); + name.AddString(GetLangCode("de"), ""); + + emo.SetName(name); + emo.RemoveBlankNames(); + + TEST_EQUAL(getCountOfNames(emo.GetName()), 1, ()); + + name.Clear(); + + name.AddString(GetLangCode("default"), ""); + name.AddString(GetLangCode("ru"), ""); + name.AddString(GetLangCode("en"), ""); + name.AddString(GetLangCode("de"), "De name"); + + emo.SetName(name); + emo.RemoveBlankNames(); + + TEST_EQUAL(getCountOfNames(emo.GetName()), 1, ()); +} } // namespace diff --git a/iphone/Maps/Classes/Editor/AdditionalNames/MWMEditorAdditionalNamesTableViewController.mm b/iphone/Maps/Classes/Editor/AdditionalNames/MWMEditorAdditionalNamesTableViewController.mm index 7b8f4cf1e8..c8d1a714fc 100644 --- a/iphone/Maps/Classes/Editor/AdditionalNames/MWMEditorAdditionalNamesTableViewController.mm +++ b/iphone/Maps/Classes/Editor/AdditionalNames/MWMEditorAdditionalNamesTableViewController.mm @@ -27,7 +27,6 @@ additionalSkipLanguageCodes:(vector)additionalSkipLanguageCodes self.delegate = delegate; m_name = name; m_additionalSkipLanguageCodes = additionalSkipLanguageCodes; - m_additionalSkipLanguageCodes.push_back(StringUtf8Multilang::kDefaultCode); self.selectedLanguageCode = selectedLanguageCode; } @@ -40,25 +39,34 @@ additionalSkipLanguageCodes:(vector)additionalSkipLanguageCodes - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + auto const getIndex = [](string const & lang) { return StringUtf8Multilang::GetLangIndex(lang); }; StringUtf8Multilang::Languages const & supportedLanguages = StringUtf8Multilang::GetSupportedLanguages(); m_languages.clear(); + if (self.selectedLanguageCode == StringUtf8Multilang::kDefaultCode || + self.selectedLanguageCode == StringUtf8Multilang::kInternationalCode) + { + return; + } + auto const kDefaultCode = StringUtf8Multilang::kDefaultCode; for (auto const & language : supportedLanguages) { - int8_t const languageIndex = StringUtf8Multilang::GetLangIndex(language.m_code); - string tmpStr; - if (self.selectedLanguageCode == StringUtf8Multilang::kDefaultCode || - self.selectedLanguageCode == StringUtf8Multilang::kInternationalCode || - (self.selectedLanguageCode == NSNotFound && m_name.GetString(languageIndex, tmpStr))) + auto const langIndex = getIndex(language.m_code); + if (self.selectedLanguageCode == NSNotFound && langIndex != kDefaultCode && m_name.HasString(langIndex)) continue; - auto it = find(m_additionalSkipLanguageCodes.begin(), m_additionalSkipLanguageCodes.end(), languageIndex); + auto it = find(m_additionalSkipLanguageCodes.begin(), m_additionalSkipLanguageCodes.end(), langIndex); if (it == m_additionalSkipLanguageCodes.end()) m_languages.push_back(language); } sort(m_languages.begin(), m_languages.end(), - [](StringUtf8Multilang::Lang const & a, StringUtf8Multilang::Lang const & b) - { - return string(a.m_code) < string(b.m_code); - }); + [&getIndex, kDefaultCode](StringUtf8Multilang::Lang const & lhs, StringUtf8Multilang::Lang const & rhs) { + // Default name can be changed in advanced mode, but it should be last in list of names. + if (getIndex(lhs.m_code) == kDefaultCode && getIndex(rhs.m_code) != kDefaultCode) + return false; + if (getIndex(lhs.m_code) != kDefaultCode && getIndex(rhs.m_code) == kDefaultCode) + return true; + + return string(lhs.m_code) < string(rhs.m_code); + }); } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath diff --git a/iphone/Maps/Classes/Editor/MWMEditorViewController.mm b/iphone/Maps/Classes/Editor/MWMEditorViewController.mm index 5271ce3a93..063ea2bae8 100644 --- a/iphone/Maps/Classes/Editor/MWMEditorViewController.mm +++ b/iphone/Maps/Classes/Editor/MWMEditorViewController.mm @@ -559,10 +559,19 @@ void registerCellsForTableView(vector const & cells, UITab { NSInteger const newAdditionalNameIndex = indexPath.row - localizedNames.size(); NSInteger const langCode = m_newAdditionalLanguages[newAdditionalNameIndex]; + + string name; + // Default name can be changed in advanced mode. + if (langCode == StringUtf8Multilang::kDefaultCode) + { + name = m_mapObject.GetDefaultName(); + m_mapObject.EnableNamesAdvancedMode(); + } + [tCell configWithDelegate:self langCode:langCode langName:@(StringUtf8Multilang::GetLangNameByCode(langCode)) - name:@"" + name:@(name.c_str()) keyboardType:UIKeyboardTypeDefault]; } break; diff --git a/map/framework.cpp b/map/framework.cpp index c8ac3f2907..db1543cf87 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -2935,6 +2935,8 @@ osm::Editor::SaveResult Framework::SaveEditedMapObject(osm::EditableMapObject em " without a user's input. Feel free to close it if it's wrong)."); } + emo.RemoveNeedlessNames(); + return osm::Editor::Instance().SaveEditedFeature(emo); }