diff --git a/base/base.pro b/base/base.pro index da59a8bca6..7e8cdb6d7a 100644 --- a/base/base.pro +++ b/base/base.pro @@ -26,6 +26,8 @@ SOURCES += \ threaded_container.cpp \ resource_pool.cpp \ fence_manager.cpp \ + strings_bundle.cpp \ + string_format.cpp \ HEADERS += \ SRC_FIRST.hpp \ @@ -73,3 +75,5 @@ HEADERS += \ threaded_priority_queue.hpp \ std_serialization.hpp \ fence_manager.hpp \ + strings_bundle.hpp \ + string_format.hpp \ diff --git a/base/base_tests/base_tests.pro b/base/base_tests/base_tests.pro index 022ad76c62..021127f81c 100644 --- a/base/base_tests/base_tests.pro +++ b/base/base_tests/base_tests.pro @@ -33,9 +33,10 @@ SOURCES += \ threaded_list_test.cpp \ condition_test.cpp \ containers_test.cpp \ - fence_manager_test.cpp - + fence_manager_test.cpp \ + string_format_test.cpp \ HEADERS += + diff --git a/base/base_tests/string_format_test.cpp b/base/base_tests/string_format_test.cpp new file mode 100644 index 0000000000..0849838e65 --- /dev/null +++ b/base/base_tests/string_format_test.cpp @@ -0,0 +1,11 @@ +#include "../../testing/testing.hpp" +#include "../string_format.hpp" + +UNIT_TEST(StringFormat_Smoke) +{ + TEST_EQUAL(strings::Format("this is % % % %", "a", "very", "simple", "test"), "this is a very simple test", ()); + + TEST_EQUAL(strings::Format("this", "a"), "this", ()); + + TEST_EQUAL(strings::Format("this % %", "is"), "this is %", ()); +} diff --git a/base/string_format.cpp b/base/string_format.cpp new file mode 100644 index 0000000000..64dd6c36a5 --- /dev/null +++ b/base/string_format.cpp @@ -0,0 +1,53 @@ +#include "string_format.hpp" +#include "logging.hpp" + +namespace strings +{ + string const FormatImpl(string const & s, list const & l) + { + size_t offs = 0; + list fieldOffs; + + string temp = s; + + LOG(LINFO, (s)); + + while (true) + { + offs = temp.find("%", offs); + if (offs == string::npos) + break; + else + { + if ((offs != 0) && (temp[offs - 1] == '\\')) + { + temp = temp.erase(offs - 1, 1); + --offs; + } + else + fieldOffs.push_back(offs); + + ++offs; + } + } + + offs = 0; + + string res = temp; + + list::const_iterator offsIt; + list::const_iterator strIt; + + for (offsIt = fieldOffs.begin(), strIt = l.begin(); + (offsIt != fieldOffs.end()) && (strIt != l.end()); + ++offsIt, ++strIt) + { + res.replace(*offsIt + offs, 1, *strIt); + offs += strIt->size() - 1; + } + + LOG(LINFO, (res)); + + return res; + } +} diff --git a/base/string_format.hpp b/base/string_format.hpp new file mode 100644 index 0000000000..d97a8faeb9 --- /dev/null +++ b/base/string_format.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "../std/string.hpp" +#include "../std/sstream.hpp" +#include "../std/list.hpp" + +namespace strings +{ + template + string ToString(T t) + { + ostringstream out; + out << t; + return out.str(); + } + + string const FormatImpl(string const & s, list const & l); + + template + string const Format(string const & s, T1 const & t1) + { + list l; + l.push_back(ToString(t1)); + + return FormatImpl(s, l); + } + + template + string const Format(string const & s, T1 const & t1, T2 const & t2) + { + list l; + l.push_back(ToString(t1)); + l.push_back(ToString(t2)); + + return FormatImpl(s, l); + } + + template + string const Format(string const & s, T1 const & t1, T2 const & t2, T3 const & t3) + { + list l; + l.push_back(ToString(t1)); + l.push_back(ToString(t2)); + l.push_back(ToString(t3)); + + return FormatImpl(s, l); + } + + template + string const Format(string const & s, T1 const & t1, T2 const & t2, T3 const & t3, T4 const & t4) + { + list l; + l.push_back(ToString(t1)); + l.push_back(ToString(t2)); + l.push_back(ToString(t3)); + l.push_back(ToString(t4)); + + return FormatImpl(s, l); + } + + template + string const Format(string const & s, T1 const & t1, T2 const & t2, T3 const & t3, T4 const & t4, T5 const & t5) + { + list l; + l.push_back(ToString(t1)); + l.push_back(ToString(t2)); + l.push_back(ToString(t3)); + l.push_back(ToString(t4)); + l.push_back(ToString(t5)); + + return FormatImpl(s, l); + } +} diff --git a/base/strings_bundle.cpp b/base/strings_bundle.cpp new file mode 100644 index 0000000000..1706853658 --- /dev/null +++ b/base/strings_bundle.cpp @@ -0,0 +1,30 @@ +#include "strings_bundle.hpp" + +void StringsBundle::SetDefaultString(string const & name, string const & value) +{ + m_defValues[name] = value; +} + +void StringsBundle::SetString(string const & name, string const & value) +{ + m_values[name] = value; +} + +string const StringsBundle::GetString(string const & name) const +{ + TStringMap::const_iterator it = m_values.find(name); + if (it != m_values.end()) + return it->second; + else + { + it = m_defValues.find(name); + if (it != m_defValues.end()) + return it->second; + } + return ""; +} + +string const FormatString(char const * msg, ...) +{ + return ""; +} diff --git a/base/strings_bundle.hpp b/base/strings_bundle.hpp new file mode 100644 index 0000000000..fa864e4fef --- /dev/null +++ b/base/strings_bundle.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "../std/string.hpp" +#include "../std/map.hpp" + +class StringsBundle +{ +private: + + typedef map TStringMap; + TStringMap m_values; + TStringMap m_defValues; + +public: + + void SetDefaultString(string const & name, string const & value); + void SetString(string const & name, string const & value); + string const GetString(string const & name) const; +}; + +string const & FormatString(string const & msg); + + diff --git a/gui/controller.cpp b/gui/controller.cpp index a47dd6a5d4..24d20541ce 100644 --- a/gui/controller.cpp +++ b/gui/controller.cpp @@ -157,4 +157,22 @@ namespace gui { return m_GlyphCache; } + + void Controller::SetStringsBundle(StringsBundle const * bundle) + { + m_bundle = bundle; + + for (elem_list_t::const_iterator it = m_Elements.begin(); + it != m_Elements.end(); + ++it) + { + (*it)->setIsDirtyRect(true); + (*it)->setIsDirtyDrawing(true); + } + } + + StringsBundle const * Controller::GetStringsBundle() const + { + return m_bundle; + } } diff --git a/gui/controller.hpp b/gui/controller.hpp index 39cee17d1a..09c7725c35 100644 --- a/gui/controller.hpp +++ b/gui/controller.hpp @@ -3,10 +3,16 @@ #include "../std/shared_ptr.hpp" #include "../std/function.hpp" #include "../std/list.hpp" -#include "../yg/overlay.hpp" + +#include "../geometry/point2d.hpp" + +#include "../base/strings_bundle.hpp" namespace yg { + class GlyphCache; + class OverlayElement; + namespace gl { class Screen; @@ -48,6 +54,9 @@ namespace gui /// GlyphCache for text rendering by GUI elements. yg::GlyphCache * m_GlyphCache; + /// Localized strings for GUI. + StringsBundle const * m_bundle; + public: /// Constructor with GestureDetector to route events from. @@ -76,6 +85,8 @@ namespace gui /// Attach GUI Controller to the renderer void SetRenderParams(RenderParams const & p); + /// Set the bundle with localized strings + void SetStringsBundle(StringsBundle const * bundle); /// Detach GUI Controller from the renderer void ResetRenderParams(); /// Invalidate the scene @@ -86,6 +97,8 @@ namespace gui void AddElement(shared_ptr const & e); /// Get VisualScale parameter double GetVisualScale() const; + /// Get localized strings bundle + StringsBundle const * GetStringsBundle() const; /// Get GLyphCache yg::GlyphCache * GetGlyphCache() const; /// Redraw GUI diff --git a/map/country_status_display.cpp b/map/country_status_display.cpp index 67ceb76d7d..83f5514c35 100644 --- a/map/country_status_display.cpp +++ b/map/country_status_display.cpp @@ -5,6 +5,8 @@ #include "../std/bind.hpp" #include "../std/sstream.hpp" +#include "../base/string_format.hpp" + #include "../storage/storage.hpp" #include "../yg/overlay_renderer.hpp" @@ -23,8 +25,27 @@ void CountryStatusDisplay::cache() m_statusMsg->setIsVisible(false); - string const dn = displayName(); - strings::UniString s(strings::MakeUniString(dn)); + string dn = displayName(); + strings::UniString udn(strings::MakeUniString(dn)); + + StringsBundle const * stringsBundle = m_controller->GetStringsBundle(); + + string prefixedName; + string postfixedName; + + if (udn.size() > 13) + { + if (strings::MakeUniString(m_mapName).size() > 13) + dn = m_mapName + "\n" + "(" + m_mapGroupName + ")"; + + prefixedName = "\n" + dn; + postfixedName = dn + "\n"; + } + else + { + prefixedName = " " + dn; + postfixedName = dn + " "; + } if (m_countryIdx != storage::TIndex()) { @@ -33,15 +54,9 @@ void CountryStatusDisplay::cache() case storage::EInQueue: { m_statusMsg->setIsVisible(true); + string countryStatusAddedToQueue = stringsBundle->GetString("country_status_added_to_queue"); - ostringstream out; - - if (s.size() > 13) - out << dn << "\nis added to the\ndownloading queue"; - else - out << dn << " is added to the\ndownloading queue"; - - m_statusMsg->setText(out.str()); + m_statusMsg->setText(strings::Format(countryStatusAddedToQueue, postfixedName)); } break; @@ -49,44 +64,29 @@ void CountryStatusDisplay::cache() { m_statusMsg->setIsVisible(true); - ostringstream out; + int percent = m_countryProgress.first * 100 / m_countryProgress.second; - if (s.size() > 13) - out << "Downloading\n" << dn; - else - out << "Downloading " << dn; - - out << "(" << m_countryProgress.first * 100 / m_countryProgress.second << "%)"; - - m_statusMsg->setText(out.str()); + string countryStatusDownloading = stringsBundle->GetString("country_status_downloading"); + m_statusMsg->setText(strings::Format(countryStatusDownloading, prefixedName, percent)); } break; case storage::ENotDownloaded: { m_downloadButton->setIsVisible(true); - if (s.size() > 13) - m_downloadButton->setText("Download\n" + dn); - else - m_downloadButton->setText("Download " + dn); + string countryStatusDownload = stringsBundle->GetString("country_status_download"); + m_downloadButton->setText(strings::Format(countryStatusDownload, prefixedName)); } break; case storage::EDownloadFailed: { m_downloadButton->setIsVisible(true); - m_downloadButton->setText("Try again"); + m_downloadButton->setText(stringsBundle->GetString("try_again")); - ostringstream out; - - if (s.size() > 13) - out << "Downloading\n" << dn; - else - out << "Downloading " << dn; - - out << "\nhas failed."; + string countryStatusDownloadFailed = stringsBundle->GetString("country_status_download_failed"); + m_statusMsg->setText(strings::Format(countryStatusDownloadFailed, prefixedName)); m_statusMsg->setIsVisible(true); - m_statusMsg->setText(out.str()); setPivot(pivot()); } diff --git a/map/framework.cpp b/map/framework.cpp index 6d2f900316..17b953e6cd 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -121,6 +121,10 @@ Framework::Framework() { m_guiController.reset(new gui::Controller()); + SetStringsBundle(GetDefaultStringsBundle()); + + m_guiController->SetStringsBundle(&m_stringsBundle); + m_informationDisplay.setController(m_guiController.get()); #ifdef DRAW_TOUCH_POINTS @@ -971,6 +975,25 @@ gui::Controller * Framework::GetGuiController() const return m_guiController.get(); } +StringsBundle const Framework::GetDefaultStringsBundle() const +{ + StringsBundle res; + + res.SetDefaultString("country_status_added_to_queue", "%is added to the\ndownloading queue."); + res.SetDefaultString("country_status_downloading", "Downloading%(%\\%)"); + res.SetDefaultString("country_status_download", "Download%"); + res.SetDefaultString("country_status_download_failed", "Downloading%\nhas failed"); + res.SetDefaultString("try_again", "Try Again"); + + return res; +} + +void Framework::SetStringsBundle(StringsBundle const & bundle) +{ + m_stringsBundle = bundle; + m_guiController->SetStringsBundle(&m_stringsBundle); +} + bool Framework::SetViewportByURL(string const & url) { using namespace url_scheme; diff --git a/map/framework.hpp b/map/framework.hpp index 393b5a1277..a1db88a8d0 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -40,6 +40,7 @@ #include "../base/logging.hpp" //#include "../base/mutex.hpp" #include "../base/timer.hpp" +#include "../base/strings_bundle.hpp" #include "../std/vector.hpp" #include "../std/shared_ptr.hpp" @@ -62,6 +63,9 @@ class CountryStatusDisplay; class Framework { protected: + + StringsBundle m_stringsBundle; + mutable scoped_ptr m_pSearchEngine; model::FeaturesFetcher m_model; Navigator m_navigator; @@ -291,6 +295,10 @@ public: gui::Controller * GetGuiController() const; + /// Set the localized strings bundle + void SetStringsBundle(StringsBundle const & bundle); + StringsBundle const GetDefaultStringsBundle() const; + private: //bool IsEmptyModel() const; bool IsEmptyModel(m2::PointD const & pt);