13d5f7d104412147159b8d85af7d76d7ed26f38d
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / styling / style-manager-impl.cpp
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 // CLASS HEADER
18 #include "style-manager-impl.h"
19
20 // EXTERNAL INCLUDES
21 #include <dali/devel-api/common/singleton-service.h>
22 #include <dali/integration-api/debug.h>
23 #include <dali/public-api/adaptor-framework/application.h>
24 #include <dali/public-api/object/type-registry-helper.h>
25 #include <dali/public-api/object/type-registry.h>
26
27 // INTERNAL INCLUDES
28 #include <dali-toolkit/devel-api/asset-manager/asset-manager.h>
29 #include <dali-toolkit/internal/builder/builder-impl.h>
30 #include <dali-toolkit/internal/feedback/feedback-style.h>
31 #include <dali-toolkit/public-api/controls/control-impl.h>
32 #include <dali-toolkit/public-api/controls/control.h>
33 #include <dali-toolkit/public-api/styling/style-manager.h>
34
35 namespace
36 {
37 //const char* LANDSCAPE_QUALIFIER = "landscape";
38 const char* PORTRAIT_QUALIFIER  = "portrait";
39 const char* FONT_SIZE_QUALIFIER = "fontsize";
40
41 const char* DEFAULT_THEME_FILE_NAME = "dali-toolkit-default-theme.json";
42
43 const char* PACKAGE_PATH_KEY              = "PACKAGE_PATH";
44 const char* APPLICATION_RESOURCE_PATH_KEY = "APPLICATION_RESOURCE_PATH";
45
46 const char* DEFAULT_TOOLKIT_PACKAGE_PATH = "/toolkit/";
47
48 static constexpr int32_t COUNT_BROKEN_IMAGE_MAX = 3;
49
50 #if defined(DEBUG_ENABLED)
51 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_STYLE");
52 #endif
53
54 } // namespace
55
56 namespace Dali
57 {
58 namespace Toolkit
59 {
60 namespace Internal
61 {
62 namespace
63 {
64 BaseHandle Create()
65 {
66   BaseHandle handle = StyleManager::Get();
67
68   if(!handle)
69   {
70     SingletonService singletonService(SingletonService::Get());
71     if(singletonService)
72     {
73       Toolkit::StyleManager manager = Toolkit::StyleManager(new Internal::StyleManager());
74       singletonService.Register(typeid(manager), manager);
75       handle = manager;
76     }
77   }
78
79   return handle;
80 }
81
82 DALI_TYPE_REGISTRATION_BEGIN_CREATE(Toolkit::StyleManager, Dali::BaseHandle, Create, true)
83 DALI_TYPE_REGISTRATION_END()
84
85 } // namespace
86
87 Toolkit::StyleManager StyleManager::Get()
88 {
89   Toolkit::StyleManager manager;
90
91   SingletonService singletonService(SingletonService::Get());
92   if(singletonService)
93   {
94     // Check whether the style manager is already created
95     Dali::BaseHandle handle = singletonService.GetSingleton(typeid(Toolkit::StyleManager));
96     if(handle)
97     {
98       // If so, downcast the handle of singleton
99       manager = Toolkit::StyleManager(dynamic_cast<StyleManager*>(handle.GetObjectPtr()));
100     }
101   }
102
103   return manager;
104 }
105
106 StyleManager::StyleManager()
107 : mDefaultFontSize(-1),
108   mDefaultFontFamily(""),
109   mDefaultThemeFilePath(),
110   mFeedbackStyle(nullptr)
111 {
112   // Add theme builder constants
113   const std::string dataReadOnlyDir                     = AssetManager::GetDaliDataReadOnlyPath();
114   mThemeBuilderConstants[PACKAGE_PATH_KEY]              = dataReadOnlyDir + DEFAULT_TOOLKIT_PACKAGE_PATH;
115   mThemeBuilderConstants[APPLICATION_RESOURCE_PATH_KEY] = Application::GetResourcePath();
116
117   mStyleMonitor = StyleMonitor::Get();
118   if(mStyleMonitor)
119   {
120     mStyleMonitor.StyleChangeSignal().Connect(this, &StyleManager::StyleMonitorChange);
121     mDefaultFontSize = mStyleMonitor.GetDefaultFontSize();
122   }
123
124   // Set the full path for the default style theme.
125   const std::string styleDirPath = AssetManager::GetDaliStylePath();
126   mDefaultThemeFilePath          = styleDirPath + DEFAULT_THEME_FILE_NAME;
127
128   // Sound & haptic style
129   mFeedbackStyle = new FeedbackStyle();
130
131   // Initialize BrokenImages
132   mBrokenImageUrls.assign(COUNT_BROKEN_IMAGE_MAX, "");
133 }
134
135 StyleManager::~StyleManager()
136 {
137   delete mFeedbackStyle;
138 }
139
140 void StyleManager::ApplyTheme(const std::string& themeFile)
141 {
142   SetTheme(themeFile);
143 }
144
145 void StyleManager::ApplyDefaultTheme()
146 {
147   SetTheme(mDefaultThemeFilePath);
148 }
149
150 const std::string& StyleManager::GetDefaultFontFamily() const
151 {
152   return mDefaultFontFamily;
153 }
154
155 void StyleManager::SetStyleConstant(const std::string& key, const Property::Value& value)
156 {
157   mStyleBuilderConstants[key] = value;
158 }
159
160 bool StyleManager::GetStyleConstant(const std::string& key, Property::Value& valueOut)
161 {
162   Property::Value* value = mStyleBuilderConstants.Find(key);
163   if(value)
164   {
165     valueOut = *value;
166     return true;
167   }
168
169   return false;
170 }
171
172 void StyleManager::ApplyThemeStyle(Toolkit::Control control)
173 {
174   if(!mThemeBuilder)
175   {
176     ApplyDefaultTheme();
177   }
178
179   if(mThemeBuilder)
180   {
181     ApplyStyle(mThemeBuilder, control);
182   }
183 }
184
185 void StyleManager::ApplyThemeStyleAtInit(Toolkit::Control control)
186 {
187   ApplyThemeStyle(control);
188
189   if(mFeedbackStyle)
190   {
191     mFeedbackStyle->ObjectCreated(control);
192   }
193 }
194
195 void StyleManager::ApplyStyle(Toolkit::Control control, const std::string& jsonFileName, const std::string& styleName)
196 {
197   bool builderReady = false;
198
199   // First look in the cache
200   Toolkit::Builder builder = FindCachedBuilder(jsonFileName);
201   if(builder)
202   {
203     builderReady = true;
204   }
205   else
206   {
207     // Merge theme and style constants
208     Property::Map constants(mThemeBuilderConstants);
209     constants.Merge(mStyleBuilderConstants);
210
211     // Create it
212     builder = CreateBuilder(constants);
213
214     if(LoadJSON(builder, jsonFileName))
215     {
216       CacheBuilder(builder, jsonFileName);
217       builderReady = true;
218     }
219   }
220
221   // Apply the style to the control
222   if(builderReady)
223   {
224     builder.ApplyStyle(styleName, control);
225   }
226 }
227
228 Toolkit::StyleManager::StyleChangedSignalType& StyleManager::StyleChangedSignal()
229 {
230   return mStyleChangedSignal;
231 }
232
233 Toolkit::StyleManager::StyleChangedSignalType& StyleManager::ControlStyleChangeSignal()
234 {
235   return mControlStyleChangeSignal;
236 }
237
238 void StyleManager::SetTheme(const std::string& themeFile)
239 {
240   bool themeLoaded = false;
241   bool loading     = false;
242
243   // If we haven't loaded a theme, or the stored theme file is empty, or
244   // the previously loaded theme is different to the requested theme,
245   // first reset the builder and load the default theme.
246   if(!mThemeBuilder || mThemeFile.empty() || mThemeFile.compare(themeFile) != 0)
247   {
248     loading       = true;
249     mThemeBuilder = CreateBuilder(mThemeBuilderConstants);
250     themeLoaded   = LoadJSON(mThemeBuilder, mDefaultThemeFilePath); // Sets themeLoaded to true if theme exists
251   }
252
253   if(themeFile.compare(mDefaultThemeFilePath) != 0)
254   {
255     // The theme is different to the default: Merge it
256     loading = true;
257     themeLoaded |= LoadJSON(mThemeBuilder, themeFile);
258   }
259
260   if(loading)
261   {
262     mThemeFile = themeFile;
263
264     if(themeLoaded)
265     {
266       // We've successfully loaded the theme file
267       if(mFeedbackStyle)
268       {
269         mFeedbackStyle->StyleChanged(mThemeFile, StyleChange::THEME_CHANGE);
270       }
271
272       EmitStyleChangeSignals(StyleChange::THEME_CHANGE);
273     }
274     else
275     {
276       // We tried to load a theme, but it failed. Ensure the builder is reset
277       mThemeBuilder.Reset();
278       mThemeFile.clear();
279     }
280   }
281 }
282
283 const Property::Map StyleManager::GetConfigurations()
284 {
285   DALI_LOG_STREAM(gLogFilter, Debug::Concise, "GetConfigurations()\n On entry, mThemeBuilder: " << (bool(mThemeBuilder) ? "Created" : "Empty") << "  mThemeFile: " << mThemeFile);
286
287   Property::Map result;
288   if(mThemeBuilder)
289   {
290     result = mThemeBuilder.GetConfigurations();
291   }
292   else
293   {
294     DALI_LOG_STREAM(gLogFilter, Debug::Concise, "GetConfigurations()  Loading default theme");
295
296     bool themeLoaded = false;
297
298     mThemeBuilder = CreateBuilder(mThemeBuilderConstants);
299
300     // Load default theme because this is first try to load stylesheet.
301     themeLoaded = LoadJSON(mThemeBuilder, mDefaultThemeFilePath);
302     mThemeFile  = mDefaultThemeFilePath;
303
304     if(themeLoaded)
305     {
306       result = mThemeBuilder.GetConfigurations();
307     }
308     DALI_LOG_STREAM(gLogFilter, Debug::Concise, "  themeLoaded" << (themeLoaded ? "success" : "failure"));
309   }
310
311   DALI_LOG_STREAM(gLogFilter, Debug::Concise, "GetConfigurations()\n On exit, result Count: " << (result.Count() != 0));
312   DALI_LOG_STREAM(gLogFilter, Debug::Verbose, "          result: " << result);
313
314   return result;
315 }
316
317 void StyleManager::SetBrokenImageUrl(BrokenImageType brokenImageType, const std::string& brokenImageUrl)
318 {
319   mBrokenImageUrls[brokenImageType] = brokenImageUrl;
320   EmitStyleChangeSignals(StyleChange::THEME_CHANGE);
321 }
322
323 std::string StyleManager::GetBrokenImageUrl(BrokenImageType brokenImageType)
324 {
325   return mBrokenImageUrls[brokenImageType];
326 }
327
328 bool StyleManager::LoadFile(const std::string& filename, std::string& stringOut)
329 {
330   DALI_ASSERT_DEBUG(0 != filename.length());
331
332   // as toolkit is platform agnostic, it cannot load files from filesystem
333   // ask style monitor to load the style sheet
334   if(mStyleMonitor)
335   {
336     return mStyleMonitor.LoadThemeFile(filename, stringOut);
337   }
338
339   return false;
340 }
341
342 Toolkit::Builder StyleManager::CreateBuilder(const Property::Map& constants)
343 {
344   Toolkit::Builder builder = Toolkit::Builder::New();
345   builder.AddConstants(constants);
346
347   return builder;
348 }
349
350 bool StyleManager::LoadJSON(Toolkit::Builder builder, const std::string& jsonFilePath)
351 {
352   std::string fileString;
353   if(LoadFile(jsonFilePath, fileString))
354   {
355     builder.LoadFromString(fileString);
356     return true;
357   }
358   else
359   {
360     DALI_LOG_WARNING("Error loading file '%s'\n", jsonFilePath.c_str());
361     return false;
362   }
363 }
364
365 static void CollectQualifiers(std::vector<std::string>& qualifiersOut)
366 {
367   // Append the relevant qualifier for orientation
368   // int orientation = 0; // Get the orientation from the system
369   /*
370   //// To Do /////
371   Getting orientation from the system, and determine Qualifie LANDSCAPE or PORTRAIT
372   orientation  0, 180 : PORTRAIT_QUALIFIER (default)
373   orientation 90, 270 : LANDSCAPE_QUALIFIER
374   */
375
376   qualifiersOut.push_back(std::string(PORTRAIT_QUALIFIER));
377 }
378
379 /**
380  * @brief Construct a qualified style name out of qualifiers
381  *
382  * A qualifed style name will be in the format: style-qualifier0-qualifier1-qualifierN
383  *
384  * @param[in] styleName The root name of the style
385  * @param[in] qualifiers List of qualifier names
386  * @param[out] qualifiedStyleOut The qualified style name
387  */
388 static void BuildQualifiedStyleName(
389   const std::string&              styleName,
390   const std::vector<std::string>& qualifiers,
391   std::string&                    qualifiedStyleOut)
392 {
393   qualifiedStyleOut.append(styleName);
394
395   for(std::vector<std::string>::const_iterator it    = qualifiers.begin(),
396                                                itEnd = qualifiers.end();
397       it != itEnd;
398       ++it)
399   {
400     const std::string& str = *it;
401
402     qualifiedStyleOut.append("-");
403     qualifiedStyleOut.append(str);
404   }
405 }
406
407 static bool GetStyleNameForControl(Toolkit::Builder builder, Toolkit::Control control, std::string& styleName)
408 {
409   styleName = control.GetStyleName();
410
411   if(styleName.empty())
412   {
413     styleName = control.GetTypeName();
414   }
415
416   // Apply the style after choosing the correct actual style (e.g. landscape or portrait)
417   std::vector<std::string> qualifiers;
418   CollectQualifiers(qualifiers);
419
420   bool        found = 0;
421   std::string qualifiedStyleName;
422   do
423   {
424     qualifiedStyleName.clear();
425     BuildQualifiedStyleName(styleName, qualifiers, qualifiedStyleName);
426
427     // Break if style found or we have tried the root style name (qualifiers is empty)
428     if(GetImpl(builder).LookupStyleName(qualifiedStyleName))
429     {
430       found = true;
431       break;
432     }
433     if(qualifiers.size() == 0)
434     {
435       break;
436     }
437     // Remove the last qualifier in an attempt to find a style that is valid
438     qualifiers.pop_back();
439   } while(!found);
440
441   if(found)
442   {
443     styleName = qualifiedStyleName;
444   }
445   return found;
446 }
447
448 void StyleManager::ApplyStyle(Toolkit::Builder builder, Toolkit::Control control)
449 {
450   std::string styleName = control.GetStyleName();
451   if(GetStyleNameForControl(builder, control, styleName))
452   {
453     builder.ApplyStyle(styleName, control);
454   }
455
456   if(mDefaultFontSize >= 0)
457   {
458     // Apply the style for logical font size
459     std::stringstream fontSizeQualifier;
460     fontSizeQualifier << styleName << FONT_SIZE_QUALIFIER << mDefaultFontSize;
461     builder.ApplyStyle(fontSizeQualifier.str(), control);
462   }
463 }
464
465 const StylePtr StyleManager::GetRecordedStyle(Toolkit::Control control)
466 {
467   if(mThemeBuilder)
468   {
469     std::string styleName = control.GetStyleName();
470
471     if(GetStyleNameForControl(mThemeBuilder, control, styleName))
472     {
473       const StylePtr style = GetImpl(mThemeBuilder).GetStyle(styleName);
474       return style;
475     }
476   }
477   return StylePtr(NULL);
478 }
479
480 Toolkit::Builder StyleManager::FindCachedBuilder(const std::string& key)
481 {
482   BuilderMap::iterator builderIt = mBuilderCache.find(key);
483   if(builderIt != mBuilderCache.end())
484   {
485     return builderIt->second;
486   }
487
488   return Toolkit::Builder();
489 }
490
491 void StyleManager::CacheBuilder(Toolkit::Builder builder, const std::string& key)
492 {
493   mBuilderCache[key] = builder;
494 }
495
496 void StyleManager::StyleMonitorChange(StyleMonitor styleMonitor, StyleChange::Type styleChange)
497 {
498   switch(styleChange)
499   {
500     case StyleChange::DEFAULT_FONT_CHANGE:
501     {
502       mDefaultFontFamily = styleMonitor.GetDefaultFontFamily();
503       break;
504     }
505
506     case StyleChange::DEFAULT_FONT_SIZE_CHANGE:
507     {
508       mDefaultFontSize = styleMonitor.GetDefaultFontSize();
509       break;
510     }
511
512     case StyleChange::THEME_CHANGE:
513     {
514       SetTheme(styleMonitor.GetTheme());
515       break;
516     }
517   }
518   EmitStyleChangeSignals(styleChange);
519 }
520
521 void StyleManager::EmitStyleChangeSignals(StyleChange::Type styleChange)
522 {
523   Toolkit::StyleManager styleManager = StyleManager::Get();
524
525   // Update Controls first
526   mControlStyleChangeSignal.Emit(styleManager, styleChange);
527
528   // Inform application last
529   mStyleChangedSignal.Emit(styleManager, styleChange);
530 }
531
532 } // namespace Internal
533
534 } // namespace Toolkit
535
536 } // namespace Dali