diff options
Diffstat (limited to 'comm/suite/components/sidebar')
16 files changed, 3428 insertions, 0 deletions
diff --git a/comm/suite/components/sidebar/SuiteSidebar.manifest b/comm/suite/components/sidebar/SuiteSidebar.manifest new file mode 100644 index 0000000000..3506176b04 --- /dev/null +++ b/comm/suite/components/sidebar/SuiteSidebar.manifest @@ -0,0 +1,4 @@ +component {22117140-9c6e-11d3-aaf1-00805f8a4905} nsSidebar.js +contract @mozilla.org/sidebar;1 {22117140-9c6e-11d3-aaf1-00805f8a4905} +category JavaScript-global-property sidebar @mozilla.org/sidebar;1 +category JavaScript-global-property external @mozilla.org/sidebar;1 diff --git a/comm/suite/components/sidebar/content/PageNotFound.xul b/comm/suite/components/sidebar/content/PageNotFound.xul new file mode 100644 index 0000000000..960357f947 --- /dev/null +++ b/comm/suite/components/sidebar/content/PageNotFound.xul @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?> + +<!DOCTYPE page SYSTEM "chrome://communicator/locale/sidebar/sidebarOverlay.dtd"> + +<page xmlns ="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <description class="header">&sidebar.pagenotfound.label;</description> +</page> + diff --git a/comm/suite/components/sidebar/content/customize-panel.js b/comm/suite/components/sidebar/content/customize-panel.js new file mode 100644 index 0000000000..912619108a --- /dev/null +++ b/comm/suite/components/sidebar/content/customize-panel.js @@ -0,0 +1,43 @@ +/* -*- Mode: Java -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// the rdf service +var RDF = Cc["@mozilla.org/rdf/rdf-service;"] + .getService(Ci.nsIRDFService); + +var NC = "http://home.netscape.com/NC-rdf#"; + +var sidebarObj = new Object; +var customizeObj = new Object; + +function Init() +{ + customizeObj.id = window.arguments[0]; + customizeObj.url = window.arguments[1]; + sidebarObj.datasource_uri = window.arguments[2]; + sidebarObj.resource = window.arguments[3]; + + sidebarObj.datasource = RDF.GetDataSource(sidebarObj.datasource_uri); + + var customize_frame = document.getElementById('customize_frame'); + customize_frame.setAttribute('src', customizeObj.url); +} + +// Use an assertion to pass a "refresh" event to all the sidebars. +// They use observers to watch for this assertion (in sidebarOverlay.js). +function RefreshPanel() { + var sb_resource = RDF.GetResource(sidebarObj.resource); + var refresh_resource = RDF.GetResource(NC + "refresh_panel"); + var panel_resource = RDF.GetLiteral(customizeObj.id); + + sidebarObj.datasource.Assert(sb_resource, + refresh_resource, + panel_resource, + true); + sidebarObj.datasource.Unassert(sb_resource, + refresh_resource, + panel_resource); +} + diff --git a/comm/suite/components/sidebar/content/customize-panel.xul b/comm/suite/components/sidebar/content/customize-panel.xul new file mode 100644 index 0000000000..3fcd3908f1 --- /dev/null +++ b/comm/suite/components/sidebar/content/customize-panel.xul @@ -0,0 +1,23 @@ +<?xml version="1.0"?> <!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- --> +<!-- + + This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> + +<!DOCTYPE window> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + windowtype="navigator:browser" + onload="Init();" + onunload="RefreshPanel();;"> + + <script src="chrome://communicator/content/sidebar/customize-panel.js" /> + + <browser id="customize_frame" + type="content" + primary="true" + src="about:blank" + flex="1"/> +</window> diff --git a/comm/suite/components/sidebar/content/customize.js b/comm/suite/components/sidebar/content/customize.js new file mode 100644 index 0000000000..e8400e909c --- /dev/null +++ b/comm/suite/components/sidebar/content/customize.js @@ -0,0 +1,692 @@ +/* -*- Mode: Java; tab-width: 4; insert-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +////////////////////////////////////////////////////////////// +// Import modules +////////////////////////////////////////////////////////////// + +var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +////////////////////////////////////////////////////////////// +// Global variables +////////////////////////////////////////////////////////////// + +// Set to true for noise +const CUST_DEBUG = false; + +// the rdf service +var RDF = Cc["@mozilla.org/rdf/rdf-service;1"] + .getService(Ci.nsIRDFService); +var NC = "http://home.netscape.com/NC-rdf#"; + +var sidebarObj = new Object; +var allPanelsObj = new Object; +var original_panels = new Array(); + +////////////////////////////////////////////////////////////// +// Sidebar Init/Destroy +////////////////////////////////////////////////////////////// + +function sidebar_customize_init() +{ + allPanelsObj.datasources = window.arguments[0]; + allPanelsObj.resource = window.arguments[1]; + sidebarObj.datasource_uri = window.arguments[2]; + sidebarObj.resource = window.arguments[3]; + + debug("Init: all panels datasources = " + allPanelsObj.datasources); + debug("Init: all panels resource = " + allPanelsObj.resource); + debug("Init: sidebarObj.datasource_uri = " + sidebarObj.datasource_uri); + debug("Init: sidebarObj.resource = " + sidebarObj.resource); + + var all_panels = document.getElementById('other-panels'); + var current_panels = document.getElementById('current-panels'); + + debug("Adding observer to all panels database."); + all_panels.database.AddObserver(panels_observer); + + allPanelsObj.datasources = allPanelsObj.datasources.trim().split(/\s+/); + for (var ii = 0; ii < allPanelsObj.datasources.length; ii++) { + debug("Init: Adding "+allPanelsObj.datasources[ii]); + + // This will load the datasource, if it isn't already. + var datasource = RDF.GetDataSource(allPanelsObj.datasources[ii]); + all_panels.database.AddDataSource(datasource); + } + + // Add the datasource for current list of panels. It selects panels out + // of the other datasources. + debug("Init: Adding current panels, "+sidebarObj.datasource_uri); + sidebarObj.datasource = RDF.GetDataSource(sidebarObj.datasource_uri); + + // Root the customize dialog at the correct place. + debug("Init: reset all panels ref, "+allPanelsObj.resource); + all_panels.setAttribute('ref', allPanelsObj.resource); + + // Create a "container" wrapper around the current panels to + // manipulate the RDF:Seq more easily. + var panel_list = sidebarObj.datasource.GetTarget(RDF.GetResource(sidebarObj.resource), RDF.GetResource(NC + "panel-list"), true); + sidebarObj.container = Cc["@mozilla.org/rdf/container;1"].createInstance(Ci.nsIRDFContainer); + sidebarObj.container.Init(sidebarObj.datasource, panel_list); + + // Add all the current panels to the tree + current_panels = sidebarObj.container.GetElements(); + while (current_panels.hasMoreElements()) { + var panel = current_panels.getNext().QueryInterface(Ci.nsIRDFResource); + if (add_node_to_current_list(sidebarObj.datasource, panel) >= 0) { + original_panels.push(panel.Value); + original_panels[panel.Value] = true; + } + } + + var links = + all_panels.database.GetSources(RDF.GetResource(NC + "haslink"), + RDF.GetLiteral("true"), true); + + while (links.hasMoreElements()) { + var folder = + links.getNext().QueryInterface(Ci.nsIRDFResource); + var folder_name = folder.Value; + debug("+++ fixing up remote container " + folder_name + "\n"); + fixup_remote_container(folder_name); + } + + sizeToContent(); +} + +function sidebar_customize_destruct() +{ + var all_panels = document.getElementById('other-panels'); + debug("Removing observer from all_panels database."); + all_panels.database.RemoveObserver(panels_observer); +} + + +////////////////////////////////////////////////////////////////// +// Panels' RDF Datasource Observer +////////////////////////////////////////////////////////////////// +var panels_observer = { + onAssert : function(ds,src,prop,target) { + //debug ("observer: assert"); + // "refresh" is asserted by select menu and by customize.js. + if (prop == RDF.GetResource(NC + "link")) { + setTimeout(fixup_remote_container, 100, src.Value); + } + }, + onUnassert : function(ds,src,prop,target) { + //debug ("observer: unassert"); + }, + onChange : function(ds,src,prop,old_target,new_target) { + //debug ("observer: change"); + }, + onMove : function(ds,old_src,new_src,prop,target) { + //debug ("observer: move"); + }, + onBeginUpdateBatch : function(ds) { + //debug ("observer: onBeginUpdateBatch"); + }, + onEndUpdateBatch : function(ds) { + //debug ("observer: onEndUpdateBatch"); + } +}; + +function fixup_remote_container(id) +{ + debug('fixup_remote_container('+id+')'); + + var container = document.getElementById(id); + if (container) { + container.setAttribute('container', 'true'); + container.removeAttribute('open'); + } +} + +function fixup_children(id) { + // Add container="true" on nodes with "link" attribute + var treeitem = document.getElementById(id); + + var children = treeitem.childNodes.item(1).childNodes; + for (var ii=0; ii < children.length; ii++) { + var child = children.item(ii); + if (child.getAttribute('link') != '' && + child.getAttribute('container') != 'true') { + child.setAttribute('container', 'true'); + child.removeAttribute('open'); + } + } +} + +function get_attr(registry,service,attr_name) +{ + var attr = registry.GetTarget(service, + RDF.GetResource(NC + attr_name), + true); + if (attr) + attr = attr.QueryInterface(Ci.nsIRDFLiteral); + if (attr) + attr = attr.Value; + return attr; +} + +function SelectChangeForOtherPanels(event, target) +{ + enable_buttons_for_other_panels(); +} + +function ClickOnOtherPanels(event) +{ + var tree = document.getElementById("other-panels"); + + var rowIndex = -1; + if (event.type == "click" && event.button == 0) { + var b = tree.treeBoxObject; + var cell = b.getCellAt(event.clientX, event.clientY); + + if (cell.childElt == "twisty" || event.detail == 2) { + rowIndex = cell.row; + } + } + + if (rowIndex < 0) return; + + var treeitem = tree.contentView.getItemAtIndex(rowIndex); + var res = RDF.GetResource(treeitem.id); + + if (treeitem.getAttribute('container') == 'true') { + if (treeitem.getAttribute('open') == 'true') { + var link = treeitem.getAttribute('link'); + var loaded_link = treeitem.getAttribute('loaded_link'); + if (link != '' && !loaded_link) { + debug("Has remote datasource: "+link); + add_datasource_to_other_panels(link); + treeitem.setAttribute('loaded_link', 'true'); + } else { + setTimeout(fixup_children, 100, treeitem.getAttribute('id')); + } + } + } + + // Remove the selection in the "current" panels list + var current_panels = document.getElementById('current-panels'); + current_panels.view.selection.clearSelection(); + enable_buttons_for_current_panels(); +} + +function add_datasource_to_other_panels(link) { + // Convert the |link| attribute into a URL + var url = document.location; + debug("Current URL: " +url); + debug("Current link: " +link); + + var uri = Cc['@mozilla.org/network/standard-url;1'].createInstance(); + uri = uri.QueryInterface(Ci.nsIURI); + uri.spec = url; + uri = uri.resolve(link); + + debug("New URL: " +uri); + + // Add the datasource to the tree + var all_panels = document.getElementById('other-panels'); + all_panels.database.AddDataSource(RDF.GetDataSource(uri)); + + // XXX This is a hack to force re-display + //all_panels.setAttribute('ref', allPanelsObj.resource); +} + +// Handle a selection change in the current panels. +function SelectChangeForCurrentPanels() { + // Remove the selection in the available panels list + var all_panels = document.getElementById('other-panels'); + all_panels.view.selection.clearSelection(); + + enable_buttons_for_current_panels(); +} + +// Move the selected item up the the current panels list. +function MoveUp() { + var tree = document.getElementById('current-panels'); + if (tree.view.selection.count == 1) { + var index = tree.currentIndex; + var selected = tree.contentView.getItemAtIndex(index); + var before = selected.previousSibling; + if (before) { + selected.remove(); + before.parentNode.insertBefore(selected, before); + tree.view.selection.select(index-1); + tree.treeBoxObject.ensureRowIsVisible(index-1); + } + } +} + +// Move the selected item down the the current panels list. +function MoveDown() { + var tree = document.getElementById('current-panels'); + if (tree.view.selection.count == 1) { + var index = tree.currentIndex; + var selected = tree.contentView.getItemAtIndex(index); + if (selected.nextSibling) { + if (selected.nextSibling.nextSibling) + selected.parentNode.insertBefore(selected, selected.nextSibling.nextSibling); + else + selected.parentNode.appendChild(selected); + tree.view.selection.select(index+1); + tree.treeBoxObject.ensureRowIsVisible(index+1); + } + } +} + +function PreviewPanel() +{ + var tree = document.getElementById('other-panels'); + var database = tree.database; + var sel = tree.view.selection; + var rangeCount = sel.getRangeCount(); + for (var range = 0; range < rangeCount; ++range) { + var min = {}, max = {}; + sel.getRangeAt(range, min, max); + for (var index = min.value; index <= max.value; ++index) { + var item = tree.contentView.getItemAtIndex(index); + var res = RDF.GetResource(item.id); + + var preview_name = get_attr(database, res, 'title'); + var preview_URL = get_attr(database, res, 'content'); + if (!preview_URL || !preview_name) continue; + + window.openDialog("chrome://communicator/content/sidebar/preview.xul", + "_blank", "chrome,resizable,close,dialog=no", + preview_name, preview_URL); + } + } +} + +// Add the selected panel(s). +function AddPanel() +{ + var added = -1; + + var tree = document.getElementById('other-panels'); + var database = tree.database; + var sel = tree.view.selection; + var ranges = sel.getRangeCount(); + for (var range = 0; range < ranges; ++range) { + var min = {}, max = {}; + sel.getRangeAt(range, min, max); + for (var index = min.value; index <= max.value; ++index) { + var item = tree.contentView.getItemAtIndex(index); + if (item.getAttribute("container") != "true") { + var res = RDF.GetResource(item.id); + // Add the panel to the current list. + added = add_node_to_current_list(database, res); + } + } + } + + if (added >= 0) { + // Remove the selection in the other list. + // Selection will move to "current" list. + tree.view.selection.clearSelection(); + + var current_panels = document.getElementById('current-panels'); + current_panels.view.selection.select(added); + current_panels.treeBoxObject.ensureRowIsVisible(added); + } +} + +// Copy a panel node into a database such as the current panel list. +function add_node_to_current_list(registry, service) +{ + debug("Adding "+service.Value); + + // Copy out the attributes we want + var option_title = get_attr(registry, service, 'title'); + var option_customize = get_attr(registry, service, 'customize'); + var option_content = get_attr(registry, service, 'content'); + if (!option_title || !option_content) + return -1; + + var tree = document.getElementById('current-panels'); + var tree_root = tree.lastChild; + + // Check to see if the panel already exists... + var i = 0; + for (var treeitem = tree_root.firstChild; treeitem; treeitem = treeitem.nextSibling) { + if (treeitem.id == service.Value) + // The panel is already in the current panel list. + // Avoid adding it twice. + return i; + ++i; + } + + // Create a treerow for the new panel + var item = document.createElement('treeitem'); + var row = document.createElement('treerow'); + var cell = document.createElement('treecell'); + + // Copy over the attributes + item.setAttribute('id', service.Value); + cell.setAttribute('label', option_title); + + // Add it to the current panels tree + item.appendChild(row); + row.appendChild(cell); + tree_root.appendChild(item); + return i; +} + +// Remove the selected panel(s) from the current list tree. +function RemovePanel() +{ + var tree = document.getElementById('current-panels'); + var sel = tree.view.selection; + + var nextNode = -1; + var rangeCount = sel.getRangeCount(); + for (var range = rangeCount-1; range >= 0; --range) { + var min = {}, max = {}; + sel.getRangeAt(range, min, max); + for (var index = max.value; index >= min.value; --index) { + var item = tree.contentView.getItemAtIndex(index); + nextNode = item.nextSibling ? index : -1; + item.remove(); + } + } + + if (nextNode >= 0) + sel.select(nextNode); +} + +// Bring up a new window with the customize url +// for an individual panel. +function CustomizePanel() +{ + var tree = document.getElementById('current-panels'); + var numSelected = tree.view.selection.count; + + if (numSelected == 1) { + var index = tree.currentIndex; + var selectedNode = tree.contentView.getItemAtIndex(index); + var panel_id = selectedNode.getAttribute('id'); + var customize_url = selectedNode.getAttribute('customize'); + + debug("url = " + customize_url); + + if (!customize_url) return; + + window.openDialog('chrome://communicator/content/sidebar/customize-panel.xul', + '_blank', + 'chrome,resizable,width=690,height=600,dialog=no,close', + panel_id, + customize_url, + sidebarObj.datasource_uri, + sidebarObj.resource); + } +} + +function BrowseMorePanels() +{ + var url = ''; + var browser_url = "chrome://navigator/content/navigator.xul"; + var locale; + try { + url = Services.prefs.getCharPref("sidebar.customize.more_panels.url"); + var temp = Services.prefs.getCharPref("browser.chromeURL"); + if (temp) + browser_url = temp; + } catch(ex) { + debug("Unable to get prefs: "+ex); + } + window.openDialog(browser_url, "_blank", "chrome,all,dialog=no", url); +} + +function customize_getBrowserURL() +{ + return url; +} + +// Serialize the new list of panels. +function Save() +{ + persist_dialog_dimensions(); + + var all_panels = document.getElementById('other-panels'); + var current_panels = document.getElementById('current-panels'); + + // See if list membership has changed + var panels = []; + var tree_root = current_panels.lastChild.childNodes; + var list_unchanged = (tree_root.length == original_panels.length); + for (var i = 0; i < tree_root.length; i++) { + var panel = tree_root[i].id; + panels.push(panel); + panels[panel] = true; + if (list_unchanged && original_panels[i] != panel) + list_unchanged = false; + } + if (list_unchanged) + return; + + // Remove all the current panels from the datasource. + current_panels = sidebarObj.container.GetElements(); + while (current_panels.hasMoreElements()) { + panel = current_panels.getNext().QueryInterface(Ci.nsIRDFResource); + + // "Check if the item is one of the broadcaster panels imported to RDF from + // mainBroadcasterSet. If so, then don't remove it from datasource. + var master_list = sidebarObj.datasource.GetTarget(RDF.GetResource(allPanelsObj.resource), RDF.GetResource(NC + "panel-list"), true); + var masterSeq = Cc["@mozilla.org/rdf/container;1"] + .createInstance(Ci.nsIRDFContainer); + masterSeq.Init(sidebarObj.datasource, master_list); + var inmaster = (masterSeq.IndexOf(panel) != -1); + + if (panel.Value in panels || inmaster) { + // This panel will remain in the sidebar. + // Remove the resource, but keep all the other attributes. + // Removing it will allow it to be added in the correct order. + // Saving the attributes will preserve things such as the exclude state. + sidebarObj.container.RemoveElement(panel, false); + } else { + // Kiss it goodbye. + delete_resource_deeply(sidebarObj.container, panel); + } + } + + // Add the new list of panels + for (var ii = 0; ii < panels.length; ++ii) { + var id = panels[ii]; + var resource = RDF.GetResource(id); + if (id in original_panels) { + sidebarObj.container.AppendElement(resource); + } else { + copy_resource_deeply(all_panels.database, resource, sidebarObj.container); + } + } + refresh_all_sidebars(); + + // Write the modified panels out. + sidebarObj.datasource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush(); +} + +// Search for an element in an array +function has_element(array, element) { + for (var ii=0; ii < array.length; ii++) { + if (array[ii] == element) { + return true; + } + } + return false; +} + +// Search for targets from resource in datasource +function has_targets(datasource, resource) { + var arcs = datasource.ArcLabelsOut(resource); + return arcs.hasMoreElements(); +} + +// Use an assertion to pass a "refresh" event to all the sidebars. +// They use observers to watch for this assertion (in sidebarOverlay.js). +function refresh_all_sidebars() { + sidebarObj.datasource.Assert(RDF.GetResource(sidebarObj.resource), + RDF.GetResource(NC + "refresh"), + RDF.GetLiteral("true"), + true); + sidebarObj.datasource.Unassert(RDF.GetResource(sidebarObj.resource), + RDF.GetResource(NC + "refresh"), + RDF.GetLiteral("true")); +} + +// Remove a resource and all the arcs out from it. +function delete_resource_deeply(container, resource) { + var arcs = container.DataSource.ArcLabelsOut(resource); + while (arcs.hasMoreElements()) { + var arc = arcs.getNext(); + var targets = container.DataSource.GetTargets(resource, arc, true); + while (targets.hasMoreElements()) { + var target = targets.getNext(); + container.DataSource.Unassert(resource, arc, target, true); + } + } + container.RemoveElement(resource, false); +} + +// Copy a resource and all its arcs out to a new container. +function copy_resource_deeply(source_datasource, resource, dest_container) { + var arcs = source_datasource.ArcLabelsOut(resource); + while (arcs.hasMoreElements()) { + var arc = arcs.getNext(); + var targets = source_datasource.GetTargets(resource, arc, true); + while (targets.hasMoreElements()) { + var target = targets.getNext(); + dest_container.DataSource.Assert(resource, arc, target, true); + } + } + dest_container.AppendElement(resource); +} + +function enable_buttons_for_other_panels() +{ + var add_button = document.getElementById('add_button'); + var preview_button = document.getElementById('preview_button'); + var all_panels = document.getElementById('other-panels'); + + var sel = all_panels.view.selection; + var num_selected = sel ? sel.count : 0; + if (sel) { + var ranges = sel.getRangeCount(); + for (var range = 0; range < ranges; ++range) { + var min = {}, max = {}; + sel.getRangeAt(range, min, max); + for (var index = min; index <= max; ++index) { + var node = all_panels.contentView.getItemAtIndex(index); + if (node.getAttribute('container') != 'true') { + ++num_selected; + } + } + } + } + + if (num_selected > 0) { + add_button.removeAttribute('disabled'); + preview_button.removeAttribute('disabled'); + } else { + add_button.setAttribute('disabled','true'); + preview_button.setAttribute('disabled','true'); + } +} + +function enable_buttons_for_current_panels() { + var up = document.getElementById('up'); + var down = document.getElementById('down'); + var tree = document.getElementById('current-panels'); + var customize = document.getElementById('customize-button'); + var remove = document.getElementById('remove-button'); + + var numSelected = tree.view.selection.count; + var canMoveUp = false, canMoveDown = false, customizeURL = ''; + + if (numSelected == 1 && tree.view.selection.isSelected(tree.currentIndex)) { + var selectedNode = tree.view.getItemAtIndex(tree.currentIndex); + customizeURL = selectedNode.getAttribute('customize'); + canMoveUp = selectedNode != selectedNode.parentNode.firstChild; + canMoveDown = selectedNode != selectedNode.parentNode.lastChild; + } + + up.disabled = !canMoveUp; + down.disabled = !canMoveDown; + customize.disabled = !customizeURL; + remove.disabled = !numSelected; +} + +function persist_dialog_dimensions() { + // Stole this code from navigator.js to + // insure the windows dimensions are saved. + + // Get the current window position/size. + var x = window.screenX; + var y = window.screenY; + var h = window.outerHeight; + var w = window.outerWidth; + + // Store these into the window attributes (for persistence). + var win = document.getElementById( "main-window" ); + win.setAttribute( "x", x ); + win.setAttribute( "y", y ); + win.setAttribute( "height", h ); + win.setAttribute( "width", w ); +} + +/////////////////////////////////////////////////////////////// +// Handy Debug Tools +////////////////////////////////////////////////////////////// +var debug = null; +var dump_attributes = null; +var dump_tree = null; +var _dump_tree_recur = null; + +if (!CUST_DEBUG) { + debug = function (s) {}; + dump_attributes = function (node, depth) {}; + dump_tree = function (node) {}; + _dump_tree_recur = function (node, depth, index) {}; +} else { + debug = function (s) { dump("-*- sb customize: " + s + "\n"); }; + + dump_attributes = function (node, depth) { + var attributes = node.attributes; + var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | . "; + + if (!attributes || attributes.length == 0) { + debug(indent.substr(indent.length - depth*2) + "no attributes"); + } + for (var ii=0; ii < attributes.length; ii++) { + var attr = attributes.item(ii); + debug(indent.substr(indent.length - depth*2) + attr.name + + "=" + attr.value); + } + } + dump_tree = function (node) { + _dump_tree_recur(node, 0, 0); + } + _dump_tree_recur = function (node, depth, index) { + if (!node) { + debug("dump_tree: node is null"); + } + var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | + "; + debug(indent.substr(indent.length - depth*2) + index + + " " + node.nodeName); + if (node.nodeType != Node.TEXT_NODE) { + dump_attributes(node, depth); + } + var kids = node.childNodes; + for (var ii=0; ii < kids.length; ii++) { + _dump_tree_recur(kids[ii], depth + 1, ii); + } + } +} + +////////////////////////////////////////////////////////////// +// Install the load/unload handlers +////////////////////////////////////////////////////////////// +addEventListener("load", sidebar_customize_init, false); +addEventListener("unload", sidebar_customize_destruct, false); diff --git a/comm/suite/components/sidebar/content/customize.xul b/comm/suite/components/sidebar/content/customize.xul new file mode 100644 index 0000000000..0e76f65445 --- /dev/null +++ b/comm/suite/components/sidebar/content/customize.xul @@ -0,0 +1,137 @@ +<?xml version="1.0"?> <!-- -*- Mode: HTML; indent-tabs-mode: nil; -*- --> +<!-- + + This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://communicator/skin/sidebar/customize.css" + type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % customizeDTD SYSTEM "chrome://communicator/locale/sidebar/customize.dtd" > +%customizeDTD; +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > +%brandDTD; +]> + +<dialog + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + id="main-window" + title="&sidebar.customize.title;" + windowtype="sidebar:customize" + height="400" + persist="screenX screenY width height" + buttons="accept,cancel,extra2" + spacerflex="1" + buttonlabelextra2="&sidebar.more.label;" + buttonaccesskeyextra2="&sidebar.more.accesskey;" + ondialogextra2="BrowseMorePanels();" + ondialogaccept="return Save();"> + + <script src="chrome://communicator/content/sidebar/customize.js"/> + + <hbox flex="1"> + <vbox flex="1"> + <label accesskey="&sidebar.customize.additional.accesskey;" + control="other-panels" value="&sidebar.customize.additional.label;" + crop="right"/> + + <tree id="other-panels" flex="1" + datasources="rdf:null" hidecolumnpicker="true" + containment="http://home.netscape.com/NC-rdf#panel-list" + onselect="SelectChangeForOtherPanels(event, event.target.parentNode.parentNode);" + onclick="if (event.detail == 2) { AddPanel(); } ClickOnOtherPanels(event);"> + + <template> + <rule> + <conditions> + <content uri="?uri"/> + <triple subject="?uri" object="?panel-list" + predicate="http://home.netscape.com/NC-rdf#panel-list"/> + <member container="?panel-list" child="?panel"/> + </conditions> + + <bindings> + <binding subject="?panel" object="?title" + predicate="http://home.netscape.com/NC-rdf#title"/> + <binding subject="?panel" object="?link" + predicate="http://home.netscape.com/NC-rdf#link"/> + </bindings> + + <action> + <treechildren> + <treeitem uri="?panel" link="?link"> + <treerow> + <treecell label="?title"/> + </treerow> + </treeitem> + </treechildren> + </action> + </rule> + </template> + + <treecols> + <treecol id="AvailNameCol" flex="1" primary="true" hideheader="true"/> + </treecols> + </tree> + + <!-- xxxslamm Need to add descriptive panel text here --> + <hbox class="button-group"> + <button id="add_button" oncommand="AddPanel()" + label="&sidebar.customize.add.label;" + accesskey="&sidebar.customize.add.accesskey;" disabled="true"/> + + <button id="preview_button" oncommand="PreviewPanel()" + label="&sidebar.customize.preview.label;" + accesskey="&sidebar.customize.preview.accesskey;" + disabled="true"/> + </hbox> + </vbox> + + <separator orient="vertical"/> + + <!-- The panels that the user currently has chosen --> + <vbox flex="1"> + <label value="&sidebar.customize.current2.label;" + accesskey="&sidebar.customize.current2.accesskey;" + control="current-panels" crop="right"/> + <tree id="current-panels" flex="1" hidecolumnpicker="true" + onselect="SelectChangeForCurrentPanels();"> + <treecols> + <treecol id="CurrentNameCol" flex="1" hideheader="true"/> + </treecols> + + <treechildren/> + </tree> + + <hbox class="button-group"> + <button id="customize-button" oncommand="CustomizePanel();" + label="&sidebar.customize.customize.label;" disabled="true" + accesskey="&sidebar.customize.customize.accesskey;"/> + <button id="remove-button" oncommand="RemovePanel()" + label="&sidebar.customize.remove.label;" disabled="true" + accesskey="&sidebar.customize.remove.accesskey;"/> + </hbox> + </vbox> + + <separator orient="vertical" class="thin"/> + + <!-- The 'reorder' buttons --> + <vbox id="reorder"> + <spacer flex="1"/> + <button oncommand="MoveUp();" id="up" class="up" + disabled="true" label="&sidebar.customize.up.label;" + accesskey="&sidebar.customize.up.accesskey;"/> + <button oncommand="MoveDown();" id="down" class="down" + disabled="true" label="&sidebar.customize.down.label;" + accesskey="&sidebar.customize.down.accesskey;"/> + <spacer flex="1"/> + </vbox> + + </hbox> + +</dialog> + diff --git a/comm/suite/components/sidebar/content/preview.js b/comm/suite/components/sidebar/content/preview.js new file mode 100644 index 0000000000..c5238beacd --- /dev/null +++ b/comm/suite/components/sidebar/content/preview.js @@ -0,0 +1,15 @@ +/* -*- Mode: Java -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function Init() +{ + var panel_name = window.arguments[0]; + var panel_URL = window.arguments[1]; + + var panel_title = document.getElementById('paneltitle'); + var preview_frame = document.getElementById('previewframe'); + panel_title.setAttribute('label', panel_name); + preview_frame.setAttribute('src', panel_URL); +} diff --git a/comm/suite/components/sidebar/content/preview.xul b/comm/suite/components/sidebar/content/preview.xul new file mode 100644 index 0000000000..ef2434c278 --- /dev/null +++ b/comm/suite/components/sidebar/content/preview.xul @@ -0,0 +1,30 @@ +<?xml version="1.0"?> <!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- --> +<!-- + + This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://communicator/skin/sidebar/sidebar.css" + type="text/css"?> +<?xml-stylesheet href="chrome://communicator/skin/sidebar/preview.css" + type="text/css"?> + +<!DOCTYPE window SYSTEM "chrome://communicator/locale/sidebar/preview.dtd" > + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="Init();" + title="&sidebar.preview.title.label;"> + + <script src="chrome://communicator/content/sidebar/preview.js" /> + + <vbox id="panel-container" flex="1"> + + <hbox id="paneltitle" class="box-texttab texttab-sidebar" selected="true"/> + <!-- <iframe id="previewframe" type="content" src="about:blank" flex="1"/>--> + <iframe class="box-panel" id="previewframe" type="content" src="about:blank" flex="1"/> + + </vbox> + +</window> diff --git a/comm/suite/components/sidebar/content/sidebarBindings.xml b/comm/suite/components/sidebar/content/sidebarBindings.xml new file mode 100644 index 0000000000..675d591957 --- /dev/null +++ b/comm/suite/components/sidebar/content/sidebarBindings.xml @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<bindings id="globalBindings" + xmlns="http://www.mozilla.org/xbl" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:xbl="http://www.mozilla.org/xbl"> + + <binding id="texttab"> + <content> + <xul:image class="box-texttab-left"/> + <xul:vbox class="box-texttab-text-container" xbl:inherits="value" flex="1"> + <xul:spacer flex="1"/> + <xul:label class="box-texttab-text" xbl:inherits="value=label" crop="right"/> + <xul:spacer flex="1"/> + </xul:vbox> + <xul:image class="box-texttab-right"/> + <xul:spacer class="box-texttab-right-space"/> + </content> + </binding> + + <binding id="sidebar-header-box" extends="xul:box"> + <content align="center"> + <xul:label class="sidebar-header-text" xbl:inherits="value=label,crop" crop="right" flex="1"/> + <xul:box> + <children/> + </xul:box> + </content> + </binding> + +</bindings> diff --git a/comm/suite/components/sidebar/content/sidebarOverlay.css b/comm/suite/components/sidebar/content/sidebarOverlay.css new file mode 100644 index 0000000000..0d4df5f16a --- /dev/null +++ b/comm/suite/components/sidebar/content/sidebarOverlay.css @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** sidebarOverlay.css [CONTENT] + * This file is for style rules essential for correct sidebar operation. + * These rules will not change on a skin-by-skin basis. + **/ + +#sidebar-box { + width: 162px; + min-height: 10px; + min-width: 30px; + max-width: 400px; +} + +#sidebar-panels { + min-width: 1px; + min-height: 10px; +} + +.iframe-panel { + min-width: 1px; + min-height: 1px; +} + +#sidebar-iframe-no-panels { + min-width: 1px; + min-height: 1px; + overflow: auto; +} + +.browser-sidebar { + min-width: 1px; + min-height: 1px; +} + +/* + * Sidebar and Panel title buttons + */ +sidebarheader[type="box"] { + -moz-binding: url(chrome://communicator/content/sidebar/sidebarBindings.xml#sidebar-header-box); +} +sidebarheader[type="splitter"] { + -moz-binding: url(chrome://communicator/content/sidebar/sidebarBindings.xml#sidebar-header-splitter); + /* a vertical splitter */ + cursor: n-resize; +} + +.sidebarheader-main { + min-width: 1px; + min-height: 1px; +} + +/** + * texttab folder lookalike e.g. for sidebar panel headers + */ + .box-texttab + { + min-height : 10px; + min-width : 10px; + } + + .box-texttab-right-space + { + min-width : 1px; + } + +/** + * prevent the notification in the sidebar from being too wide + */ +.sidebar-notificationbox > notification { + -moz-binding: url(chrome://communicator/content/bindings/notification.xml#sidebar-notification); +} + +.sidebar-notificationbox > notification[value="addon-progress"] { + -moz-binding: url(chrome://communicator/content/bindings/notification.xml#sidebar-addon-progress-notification); +} diff --git a/comm/suite/components/sidebar/content/sidebarOverlay.js b/comm/suite/components/sidebar/content/sidebarOverlay.js new file mode 100644 index 0000000000..95b0af029d --- /dev/null +++ b/comm/suite/components/sidebar/content/sidebarOverlay.js @@ -0,0 +1,1704 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// xul/id summary: +// +// <box> sidebar-box +// <splitter> sidebar-panels-splitter +// <box> sidebar-panels-splitter-box* +// <sidebarheader> sidebar-title-box +// <menubutton> sidebar-panel-picker* +// <menupopup> sidebar-panel-picker-popup +// <box> sidebar-panels +// <template> sidebar-template* +// <box> sidebar-iframe-no-panels +// <splitter> sidebar-splitter +// <menupopup> menu_View_Popup* +// <menuitem> sidebar-menu + +////////////////////////////////////////////////////////////// +// Import modules +////////////////////////////////////////////////////////////// + +var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +////////////////////////////////////////////////////////////// +// Global variables +////////////////////////////////////////////////////////////// + + +var gCurFrame; +var gTimeoutID = null; +var gMustInit = true; +var gAboutToUncollapse = false; +var gCheckMissingPanels = true; + +function setBlank() +{ + gTimeoutID = null; + gCurFrame.setAttribute('src', 'chrome://communicator/content/sidebar/PageNotFound.xul'); +} + + +// Uncomment for debug output +const SB_DEBUG = false; + +// pref for limiting number of tabs in view +// initialized in sidebar_overlay_init() +var gNumTabsInViewPref; + +// The rdf service +var RDF = Cc["@mozilla.org/rdf/rdf-service;1"] + .getService(Ci.nsIRDFService); + +const NC = "http://home.netscape.com/NC-rdf#"; + +// The directory services property to find panels.rdf +const PANELS_RDF_FILE = "UPnls"; +const SIDEBAR_VERSION = "0.1"; + +// The default sidebar: +var sidebarObj = new Object; +sidebarObj.never_built = true; + +////////////////////////////////////////////////////////////////////// +// sbPanelList Class +// +// Wrapper around DOM representation of the sidebar. This UI event +// handlers and the sidebar datasource observers call into this. This +// class is responsible for keeping the DOM state of the sidebar up to +// date. +// This class does not make any changes to the sidebar rdf datasource. +////////////////////////////////////////////////////////////////////// + +function sbPanelList(container_id) +{ + debug("sbPanelList("+container_id+")"); + this.node = document.getElementById(container_id); + this.childNodes = this.node.childNodes; + this.initialized = false; // set after first display of tabs +} + +sbPanelList.prototype.get_panel_from_id = +function (id) +{ + debug("get_panel_from_id(" + id + ")"); + var index = 0; + var header = null; + if (id && id != '') { + for (var ii=2; ii < this.node.childNodes.length; ii += 2) { + header = this.node.childNodes.item(ii); + if (header.getAttribute('id') == id) { + debug("get_panel_from_id: Found at index, " + ii); + index = ii; + break; + } + } + } + if (index > 0) { + return new sbPanel(id, header, index); + } else { + return null; + } +} + +sbPanelList.prototype.get_panel_from_header_node = +function (node) +{ + return this.get_panel_from_id(node.getAttribute('id')); +} + +sbPanelList.prototype.get_panel_from_header_index = +function (index) +{ + return this.get_panel_from_header_node(this.node.childNodes.item(index)); +} + +sbPanelList.prototype.find_first = +function (panels) +{ + debug("pick_default_panel: length=" + this.node.childNodes.length); + for (var ii = 2; ii < this.node.childNodes.length; ii += 2) { + var panel = this.get_panel_from_header_index(ii); + if (!panel.is_excluded() && panel.is_in_view()) { + return panel; + } + } + return null; +} + +sbPanelList.prototype.find_last = +function (panels) +{ + debug("pick_default_panel: length=" + this.node.childNodes.length); + for (var ii=(this.node.childNodes.length - 1); ii >= 2; ii -= 2) { + var panel = this.get_panel_from_header_index(ii); + if (!panel.is_excluded() && panel.is_in_view()) { + return panel; + } + } + return null; +} + +sbPanelList.prototype.visible_panels_exist = +function () +{ + var i; + var panels = this.node.childNodes; + for (i = 2; i < panels.length; i += 2) + { + if (!panels.item(i).hidden) + return true; + } + return false; +} + +sbPanelList.prototype.num_panels_included = +function () +{ + var count = 0; + var panels = this.node.childNodes; + for (var i = 2; i < panels.length; i += 2) + { + var curr = this.get_panel_from_header_index(i); + if (!curr.is_excluded()) + count++; + } + return count; +} + +sbPanelList.prototype.num_panels_in_view = +function () +{ + var count = 0; + var panels = this.node.childNodes; + for (var i = 2; i < panels.length; i += 2) + { + var curr = this.get_panel_from_header_index(i); + if (curr.is_in_view()) + count++; + } + return count; +} + +sbPanelList.prototype.select = +function (panel, force_reload) +{ + if (!force_reload && panel.is_selected()) { + return; + } + // select(): Open this panel and possibly reload it. + if (this.node.getAttribute('last-selected-panel') != panel.id) { + // "last-selected-panel" is used as a global variable. + // this.update() will reference "last-selected-panel". + // This way the value can be persisted in xulstore.json. + this.node.setAttribute('last-selected-panel', panel.id); + } + this.update(force_reload); +} + +sbPanelList.prototype.exclude = +function (panel) +{ + if (this.node.getAttribute('last-selected-panel') == panel.id) { + this.select_default_panel(); + } else { + this.update(false); + } +} + + +sbPanelList.prototype.select_default_panel = +function () +{ + var default_panel = null + + // First, check the XUL for the "defaultpanel" attribute of "sidebar-box". + var sidebar_container = document.getElementById('sidebar-box'); + var content_default_id = sidebar_container.getAttribute('defaultpanel'); + if (content_default_id != '') { + var content = sidebarObj.panels.get_panel_from_id(content_default_id); + if (content && !content.is_excluded() && content.is_in_view()) { + default_panel = content; + } + } + + // Second, try to use the panel persisted in 'last-selected-panel'. + if (!default_panel) { + var last_selected_id = this.node.getAttribute('last-selected-panel'); + if (last_selected_id != '') { + var last = sidebarObj.panels.get_panel_from_id(last_selected_id); + if (last && !last.is_excluded() && last.is_in_view()) { + default_panel = last; + } + } + } + + // Finally, just use the last one in the list. + if (!default_panel) { + default_panel = this.find_last(); + } + + if (default_panel) { + this.node.setAttribute('last-selected-panel', default_panel.id); + } + this.update(false); +} + +sbPanelList.prototype.refresh = +function () +{ + var last_selected_id = this.node.getAttribute('last-selected-panel'); + var last_selected = sidebarObj.panels.get_panel_from_id(last_selected_id); + if (last_selected && last_selected.is_selected()) { + // The desired panel is already selected + this.update(false); + } else { + this.select_default_panel(); + } +} + +// panel_loader(): called from a timer that is set in sbPanelList.update() +// Removes the "Loading..." screen when the panel has finished loading. +function panel_loader() { + debug("panel_loader()"); + + if (gTimeoutID != null) { + clearTimeout(gTimeoutID); + gTimeoutID = null; + } + + this.removeEventListener("load", panel_loader, true); + this.removeAttribute('collapsed'); + // uncollapse the notificationbox element + this.parentNode.removeAttribute('collapsed'); + // register a progress listener for the notificationbox, + // now that this browser has a docShell + this.parentNode.addProgressListener(); + this.setAttribute('loadstate', 'loaded'); + // hide the load area + this.parentNode.parentNode.firstChild.setAttribute('hidden', 'true'); + + if (this.hasAttribute('focusOnLoad')) { + var elementToFocus = this.contentDocument.documentElement.getAttribute('elementtofocus'); + if (elementToFocus) { + var element = this.contentDocument.getElementById(elementToFocus); + if (element) + element.focus(); + else + dump(elementToFocus + ' element was not found to focus!\n'); + } else { + this.contentWindow.focus(); + } + this.removeAttribute('focusOnLoad'); + } +} +sbPanelList.prototype.update = +function (force_reload) +{ + // This function requires that the attribute 'last-selected-panel' + // holds the id of a non-excluded panel. If it doesn't, no panel will + // be selected. The attribute is used instead of a function + // parameter to allow the value to be persisted in xulstore.json. + var selected_id = this.node.getAttribute('last-selected-panel'); + + if (sidebar_is_collapsed()) { + sidebarObj.collapsed = true; + } else { + sidebarObj.collapsed = false; + } + + var num_included = sidebarObj.panels.num_panels_included(); + if (num_included > gNumTabsInViewPref) + document.getElementById("nav-buttons-box").hidden = false; + else + document.getElementById("nav-buttons-box").hidden = true; + + var have_set_top = 0; + var have_set_after_selected = 0; + var is_after_selected = 0; + var last_header = 0; + var num_in_view = 0; + debug("this.initialized: " + this.initialized); + for (var ii=2; ii < this.node.childNodes.length; ii += 2) { + var header = this.node.childNodes.item(ii); + var content = this.node.childNodes.item(ii+1); + var id = header.getAttribute('id'); + var panel = new sbPanel(id, header, ii); + var excluded = panel.is_excluded(); + var in_view = false; + if (!this.initialized) + { + if (num_in_view < gNumTabsInViewPref) + in_view = true; + } + else + { + if (header.getAttribute("in-view") == "true") + in_view = true; + } + if (excluded || !in_view) + { + debug("item("+ii/2+") excluded: " + excluded + + " in view: " + in_view); + header.setAttribute('hidden','true'); + content.setAttribute('hidden','true'); + if (!in_view) + { + header.setAttribute("in-view", false); + header.removeAttribute("top-panel"); + header.removeAttribute("last-panel"); + } + } else { + // only set if in view + if (!this.initialized || (num_in_view < gNumTabsInViewPref)) + last_header = header; + header.removeAttribute('last-panel'); + // only set if in view + if (!have_set_top && + (!this.initialized || (header.getAttribute("in-view") == "true"))) + { + header.setAttribute('top-panel','true'); + have_set_top = 1; + } else { + header.removeAttribute('top-panel'); + } + if (!have_set_after_selected && is_after_selected) { + header.setAttribute('first-panel-after-selected','true'); + have_set_after_selected = 1 + } else { + header.removeAttribute('first-panel-after-selected'); + } + header.removeAttribute('hidden'); + header.setAttribute("in-view", true); + num_in_view++; + + // (a) when we have hit the maximum number of tabs that can be in view and no tab + // has been selected yet + // -or- + // (b) when we have reached the last tab we are about to display + if ( ((num_in_view == num_included) || + (num_in_view == gNumTabsInViewPref)) && + !is_after_selected ) + { + selected_id = id; + this.node.setAttribute('last-selected-panel', id); + } + + // Pick sandboxed, or unsandboxed iframe + var iframe = panel.get_iframe(); + var notificationbox = iframe.parentNode; + var load_state; + + if (selected_id == id) { + is_after_selected = 1 + debug("item("+ii/2+") selected"); + header.setAttribute('selected', 'true'); + content.removeAttribute('hidden'); + content.removeAttribute('collapsed'); + + if (sidebarObj.collapsed && panel.is_sandboxed()) { + if (!panel.is_persistent()) { + debug(" set src=about:blank"); + iframe.setAttribute('src', 'about:blank'); + } + } else { + var saved_src = iframe.getAttribute('content'); + var src = iframe.getAttribute('src'); + // either we have been requested to force_reload or the + // panel src has changed so we must restore the original src + if (force_reload || (saved_src != src)) { + debug(" set src="+saved_src); + iframe.setAttribute('src', saved_src); + + if (gTimeoutID != null) + clearTimeout(gTimeoutID); + + gCurFrame = iframe; + gTimeoutID = setTimeout(setBlank, 20000); + } + } + + load_state = content.getAttribute('loadstate'); + if (load_state == 'stopped') { + load_state = 'never loaded'; + toggleLoadarea(content); + } + if (load_state == 'never loaded') { + iframe.removeAttribute('hidden'); + iframe.setAttribute('loadstate', 'loading'); + iframe.addEventListener('load', panel_loader, true); + } + } else { + debug("item("+ii/2+")"); + header.removeAttribute('selected'); + content.setAttribute('collapsed','true'); + + if (!panel.is_persistent()) { + iframe.setAttribute('src', 'about:blank'); + load_state = content.getAttribute('loadstate'); + if (load_state == 'loading') { + iframe.removeEventListener("load", panel_loader, true); + content.setAttribute('hidden','true'); + iframe.setAttribute('loadstate', 'never loaded'); + } + } + } + } + } + if (last_header) { + last_header.setAttribute('last-panel','true'); + } + + var no_panels_iframe = document.getElementById('sidebar-iframe-no-panels'); + if (have_set_top) { + no_panels_iframe.setAttribute('hidden','true'); + // The hide and show of 'sidebar-panels' should not be needed, + // but some old profiles may have this persisted as hidden (50973). + this.node.removeAttribute('hidden'); + } else { + no_panels_iframe.removeAttribute('hidden'); + } + + this.initialized = true; +} + + +////////////////////////////////////////////////////////////////////// +// sbPanel Class +// +// Like sbPanelList, this class is a wrapper around DOM representation +// of individual panels in the sidebar. This UI event handlers and the +// sidebar datasource observers call into this. This class is +// responsible for keeping the DOM state of the sidebar up to date. +// This class does not make any changes to the sidebar rdf datasource. +////////////////////////////////////////////////////////////////////// + +function sbPanel(id, header, index) +{ + // This constructor should only be called by sbPanelList class. + // To create a panel instance, use the helper functions in sbPanelList: + // sb_panel.get_panel_from_id(id) + // sb_panel.get_panel_from_header_node(dom_node) + // sb_panel.get_panel_from_header_index(index) + this.id = id; + this.header = header; + this.index = index; + this.parent = sidebarObj.panels; +} + +sbPanel.prototype.get_header = +function () +{ + return this.header; +} + +sbPanel.prototype.get_content = +function () +{ + return this.get_header().nextSibling; +} + +sbPanel.prototype.is_sandboxed = +function () +{ + if (typeof this.sandboxed == "undefined") { + var notificationbox = this.get_content().childNodes.item(1); + var unsandboxed_iframe = notificationbox.firstChild; + this.sandboxed = !unsandboxed_iframe.getAttribute('content').match(/^chrome:/); + } + return this.sandboxed; +} + +sbPanel.prototype.get_iframe = +function () +{ + if (typeof this.iframe == "undefined") { + var notificationbox = this.get_content().childNodes.item(1); + this.iframe = this.is_sandboxed() ? notificationbox.lastChild : + notificationbox.firstChild; + } + return this.iframe; +} + +// This exclude function is used on panels and on the panel picker menu. +// That is why it is hanging out in the global name space instead of +// minding its own business in the class. +function sb_panel_is_excluded(node) +{ + var exclude = node.getAttribute('exclude'); + return ( exclude && exclude != '' && + exclude.includes(sidebarObj.component)); +} +sbPanel.prototype.is_excluded = +function () +{ + return sb_panel_is_excluded(this.get_header()); +} + +sbPanel.prototype.is_in_view = +function() +{ + return (this.header.getAttribute("in-view") == "true"); +} + +sbPanel.prototype.is_selected = +function (panel_id) +{ + return 'true' == this.get_header().getAttribute('selected'); +} + +sbPanel.prototype.is_persistent = +function () +{ + var rv = false; + var datasource = sidebarObj.datasource; + var persistNode = datasource.GetTarget(RDF.GetResource(this.id), + RDF.GetResource(NC + "persist"), + true); + if (persistNode) + { + persistNode = + persistNode.QueryInterface(Ci.nsIRDFLiteral); + rv = persistNode.Value == 'true'; + } + + return rv; +} + +sbPanel.prototype.select = +function (force_reload) +{ + this.parent.select(this, force_reload); +} + +sbPanel.prototype.stop_load = +function () +{ + var iframe = this.get_iframe(); + var content = this.get_content(); + var load_state = iframe.getAttribute('loadstate'); + if (load_state == "loading") { + debug("Stop the presses"); + iframe.removeEventListener("load", panel_loader, true); + content.setAttribute("loadstate", "stopped"); + iframe.setAttribute('src', 'about:blank'); + toggleLoadarea(content); + } +} + +function toggleLoadarea(content) +{ + // toggle between "loading" and "load stopped" in the UI + var widgetBox = content.firstChild.firstChild; + var widgetBoxKids = widgetBox.childNodes; + var stopButton = widgetBoxKids.item(3); + var reloadButton = widgetBoxKids.item(4); + var loadingImage = widgetBox.firstChild; + var loadingText = loadingImage.nextSibling; + var loadStoppedText = loadingText.nextSibling; + + // sanity check + if (stopButton.getAttribute("type") != "stop") + { + debug("Error: Expected button of type=\"stop\" but didn't get one!"); + return; + } + + if (!stopButton.hidden) + { + // change button from "stop" to "reload" + stopButton.hidden = "true"; + reloadButton.removeAttribute("hidden"); + + // hide the loading image and set text to "load stopped" + loadingImage.hidden = "true"; + loadingText.hidden = "true"; + loadStoppedText.removeAttribute("hidden"); + } + else + { + // change button from "reload" to "stop" + stopButton.removeAttribute("hidden"); + reloadButton.hidden = "true"; + + // show the loading image and set text to "loading" + loadingImage.removeAttribute("hidden"); + loadingText.removeAttribute("hidden"); + loadStoppedText.hidden = "true"; + } +} + +sbPanel.prototype.exclude = +function () +{ + // Exclusion is handled by the datasource, + // but we need to make sure this panel is no longer selected. + this.get_header().removeAttribute('selected'); + this.parent.exclude(this); +} + +sbPanel.prototype.reload = +function () +{ + if (!this.is_excluded()) { + this.select(true); + } +} + +////////////////////////////////////////////////////////////////// +// Panels' RDF Datasource Observer +// +// This observer will ensure that the Sidebar UI stays current +// when the datasource changes. +// - When "refresh" is asserted, the sidebar refreshed. +// Currently this happens when a panel is included/excluded or +// added/removed (the later comes from the customize dialog). +// - When "refresh_panel" is asserted, the targeted panel is reloaded. +// Currently this happens when the customize panel dialog is closed. +////////////////////////////////////////////////////////////////// +var panel_observer = { + onAssert : function(ds,src,prop,target) { + //debug ("observer: assert"); + // "refresh" is asserted by select menu and by customize.js. + if (prop == RDF.GetResource(NC + "refresh")) { + sidebarObj.panels.initialized = false; // reset so panels are put in view + sidebarObj.panels.refresh(); + } else if (prop == RDF.GetResource(NC + "refresh_panel")) { + var panel_id = target.QueryInterface(Ci.nsIRDFLiteral).Value; + var panel = sidebarObj.panels.get_panel_from_id(panel_id); + panel.reload(); + } + }, + onUnassert : function(ds,src,prop,target) { + //debug ("observer: unassert"); + }, + onChange : function(ds,src,prop,old_target,new_target) { + //debug ("observer: change"); + }, + onMove : function(ds,old_src,new_src,prop,target) { + //debug ("observer: move"); + }, + onBeginUpdateBatch : function(ds) { + //debug ("observer: onBeginUpdateBatch"); + }, + onEndUpdateBatch : function(ds) { + //debug ("observer: onEndUpdateBatch"); + } +}; + +// Use an assertion to pass a "refresh" event to all the sidebars. +// They use observers to watch for this assertion (see above). +function refresh_all_sidebars() { + sidebarObj.datasource.Assert(RDF.GetResource(sidebarObj.resource), + RDF.GetResource(NC + "refresh"), + RDF.GetLiteral("true"), + true); + sidebarObj.datasource.Unassert(RDF.GetResource(sidebarObj.resource), + RDF.GetResource(NC + "refresh"), + RDF.GetLiteral("true")); +} + +////////////////////////////////////////////////////////////// +// Sidebar Init +////////////////////////////////////////////////////////////// +function sidebar_overlay_init() { + if (sidebar_is_collapsed() && !gAboutToUncollapse) + return; + gMustInit = false; + sidebarObj.panels = new sbPanelList('sidebar-panels'); + sidebarObj.datasource_uri = get_sidebar_datasource_uri(); + sidebarObj.datasource = RDF.GetDataSourceBlocking(sidebarObj.datasource_uri); + sidebarObj.resource = 'urn:sidebar:current-panel-list'; + + sidebarObj.master_datasources = sidebarObj.datasource_uri; + sidebarObj.master_resource = 'urn:sidebar:master-panel-list'; + sidebarObj.component = gPrivate ? "navigator:browser" : + document.documentElement.getAttribute('windowtype'); + debug("sidebarObj.component is " + sidebarObj.component); + + // Sync RDF with broadcasters. + SidebarBroadcastersToRDF(); + + // Initialize the display + var sidebar_element = document.getElementById('sidebar-box'); + var sidebar_menuitem = document.getElementById('sidebar-menu'); + if (sidebar_is_hidden()) { + if (sidebar_menuitem) { + sidebar_menuitem.setAttribute('checked', 'false'); + } + } else { + if (sidebar_menuitem) { + sidebar_menuitem.setAttribute('checked', 'true'); + } + + // for old profiles that don't persist the hidden attribute when splitter is not hidden. + var sidebar_splitter = document.getElementById('sidebar-splitter') + if (sidebar_splitter) + sidebar_splitter.setAttribute('hidden', 'false'); + + if (sidebarObj.never_built) { + sidebarObj.never_built = false; + + debug("sidebar = " + sidebarObj); + debug("sidebarObj.resource = " + sidebarObj.resource); + debug("sidebarObj.datasource_uri = " + sidebarObj.datasource_uri); + + // Obtain the pref for limiting the number of tabs in view, defaults to 8. + gNumTabsInViewPref = Services.prefs.getIntPref("sidebar.num_tabs_in_view", 8); + + // Show the header for the panels area. Use a splitter if there + // is stuff over the panels area. + var sidebar_panels_splitter = document.getElementById('sidebar-panels-splitter'); + if (sidebar_element.firstChild != sidebar_panels_splitter) { + debug("Showing the panels splitter"); + sidebar_panels_splitter.removeAttribute('hidden'); + } + } + if (sidebar_is_collapsed()) { + sidebarObj.collapsed = true; + } else { + sidebarObj.collapsed = false; + } + + sidebar_open_default_panel(100, 0); + } +} + +function sidebar_overlay_destruct() { + var panels = document.getElementById('sidebar-panels'); + debug("Removing observer from database."); + panels.database.RemoveObserver(panel_observer); +} + +var gBusyOpeningDefault = false; + +function sidebar_open_default_panel(wait, tries) { + // check for making function reentrant + if (gBusyOpeningDefault) + return; + gBusyOpeningDefault = true; + + var ds = sidebarObj.datasource; + var currentListRes = RDF.GetResource("urn:sidebar:current-panel-list"); + var panelListRes = RDF.GetResource("http://home.netscape.com/NC-rdf#panel-list"); + var container = ds.GetTarget(currentListRes, panelListRes, true); + if (container) { + // Add the user's current panel choices to the template builder, + // which will aggregate it with the other datasources that describe + // the individual panel's title, customize URL, and content URL. + var panels = document.getElementById('sidebar-panels'); + panels.database.AddDataSource(ds); + + debug("Adding observer to database."); + panels.database.AddObserver(panel_observer); + + // XXX This is a hack to force re-display + panels.builder.rebuild(); + } else { + if (tries < 3) { + // No children yet, try again later + setTimeout(sidebar_open_default_panel, wait, wait*2, ++tries); + gBusyOpeningDefault = false; + return; + } else { + sidebar_fixup_datasource(); + } + } + + sidebarObj.panels.refresh(); + gBusyOpeningDefault = false; + if (gCheckMissingPanels) + check_for_missing_panels(); +} + +function SidebarRebuild() { + sidebarObj.panels.initialized = false; // reset so panels are brought in view + var panels = document.getElementById("sidebar-panels"); + panels.builder.rebuild(); + sidebar_open_default_panel(100, 0); +} + +function check_for_missing_panels() { + var tabs = sidebarObj.panels.node.childNodes; + var currHeader; + var currTab; + for (var i = 2; i < tabs.length; i += 2) { + currHeader = tabs[i]; + currTab = new sbPanel(currHeader.getAttribute("id"), currHeader, i); + if (!currTab.is_excluded()) { + if (currHeader.hasAttribute("prereq") && currHeader.getAttribute("prereq") != "") { + var prereq_file = currHeader.getAttribute("prereq"); + var channel = + Services.io.newChannelFromURI(Services.io.newURI(prereq_file), + null, + Services.scriptSecurityManager.getSystemPrincipal(), + null, + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); + try { + channel.open(); + } + catch (ex) { + if (ex.result != Cr.NS_ERROR_FILE_NOT_FOUND) { + throw ex; + } + sidebarObj.datasource.Assert(RDF.GetResource(currHeader.getAttribute("id")), + RDF.GetResource(NC + "exclude"), + RDF.GetLiteral(sidebarObj.component), + true); + currTab.exclude(); + } + } + } + } + gCheckMissingPanels = false; +} + +////////////////////////////////////////////////////////////// +// Sidebar File and Datasource functions +////////////////////////////////////////////////////////////// + +function sidebar_get_panels_file() { + try { + // Use the fileLocator to look in the profile directory to find + // 'panels.rdf', which is the database of the user's currently + // selected panels. + // If <profile>/panels.rdf doesn't exist, GetFileLocation() will copy + // bin/defaults/profile/panels.rdf to <profile>/panels.rdf + var sidebar_file = GetSpecialDirectory(PANELS_RDF_FILE); + if (!sidebar_file.exists()) { + // This should not happen, as GetFileLocation() should copy + // defaults/panels.rdf to the users profile directory + debug("Sidebar panels file does not exist"); + throw("Panels file does not exist"); + } + return sidebar_file; + } catch (ex) { + // This should not happen + debug("Error: Unable to grab panels file.\n"); + throw(ex); + } + return null; +} + +function sidebar_revert_to_default_panels() { + try { + var sidebar_file = sidebar_get_panels_file(); + + sidebar_file.remove(false); + + // Since we just removed the panels file, + // this should copy the defaults over. + sidebar_file = sidebar_get_panels_file(); + + debug("sidebar defaults reloaded"); + var datasource = sidebarObj.datasource; + datasource.QueryInterface(Ci.nsIRDFRemoteDataSource).Refresh(true); + } catch (ex) { + debug("Error: Unable to reload panel defaults file.\n"); + } + return null; +} + +function get_sidebar_datasource_uri() { + try { + var sidebar_file = sidebar_get_panels_file(); + + var fileHandler = Services.io.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler); + + return fileHandler.getURLSpecFromFile(sidebar_file); + } catch (ex) { + // This should not happen + debug("Error: Unable to load panels file.\n"); + } + return null; +} + +function sidebar_fixup_datasource() { + var datasource = sidebarObj.datasource; + var resource = RDF.GetResource(sidebarObj.resource); + + var panel_list = datasource.GetTarget(resource, + RDF.GetResource(NC+"panel-list"), + true); + if (!panel_list) { + debug("Sidebar datasource is an old format or busted\n"); + sidebar_revert_to_default_panels(); + } else { + // The datasource is ok, but it just has no panels. + // sidebar_refresh() will display some helper content. + // Do nothing here. + } +} + +////////////////////////////////////////////////////////////// +// Sidebar Interface for XUL +////////////////////////////////////////////////////////////// + +// Change the sidebar content to the selected panel. +// Called when a panel title is clicked. +function SidebarSelectPanel(header, should_popopen, should_unhide) { + debug("SidebarSelectPanel("+header+","+should_popopen+","+should_unhide+")"); + var panel = sidebarObj.panels.get_panel_from_header_node(header); + + if (!panel) { + return false; + } + + var popopen = false; + var unhide = false; + + if (panel.is_excluded()) { + return false; + } + if (sidebar_is_hidden()) { + if (should_unhide) { + unhide = true; + } else { + return false; + } + } + if (sidebar_is_collapsed()) { + if (should_popopen) { + popopen = true; + } else { + return false; + } + } + if (unhide) SidebarShowHide(); + if (popopen) SidebarExpandCollapse(); + + try { + panel.get_iframe().setAttribute('focusOnLoad', true); + } catch (ex) { + // ignore exception for cases where content isn't built yet + // e.g., auto opening search tab: we don't want to focus search field + } + if (!panel.is_selected()) panel.select(false); + + return true; +} + +function SidebarGetLastSelectedPanel() +{ + return (sidebarObj.panels && + sidebarObj.panels.node.getAttribute('last-selected-panel')); +} + +function SidebarGetRelativePanel(direction) +{ + // direction == 1 to view next panel, -1 to view prev panel + + if (sidebar_is_hidden()) + SidebarShowHide(); + if (sidebar_is_collapsed()) + SidebarExpandCollapse(); + + var currentPanel = sidebarObj.panels.get_panel_from_id(SidebarGetLastSelectedPanel()); + if (!currentPanel) { + sidebarObj.panels.select_default_panel(); + return; + } + + var newPanel = currentPanel; + + do { + var newPanelIndex = newPanel.index + (direction * 2); + if (newPanelIndex < 2 || newPanelIndex >= sidebarObj.panels.node.childNodes.length) + newPanel = (direction == 1)? sidebarObj.panels.find_first(): sidebarObj.panels.find_last(); + else + newPanel = sidebarObj.panels.get_panel_from_header_index(newPanelIndex); + + if (!newPanel) + break; + + if (!newPanel.is_excluded()) { + SidebarSelectPanel(newPanel.header, true, true); // found a panel that's not excluded to select -- do it + break; + } + } while (newPanel != currentPanel); // keep looking for a panel, but don't loop infinitely +} + +function SidebarStopPanelLoad(header) { + var panel = sidebarObj.panels.get_panel_from_header_node(header); + panel.stop_load(); +} + +function SidebarReloadPanel(header) { + var panel = sidebarObj.panels.get_panel_from_header_node(header); + panel.reload(); +} + +// No one is calling this right now. +function SidebarReload() { + sidebarObj.panels.refresh(); +} + +// Set up a lame hack to avoid opening two customize +// windows on a double click. +var gDisableCustomize = false; +function enable_customize() { + gDisableCustomize = false; +} + +// Bring up the Sidebar customize dialog. +function SidebarCustomize() { + // Use a single sidebar customize dialog + var customizeWindow = Services.wm.getMostRecentWindow('sidebar:customize'); + + if (customizeWindow) { + debug("Reuse existing customize dialog"); + customizeWindow.focus(); + } else { + debug("Open a new customize dialog"); + + if (false == gDisableCustomize) { + debug("First time creating customize dialog"); + gDisableCustomize = true; + + var panels = document.getElementById('sidebar-panels'); + + customizeWindow = window.openDialog( + 'chrome://communicator/content/sidebar/customize.xul', + '_blank','centerscreen,chrome,resizable,dialog=no,dependent', + sidebarObj.master_datasources, + sidebarObj.master_resource, + sidebarObj.datasource_uri, + sidebarObj.resource); + setTimeout(enable_customize, 2000); + } + } +} + +function BrowseMorePanels() +{ + var url = ''; + var browser_url = "chrome://navigator/content/navigator.xul"; + var locale; + try { + url = Services.prefs.getCharPref("sidebar.customize.directory.url"); + var temp = Services.prefs.getCharPref("browser.chromeURL"); + if (temp) + browser_url = temp; + } catch(ex) { + debug("Unable to get prefs: "+ex); + } + window.openDialog(browser_url, "_blank", "chrome,all,dialog=no", url); +} + + + +function sidebar_is_collapsed() { + var sidebar_splitter = document.getElementById('sidebar-splitter'); + return (sidebar_splitter && + sidebar_splitter.getAttribute('state') == 'collapsed'); +} + +function SidebarExpandCollapse() { + var sidebar_splitter = document.getElementById('sidebar-splitter'); + var sidebar_box = document.getElementById('sidebar-box'); + if (sidebar_splitter.getAttribute('state') == 'collapsed') { + if (gMustInit) + sidebar_overlay_init(); + debug("Expanding the sidebar"); + sidebar_splitter.removeAttribute('state'); + sidebar_box.removeAttribute('collapsed'); + SidebarSetButtonOpen(true); + } else { + debug("Collapsing the sidebar"); + sidebar_splitter.setAttribute('state', 'collapsed'); + sidebar_box.setAttribute('collapsed', 'true'); + SidebarSetButtonOpen(false); + } +} + +// sidebar_is_hidden() - Helper function for SidebarShowHide(). +function sidebar_is_hidden() { + var sidebar_title = document.getElementById('sidebar-title-box'); + var sidebar_box = document.getElementById('sidebar-box'); + return sidebar_box.getAttribute('hidden') == 'true' + || sidebar_title.getAttribute('hidden') == 'true'; +} + +// Show/Hide the entire sidebar. +// Invoked by the "View / Sidebar" menu option. +function SidebarShowHide() { + var sidebar_box = document.getElementById('sidebar-box'); + var title_box = document.getElementById('sidebar-title-box'); + var sidebar_panels_splitter = document.getElementById('sidebar-panels-splitter'); + var sidebar_panels_splitter_box = document.getElementById('sidebar-panels-splitter-box'); + var sidebar_splitter = document.getElementById('sidebar-splitter'); + var sidebar_menu_item = document.getElementById('sidebar-menu'); + var tabs_menu = document.getElementById('sidebar-panel-picker'); + + if (sidebar_is_hidden()) { + debug("Showing the sidebar"); + + // for older profiles: + sidebar_box.setAttribute('hidden', 'false'); + sidebar_panels_splitter_box.setAttribute('hidden', 'false'); + + sidebar_box.removeAttribute('collapsed'); + if (sidebar_splitter.getAttribute('state') == 'collapsed') + sidebar_splitter.removeAttribute('state'); + title_box.removeAttribute('hidden'); + sidebar_panels_splitter_box.removeAttribute('collapsed'); + sidebar_splitter.setAttribute('hidden', 'false'); + if (sidebar_box.firstChild != sidebar_panels_splitter) { + debug("Showing the panels splitter"); + sidebar_panels_splitter.removeAttribute('hidden'); + if (sidebar_panels_splitter.getAttribute('state') == 'collapsed') + sidebar_panels_splitter.removeAttribute('state'); + } + sidebar_overlay_init(); + sidebar_menu_item.setAttribute('checked', 'true'); + tabs_menu.removeAttribute('hidden'); + SidebarSetButtonOpen(true); + } else { + debug("Hiding the sidebar"); + var hide_everything = sidebar_panels_splitter.getAttribute('hidden') == 'true'; + if (hide_everything) { + debug("Hide everything"); + sidebar_box.setAttribute('collapsed', 'true'); + sidebar_splitter.setAttribute('hidden', 'true'); + } else { + sidebar_panels_splitter.setAttribute('hidden', 'true'); + } + title_box.setAttribute('hidden', 'true'); + sidebar_panels_splitter_box.setAttribute('collapsed', 'true'); + sidebar_menu_item.setAttribute('checked', 'false'); + tabs_menu.setAttribute('hidden', 'true'); + SidebarSetButtonOpen(false); + } + // Immediately save persistent values + document.persist('sidebar-title-box', 'hidden'); + PersistWidth(); + window.content.focus(); +} + +function SidebarGetState() { + if (sidebar_is_hidden()) + return "hidden"; + if (sidebar_is_collapsed()) + return "collapsed"; + return "visible"; +} + +function SidebarSetState(aState) { + document.getElementById("sidebar-box").hidden = aState != "visible"; + document.getElementById("sidebar-splitter").hidden = aState == "hidden"; +} + +function SidebarBuildPickerPopup() { + var menu = document.getElementById('sidebar-panel-picker-popup'); + menu.database.AddDataSource(sidebarObj.datasource); + menu.builder.rebuild(); + + for (var ii=3; ii < menu.childNodes.length; ii++) { + var panel_menuitem = menu.childNodes.item(ii); + if (sb_panel_is_excluded(panel_menuitem)) { + debug(ii+": "+panel_menuitem.getAttribute('label')+ ": excluded; uncheck."); + panel_menuitem.removeAttribute('checked'); + } else { + debug(ii+": "+panel_menuitem.getAttribute('label')+ ": included; check."); + panel_menuitem.setAttribute('checked', 'true'); + } + } +} + +function SidebarTogglePanel(panel_menuitem) { + if (!panel_menuitem.classList.contains("menuitem-sidebar") && + !panel_menuitem.classList.contains("texttab-sidebar")) + return; + + // Create a "container" wrapper around the current panels to + // manipulate the RDF:Seq more easily. + + var did_exclude = false; + var panel_id = panel_menuitem.getAttribute('id'); + var panel = sidebarObj.panels.get_panel_from_id(panel_id); + var panel_exclude = panel_menuitem.getAttribute('exclude') + if (panel_exclude == '') { + // Nothing excluded for this panel yet, so add this component to the list. + debug("Excluding " + panel_id + " from " + sidebarObj.component); + sidebarObj.datasource.Assert(RDF.GetResource(panel_id), + RDF.GetResource(NC + "exclude"), + RDF.GetLiteral(sidebarObj.component), + true); + panel.exclude(); + did_exclude = true; + } else { + // Panel has an exclude string, but it may or may not have the + // current component listed in the string. + debug("Current exclude string: " + panel_exclude); + var new_exclude = panel_exclude; + if (sb_panel_is_excluded(panel_menuitem)) { + debug("Plucking this component out of the exclude list"); + var replace_pat = new RegExp(sidebarObj.component + "\s*"); + new_exclude = new_exclude.replace(replace_pat, "").trimLeft(); + // did_exclude remains false + } else { + debug("Adding this component to the exclude list"); + new_exclude = new_exclude + " " + sidebarObj.component; + panel.exclude(); + did_exclude = true; + } + if (new_exclude == '') { + debug("Removing exclude list"); + sidebarObj.datasource.Unassert(RDF.GetResource(panel_id), + RDF.GetResource(NC + "exclude"), + RDF.GetLiteral(sidebarObj.component)); + } else { + debug("New exclude string: " + new_exclude); + exclude_target = + sidebarObj.datasource.GetTarget(RDF.GetResource(panel_id), + RDF.GetResource(NC + "exclude"), + true); + sidebarObj.datasource.Change(RDF.GetResource(panel_id), + RDF.GetResource(NC + "exclude"), + exclude_target, + RDF.GetLiteral(new_exclude)); + } + } + + var tabs = sidebarObj.panels.node.childNodes; + + if (did_exclude) + { + // if we excluded a tab in view then add another one + if (panel.is_in_view()) + { + // we excluded one so let's try to bring a non-excluded one into view + var newFirst = null; + var added = false; + for (var i = 2; i < tabs.length ; i += 2) + { + var currTab = sidebarObj.panels.get_panel_from_header_index(i); + var hasPotential = !currTab.is_excluded() && !currTab.is_in_view(); + + // set potential new first tab in case we can't find one after the + // tab that was just excluded + if (!newFirst && hasPotential) + newFirst = currTab; + + if (i > panel.index && hasPotential) + { + currTab.header.setAttribute("in-view", true); + added = true; + break; + } + } + if (!added && newFirst) + newFirst.header.setAttribute("in-view", true); + + // lose it from current view + panel.header.setAttribute("in-view", false); + } + } + else + { + panel.header.setAttribute("in-view", true); + + // if we have one too many tabs we better get rid of an old one + if (sidebarObj.panels.num_panels_in_view() > gNumTabsInViewPref) + { + // we included a new tab so let's take the last one out of view + for (i = 2; i < tabs.length; i += 2) + { + var currHeader = tabs[i]; + if (currHeader.hasAttribute("last-panel")) + currHeader.setAttribute("in-view", false); + } + } + + panel.select(false); + } + + if (did_exclude && !sidebarObj.panels.visible_panels_exist()) + // surrender focus to main content area + window.content.focus(); + else + // force all the sidebars to update + refresh_all_sidebars(); + + // Write the modified panels out. + sidebarObj.datasource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush(); +} + +function SidebarNavigate(aDirection) +{ + debug("SidebarNavigate " + aDirection); + + var tabs = sidebarObj.panels.node.childNodes; + var i; + var currHeader; + var currTab; + // move forward a tab (down in the template) + if (aDirection > 0) + { + // ensure we have a tab below the last one + var foundLast = false; + var oldFirst = null; + for (i = 2; i < tabs.length; i += 2) + { + currHeader = tabs[i]; + currTab = new sbPanel(currHeader.getAttribute("id"), currHeader, i); + + if (!currTab.is_excluded()) + { + if (foundLast) + { + debug("toggling old first and new last"); + debug("new last: " + currHeader.getAttribute("id")); + debug("old first: " + oldFirst.getAttribute("id")); + currHeader.setAttribute("in-view", true); + oldFirst.setAttribute("in-view", false); + + // if old first was selected select new first instead + if (oldFirst.getAttribute("id") == + sidebarObj.panels.node.getAttribute("last-selected-panel")) + { + sidebarObj.panels.node.setAttribute('last-selected-panel', + currTab.id); + } + + break; + } + + if (!foundLast && currHeader.hasAttribute("last-panel")) + { + debug("found last"); + foundLast = true; + } + + // set the old first in case we find a new last below + // the old last and need to toggle the new first's ``in-view'' + if (!oldFirst && currTab.is_in_view()) + oldFirst = currHeader; + } + } + } + + // move back a tab (up in the template) + else if (aDirection < 0) + { + var newFirst = null, newLast = null; + var foundFirst = false; + for (i = 2; i < tabs.length; i += 2) + { + currHeader = tabs[i]; + currTab = new sbPanel(currHeader.getAttribute("id"), currHeader, i); + + if (!currTab.is_excluded()) + { + if (!foundFirst && currHeader.hasAttribute("top-panel")) + { + debug("found first"); + foundFirst = true; + } + if (!foundFirst) + { + debug("setting newFirst"); + newFirst = currHeader; + } + + if (currHeader.hasAttribute("last-panel")) + { + debug("found last"); + + // ensure we have a tab above the first one + if (newFirst) + { + debug("toggling new first and old last"); + debug("new first: " + newFirst.getAttribute("id")); + debug("old last: " + currHeader.getAttribute("id")); + + newFirst.setAttribute("in-view", true); + currHeader.setAttribute("in-view", false); // hide old last + + // if old last was selected, now select one above it + if (sidebarObj.panels.node.getAttribute("last-selected-panel") == + currTab.id) + { + sidebarObj.panels.node.setAttribute("last-selected-panel", + newLast.getAttribute("id")); + } + + break; + } + } + if (currTab.is_in_view()) + newLast = currHeader; + } + } + } + + if (aDirection) + sidebarObj.panels.update(false); +} + +////////////////////////////////////////////////////////////// +// Sidebar Hacks and Work-arounds +////////////////////////////////////////////////////////////// + +// SidebarCleanUpExpandCollapse() - Respond to grippy click. +function SidebarCleanUpExpandCollapse() { + // XXX Mini hack. Persist isn't working too well. Force the persist, + // but wait until the change has commited. + if (gMustInit) { + gAboutToUncollapse = true; + sidebar_overlay_init(); + } + + setTimeout(Persist, 100, "sidebar-box", "collapsed"); + setTimeout(() => sidebarObj.panels.refresh(), 100); +} + +function PersistHeight() { + // XXX Mini hack. Persist isn't working too well. Force the persist, + // but wait until the last drag has been committed. + // May want to do something smarter here like only force it if the + // height has really changed. + setTimeout(Persist, 100, "sidebar-panels-splitter-box", "height"); +} + +function PersistWidth() { + // XXX Mini hack. Persist isn't working too well. Force the persist, + // but wait until the width change has commited. Also see bug 16516. + setTimeout(Persist, 100, "sidebar-box", "width"); + + var is_collapsed = document.getElementById("sidebar-box") + .getAttribute("collapsed") == "true"; + SidebarSetButtonOpen(!is_collapsed); +} + +function Persist(aAttribute, aValue) { + document.persist(aAttribute, aValue); +} + +function SidebarFinishClick() { + PersistWidth(); + + var is_collapsed = document.getElementById('sidebar-box').getAttribute('collapsed') == 'true'; + debug("collapsed: " + is_collapsed); + if (is_collapsed != sidebarObj.collapsed) { + if (gMustInit) + sidebar_overlay_init(); + } +} + +function SidebarSetButtonOpen(aSidebarNowOpen) +{ + // change state so toolbar icon can be updated + var pt = document.getElementById("PersonalToolbar"); + if (pt) { + pt.setAttribute("prefixopen", aSidebarNowOpen); + + // set tooltip for toolbar icon + var header = document.getElementById("sidebar-title-box"); + var tooltip = header.getAttribute(aSidebarNowOpen ? + "tooltipclose" : "tooltipopen"); + pt.setAttribute("prefixtooltip", tooltip); + } +} + +function SidebarInitContextMenu(aMenu, aPopupNode) +{ + var panel = sidebarObj.panels.get_panel_from_header_node(aPopupNode); + var switchItem = document.getElementById("switch-ctx-item"); + var reloadItem = document.getElementById("reload-ctx-item"); + var stopItem = document.getElementById("stop-ctx-item"); + + // the current panel can be reloaded, but other panels are not showing + // any content, so we only allow you to switch to other panels + if (panel.is_selected()) + { + switchItem.setAttribute("collapsed", "true"); + reloadItem.removeAttribute("disabled"); + } + else + { + switchItem.removeAttribute("collapsed"); + reloadItem.setAttribute("disabled", "true"); + } + + // only if a panel is currently loading enable the ``Stop'' item + if (panel.get_iframe().getAttribute("loadstate") == "loading") + stopItem.removeAttribute("disabled"); + else + stopItem.setAttribute("disabled", "true"); +} + +/////////////////////////////////////////////////////////////// +// Handy Debug Tools +////////////////////////////////////////////////////////////// +var debug = null; +var dump_attributes = null; +var dump_tree = null; +if (!SB_DEBUG) { + debug = function (s) {}; + dump_attributes = function (node, depth) {}; + dump_tree = function (node) {}; + var _dump_tree_recur = function (node, depth, index) {}; +} else { + debug = function (s) { dump("-*- sbOverlay: " + s + "\n"); }; + + dump_attributes = function (node, depth) { + var attributes = node.attributes; + var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | . "; + + if (!attributes || attributes.length == 0) { + debug(indent.substr(indent.length - depth*2) + "no attributes"); + } + for (var ii=0; ii < attributes.length; ii++) { + var attr = attributes.item(ii); + debug(indent.substr(indent.length - depth*2) + attr.name + + "=" + attr.value); + } + } + dump_tree = function (node) { + _dump_tree_recur(node, 0, 0); + } + _dump_tree_recur = function (node, depth, index) { + if (!node) { + debug("dump_tree: node is null"); + } + var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | + "; + debug(indent.substr(indent.length - depth*2) + index + + " " + node.nodeName); + if (node.nodeType != Node.TEXT_NODE) { + dump_attributes(node, depth); + } + var kids = node.childNodes; + for (var ii=0; ii < kids.length; ii++) { + _dump_tree_recur(kids[ii], depth + 1, ii); + } + } +} + +function SidebarBroadcastersToRDF() +{ + // Only the broadcasters in browser are synced to panels.rdf + if (sidebarObj.component != "navigator:browser") + return; + + // Translation rules to translate between new broadcaster id and old RDF id. + const TRANSLATE = {viewBookmarksSidebar: "bookmarks", + viewHistorySidebar: "history", + viewSearchSidebar: "search", + viewAddressbookSidebar: "addressbook"}; + const URN_PREFIX = "urn:sidebar:panel:"; + + const RDFCU = Cc['@mozilla.org/rdf/container-utils;1'] + .getService(Ci.nsIRDFContainerUtils); + + /* + * Initialize RDF stuff. + */ + let ds = sidebarObj.datasource; + let panelListRes = RDF.GetResource(NC + "panel-list"); + + let currentListRes = RDF.GetResource(sidebarObj.resource); + let masterListRes = RDF.GetResource(sidebarObj.master_resource); + let currentTarget = ds.GetTarget(currentListRes, panelListRes, true); + let masterTarget = ds.GetTarget(masterListRes, panelListRes, true); + if (!masterTarget) { + // No "master-panel-list" found, so create it. + masterTarget = RDF.GetAnonymousResource(); + ds.Assert(masterListRes, panelListRes, masterTarget, true); + } + let currentSeq = RDFCU.MakeSeq(ds, currentTarget); + let masterSeq = RDFCU.MakeSeq(ds, masterTarget); + + /* + * Run over broadcasters in browser window and add/update RDF entries + * based on them. + */ + let titleRes = RDF.GetResource(NC + "title"); + let urlRes = RDF.GetResource(NC + "content"); + + let bset = document.getElementById("mainBroadcasterSet"); + let broadcasters = bset.getElementsByTagName("broadcaster"); + let bclist = {}; + for (let bId = 0; bId < broadcasters.length; bId++) { + let curBC = broadcasters[bId]; + let title = curBC.getAttribute("sidebartitle") || curBC.getAttribute("label"); + let url = curBC.getAttribute("sidebarurl"); + let bcid = (curBC.id in TRANSLATE) ? TRANSLATE[curBC.id] : curBC.id; + + if (!url || !title || !bcid) + continue; + + // This one is needed later to check for obsolete sidebars. + bclist[bcid] = 1; + + let panelRes = RDF.GetResource(URN_PREFIX + bcid); + + // Literals of values that should be in RDF. + let titleLit = RDF.GetLiteral(title); + let urlLit = RDF.GetLiteral(url); + // Literals of values that are in RDF. + let curtitleLit = ds.GetTarget(panelRes, titleRes, true); + let cururlLit = ds.GetTarget(panelRes, urlRes, true); + + // If the item doesn't already exist, create it. + if (!curtitleLit && !cururlLit) { + ds.Assert(panelRes, titleRes, titleLit, true); + ds.Assert(panelRes, urlRes, urlLit, true); + masterSeq.AppendElement(panelRes); + if (currentSeq.IndexOf(panelRes) == -1) + currentSeq.AppendElement(panelRes); + } + // Item already exists, but perhaps we need to update... + else { + let curtitle = curtitleLit.QueryInterface(Ci.nsIRDFLiteral).Value; + let cururl = cururlLit.QueryInterface(Ci.nsIRDFLiteral).Value; + + if (curtitle != title) + ds.Change(panelRes, titleRes, curtitleLit, titleLit); + + if (cururl != url) + ds.Change(panelRes, urlRes, cururlLit, urlLit); + } + } + + /* + * Do the same the other way around to delete obsolete sidebars. + */ + + let masterElements = masterSeq.GetElements(); + while (masterElements.hasMoreElements()) { + let curElementRes = masterElements.getNext(); + let curId = curElementRes.QueryInterface(Ci.nsIRDFResource).Value; + + if (curId.substr(0, URN_PREFIX.length) != URN_PREFIX) + continue; + + curId = curId.substr(URN_PREFIX.length); + if (!(curId in bclist)) { + let properties = ds.ArcLabelsOut(curElementRes); + while(properties.hasMoreElements()) { + let propertyRes = properties.getNext(); + let valueLit = ds.GetTarget(curElementRes, propertyRes, true); + ds.Unassert(curElementRes, propertyRes, valueLit); + } + masterSeq.RemoveElement(curElementRes, true); + if (currentSeq.IndexOf(curElementRes) != -1) + currentSeq.RemoveElement(curElementRes, true); + } + } + + // Write modified data. + sidebarObj.datasource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush(); +} + + +////////////////////////////////////////////////////////////// +// Install the load/unload handlers +////////////////////////////////////////////////////////////// +addEventListener("load", sidebar_overlay_init, false); +addEventListener("unload", sidebar_overlay_destruct, false); diff --git a/comm/suite/components/sidebar/content/sidebarOverlay.xul b/comm/suite/components/sidebar/content/sidebarOverlay.xul new file mode 100644 index 0000000000..0c1aa08566 --- /dev/null +++ b/comm/suite/components/sidebar/content/sidebarOverlay.xul @@ -0,0 +1,247 @@ +<?xml version="1.0"?> <!-- -*- Mode: HTML; indent-tabs-mode: nil -*- --> +<!-- + + This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<!-- This overlay requires that the files it overlays has the menupopup + contentAreaContextMenu defined for context menus to work correctly in + certain custom tabs --> + +<?xml-stylesheet href="chrome://communicator/content/sidebar/sidebarOverlay.css" type="text/css"?> +<?xml-stylesheet href="chrome://communicator/skin/sidebar/sidebar.css" type="text/css"?> + +<!DOCTYPE overlay [ +<!ENTITY % sidebarOverlayDTD SYSTEM "chrome://communicator/locale/sidebar/sidebarOverlay.dtd" > +%sidebarOverlayDTD; +]> + +<overlay id="sidebarOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <!-- Overlay of broadcasterset to get our panels in --> + <broadcasterset id="mainBroadcasterSet"> + <broadcaster id="viewBookmarksSidebar" + autoCheck="false" + type="checkbox" + group="sidebar" + sidebartitle="&sidebar.client-bookmarks.label;" + sidebarurl="chrome://communicator/content/bookmarks/bookmarksPanel.xul" + oncommand="toggleSidebar('viewBookmarksSidebar');"/> + <broadcaster id="viewHistorySidebar" + autoCheck="false" + type="checkbox" + group="sidebar" + sidebartitle="&sidebar.client-history.label;" + sidebarurl="chrome://communicator/content/history/history-panel.xul" + oncommand="toggleSidebar('viewHistorySidebar');"/> + <broadcaster id="viewSearchSidebar" + autoCheck="false" + type="checkbox" + group="sidebar" + sidebartitle="&sidebar.search.label;" + sidebarurl="chrome://communicator/content/search/search-panel.xul" + oncommand="toggleSidebar('viewSearchSidebar');"/> + <broadcaster id="viewAddressbookSidebar" + autoCheck="false" + type="checkbox" + group="sidebar" + sidebartitle="&sidebar.client-addressbook.label;" + sidebarurl="chrome://messenger/content/addressbook/addressbook-panel.xul" + oncommand="toggleSidebar('viewAddressbookSidebar');"/> + </broadcasterset> + + <command id="toggleSidebar" oncommand="SidebarShowHide();"/> +#ifndef XP_MACOSX + <key id="showHideSidebar" + keycode="VK_F9" + command="toggleSidebar"/> +#else + <key id="showHideSidebar" + key="&showHideSidebarCmd.key;" + modifiers="accel,alt" + command="toggleSidebar"/> +#endif + <menupopup id="sidebarPopup" + onpopupshowing="SidebarInitContextMenu(this, document.popupNode);"> + <menuitem id="switch-ctx-item" label="&sidebar.switch.label;" + accesskey="&sidebar.switch.accesskey;" default="true" + oncommand="SidebarSelectPanel(document.popupNode,false,false);"/> + <menuitem id="reload-ctx-item" label="&sidebar.reload.label;" + accesskey="&sidebar.reload.accesskey;" disabled="true" + oncommand="SidebarReloadPanel(document.popupNode);"/> + <menuitem id="stop-ctx-item" label="&sidebar.loading.stop.label;" + accesskey="&sidebar.loading.stop.accesskey;" disabled="true" + oncommand="SidebarStopPanelLoad(document.popupNode);"/> + <menuseparator/> + <menuitem id="hide-ctx-item" label="&sidebar.hide.label;" + accesskey="&sidebar.hide.accesskey;" + oncommand="SidebarTogglePanel(document.popupNode);"/> + <menuseparator/> + <menuitem id="customize-ctx-item" label="&sidebar.customize.label;" + accesskey="&sidebar.customize.accesskey;" + oncommand="SidebarCustomize();"/> + </menupopup> + + <!-- Overlay the sidebar panels --> + <vbox id="sidebar-box" hidden="true" persist="hidden width collapsed"> + <splitter id="sidebar-panels-splitter" collapse="after" persist="state" + onmouseup="PersistHeight();" hidden="true"> + <grippy/> + </splitter> + <vbox id="sidebar-panels-splitter-box" flex="1" + persist="collapsed"> + <sidebarheader id="sidebar-title-box" class="sidebarheader-main" + label="&sidebar.panels.label;" persist="hidden" type="box" + collapse="after" onmouseup="PersistHeight();" + tooltipopen="&sidebar.open.tooltip;" + tooltipclose="&sidebar.close.tooltip;"> + <toolbarbutton type="menu" id="sidebar-panel-picker" class="tabbable" + onpopupshowing="SidebarBuildPickerPopup();" + label="&sidebar.picker.label;" > + <menupopup id="sidebar-panel-picker-popup" + datasources="rdf:null" + ref="urn:sidebar:current-panel-list" + oncommand="SidebarTogglePanel(event.target);" > + <template> + <rule> + <conditions> + <content uri="?uri"/> + <triple subject="?uri" + predicate="http://home.netscape.com/NC-rdf#panel-list" + object="?panel-list"/> + <member container="?panel-list" child="?panel"/> + <triple subject="?panel" + predicate="http://home.netscape.com/NC-rdf#title" + object="?title" /> + </conditions> + <bindings> + <binding subject="?panel" + predicate="http://home.netscape.com/NC-rdf#exclude" + object="?exclude"/> + <binding subject="?panel" + predicate="http://home.netscape.com/NC-rdf#prereq" + object="?prereq"/> + </bindings> + <action> + <menuitem uri="?panel" type="checkbox" class="menuitem-sidebar" + label="?title" exclude="?exclude" prereq="?prereq"/> + </action> + </rule> + </template> + <menuitem label="&sidebar.customize.label;" accesskey="&sidebar.customize.accesskey;" + oncommand="SidebarCustomize();" /> + <menuitem label="&sidebar.sbDirectory.label;" + oncommand="BrowseMorePanels();" /> + <menuseparator /> + </menupopup> + </toolbarbutton> + <toolbarbutton id="sidebar-close-button" oncommand="SidebarShowHide();" + tooltiptext="&sidebar.close.tooltip;"/> + </sidebarheader> + + <vbox id="sidebar-panels" + datasources="rdf:null" + ref="urn:sidebar:current-panel-list" + last-selected-panel="urn:sidebar:panel:bookmarks" + persist="last-selected-panel height collapsed" flex="1" + onclick="return contentAreaClick(event);"> + <template id="sidebar-template"> + <rule> + <conditions> + <content uri="?uri"/> + <triple subject="?uri" object="?panel-list" + predicate="http://home.netscape.com/NC-rdf#panel-list" /> + <member container="?panel-list" child="?panel"/> + <triple subject="?panel" object="?title" + predicate="http://home.netscape.com/NC-rdf#title" /> + <triple subject="?panel" object="?content" + predicate="http://home.netscape.com/NC-rdf#content" /> + </conditions> + <bindings> + <binding subject="?panel" object="?exclude" + predicate="http://home.netscape.com/NC-rdf#exclude" /> + <binding subject="?panel" object="?prereq" + predicate="http://home.netscape.com/NC-rdf#prereq" /> + </bindings> + <action> + <hbox uri="?panel" class="box-texttab texttab-sidebar" + oncommand="SidebarSelectPanel(this,false,false)" + hidden="true" label="?title" exclude="?exclude" + prereq="?prereq" context="sidebarPopup"/> + <vbox uri="?panel" flex="1" hidden="true" + loadstate="never loaded"> + <vbox flex="1" class="iframe-panel loadarea"> + <hbox flex="1" align="center"> + <image class="image-panel-loading"/> + <label class="text-panel-loading" + value="&sidebar.loading.label;"/> + <label class="text-panel-loading" hidden="true" + loading="false" + value="&sidebar.loadstopped.label;"/> + <button type="stop" label="&sidebar.loading.stop.label;" + oncommand="SidebarStopPanelLoad(this.parentNode.parentNode.parentNode.previousSibling);"/> + <button label="&sidebar.reload.label;" hidden="true" + oncommand="SidebarReload();"/> + </hbox> + <spacer flex="100%"/> + </vbox> + <notificationbox flex="1" collapsed="true" class="sidebar-notificationbox browser-notificationbox"> + <browser flex="1" class="browser-sidebar" src="about:blank" + hidden="true" collapsed="true" content="?content" + disablehistory="true"/> + <browser flex="1" class="browser-sidebar" src="about:blank" + hidden="true" collapsed="true" content="?content" + type="content" context="contentAreaContextMenu" + disablehistory="true" tooltip="aHTMLTooltip"/> + </notificationbox> + </vbox> + </action> + </rule> + </template> + <vbox id="sidebar-iframe-no-panels" class="iframe-panel" flex="1" + hidden="true"> + <description>&sidebar.no-panels.state;</description> + <description>&sidebar.no-panels.add;</description> + <description>&sidebar.no-panels.hide;</description> + </vbox> + </vbox> + <vbox flex="0"> + <hbox id="nav-buttons-box" hidden="true"> + <toolbarbutton flex="1" pack="center" + class="sidebar-nav-button tab-fwd" onclick="SidebarNavigate(-1);"/> + <toolbarbutton flex="1" pack="center" + class="sidebar-nav-button tab-back" onclick="SidebarNavigate(1);"/> + </hbox> + </vbox> + </vbox> + </vbox> + + <!-- Splitter on the right of sidebar --> + <splitter id="sidebar-splitter" collapse="before" persist="state hidden" + class="chromeclass-extrachrome sidebar-splitter" align="center" + hidden="true" onmouseup="SidebarFinishClick();"> + <grippy class="sidebar-splitter-grippy" + onclick="SidebarCleanUpExpandCollapse();"/> + </splitter> + + <!-- View->Sidebar toggle --> + <menupopup id="menu_View_Popup"> + <menu id="menu_Toolbars"> + <menupopup id="view_toolbars_popup"> + <menuseparator/> + <menuitem id="sidebar-menu" type="checkbox" + label="&sidebarCmd.label;" + accesskey="&sidebarCmd.accesskey;" + command="toggleSidebar" + key="showHideSidebar"/> + </menupopup> + </menu> + </menupopup> + + <!-- Scripts go last, because they peek at state to tweak menus --> + <script src="chrome://communicator/content/sidebar/sidebarOverlay.js"/> + +</overlay> + diff --git a/comm/suite/components/sidebar/jar.mn b/comm/suite/components/sidebar/jar.mn new file mode 100644 index 0000000000..d6ab848a98 --- /dev/null +++ b/comm/suite/components/sidebar/jar.mn @@ -0,0 +1,16 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +comm.jar: + content/communicator/sidebar/customize-panel.js (content/customize-panel.js) + content/communicator/sidebar/customize-panel.xul (content/customize-panel.xul) + content/communicator/sidebar/customize.js (content/customize.js) + content/communicator/sidebar/customize.xul (content/customize.xul) + content/communicator/sidebar/PageNotFound.xul (content/PageNotFound.xul) + content/communicator/sidebar/preview.js (content/preview.js) + content/communicator/sidebar/preview.xul (content/preview.xul) + content/communicator/sidebar/sidebarBindings.xml (content/sidebarBindings.xml) + content/communicator/sidebar/sidebarOverlay.css (content/sidebarOverlay.css) + content/communicator/sidebar/sidebarOverlay.js (content/sidebarOverlay.js) +* content/communicator/sidebar/sidebarOverlay.xul (content/sidebarOverlay.xul) diff --git a/comm/suite/components/sidebar/moz.build b/comm/suite/components/sidebar/moz.build new file mode 100644 index 0000000000..7a2e523961 --- /dev/null +++ b/comm/suite/components/sidebar/moz.build @@ -0,0 +1,18 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + "nsISidebar.idl", +] + +XPIDL_MODULE = "suite-sidebar" + +EXTRA_COMPONENTS += [ + "nsSidebar.js", + "SuiteSidebar.manifest", +] + +JAR_MANIFESTS += ["jar.mn"] diff --git a/comm/suite/components/sidebar/nsISidebar.idl b/comm/suite/components/sidebar/nsISidebar.idl new file mode 100644 index 0000000000..515b939872 --- /dev/null +++ b/comm/suite/components/sidebar/nsISidebar.idl @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + The Sidebar API for 3rd parties + +*/ + +#include "nsISupports.idl" + +[scriptable, uuid(97bfa970-8222-4c3f-bbe8-42141e4c7982)] +interface nsISidebar : nsISupports +{ + void addPanel(in AString aTitle, in AString aContentURL, + in AString aCustomizeURL); + void addPersistentPanel(in AString aTitle, in AString aContentURL, + in AString aCustomizeURL); + void addSearchEngine(in AString engineURL, in AString iconURL, + in AString suggestedTitle, in AString suggestedCategory); + void AddSearchProvider(in AString aDescriptionURL); + unsigned long IsSearchProviderInstalled(in AString aSearchURL); +}; diff --git a/comm/suite/components/sidebar/nsSidebar.js b/comm/suite/components/sidebar/nsSidebar.js new file mode 100644 index 0000000000..472ec25a5d --- /dev/null +++ b/comm/suite/components/sidebar/nsSidebar.js @@ -0,0 +1,348 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * No magic constructor behaviour, as is de rigeur for XPCOM. + * If you must perform some initialization, and it could possibly fail (even + * due to an out-of-memory condition), you should use an Init method, which + * can convey failure appropriately (thrown exception in JS, + * NS_FAILED(nsresult) return in C++). + * + * In JS, you can actually cheat, because a thrown exception will cause the + * CreateInstance call to fail in turn, but not all languages are so lucky. + * (Though ANSI C++ provides exceptions, they are verboten in Mozilla code + * for portability reasons -- and even when you're building completely + * platform-specific code, you can't throw across an XPCOM method boundary.) + */ + +const DEBUG = false; /* set to false to suppress debug messages */ +const PANELS_RDF_FILE = "UPnls"; /* directory services property to find panels.rdf */ + +const SIDEBAR_CONTRACTID = "@mozilla.org/sidebar;1"; +const SIDEBAR_CID = Components.ID("{22117140-9c6e-11d3-aaf1-00805f8a4905}"); +const CONTAINER_CONTRACTID = "@mozilla.org/rdf/container;1"; +const NETSEARCH_CONTRACTID = "@mozilla.org/rdf/datasource;1?name=internetsearch" +const nsISupports = Ci.nsISupports; +const nsISidebar = Ci.nsISidebar; +const nsIRDFContainer = Ci.nsIRDFContainer; +const nsIProperties = Ci.nsIProperties; +const nsIFileURL = Ci.nsIFileURL; +const nsIRDFRemoteDataSource = Ci.nsIRDFRemoteDataSource; +const nsIClassInfo = Ci.nsIClassInfo; + +// File extension for Sherlock search plugin description files +const SHERLOCK_FILE_EXT_REGEXP = /\.src$/i; + +var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function nsSidebar() +{ + const RDF_CONTRACTID = "@mozilla.org/rdf/rdf-service;1"; + const nsIRDFService = Ci.nsIRDFService; + + this.rdf = Cc[RDF_CONTRACTID].getService(nsIRDFService); + this.datasource_uri = getSidebarDatasourceURI(PANELS_RDF_FILE); + gDebugLog('datasource_uri is ' + this.datasource_uri); + this.resource = 'urn:sidebar:current-panel-list'; + this.datasource = this.rdf.GetDataSource(this.datasource_uri); +} + +nsSidebar.prototype.nc = "http://home.netscape.com/NC-rdf#"; + +nsSidebar.prototype.isPanel = +function (aContentURL) +{ + var container = + Cc[CONTAINER_CONTRACTID].createInstance(nsIRDFContainer); + + container.Init(this.datasource, this.rdf.GetResource(this.resource)); + + /* Create a resource for the new panel and add it to the list */ + var panel_resource = + this.rdf.GetResource("urn:sidebar:3rdparty-panel:" + aContentURL); + + return (container.IndexOf(panel_resource) != -1); +} + +function sidebarURLSecurityCheck(url) +{ + if (!/(^http:|^ftp:|^https:)/i.test(url)) + throw "Script attempted to add sidebar panel from illegal source"; +} + +/* decorate prototype to provide ``class'' methods and property accessors */ +nsSidebar.prototype.addPanel = +function (aTitle, aContentURL, aCustomizeURL) +{ + gDebugLog("addPanel(" + aTitle + ", " + aContentURL + ", " + + aCustomizeURL + ")"); + + return this.addPanelInternal(aTitle, aContentURL, aCustomizeURL, false); +} + +nsSidebar.prototype.addPersistentPanel = +function(aTitle, aContentURL, aCustomizeURL) +{ + gDebugLog("addPersistentPanel(" + aTitle + ", " + aContentURL + ", " + + aCustomizeURL + ")\n"); + + return this.addPanelInternal(aTitle, aContentURL, aCustomizeURL, true); +} + +nsSidebar.prototype.addPanelInternal = +function (aTitle, aContentURL, aCustomizeURL, aPersist) +{ + sidebarURLSecurityCheck(aContentURL); + + // Create a "container" wrapper around the current panels to + // manipulate the RDF:Seq more easily. + var panel_list = this.datasource.GetTarget(this.rdf.GetResource(this.resource), this.rdf.GetResource(nsSidebar.prototype.nc+"panel-list"), true); + if (panel_list) { + panel_list.QueryInterface(Ci.nsIRDFResource); + } else { + // Datasource is busted. Start over. + gDebugLog("Sidebar datasource is busted\n"); + } + + var container = Cc[CONTAINER_CONTRACTID].createInstance(nsIRDFContainer); + container.Init(this.datasource, panel_list); + + /* Create a resource for the new panel and add it to the list */ + var panel_resource = + this.rdf.GetResource("urn:sidebar:3rdparty-panel:" + aContentURL); + var panel_index = container.IndexOf(panel_resource); + var stringBundle, titleMessage, dialogMessage; + if (panel_index != -1) + { + try { + stringBundle = Services.strings.createBundle("chrome://communicator/locale/sidebar/sidebar.properties"); + if (stringBundle) { + titleMessage = stringBundle.GetStringFromName("dupePanelAlertTitle"); + dialogMessage = stringBundle.GetStringFromName("dupePanelAlertMessage2"); + dialogMessage = dialogMessage.replace(/%url%/, aContentURL); + } + } + catch (e) { + titleMessage = "Sidebar"; + dialogMessage = aContentURL + " already exists in Sidebar. No string bundle"; + } + + Services.prompt.alert(null, titleMessage, dialogMessage); + + return; + } + + try { + stringBundle = Services.strings.createBundle("chrome://communicator/locale/sidebar/sidebar.properties"); + if (stringBundle) { + titleMessage = stringBundle.GetStringFromName("addPanelConfirmTitle"); + dialogMessage = stringBundle.GetStringFromName("addPanelConfirmMessage2"); + if (aPersist) + { + var warning = stringBundle.GetStringFromName("persistentPanelWarning2"); + dialogMessage += "\n" + warning; + } + dialogMessage = dialogMessage.replace(/%title%/, aTitle); + dialogMessage = dialogMessage.replace(/%url%/, aContentURL); + dialogMessage = dialogMessage.replace(/#/g, "\n"); + } + } + catch (e) { + titleMessage = "Add Tab to Sidebar"; + dialogMessage = "No string bundle. Add the Tab '" + aTitle + "' to Sidebar?\n\n" + "Source: " + aContentURL; + } + + var rv = Services.prompt.confirm(null, titleMessage, dialogMessage); + + if (!rv) + return; + + /* Now make some sidebar-ish assertions about it... */ + this.datasource.Assert(panel_resource, + this.rdf.GetResource(this.nc + "title"), + this.rdf.GetLiteral(aTitle), + true); + this.datasource.Assert(panel_resource, + this.rdf.GetResource(this.nc + "content"), + this.rdf.GetLiteral(aContentURL), + true); + if (aCustomizeURL) + this.datasource.Assert(panel_resource, + this.rdf.GetResource(this.nc + "customize"), + this.rdf.GetLiteral(aCustomizeURL), + true); + var persistValue = aPersist ? "true" : "false"; + this.datasource.Assert(panel_resource, + this.rdf.GetResource(this.nc + "persist"), + this.rdf.GetLiteral(persistValue), + true); + + container.AppendElement(panel_resource); + + // Use an assertion to pass a "refresh" event to all the sidebars. + // They use observers to watch for this assertion (in sidebarOverlay.js). + this.datasource.Assert(this.rdf.GetResource(this.resource), + this.rdf.GetResource(this.nc + "refresh"), + this.rdf.GetLiteral("true"), + true); + this.datasource.Unassert(this.rdf.GetResource(this.resource), + this.rdf.GetResource(this.nc + "refresh"), + this.rdf.GetLiteral("true")); + + /* Write the modified panels out. */ + this.datasource.QueryInterface(nsIRDFRemoteDataSource).Flush(); +} + +nsSidebar.prototype.validateSearchEngine = +function (engineURL, iconURL) +{ + try + { + // Make sure the URLs are HTTP, HTTPS, or FTP. + var isWeb = /^(https?|ftp):\/\//i; + + if (!isWeb.test(engineURL)) + throw "Unsupported search engine URL"; + + if (iconURL && !isWeb.test(iconURL)) + throw "Unsupported search icon URL."; + } + catch(ex) + { + gDebugLog(ex); + Cu.reportError("Invalid argument passed to window.sidebar.addSearchEngine: " + ex); + + var searchBundle = Services.strings.createBundle("chrome://global/locale/search/search.properties"); + var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties"); + var brandName = brandBundle.GetStringFromName("brandShortName"); + var title = searchBundle.GetStringFromName("error_invalid_engine_title"); + var msg = searchBundle.formatStringFromName("error_invalid_engine_msg", + [brandName], 1); + Services.ww.getNewPrompter(null).alert(title, msg); + return false; + } + + return true; +} + +// The suggestedTitle and suggestedCategory parameters are ignored, but remain +// for backward compatibility. +nsSidebar.prototype.addSearchEngine = +function (engineURL, iconURL, suggestedTitle, suggestedCategory) +{ + gDebugLog("addSearchEngine(" + engineURL + ", " + iconURL + ", " + + suggestedCategory + ", " + suggestedTitle + ")"); + + if (!this.validateSearchEngine(engineURL, iconURL)) + return; + + // OpenSearch files will likely be far more common than Sherlock files, and + // have less consistent suffixes, so we assume that ".src" is a Sherlock + // (text) file, and anything else is OpenSearch (XML). + var dataType; + if (SHERLOCK_FILE_EXT_REGEXP.test(engineURL)) + dataType = Ci.nsISearchEngine.DATA_TEXT; + else + dataType = Ci.nsISearchEngine.DATA_XML; + + Services.search.addEngine(engineURL, dataType, iconURL, true); +} + +// This function exists largely to implement window.external.AddSearchProvider(), +// to match other browsers' APIs. The capitalization, although nonstandard here, +// is therefore important. +nsSidebar.prototype.AddSearchProvider = +function (aDescriptionURL) +{ + // Get the favicon URL for the current page, or our best guess at the current + // page since we don't have easy access to the active document. Most search + // engines will override this with an icon specified in the OpenSearch + // description anyway. + var win = Services.wm.getMostRecentWindow("navigator:browser"); + var browser = win.getBrowser(); + var iconURL = ""; + // Use documentURIObject in the check for shouldLoadFavIcon so that we + // do the right thing with about:-style error pages. Bug 453442 + if (browser.shouldLoadFavIcon(browser.selectedBrowser + .contentDocument + .documentURIObject)) + iconURL = browser.getIcon(); + + if (!this.validateSearchEngine(aDescriptionURL, iconURL)) + return; + + const typeXML = Ci.nsISearchEngine.DATA_XML; + Services.search.addEngine(aDescriptionURL, typeXML, iconURL, true); +} + +// This function exists to implement window.external.IsSearchProviderInstalled(), +// for compatibility with other browsers. It will return an integer value +// indicating whether the given engine is installed for the current user. +// However, it is currently stubbed out due to security/privacy concerns +// stemming from difficulties in determining what domain issued the request. +// See bug 340604 and +// http://msdn.microsoft.com/en-us/library/aa342526%28VS.85%29.aspx . +// XXX Implement this! +nsSidebar.prototype.IsSearchProviderInstalled = +function (aSearchURL) +{ + return 0; +} + +nsSidebar.prototype.classInfo = XPCOMUtils.generateCI({ + classID: SIDEBAR_CID, + contractID: SIDEBAR_CONTRACTID, + classDescription: "Sidebar", + interfaces: [nsISidebar], + flags: nsIClassInfo.DOM_OBJECT}); + +nsSidebar.prototype.QueryInterface = + XPCOMUtils.generateQI([nsISidebar]); + +nsSidebar.prototype.classID = SIDEBAR_CID; + +var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsSidebar]); + +var gDebugLog; + +/* static functions */ +if (DEBUG) + gDebugLog = function (s) { dump("-*- sidebar component: " + s + "\n"); } +else + gDebugLog = function (s) {} + +function getSidebarDatasourceURI(panels_file_id) +{ + try + { + /* use the fileLocator to look in the profile directory + * to find 'panels.rdf', which is the + * database of the user's currently selected panels. + * if <profile>/panels.rdf doesn't exist, get will copy + *bin/defaults/profile/panels.rdf to <profile>/panels.rdf */ + var sidebar_file = Services.dirsvc.get(panels_file_id, + Ci.nsIFile); + + if (!sidebar_file.exists()) + { + /* this should not happen, as GetFileLocation() should copy + * defaults/panels.rdf to the users profile directory */ + gDebugLog("sidebar file does not exist"); + return null; + } + + var file_handler = Services.io.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler); + var sidebar_uri = file_handler.getURLSpecFromFile(sidebar_file); + gDebugLog("sidebar uri is " + sidebar_uri); + return sidebar_uri; + } + catch (ex) + { + /* this should not happen */ + gDebugLog("caught " + ex + " getting sidebar datasource uri"); + return null; + } +} |