/* * This source file is part of libRocket, the HTML/CSS Interface Middleware * * For the latest information, see http://www.librocket.com * * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include "precompiled.h" #include "../../Include/Rocket/Core/ElementDocument.h" #include "../../Include/Rocket/Core/StreamMemory.h" #include "../../Include/Rocket/Core.h" #include "DocumentHeader.h" #include "ElementStyle.h" #include "EventDispatcher.h" #include "LayoutEngine.h" #include "StreamFile.h" #include "StyleSheetFactory.h" #include "Template.h" #include "TemplateCache.h" #include "XMLParseTools.h" namespace Rocket { namespace Core { ElementDocument::ElementDocument(const String& tag) : Element(tag) { style_sheet = NULL; context = NULL; modal = false; layout_dirty = true; lock_layout = 0; ForceLocalStackingContext(); SetProperty(POSITION, "absolute"); } ElementDocument::~ElementDocument() { if (style_sheet != NULL) style_sheet->RemoveReference(); } void ElementDocument::ProcessHeader(const DocumentHeader* document_header) { // Store the source address that we came from source_url = document_header->source; // Construct a new header and copy the template details across DocumentHeader header; header.MergePaths(header.template_resources, document_header->template_resources, document_header->source); // Merge in any templates, note a merge may cause more templates to merge for (size_t i = 0; i < header.template_resources.size(); i++) { Template* merge_template = TemplateCache::LoadTemplate(URL(header.template_resources[i]).GetURL()); if (merge_template) header.MergeHeader(*merge_template->GetHeader()); else Log::Message(Log::LT_WARNING, "Template %s not found", header.template_resources[i].CString()); } // Merge the document's header last, as it is the most overriding. header.MergeHeader(*document_header); // Set the title to the document title. title = document_header->title; // If a style-sheet (or sheets) has been specified for this element, then we load them and set the combined sheet // on the element; all of its children will inherit it by default. StyleSheet* style_sheet = NULL; if (header.rcss_external.size() > 0) style_sheet = StyleSheetFactory::GetStyleSheet(header.rcss_external); // Combine any inline sheets. if (header.rcss_inline.size() > 0) { for (size_t i = 0;i < header.rcss_inline.size(); i++) { StyleSheet* new_sheet = new StyleSheet(); StreamMemory* stream = new StreamMemory((const byte*) header.rcss_inline[i].CString(), header.rcss_inline[i].Length()); stream->SetSourceURL(document_header->source); if (new_sheet->LoadStyleSheet(stream)) { if (style_sheet) { StyleSheet* combined_sheet = style_sheet->CombineStyleSheet(new_sheet); style_sheet->RemoveReference(); new_sheet->RemoveReference(); style_sheet = combined_sheet; } else style_sheet = new_sheet; } else new_sheet->RemoveReference(); stream->RemoveReference(); } } // If a style sheet is available, set it on the document and release it. if (style_sheet) { SetStyleSheet(style_sheet); style_sheet->RemoveReference(); } // Load external scripts. for (size_t i = 0; i < header.scripts_external.size(); i++) { StreamFile* stream = new StreamFile(); if (stream->Open(header.scripts_external[i])) LoadScript(stream, header.scripts_external[i]); stream->RemoveReference(); } // Load internal scripts. for (size_t i = 0; i < header.scripts_inline.size(); i++) { StreamMemory* stream = new StreamMemory((const byte*) header.scripts_inline[i].CString(), header.scripts_inline[i].Length()); LoadScript(stream, ""); stream->RemoveReference(); } // Hide this document. SetProperty(VISIBILITY, "hidden"); } ElementDocument* ElementDocument::GetOwnerDocument() { return this; } // Returns the document's context. Context* ElementDocument::GetContext() { return context; } // Sets the document's title. void ElementDocument::SetTitle(const String& _title) { title = _title; } const String& ElementDocument::GetTitle() const { return title; } const String& ElementDocument::GetSourceURL() const { return source_url; } // Sets the style sheet this document, and all of its children, uses. void ElementDocument::SetStyleSheet(StyleSheet* _style_sheet) { if (style_sheet == _style_sheet) return; if (style_sheet != NULL) style_sheet->RemoveReference(); style_sheet = _style_sheet; if (style_sheet != NULL) { style_sheet->AddReference(); style_sheet->BuildNodeIndex(); } GetStyle()->DirtyDefinition(); } // Returns the document's style sheet. StyleSheet* ElementDocument::GetStyleSheet() const { return style_sheet; } // Brings the document to the front of the document stack. void ElementDocument::PullToFront() { if (context != NULL) context->PullDocumentToFront(this); } // Sends the document to the back of the document stack. void ElementDocument::PushToBack() { if (context != NULL) context->PushDocumentToBack(this); } void ElementDocument::Show(int focus_flags) { // Store the modal attribute modal = (focus_flags & MODAL) > 0; // Set to visible and switch focus if necessary SetProperty(VISIBILITY, "visible"); if (focus_flags & FOCUS || focus_flags & MODAL) { // If no element could be focused, focus the window if (!FocusNextTabElement(this, true)) { Focus(); } } DispatchEvent("show", Dictionary(), false); } void ElementDocument::Hide() { SetProperty(VISIBILITY, "hidden"); DispatchEvent("hide", Dictionary(), false); } // Close this document void ElementDocument::Close() { if (context != NULL) context->UnloadDocument(this); } Element* ElementDocument::CreateElement(const String& name) { return Factory::InstanceElement(NULL, name, name, XMLAttributes()); } // Create a text element. ElementText* ElementDocument::CreateTextNode(const String& text) { // Create the element. Element* element = CreateElement("#text"); if (!element) { Log::Message(Log::LT_ERROR, "Failed to create text element, instancer returned NULL."); return NULL; } // Cast up ElementText* element_text = dynamic_cast< ElementText* >(element); if (!element_text) { Log::Message(Log::LT_ERROR, "Failed to create text element, instancer didn't return a derivative of ElementText."); element->RemoveReference(); return NULL; } // Set the text element_text->SetText(text); return element_text; } // Is the current document modal bool ElementDocument::IsModal() const { return modal; } // Default load script implementation void ElementDocument::LoadScript(Stream* ROCKET_UNUSED_PARAMETER(stream), const String& ROCKET_UNUSED_PARAMETER(source_name)) { ROCKET_UNUSED(stream); ROCKET_UNUSED(source_name); } // Updates the layout if necessary. void ElementDocument::_UpdateLayout() { layout_dirty = false; lock_layout++; Vector2f containing_block(0, 0); if (GetParentNode() != NULL) containing_block = GetParentNode()->GetBox().GetSize(); LayoutEngine layout_engine; layout_engine.FormatElement(this, containing_block); lock_layout--; } // Updates the position of the document based on the style properties. void ElementDocument::UpdatePosition() { // We are only positioned relative to our parent, so if we're not parented we may as well bail now. if (GetParentNode() == NULL) return; Vector2f position; // Work out our containing block; relative offsets are calculated against it. Vector2f containing_block = GetParentNode()->GetBox().GetSize(Box::CONTENT); const Property *left = GetLocalProperty(LEFT); const Property *right = GetLocalProperty(RIGHT); if (left != NULL && left->unit != Property::KEYWORD) position.x = ResolveProperty(LEFT, containing_block.x); else if (right != NULL && right->unit != Property::KEYWORD) position.x = (containing_block.x - GetBox().GetSize(Box::MARGIN).x) - ResolveProperty(RIGHT, containing_block.x); else position.x = GetBox().GetEdge(Box::MARGIN, Box::LEFT); const Property *top = GetLocalProperty(TOP); const Property *bottom = GetLocalProperty(BOTTOM); if (top != NULL && top->unit != Property::KEYWORD) position.y = ResolveProperty(TOP, containing_block.y); else if (bottom != NULL && bottom->unit != Property::KEYWORD) position.y = (containing_block.y - GetBox().GetSize(Box::MARGIN).y) - ResolveProperty(BOTTOM, containing_block.y); else position.y = GetBox().GetEdge(Box::MARGIN, Box::TOP); SetOffset(position, NULL); } void ElementDocument::LockLayout(bool lock) { if (lock) lock_layout++; else lock_layout--; ROCKET_ASSERT(lock_layout >= 0); } void ElementDocument::DirtyLayout() { layout_dirty = true; } bool ElementDocument::IsLayoutDirty() { return layout_dirty; } // Refreshes the document layout if required. void ElementDocument::OnUpdate() { UpdateLayout(); } // Repositions the document if necessary. void ElementDocument::OnPropertyChange(const PropertyNameList& changed_properties) { Element::OnPropertyChange(changed_properties); // If the document's font-size has been changed, we need to dirty all rem properties. if (changed_properties.find(FONT_SIZE) != changed_properties.end()) GetStyle()->DirtyRemProperties(); if (changed_properties.find(TOP) != changed_properties.end() || changed_properties.find(RIGHT) != changed_properties.end() || changed_properties.find(BOTTOM) != changed_properties.end() || changed_properties.find(LEFT) != changed_properties.end()) UpdatePosition(); } // Processes the 'onpropertychange' event, checking for a change in position or size. void ElementDocument::ProcessEvent(Event& event) { Element::ProcessEvent(event); // Process generic keyboard events for this window in capture phase if (event.GetPhase() == Event::PHASE_BUBBLE && event == KEYDOWN) { int key_identifier = event.GetParameter("key_identifier", Input::KI_UNKNOWN); // Process TAB if (key_identifier == Input::KI_TAB) { FocusNextTabElement(event.GetTargetElement(), !event.GetParameter("shift_key", false)); } // Process ENTER being pressed on a focusable object (emulate click) else if (key_identifier == Input::KI_RETURN || key_identifier == Input::KI_NUMPADENTER) { Element* focus_node = GetFocusLeafNode(); if (focus_node && focus_node->GetProperty(TAB_INDEX) == TAB_INDEX_AUTO) { focus_node->Click(); } } } else if (event.GetTargetElement() == this) { if (event == RESIZE) UpdatePosition(); } } // Find the next element to focus, starting at the current element // // This algorithm is quite sneaky, I originally thought a depth first search would // work, but it appears not. What is required is to cut the tree in half along the nodes // from current_element up the root and then either traverse the tree in a clockwise or // anticlock wise direction depending if you're searching forward or backward respectively bool ElementDocument::FocusNextTabElement(Element* current_element, bool forward) { // If we're searching forward, check the immediate children of this node first off if (forward) { for (int i = 0; i < current_element->GetNumChildren(); i++) if (SearchFocusSubtree(current_element->GetChild(i), forward)) return true; } // Now walk up the tree, testing either the bottom or top // of the tree, depending on whether we're going forwards // or backwards respectively // // If we make it all the way up to the document, then // we search the entire tree (to loop back round) bool search_enabled = false; Element* document = current_element->GetOwnerDocument(); Element* child = current_element; Element* parent = current_element->GetParentNode(); while (child != document) { for (int i = 0; i < parent->GetNumChildren(); i++) { // Calculate index into children int child_index = i; if (!forward) child_index = parent->GetNumChildren() - i - 1; Element* search_child = parent->GetChild(child_index); // Do a search if its enabled if (search_enabled && SearchFocusSubtree(search_child, forward)) return true; // If we find the child, enable searching if (search_child == child) search_enabled = true; } // Advance up the tree child = parent; parent = parent->GetParentNode(); // If we hit the top, enable searching the entire tree if (parent == document) search_enabled = true; else // otherwise enable searching if we're going backward and disable if we're going forward search_enabled = false; } return false; } bool ElementDocument::SearchFocusSubtree(Element* element, bool forward) { // Skip disabled elements if (element->IsPseudoClassSet("disabled")) { return false; } if (!element->IsVisible()) { return false; } // Check if this is the node we're looking for if (element->GetProperty(TAB_INDEX) == TAB_INDEX_AUTO) { element->Focus(); element->ScrollIntoView(false); return true; } // Check all children for (int i = 0; i < element->GetNumChildren(); i++) { int child_index = i; if (!forward) child_index = element->GetNumChildren() - i - 1; if (SearchFocusSubtree(element->GetChild(child_index), forward)) return true; } return false; } } }