DataView.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. /*
  2. * This source file is part of RmlUi, the HTML/CSS Interface Middleware
  3. *
  4. * For the latest information, see http://github.com/mikke89/RmlUi
  5. *
  6. * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
  7. * Copyright (c) 2019 The RmlUi Team, and contributors
  8. *
  9. * Permission is hereby granted, free of charge, to any person obtaining a copy
  10. * of this software and associated documentation files (the "Software"), to deal
  11. * in the Software without restriction, including without limitation the rights
  12. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. * copies of the Software, and to permit persons to whom the Software is
  14. * furnished to do so, subject to the following conditions:
  15. *
  16. * The above copyright notice and this permission notice shall be included in
  17. * all copies or substantial portions of the Software.
  18. *
  19. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. * THE SOFTWARE.
  26. *
  27. */
  28. #include "precompiled.h"
  29. #include "../../Include/RmlUi/Core/DataView.h"
  30. #include "../../Include/RmlUi/Core/DataModel.h"
  31. namespace Rml {
  32. namespace Core {
  33. DataView::~DataView() {}
  34. Element* DataView::GetElement() const
  35. {
  36. Element* result = attached_element.get();
  37. if (!result)
  38. Log::Message(Log::LT_WARNING, "Could not retrieve element in view, was it destroyed?");
  39. return result;
  40. }
  41. DataView::DataView(Element* element) : attached_element(element->GetObserverPtr()), element_depth(0) {
  42. if (element)
  43. {
  44. for (Element* parent = element->GetParentNode(); parent; parent = parent->GetParentNode())
  45. element_depth += 1;
  46. }
  47. }
  48. DataViewText::DataViewText(DataModel& model, ElementText* in_parent_element, const String& in_text, const size_t index_begin_search) : DataView(in_parent_element)
  49. {
  50. text.reserve(in_text.size());
  51. bool success = true;
  52. size_t previous_close_brackets = 0;
  53. size_t begin_brackets = index_begin_search;
  54. while ((begin_brackets = in_text.find("{{", begin_brackets)) != String::npos)
  55. {
  56. text.insert(text.end(), in_text.begin() + previous_close_brackets, in_text.begin() + begin_brackets);
  57. const size_t begin_name = begin_brackets + 2;
  58. const size_t end_name = in_text.find("}}", begin_name);
  59. if (end_name == String::npos)
  60. {
  61. success = false;
  62. break;
  63. }
  64. DataEntry entry;
  65. entry.index = text.size();
  66. String address_str = StringUtilities::StripWhitespace(StringView(in_text.data() + begin_name, in_text.data() + end_name));
  67. entry.variable_address = model.ResolveAddress(address_str, in_parent_element);
  68. data_entries.push_back(std::move(entry));
  69. previous_close_brackets = end_name + 2;
  70. begin_brackets = previous_close_brackets;
  71. }
  72. if (data_entries.empty())
  73. success = false;
  74. if (success && previous_close_brackets < in_text.size())
  75. text.insert(text.end(), in_text.begin() + previous_close_brackets, in_text.end());
  76. if (!success)
  77. {
  78. text.clear();
  79. data_entries.clear();
  80. InvalidateView();
  81. }
  82. }
  83. bool DataViewText::Update(DataModel& model)
  84. {
  85. bool entries_modified = false;
  86. for (DataEntry& entry : data_entries)
  87. {
  88. String value;
  89. bool result = model.GetValue(entry.variable_address, value);
  90. if (result && entry.value != value)
  91. {
  92. entry.value = value;
  93. entries_modified = true;
  94. }
  95. }
  96. if (entries_modified)
  97. {
  98. if (Element* element = GetElement())
  99. {
  100. RMLUI_ASSERTMSG(rmlui_dynamic_cast<ElementText*>(element), "Somehow the element type was changed from ElementText since construction of the view. Should not be possible?");
  101. if(auto text_element = static_cast<ElementText*>(element))
  102. {
  103. String new_text = BuildText();
  104. text_element->SetText(new_text);
  105. }
  106. }
  107. else
  108. {
  109. Log::Message(Log::LT_WARNING, "Could not update data view text, element no longer valid. Was it destroyed?");
  110. }
  111. }
  112. return entries_modified;
  113. }
  114. String DataViewText::BuildText() const
  115. {
  116. size_t reserve_size = text.size();
  117. for (const DataEntry& entry : data_entries)
  118. reserve_size += entry.value.size();
  119. String result;
  120. result.reserve(reserve_size);
  121. size_t previous_index = 0;
  122. for (const DataEntry& entry : data_entries)
  123. {
  124. result += text.substr(previous_index, entry.index - previous_index);
  125. result += entry.value;
  126. previous_index = entry.index;
  127. }
  128. if (previous_index < text.size())
  129. result += text.substr(previous_index);
  130. return result;
  131. }
  132. DataViewAttribute::DataViewAttribute(DataModel& model, Element* element, const String& binding_name, const String& attribute_name)
  133. : DataView(element), attribute_name(attribute_name)
  134. {
  135. variable_address = model.ResolveAddress(binding_name, element);
  136. if (attribute_name.empty())
  137. InvalidateView();
  138. }
  139. bool DataViewAttribute::Update(DataModel& model)
  140. {
  141. bool result = false;
  142. String value;
  143. Element* element = GetElement();
  144. if (model.GetValue(variable_address, value) && element)
  145. {
  146. Variant* attribute = element->GetAttribute(attribute_name);
  147. if (!attribute || (attribute && attribute->Get<String>() != value))
  148. {
  149. element->SetAttribute(attribute_name, value);
  150. result = true;
  151. }
  152. }
  153. return result;
  154. }
  155. DataViewStyle::DataViewStyle(DataModel& model, Element* element, const String& binding_name, const String& property_name)
  156. : DataView(element), property_name(property_name)
  157. {
  158. variable_address = model.ResolveAddress(binding_name, element);
  159. if (variable_address.empty() || property_name.empty())
  160. InvalidateView();
  161. }
  162. bool DataViewStyle::Update(DataModel& model)
  163. {
  164. bool result = false;
  165. String value;
  166. Element* element = GetElement();
  167. if (model.GetValue(variable_address, value) && element)
  168. {
  169. const Property* p = element->GetLocalProperty(property_name);
  170. if (!p || p->Get<String>() != value)
  171. {
  172. element->SetProperty(property_name, value);
  173. result = true;
  174. }
  175. }
  176. return result;
  177. }
  178. DataViewIf::DataViewIf(DataModel& model, Element* element, const String& binding_name) : DataView(element)
  179. {
  180. variable_address = model.ResolveAddress(binding_name, element);
  181. if (variable_address.empty())
  182. InvalidateView();
  183. }
  184. bool DataViewIf::Update(DataModel& model)
  185. {
  186. bool result = false;
  187. bool value = false;
  188. Element* element = GetElement();
  189. if (model.GetValue(variable_address, value) && element)
  190. {
  191. bool is_visible = (element->GetLocalStyleProperties().count(PropertyId::Display) == 0);
  192. if(is_visible != value)
  193. {
  194. if (value)
  195. element->RemoveProperty(PropertyId::Display);
  196. else
  197. element->SetProperty(PropertyId::Display, Property(Style::Display::None));
  198. result = true;
  199. }
  200. }
  201. return result;
  202. }
  203. DataViewFor::DataViewFor(DataModel& model, Element* element, const String& in_binding_name, const String& in_rml_content)
  204. : DataView(element), rml_contents(in_rml_content)
  205. {
  206. StringList binding_list;
  207. StringUtilities::ExpandString(binding_list, in_binding_name, ':');
  208. if (binding_list.empty() || binding_list.size() > 2 || binding_list.front().empty() || binding_list.back().empty())
  209. {
  210. Log::Message(Log::LT_WARNING, "Invalid syntax in data-for '%s'", in_binding_name.c_str());
  211. InvalidateView();
  212. return;
  213. }
  214. if (binding_list.size() == 2)
  215. alias_name = binding_list.front();
  216. else
  217. alias_name = "it";
  218. const String& binding_name = binding_list.back();
  219. variable_address = model.ResolveAddress(binding_name, element);
  220. if (variable_address.empty())
  221. {
  222. InvalidateView();
  223. return;
  224. }
  225. attributes = element->GetAttributes();
  226. attributes.erase("data-for");
  227. element->SetProperty(PropertyId::Display, Property(Style::Display::None));
  228. }
  229. bool DataViewFor::Update(DataModel& model)
  230. {
  231. Variable variable = model.GetVariable(variable_address);
  232. if (!variable)
  233. return false;
  234. bool result = false;
  235. const int size = variable.Size();
  236. const int num_elements = (int)elements.size();
  237. Element* element = GetElement();
  238. for (int i = 0; i < Math::Max(size, num_elements); i++)
  239. {
  240. if (i >= num_elements)
  241. {
  242. ElementPtr new_element_ptr = Factory::InstanceElement(nullptr, element->GetTagName(), element->GetTagName(), attributes);
  243. Address replacement_address;
  244. replacement_address.reserve(variable_address.size() + 1);
  245. replacement_address = variable_address;
  246. replacement_address.push_back(AddressEntry(i));
  247. model.InsertAlias(new_element_ptr.get(), alias_name, replacement_address);
  248. Element* new_element = element->GetParentNode()->InsertBefore(std::move(new_element_ptr), element);
  249. elements.push_back(new_element);
  250. elements[i]->SetInnerRML(rml_contents);
  251. RMLUI_ASSERT(i < (int)elements.size());
  252. }
  253. if (i >= size)
  254. {
  255. model.EraseAliases(elements[i]);
  256. elements[i]->GetParentNode()->RemoveChild(elements[i]).reset();
  257. elements[i] = nullptr;
  258. }
  259. }
  260. if (num_elements > size)
  261. elements.resize(size);
  262. return result;
  263. }
  264. DataViews::DataViews()
  265. {}
  266. DataViews::~DataViews()
  267. {}
  268. void DataViews::Add(UniquePtr<DataView> view) {
  269. views_to_add.push_back(std::move(view));
  270. }
  271. void DataViews::OnElementRemove(Element* element)
  272. {
  273. for (auto it = views.begin(); it != views.end();)
  274. {
  275. auto& view = *it;
  276. if (view && view->GetElement() == element)
  277. {
  278. views_to_remove.push_back(std::move(view));
  279. it = views.erase(it);
  280. }
  281. else
  282. ++it;
  283. }
  284. }
  285. bool DataViews::Update(DataModel & model, const SmallUnorderedSet< String >& dirty_variables)
  286. {
  287. bool result = false;
  288. // View updates may result in newly added views, thus we do it recursively but with an upper limit.
  289. // Without the loop, newly added views won't be updated until the next Update() call.
  290. for(int i = 0; i == 0 || (!views_to_add.empty() && i < 10); i++)
  291. {
  292. std::vector<DataView*> dirty_views;
  293. if (!views_to_add.empty())
  294. {
  295. views.reserve(views.size() + views_to_add.size());
  296. for (auto&& view : views_to_add)
  297. {
  298. dirty_views.push_back(view.get());
  299. for (const String& variable_name : view->GetVariableNameList())
  300. name_view_map.emplace(variable_name, view.get());
  301. views.push_back(std::move(view));
  302. }
  303. views_to_add.clear();
  304. }
  305. for (const String& variable_name : dirty_variables)
  306. {
  307. auto pair = name_view_map.equal_range(variable_name);
  308. for (auto it = pair.first; it != pair.second; ++it)
  309. dirty_views.push_back(it->second);
  310. }
  311. // Remove duplicate entries
  312. std::sort(dirty_views.begin(), dirty_views.end());
  313. auto it_remove = std::unique(dirty_views.begin(), dirty_views.end());
  314. dirty_views.erase(it_remove, dirty_views.end());
  315. // Sort by the element's depth in the document tree so that any structural changes due to a changed variable are reflected in the element's children.
  316. // Eg. the 'data-for' view will remove children if any of its data variable array size is reduced.
  317. std::sort(dirty_views.begin(), dirty_views.end(), [](auto&& left, auto&& right) { return left->GetElementDepth() < right->GetElementDepth(); });
  318. for (DataView* view : dirty_views)
  319. {
  320. RMLUI_ASSERT(view);
  321. if (!view)
  322. continue;
  323. if (view->IsValid())
  324. result |= view->Update(model);
  325. }
  326. // Destroy views marked for destruction
  327. // @performance: Horrible...
  328. if (!views_to_remove.empty())
  329. {
  330. for (const auto& view : views_to_remove)
  331. {
  332. for (auto it = name_view_map.begin(); it != name_view_map.end(); )
  333. {
  334. if (it->second == view.get())
  335. it = name_view_map.erase(it);
  336. else
  337. ++it;
  338. }
  339. }
  340. views_to_remove.clear();
  341. }
  342. }
  343. return result;
  344. }
  345. }
  346. }