From 218ee118c89221c5b63e253bc6388d02a9070556 Mon Sep 17 00:00:00 2001 From: "slamm%netscape.com" Date: Tue, 14 Sep 1999 23:09:28 +0000 Subject: [PATCH] First pass at new sidebar layout using xul overlay, boxes, and splitters. --- suite/common/sidebar/sidebarOverlay.css | 66 ++++++ suite/common/sidebar/sidebarOverlay.js | 211 ++++++++++++++++++ suite/common/sidebar/sidebarOverlay.xul | 27 +++ .../chrome/common/sidebar/sidebarOverlay.dtd | 3 + .../sidebar/resources/MANIFEST-content | 3 + .../sidebar/resources/MANIFEST-skin | 3 + xpfe/components/sidebar/resources/Makefile.in | 7 + .../sidebar/resources/locale/MANIFEST | 1 + .../resources/locale/en-US/sidebarOverlay.dtd | 3 + .../components/sidebar/resources/makefile.win | 7 + .../sidebar/resources/sidebarOverlay.css | 66 ++++++ .../sidebar/resources/sidebarOverlay.js | 211 ++++++++++++++++++ .../sidebar/resources/sidebarOverlay.xul | 27 +++ 13 files changed, 635 insertions(+) create mode 100644 suite/common/sidebar/sidebarOverlay.css create mode 100644 suite/common/sidebar/sidebarOverlay.js create mode 100644 suite/common/sidebar/sidebarOverlay.xul create mode 100644 suite/locales/en-US/chrome/common/sidebar/sidebarOverlay.dtd create mode 100644 xpfe/components/sidebar/resources/locale/en-US/sidebarOverlay.dtd create mode 100644 xpfe/components/sidebar/resources/sidebarOverlay.css create mode 100644 xpfe/components/sidebar/resources/sidebarOverlay.js create mode 100644 xpfe/components/sidebar/resources/sidebarOverlay.xul diff --git a/suite/common/sidebar/sidebarOverlay.css b/suite/common/sidebar/sidebarOverlay.css new file mode 100644 index 000000000000..2dc2d4d3c282 --- /dev/null +++ b/suite/common/sidebar/sidebarOverlay.css @@ -0,0 +1,66 @@ +box#sidebarbox[hidden="true"] { + display: none +} + +splitter#sidebarsplitter[hidden="true"] { + display: none +} + +box#sidebarbox +{ + border: 2px inset gray; + height: 200px; + width: 200px; + overflow: auto +} + +box#appcontent +{ + border: 2px inset gray; +} + +iframe.panelframe { + width: 100px; + height: 125px; + border: 0px; +} + +iframe.notlast +{ + height: 125px; +} + +/* + * Sidebar and Panel title buttons + */ +box#titlebox +{ + background-color: #666699; +} + +box#titlebox titledbutton +{ + color: white; +} + +splitter.panelbar +{ + padding: 0px; + border-top: 1px solid #ffffff; +} + +splitter.panelbar div +{ + padding: 5px; +} + +splitter.panelbar titledbutton +{ + padding: 0px; +} + +titledbutton.showhide +{ + list-style-image:url(chrome://sidebar/skin/showhide.gif); +} + diff --git a/suite/common/sidebar/sidebarOverlay.js b/suite/common/sidebar/sidebarOverlay.js new file mode 100644 index 000000000000..3a72988090a1 --- /dev/null +++ b/suite/common/sidebar/sidebarOverlay.js @@ -0,0 +1,211 @@ +/* -*- Mode: Java; tab-width: 2; c-basic-offset: 2 -*- + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The Original Code is ______________________________________. + * The Initial Developer of the Original Code is ________________________. + * Portions created by ______________________ are Copyright (C) ______ + * _______________________. All Rights Reserved. + * Contributor(s): ______________________________________. + * + * Alternatively, the contents of this file may be used under the terms + * of the _____ license (the ?[___] License?), in which case the + * provisions of [______] License are applicable instead of those above. + * If you wish to allow use of your version of this file only under the + * terms of the [____] License and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the [___] License. If you do not delete + * the provisions above, a recipient may use your version of this file + * under either the MPL or the [___] License. + */ + +// the rdf service +var RDF = Components.classes['component://netscape/rdf/rdf-service'].getService() +RDF = RDF.QueryInterface(Components.interfaces.nsIRDFService) + +var sidebar = new Object +sidebar.db = 'chrome://sidebar/content/sidebar.rdf' +sidebar.resource = 'NC:SidebarRoot' + +function sidebarOverlayInit() +{ + var sidebar_element = document.getElementById('sidebarbox'); + if (sidebar_element.getAttribute('hidden')) { + return + } + + var registry + try { + // First try to construct a new one and load it + // synchronously. nsIRDFService::GetDataSource() loads RDF/XML + // asynchronously by default. + var xmlsrc = 'component://netscape/rdf/datasource?name=xml-datasource' + registry = Components.classes[xmlsrc].createInstance() + registry = registry.QueryInterface(Components.interfaces.nsIRDFDataSource) + + var remote = Components.interfaces.nsIRDFRemoteDataSource + remote = registry.QueryInterface(remote) + // this will throw if it's already been opened and registered. + remote.Init(sidebar.db) + + // read it in synchronously. + remote.Refresh(true) + } + catch (ex) { + // if we get here, then the RDF/XML has been opened and read + // once. We just need to grab the datasource. + registry = RDF.GetDataSource(sidebar.db) + } + + // Create a 'container' wrapper around the sidebar.resources + // resource so we can use some utility routines that make access a + // bit easier. + var sb_datasource = Components.classes['component://netscape/rdf/container'] + sb_datasource = sb_datasource.createInstance() + var containder = Components.interfaces.nsIRDFContainer + sb_datasource = sb_datasource.QueryInterface(containder) + sb_datasource.Init(registry, RDF.GetResource(sidebar.resource)) + + var mypanelsbox = document.getElementById('sidebarbox') + + // Now enumerate all of the flash datasources. + var enumerator = sb_datasource.GetElements() + + while (enumerator.HasMoreElements()) { + var service = enumerator.GetNext() + service = service.QueryInterface(Components.interfaces.nsIRDFResource) + + var is_last = !enumerator.HasMoreElements() + var new_panel = addSidebarPanel(mypanelsbox, registry, service, is_last) + } +} + +function addSidebarPanel(parent, registry, service, is_last) { + var panel_title = getSidebarAttr(registry, service, 'title') + var panel_content = getSidebarAttr(registry, service, 'content') + var panel_height = getSidebarAttr(registry, service, 'height') + + var iframe = document.createElement('html:iframe') + + iframe.setAttribute('src', panel_content) + dump("panel_content="+panel_content+"\n") + if (is_last) { + //iframe.setAttribute('flex', '100%') + iframe.setAttribute('class','panelframe') + } else { + iframe.setAttribute('class','panelframe notlast') + } + + addSidebarPanelTitle(parent, panel_title, is_last) + parent.appendChild(iframe) +} + +function addSidebarPanelTitle(parent, titletext, is_last) +{ + var splitter = document.createElement('splitter') + splitter.setAttribute('class', 'panelbar') + splitter.setAttribute('resizeafter', 'grow') + splitter.setAttribute('collapse', 'after') + + var label = document.createElement('html:div') + var text = document.createTextNode(titletext) + label.appendChild(text) + label.setAttribute('class','panelbar') + + var spring = document.createElement('spring') + spring.setAttribute('flex','100%') + + var titledbutton = document.createElement('titledbutton') + titledbutton.setAttribute('class', 'borderless showhide') + if (!is_last) { + titledbutton.setAttribute('onclick', 'openCloseSidebarPanel(this.parentNode)') + } + + //var corner = document.createElement('html:img') + //corner.setAttribute('src', 'chrome://sidebar/skin/corner.gif') + //splitter.appendChild(corner) + + splitter.appendChild(label) + splitter.appendChild(spring) + splitter.appendChild(titledbutton) + parent.appendChild(splitter) +} + +function getSidebarAttr(registry,service,attr_name) { + var attr = registry.GetTarget(service, + RDF.GetResource('http://home.netscape.com/NC-rdf#' + attr_name), + true) + if (attr) + attr = attr.QueryInterface( + Components.interfaces.nsIRDFLiteral) + if (attr) + attr = attr.Value + return attr +} + +function openCloseSidebarPanel(splitter) { + var state = splitter.getAttribute("state") + + if (state == "" || state == "open") { + splitter.setAttribute("state", "collapsed") + } else { + splitter.setAttribute("state", "") + } +} + +function reloadSidebar() { + var titlebox = document.getElementById('titlebox') + var panel = titlebox.nextSibling + while (panel) { + var next = panel.nextSibling + panel.parentNode.removeChild(panel) + panel = next + } + sidebarOverlayInit(sidebar.db, sidebar.resource) +} + +function customizeSidebar() { + var newWin = window.openDialog('chrome://sidebar/content/customize.xul','New','chrome', sidebar.db, sidebar.resource) + return newWin +} + +function sidebarShowHide() { + var sidebar = document.getElementById('sidebarbox') + var sidebar_splitter = document.getElementById('sidebarsplitter') + var is_hidden = sidebar.getAttribute('hidden') + if (is_hidden && is_hidden == "true") { + //dump("Showing the sidebar\n") + sidebar.setAttribute('hidden','') + sidebar_splitter.setAttribute('hidden','') + sidebarOverlayInit(sidebar.db, sidebar.resource) + } else { + //dump("Hiding the sidebar\n") + sidebar.setAttribute('hidden','true') + sidebar_splitter.setAttribute('hidden','true') + } +} + +function dumpTree(node, depth) { + var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | | + " + var kids = node.childNodes + dump(indent.substr(indent.length - depth*2)) + + // Print your favorite attributes here + dump(node.nodeName) + dump(" "+node.getAttribute('id')) + dump("\n") + + for (var ii=0; ii < kids.length; ii++) { + dumpTree(kids[ii], depth + 1) + } +} + diff --git a/suite/common/sidebar/sidebarOverlay.xul b/suite/common/sidebar/sidebarOverlay.xul new file mode 100644 index 000000000000..739338ac8341 --- /dev/null +++ b/suite/common/sidebar/sidebarOverlay.xul @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/suite/locales/en-US/chrome/common/sidebar/sidebarOverlay.dtd b/suite/locales/en-US/chrome/common/sidebar/sidebarOverlay.dtd new file mode 100644 index 000000000000..48becab14665 --- /dev/null +++ b/suite/locales/en-US/chrome/common/sidebar/sidebarOverlay.dtd @@ -0,0 +1,3 @@ + + + diff --git a/xpfe/components/sidebar/resources/MANIFEST-content b/xpfe/components/sidebar/resources/MANIFEST-content index 62f322113618..86ef7c5094d6 100644 --- a/xpfe/components/sidebar/resources/MANIFEST-content +++ b/xpfe/components/sidebar/resources/MANIFEST-content @@ -13,3 +13,6 @@ sidebar-browser.rdf sidebar-browser.xul sidebar-registry.rdf sidebar.js +sidebar.rdf +sidebarOverlay.js +sidebarOverlay.xul diff --git a/xpfe/components/sidebar/resources/MANIFEST-skin b/xpfe/components/sidebar/resources/MANIFEST-skin index 118d63f68691..9630ea9de598 100644 --- a/xpfe/components/sidebar/resources/MANIFEST-skin +++ b/xpfe/components/sidebar/resources/MANIFEST-skin @@ -2,9 +2,12 @@ customize-panel.css customize.css flash.css sidebar.css +sidebarOverlay.css corner.gif flames.gif list-down-dis.gif list-down.gif list-up-dis.gif list-up.gif +showhide.gif +showhide-inv.gif diff --git a/xpfe/components/sidebar/resources/Makefile.in b/xpfe/components/sidebar/resources/Makefile.in index 49ec4f7b1bdd..001f754d52f4 100644 --- a/xpfe/components/sidebar/resources/Makefile.in +++ b/xpfe/components/sidebar/resources/Makefile.in @@ -41,6 +41,9 @@ CHROME_CONTENT = \ sidebar-browser.xul \ sidebar-registry.rdf \ sidebar.js \ + sidebar.rdf \ + sidebarOverlay.js \ + sidebarOverlay.xul \ $(NULL) CHROME_SKIN = \ @@ -48,12 +51,15 @@ CHROME_SKIN = \ customize.css \ flash.css \ sidebar.css \ + sidebarOverlay.css \ corner.gif \ flames.gif \ list-down-dis.gif \ list-down.gif \ list-up-dis.gif \ list-up.gif \ + showhide.gif \ + showhide-inv.gif \ $(NULL) CHROME_L10N = \ @@ -64,6 +70,7 @@ CHROME_L10N = \ ./locale/en-US/sidebar-browser-rdf.dtd \ ./locale/en-US/sidebar-browser.dtd \ ./locale/en-US/sidebar-registry-rdf.dtd \ + ./locale/en-US/sidebarOverlay.dtd \ $(NULL) include $(DEPTH)/config/autoconf.mk diff --git a/xpfe/components/sidebar/resources/locale/MANIFEST b/xpfe/components/sidebar/resources/locale/MANIFEST index f2e47b36644b..d82bce87add9 100644 --- a/xpfe/components/sidebar/resources/locale/MANIFEST +++ b/xpfe/components/sidebar/resources/locale/MANIFEST @@ -5,3 +5,4 @@ en-US:preview.dtd en-US:sidebar-browser-rdf.dtd en-US:sidebar-browser.dtd en-US:sidebar-registry-rdf.dtd +en-US:sidebarOverlay.dtd diff --git a/xpfe/components/sidebar/resources/locale/en-US/sidebarOverlay.dtd b/xpfe/components/sidebar/resources/locale/en-US/sidebarOverlay.dtd new file mode 100644 index 000000000000..48becab14665 --- /dev/null +++ b/xpfe/components/sidebar/resources/locale/en-US/sidebarOverlay.dtd @@ -0,0 +1,3 @@ + + + diff --git a/xpfe/components/sidebar/resources/makefile.win b/xpfe/components/sidebar/resources/makefile.win index 27b442e623c6..52883252f0df 100644 --- a/xpfe/components/sidebar/resources/makefile.win +++ b/xpfe/components/sidebar/resources/makefile.win @@ -37,6 +37,9 @@ CHROME_CONTENT = \ .\sidebar-browser.xul \ .\sidebar-registry.rdf \ .\sidebar.js \ + .\sidebar.rdf \ + .\sidebarOverlay.js \ + .\sidebarOverlay.xul \ $(NULL) CHROME_SKIN = \ @@ -44,12 +47,15 @@ CHROME_SKIN = \ .\customize.css \ .\flash.css \ .\sidebar.css \ + .\sidebarOverlay.css \ .\corner.gif \ .\flames.gif \ .\list-down-dis.gif \ .\list-down.gif \ .\list-up-dis.gif \ .\list-up.gif \ + .\showhide.gif \ + .\showhide-inv.gif \ $(NULL) CHROME_L10N = \ @@ -60,6 +66,7 @@ CHROME_L10N = \ .\locale\en-US\sidebar-browser-rdf.dtd \ .\locale\en-US\sidebar-browser.dtd \ .\locale\en-US\sidebar-registry-rdf.dtd \ + .\locale\en-US\sidebarOverlay.dtd \ $(NULL) include <$(DEPTH)\config\rules.mak> diff --git a/xpfe/components/sidebar/resources/sidebarOverlay.css b/xpfe/components/sidebar/resources/sidebarOverlay.css new file mode 100644 index 000000000000..2dc2d4d3c282 --- /dev/null +++ b/xpfe/components/sidebar/resources/sidebarOverlay.css @@ -0,0 +1,66 @@ +box#sidebarbox[hidden="true"] { + display: none +} + +splitter#sidebarsplitter[hidden="true"] { + display: none +} + +box#sidebarbox +{ + border: 2px inset gray; + height: 200px; + width: 200px; + overflow: auto +} + +box#appcontent +{ + border: 2px inset gray; +} + +iframe.panelframe { + width: 100px; + height: 125px; + border: 0px; +} + +iframe.notlast +{ + height: 125px; +} + +/* + * Sidebar and Panel title buttons + */ +box#titlebox +{ + background-color: #666699; +} + +box#titlebox titledbutton +{ + color: white; +} + +splitter.panelbar +{ + padding: 0px; + border-top: 1px solid #ffffff; +} + +splitter.panelbar div +{ + padding: 5px; +} + +splitter.panelbar titledbutton +{ + padding: 0px; +} + +titledbutton.showhide +{ + list-style-image:url(chrome://sidebar/skin/showhide.gif); +} + diff --git a/xpfe/components/sidebar/resources/sidebarOverlay.js b/xpfe/components/sidebar/resources/sidebarOverlay.js new file mode 100644 index 000000000000..3a72988090a1 --- /dev/null +++ b/xpfe/components/sidebar/resources/sidebarOverlay.js @@ -0,0 +1,211 @@ +/* -*- Mode: Java; tab-width: 2; c-basic-offset: 2 -*- + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The Original Code is ______________________________________. + * The Initial Developer of the Original Code is ________________________. + * Portions created by ______________________ are Copyright (C) ______ + * _______________________. All Rights Reserved. + * Contributor(s): ______________________________________. + * + * Alternatively, the contents of this file may be used under the terms + * of the _____ license (the ?[___] License?), in which case the + * provisions of [______] License are applicable instead of those above. + * If you wish to allow use of your version of this file only under the + * terms of the [____] License and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the [___] License. If you do not delete + * the provisions above, a recipient may use your version of this file + * under either the MPL or the [___] License. + */ + +// the rdf service +var RDF = Components.classes['component://netscape/rdf/rdf-service'].getService() +RDF = RDF.QueryInterface(Components.interfaces.nsIRDFService) + +var sidebar = new Object +sidebar.db = 'chrome://sidebar/content/sidebar.rdf' +sidebar.resource = 'NC:SidebarRoot' + +function sidebarOverlayInit() +{ + var sidebar_element = document.getElementById('sidebarbox'); + if (sidebar_element.getAttribute('hidden')) { + return + } + + var registry + try { + // First try to construct a new one and load it + // synchronously. nsIRDFService::GetDataSource() loads RDF/XML + // asynchronously by default. + var xmlsrc = 'component://netscape/rdf/datasource?name=xml-datasource' + registry = Components.classes[xmlsrc].createInstance() + registry = registry.QueryInterface(Components.interfaces.nsIRDFDataSource) + + var remote = Components.interfaces.nsIRDFRemoteDataSource + remote = registry.QueryInterface(remote) + // this will throw if it's already been opened and registered. + remote.Init(sidebar.db) + + // read it in synchronously. + remote.Refresh(true) + } + catch (ex) { + // if we get here, then the RDF/XML has been opened and read + // once. We just need to grab the datasource. + registry = RDF.GetDataSource(sidebar.db) + } + + // Create a 'container' wrapper around the sidebar.resources + // resource so we can use some utility routines that make access a + // bit easier. + var sb_datasource = Components.classes['component://netscape/rdf/container'] + sb_datasource = sb_datasource.createInstance() + var containder = Components.interfaces.nsIRDFContainer + sb_datasource = sb_datasource.QueryInterface(containder) + sb_datasource.Init(registry, RDF.GetResource(sidebar.resource)) + + var mypanelsbox = document.getElementById('sidebarbox') + + // Now enumerate all of the flash datasources. + var enumerator = sb_datasource.GetElements() + + while (enumerator.HasMoreElements()) { + var service = enumerator.GetNext() + service = service.QueryInterface(Components.interfaces.nsIRDFResource) + + var is_last = !enumerator.HasMoreElements() + var new_panel = addSidebarPanel(mypanelsbox, registry, service, is_last) + } +} + +function addSidebarPanel(parent, registry, service, is_last) { + var panel_title = getSidebarAttr(registry, service, 'title') + var panel_content = getSidebarAttr(registry, service, 'content') + var panel_height = getSidebarAttr(registry, service, 'height') + + var iframe = document.createElement('html:iframe') + + iframe.setAttribute('src', panel_content) + dump("panel_content="+panel_content+"\n") + if (is_last) { + //iframe.setAttribute('flex', '100%') + iframe.setAttribute('class','panelframe') + } else { + iframe.setAttribute('class','panelframe notlast') + } + + addSidebarPanelTitle(parent, panel_title, is_last) + parent.appendChild(iframe) +} + +function addSidebarPanelTitle(parent, titletext, is_last) +{ + var splitter = document.createElement('splitter') + splitter.setAttribute('class', 'panelbar') + splitter.setAttribute('resizeafter', 'grow') + splitter.setAttribute('collapse', 'after') + + var label = document.createElement('html:div') + var text = document.createTextNode(titletext) + label.appendChild(text) + label.setAttribute('class','panelbar') + + var spring = document.createElement('spring') + spring.setAttribute('flex','100%') + + var titledbutton = document.createElement('titledbutton') + titledbutton.setAttribute('class', 'borderless showhide') + if (!is_last) { + titledbutton.setAttribute('onclick', 'openCloseSidebarPanel(this.parentNode)') + } + + //var corner = document.createElement('html:img') + //corner.setAttribute('src', 'chrome://sidebar/skin/corner.gif') + //splitter.appendChild(corner) + + splitter.appendChild(label) + splitter.appendChild(spring) + splitter.appendChild(titledbutton) + parent.appendChild(splitter) +} + +function getSidebarAttr(registry,service,attr_name) { + var attr = registry.GetTarget(service, + RDF.GetResource('http://home.netscape.com/NC-rdf#' + attr_name), + true) + if (attr) + attr = attr.QueryInterface( + Components.interfaces.nsIRDFLiteral) + if (attr) + attr = attr.Value + return attr +} + +function openCloseSidebarPanel(splitter) { + var state = splitter.getAttribute("state") + + if (state == "" || state == "open") { + splitter.setAttribute("state", "collapsed") + } else { + splitter.setAttribute("state", "") + } +} + +function reloadSidebar() { + var titlebox = document.getElementById('titlebox') + var panel = titlebox.nextSibling + while (panel) { + var next = panel.nextSibling + panel.parentNode.removeChild(panel) + panel = next + } + sidebarOverlayInit(sidebar.db, sidebar.resource) +} + +function customizeSidebar() { + var newWin = window.openDialog('chrome://sidebar/content/customize.xul','New','chrome', sidebar.db, sidebar.resource) + return newWin +} + +function sidebarShowHide() { + var sidebar = document.getElementById('sidebarbox') + var sidebar_splitter = document.getElementById('sidebarsplitter') + var is_hidden = sidebar.getAttribute('hidden') + if (is_hidden && is_hidden == "true") { + //dump("Showing the sidebar\n") + sidebar.setAttribute('hidden','') + sidebar_splitter.setAttribute('hidden','') + sidebarOverlayInit(sidebar.db, sidebar.resource) + } else { + //dump("Hiding the sidebar\n") + sidebar.setAttribute('hidden','true') + sidebar_splitter.setAttribute('hidden','true') + } +} + +function dumpTree(node, depth) { + var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | | + " + var kids = node.childNodes + dump(indent.substr(indent.length - depth*2)) + + // Print your favorite attributes here + dump(node.nodeName) + dump(" "+node.getAttribute('id')) + dump("\n") + + for (var ii=0; ii < kids.length; ii++) { + dumpTree(kids[ii], depth + 1) + } +} + diff --git a/xpfe/components/sidebar/resources/sidebarOverlay.xul b/xpfe/components/sidebar/resources/sidebarOverlay.xul new file mode 100644 index 000000000000..739338ac8341 --- /dev/null +++ b/xpfe/components/sidebar/resources/sidebarOverlay.xul @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + +