RetroZilla/extensions/editor/cascades/resources/content/EdCssProps.js
2015-10-20 23:03:22 -04:00

1715 lines
64 KiB
JavaScript

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is CaScadeS, a stylesheet editor for Composer.
*
* The Initial Developer of the Original Code is
* Daniel Glazman.
* Portions created by the Initial Developer are Copyright (C) 2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Original author: Daniel Glazman <daniel@glazman.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
const SHEET = 1;
const STYLE_RULE = 2;
const IMPORT_RULE = 3;
const MEDIA_RULE = 4;
const CHARSET_RULE = 5;
const PAGE_RULE = 6;
const OWNER_NODE = 7;
// const COMPATIBILITY_TAB = 1;
const GENERAL_TAB = 2;
const TEXT_TAB = 3;
const BACKGROUND_TAB = 4;
const BORDER_TAB = 5;
const BOX_TAB = 6;
const AURAL_TAB = 7;
const GENERIC_SELECTOR = 0;
const TYPE_ELEMENT_SELECTOR = 1;
const CLASS_SELECTOR = 2;
const kAsyncTimeout = 1500; // 1.5 second
var objectsArray = null;
var gTimerID;
var gAsyncLoadingTimerID;
// needed for commonCssProps.js
var gHaveDocumentUrl = false;
var gInsertIndex = -1;
// * dialog initialization code
function Startup()
{
// are we in a pre-1.3 Mozilla ?
if (typeof window.InitEditorShell == "function") {
// yes, so let's get an editorshell
if (!InitEditorShell())
return;
}
else if (typeof window.GetCurrentEditor != "function" || !GetCurrentEditor()) {
window.close();
return;
}
// gDialog is declared in EdDialogCommon.js
gDialog.selectionBased = false;
// Set commonly-used widgets like this:
gDialog.selectedTab = TEXT_TAB;
gDialog.sheetsTreechildren = document.getElementById("stylesheetsTree");
gDialog.sheetsTree = document.getElementById("sheetsTree");
gDialog.sheetInfoTab = document.getElementById("sheetInfoTab");
gDialog.atimportButton = document.getElementById("atimportButton");
gDialog.atmediaButton = document.getElementById("atmediaButton");
gDialog.linkButton = document.getElementById("linkButton");
gDialog.styleButton = document.getElementById("styleButton");
gDialog.ruleButton = document.getElementById("ruleButton");
gDialog.removeButton = document.getElementById("removeButton");
gDialog.upButton = document.getElementById("upButton");
gDialog.downButton = document.getElementById("downButton");
gDialog.selectedTab = GENERAL_TAB;
gDialog.sheetInfoTabPanelTitle = document.getElementById("sheetInfoTabPanelTitle");
gDialog.textTab = document.getElementById("textTab");
gDialog.brownFoxLabel = document.getElementById("brownFoxLabel");
gDialog.backgroundImageInput = document.getElementById("backgroundImageInput");
gDialog.backgroundPreview = document.getElementById("backgroundPreview");
gDialog.sheetTabbox = document.getElementById("sheetTabbox");
gDialog.backgroundColorInput = document.getElementById("backgroundColorInput");
gDialog.textColorInput = document.getElementById("textColorInput");
gDialog.backgroundRepeatMenulist = document.getElementById("backgroundRepeatMenulist");
gDialog.backgroundAttachmentCheckbox = document.getElementById("backgroundAttachmentCheckbox");
gDialog.xBackgroundPositionRadiogroup = document.getElementById("xBackgroundPositionRadiogroup");
gDialog.yBackgroundPositionRadiogroup = document.getElementById("yBackgroundPositionRadiogroup");
gDialog.fontFamilyRadiogroup = document.getElementById("fontFamilyRadiogroup");
gDialog.customFontFamilyInput = document.getElementById("customFontFamilyInput");
gDialog.predefFontFamilyMenulist = document.getElementById("predefFontFamilyMenulist");
gDialog.fontSizeInput = document.getElementById("fontSizeInput");
gDialog.lineHeightInput = document.getElementById("lineHeightInput");
gDialog.textUnderlineCheckbox = document.getElementById("underlineTextDecorationCheckbox");
gDialog.textOverlineCheckbox = document.getElementById("overlineTextDecorationCheckbox");
gDialog.textLinethroughCheckbox = document.getElementById("linethroughTextDecorationCheckbox");
gDialog.textBlinkCheckbox = document.getElementById("blinkTextDecorationCheckbox");
gDialog.noDecorationCheckbox = document.getElementById("noneTextDecorationCheckbox");
gDialog.topBorderStyleMenulist = document.getElementById("topBorderStyleMenulist");
gDialog.topBorderWidthInput = document.getElementById("topBorderWidthInput");
gDialog.topBorderColorInput = document.getElementById("topBorderColorInput");
gDialog.leftBorderStyleMenulist = document.getElementById("leftBorderStyleMenulist");
gDialog.leftBorderWidthInput = document.getElementById("leftBorderWidthInput");
gDialog.leftBorderColorInput = document.getElementById("leftBorderColorInput");
gDialog.rightBorderStyleMenulist = document.getElementById("rightBorderStyleMenulist");
gDialog.rightBorderWidthInput = document.getElementById("rightBorderWidthInput");
gDialog.rightBorderColorInput = document.getElementById("rightBorderColorInput");
gDialog.bottomBorderStyleMenulist = document.getElementById("bottomBorderStyleMenulist");
gDialog.bottomBorderWidthInput = document.getElementById("bottomBorderWidthInput");
gDialog.bottomBorderColorInput = document.getElementById("bottomBorderColorInput");
gDialog.allFourBordersSame = document.getElementById("allFourBordersSame");
gDialog.borderPreview = document.getElementById("borderPreview");
gDialog.volumeScrollbar = document.getElementById("volumeScrollbar");
gDialog.volumeMenulist = document.getElementById("volumeMenulist");
gDialog.muteVolumeCheckbox = document.getElementById("muteVolumeCheckbox");
gDialog.opacityScrollbar = document.getElementById("opacityScrollbar");
gDialog.opacityLabel = document.getElementById("opacityLabel");
gDialog.sheetInfoTabGridRows = document.getElementById("sheetInfoTabGridRows");
gDialog.sheetInfoTabGrid = document.getElementById("sheetInfoTabGrid");
gDialog.expertMode = true;
gDialog.modified = false;
gDialog.selectedIndex = -1;
gHaveDocumentUrl = GetDocumentBaseUrl();
// Initialize all dialog widgets here,
// e.g., get attributes from an element for property dialog
InitSheetsTree(gDialog.sheetsTreechildren);
// Set window location relative to parent window (based on persisted attributes)
SetWindowLocation();
}
// * Toggles on/off expert mode. In expert mode, all buttons are enabled
// including buttons for stylesheet creation. When the mode is off, only
// the "create rule" button is enabled, a stylesheet being created to contain
// the new rule if necessary
function toggleExpertMode()
{
// toggle the boolean
gDialog.expertMode = !gDialog.expertMode;
if (gDialog.expertMode) {
if (gDialog.selectedIndex == -1) {
// if expert mode is on but no selection in the tree, only
// sheet creation buttons are enabled
UpdateButtons(false, false, true, true, false, false);
}
else {
// if expert mode is on and we have something selected in the tree,
// the state of the buttons depend on the type of the selection
var external = objectsArray[gDialog.selectedIndex].external;
var type = objectsArray[gDialog.selectedIndex].type;
UpdateButtons(!external, !external, true, true, !external, (!external || (type == SHEET)) );
}
}
else {
// if we're not in expert mode, allow only rule creation button
UpdateButtons(!gDialog.atimportButton.hasAttribute("disabled"),
!gDialog.atmediaButton.hasAttribute("disabled"),
!gDialog.linkButton.hasAttribute("disabled"),
!gDialog.styleButton.hasAttribute("disabled"),
!gDialog.ruleButton.hasAttribute("disabled"),
!gDialog.removeButton.hasAttribute("disabled"));
}
}
// * This function recreates the contents of the STYLE elements and
// of the stylesheets local to the filesystem
function FlushChanges()
{
if (gDialog.modified) {
// let's make sure the editor is going to require save on exit
getCurrentEditor().incrementModificationCount(1);
}
// Validate all user data and set attributes and possibly insert new element here
// If there's an error the user must correct, return false to keep dialog open.
var sheet;
for (var i = 0; i < objectsArray.length; i++) {
if (objectsArray[i].modified && !objectsArray[i].external &&
objectsArray[i].type == SHEET) {
/* let's serialize this stylesheet ! */
sheet = objectsArray[i].cssElt;
if (sheet.ownerNode.nodeName.toLowerCase() == "link")
SerializeExternalSheet(sheet, null);
else
SerializeEmbeddedSheet(sheet);
}
}
SaveWindowLocation();
return true; // do close the window
}
// * removes all the content in a tree
// param XULElement sheetsTree
function CleanSheetsTree(sheetsTreeChildren)
{
// we need to clear the selection in the tree otherwise the onselect
// action on the tree will be fired when we removed the selected entry
ClearTreeSelection(gDialog.sheetsTree);
var elt = sheetsTreeChildren.firstChild;
while (elt) {
var tmp = elt.nextSibling;
sheetsTreeChildren.removeChild(elt);
elt = tmp;
}
}
function AddSheetEntryToTree(sheetsTree, ownerNode)
{
if (ownerNode.nodeType == Node.ELEMENT_NODE) {
var ownerTag = ownerNode.nodeName.toLowerCase()
var relType = ownerNode.getAttribute("rel");
if (relType) relType = relType.toLowerCase();
if (ownerTag == "style" ||
(ownerTag == "link" && relType.indexOf("stylesheet") != -1)) {
var treeitem = document.createElementNS(XUL_NS, "treeitem");
var treerow = document.createElementNS(XUL_NS, "treerow");
var treecell = document.createElementNS(XUL_NS, "treecell");
// what kind of owner node do we have here ?
// a style element indicates an embedded stylesheet,
// while a link element indicates an external stylesheet;
// the case of an XML Processing Instruction is not handled, we
// are supposed to be in HTML 4
var external = false;
if (ownerTag == "style") {
treecell.setAttribute("label", "internal stylesheet");
}
else if (ownerTag == "link") {
// external stylesheet, let's present its URL to user
treecell.setAttribute("label", ownerNode.href);
external = true;
if ( /(\w*):.*/.test(ownerNode.href) ) {
if (RegExp.$1 == "file") {
external = false;
}
}
else
external = false;
}
// add a new entry to the tree
var o = newObject( treeitem, external, SHEET, ownerNode.sheet, false, 0 );
PushInObjectsArray(o);
treerow.appendChild(treecell);
treeitem.appendChild(treerow);
treeitem.setAttribute("container", "true");
// add enties to the tree for the rules in the current stylesheet
var rules = null;
if (ownerNode.sheet)
rules = ownerNode.sheet.cssRules;
AddRulesToTreechildren(treeitem, rules, external, 1);
sheetsTree.appendChild(treeitem);
}
}
}
function PushInObjectsArray(o)
{
if (gInsertIndex == -1)
objectsArray.push(o);
else {
objectsArray.splice(gInsertIndex, 0, o);
gInsertIndex++;
}
}
// * populates the tree in the dialog with entries
// corresponding to all stylesheets and css rules attached to
// document
// param XULElement sheetsTree
function InitSheetsTree(sheetsTree)
{
// remove all entries in the tree
CleanSheetsTree(sheetsTree);
// Look for the stylesheets attached to the current document
// Get them from the STYLE and LINK elements because of async sheet loading :
// the LINK element is always here while the corresponding sheet might be
// delayed by network
var headNode = GetHeadElement();
if ( headNode && headNode.hasChildNodes() ) {
var ssn = headNode.childNodes.length;
objectsArray = new Array();
if (ssn) {
var i;
gInsertIndex = -1;
for (i=0; i<ssn; i++) {
var ownerNode = headNode.childNodes[i];
AddSheetEntryToTree(sheetsTree, ownerNode);
}
}
}
}
// * create a new "object" corresponding to an entry in the tree
// unfortunately, it's still impossible to attach a JS object to
// a treecell :-(
// param XULElement xulElt
// param boolean external
// param integer type
// param (DOMNode|DOMCSSStyleSheet|DOMCSSRule) cssElt
// param boolean modified
// param integer depth
function newObject( xulElt, external, type, cssElt, modified, depth)
{
return {xulElt:xulElt,
external:external,
type:type,
cssElt:cssElt,
modified:modified,
depth:depth};
}
function AddStyleRuleToTreeChildren(rule, external, depth)
{
var subtreeitem = document.createElementNS(XUL_NS, "treeitem");
var subtreerow = document.createElementNS(XUL_NS, "treerow");
var subtreecell = document.createElementNS(XUL_NS, "treecell");
// show the selector attached to the rule
subtreecell.setAttribute("label", rule.selectorText);
var o = newObject( subtreeitem, external, STYLE_RULE, rule, false, depth );
PushInObjectsArray(o);
if (external) {
subtreecell.setAttribute("properties", "external");
}
subtreerow.appendChild(subtreecell);
subtreeitem.appendChild(subtreerow);
return subtreeitem;
}
function AddPageRuleToTreeChildren(rule, external, depth)
{
var subtreeitem = document.createElementNS(XUL_NS, "treeitem");
var subtreerow = document.createElementNS(XUL_NS, "treerow");
var subtreecell = document.createElementNS(XUL_NS, "treecell");
// show the selector attached to the rule
subtreecell.setAttribute("label", "@page " + rule.selectorText);
var o = newObject( subtreeitem, external, PAGE_RULE, rule, false, depth );
PushInObjectsArray(o);
if (external) {
subtreecell.setAttribute("properties", "external");
}
subtreerow.appendChild(subtreecell);
subtreeitem.appendChild(subtreerow);
return subtreeitem;
}
function AddImportRuleToTreeChildren(rule, external, depth)
{
var subtreeitem = document.createElementNS(XUL_NS, "treeitem");
var subtreerow = document.createElementNS(XUL_NS, "treerow");
var subtreecell = document.createElementNS(XUL_NS, "treecell");
// show "@import" and the URL
subtreecell.setAttribute("label", "@import "+rule.href, external);
var o = newObject( subtreeitem, external, IMPORT_RULE, rule, false, depth );
PushInObjectsArray(o);
if (external) {
subtreecell.setAttribute("properties", "external");
}
subtreerow.appendChild(subtreecell);
subtreeitem.appendChild(subtreerow);
subtreeitem.setAttribute("container", "true");
if (rule.styleSheet) {
// if we have a stylesheet really imported, let's browse it too
// increasing the depth and marking external
AddRulesToTreechildren(subtreeitem , rule.styleSheet.cssRules, true, depth+1);
}
return subtreeitem;
}
// * adds subtreeitems for the CSS rules found
// into rules; in case of an at-rule, the method calls itself with
// a subtreeitem, the rules in the at-rule, a boolean specifying if
// the rules are external to the document or not, and an increased
// depth.
// param XULElement treeItem
// param CSSRuleList rules
// param boolean external
// param integer depth
function AddRulesToTreechildren(treeItem, rules, external, depth)
{
// is there any rule in the stylesheet ; earlay way out if not
if (rules && rules.length) {
var subtreechildren = document.createElementNS(XUL_NS, "treechildren");
var j, o;
var subtreeitem, subtreerow, subtreecell;
// let's browse all the rules
for (j=0; j< rules.length; j++) {
switch (rules[j].type) {
case CSSRule.STYLE_RULE:
// this is a CSSStyleRule
subtreeitem = AddStyleRuleToTreeChildren(rules[j], external, depth);
subtreechildren.appendChild(subtreeitem);
break;
case CSSRule.PAGE_RULE:
// this is a CSSStyleRule
subtreeitem = AddPageRuleToTreeChildren(rules[j], external, depth);
subtreechildren.appendChild(subtreeitem);
break;
case CSSRule.IMPORT_RULE:
// this is CSSImportRule for @import
subtreeitem = AddImportRuleToTreeChildren(rules[j], external, depth);
subtreechildren.appendChild(subtreeitem);
break;
case CSSRule.MEDIA_RULE:
// this is a CSSMediaRule @media
subtreeitem = document.createElementNS(XUL_NS, "treeitem");
subtreerow = document.createElementNS(XUL_NS, "treerow");
subtreecell = document.createElementNS(XUL_NS, "treecell");
// show "@media" and media list
subtreecell.setAttribute("label", "@media "+rules[j].media.mediaText, external);
o = newObject( subtreeitem, external, MEDIA_RULE, rules[j], false, depth );
PushInObjectsArray(o);
if (external) {
subtreecell.setAttribute("properties", "external");
}
subtreerow.appendChild(subtreecell);
subtreeitem.appendChild(subtreerow);
subtreeitem.setAttribute("container", "true");
// let's browse the rules attached to this CSSMediaRule, keeping the
// current external and increasing depth
AddRulesToTreechildren(subtreeitem, rules[j].cssRules, external, depth+1);
subtreechildren.appendChild(subtreeitem);
break;
case CSSRule.CHARSET_RULE:
// this is a CSSCharsetRule
subtreeitem = document.createElementNS(XUL_NS, "treeitem");
subtreerow = document.createElementNS(XUL_NS, "treerow");
subtreecell = document.createElementNS(XUL_NS, "treecell");
// show "@charset" and the encoding
subtreecell.setAttribute("label", "@charset "+rules[j].encoding, external);
o = newObject( subtreeitem, external, CHARSET_RULE, rules[j], false, depth );
PushInObjectsArray(o);
if (external) {
subtreecell.setAttribute("properties", "external");
}
subtreerow.appendChild(subtreecell);
subtreeitem.appendChild(subtreerow);
subtreechildren.appendChild(subtreeitem);
break;
}
}
treeItem.appendChild(subtreechildren);
}
}
function GetSelectedItemData()
{
// get the selected tree item (if any)
var selectedItem = getSelectedItem(gDialog.sheetsTree);
gDialog.selectedIndex = -1;
gDialog.selectedObject = null;
if (!objectsArray)
return;
// look for the object in objectsArray corresponding to the
// selectedItem
var i, l = objectsArray.length;
if (selectedItem) {
for (i=0; i<l; i++) {
if (objectsArray[i].xulElt == selectedItem) {
gDialog.selectedIndex = i;
gDialog.treeItem = objectsArray[i].xulElt;
gDialog.externalObject = objectsArray[i].external;
gDialog.selectedType = objectsArray[i].type;
gDialog.selectedObject = objectsArray[i].cssElt;
gDialog.modifiedObject = objectsArray[i].modified;
gDialog.depthObject = objectsArray[i].depth;
break;
}
}
}
}
// * selects either a tree item (sheet, rule) or a tab
// in the former case, the parameter is null ; in the latter,
// the parameter is a string containing the name of the selected tab
// param String tab
function onSelectCSSTreeItem(tab)
{
// convert the tab string into a tab id
if (tab == "general") tab = GENERAL_TAB;
else if (tab == "text") tab = TEXT_TAB;
else if (tab == "background") tab = BACKGROUND_TAB;
else if (tab == "border") tab = BORDER_TAB;
else if (tab == "box") tab = BOX_TAB;
else if (tab == "aural") tab = AURAL_TAB;
GetSelectedItemData();
if (gDialog.selectedIndex == -1) {
// there is no tree item selected, let's fallback to the Info tab
// but there is nothing we can display in that tab...
gDialog.sheetTabbox.selectedTab = gDialog.sheetInfoTab;
return;
}
var external = objectsArray[gDialog.selectedIndex].external;
var type = objectsArray[gDialog.selectedIndex].type;
var cssObject = gDialog.selectedObject;
// Let's update the buttons depending on what kind of object is
// selected in the tree
UpdateButtons(!external, !external, true, true, !external, (!external || (type == SHEET)) );
if (gDialog.selectedType != STYLE_RULE) {
// user did not select a CSSStyleRule, let's fallback to Info tab
tab = GENERAL_TAB;
}
if (!tab) {
// this method gets called by a selection in the tree. Is there a
// tab already selected ? If yes, keep it; if no, fallback to the
// Info tab
tab = gDialog.selectedTab ? gDialog.selectedTab : GENERAL_TAB;
}
switch (tab) {
case TEXT_TAB:
// we have to update the text preview, let's remove its style attribute
gDialog.brownFoxLabel.removeAttribute("style");
InitTextTabPanel();
return;
break;
case BACKGROUND_TAB:
InitBackgroundTabPanel();
return;
break;
case BORDER_TAB:
InitBorderTabPanel();
return;
break;
case BOX_TAB:
InitBoxTabPanel();
return;
break;
case AURAL_TAB:
InitAuralTabPanel();
return;
break;
}
// if we are here, the Info tab is our choice
gDialog.selectedTab = GENERAL_TAB;
gDialog.sheetTabbox.selectedTab = gDialog.sheetInfoTab;
var gridrows = gDialog.sheetInfoTabGridRows;
var grid = gDialog.sheetInfoTabGrid;
if (gridrows) {
// first, remove all information present in the Info tab
grid.removeChild(gridrows);
}
gridrows = document.createElementNS(XUL_NS, "rows");
gDialog.sheetInfoTabGridRows = gridrows;
gridrows.setAttribute("id", "sheetInfoTabGridRows");
grid.appendChild(gridrows);
grid.removeAttribute("style");
switch (type) {
case OWNER_NODE:
// this case is a workaround when we have a LINK element but the
// corresponding stylesheet is not loaded yet
if (gDialog.selectedObject.sheet) {
var index = gDialog.selectedIndex;
sheetLoadedTimeoutCallback(index);
onSelectCSSTreeItem(tab);
return;
}
break;
case SHEET:
if (cssObject) {
var alternate = "";
if (external &&
cssObject.ownerNode.getAttribute("rel").toLowerCase().indexOf("alternate") != -1) {
alternate = " (alternate)";
}
gDialog.sheetInfoTabPanelTitle.setAttribute("value", "Stylesheet"+alternate);
AddLabelToInfobox(gridrows, "Type:", cssObject.type, null, false);
AddCheckboxToInfobox(gridrows, "Disabled:", "check to disable stylesheet (cannot be saved)",
cssObject.disabled, "onStylesheetDisabledChange");
var href;
if (cssObject.ownerNode.nodeName.toLowerCase() == "link") {
href = cssObject.href;
}
else {
href = "none (embedded into the document)";
}
AddLabelToInfobox(gridrows, "URL:", href, null, false);
var mediaList = cssObject.media.mediaText;
if (!mediaList || mediaList == "") {
mediaList = "all";
}
AddEditableZoneToInfobox(gridrows, "Media list:", mediaList, "onStylesheetMediaChange", false);
if (cssObject.title) {
AddEditableZoneToInfobox(gridrows, "Title:", cssObject.title, "onStylesheetTitleChange", false);
}
if (cssObject.cssRules) {
AddLabelToInfobox(gridrows, "Number of rules:", cssObject.cssRules.length, null, false);
}
if (!external && cssObject.ownerNode.nodeName.toLowerCase() == "style")
// we can export only embedded stylesheets
AddSingleButtonToInfobox(gridrows, "Export stylesheet and switch to exported version", "onExportStylesheet")
}
else
AddLabelToInfobox(gridrows, "Unable to retrieve stylesheet", null, null, false);
break;
case STYLE_RULE:
case PAGE_RULE:
var h = document.defaultView.getComputedStyle(grid.parentNode, "").getPropertyValue("height");
// let's prepare the grid for the case the rule has a laaaaarge number
// of property declarations
grid.setAttribute("style", "overflow: auto; height: "+h);
gDialog.sheetInfoTabPanelTitle.setAttribute("value",
(type == STYLE_RULE) ? "Style rule" : "Page rule" );
AddLabelToInfobox(gridrows, "Selector:", cssObject.selectorText, null, false);
if (cssObject.style.length) {
AddLabelToInfobox(gridrows, "Declarations:", " ", "Importance", false);
for (var i=0; i<cssObject.style.length; i++) {
AddDeclarationToInfobox(gridrows, cssObject, i, false);
}
}
// TO BE DONE : need a way of changing the importance of a given declaration
break;
case MEDIA_RULE:
gDialog.sheetInfoTabPanelTitle.setAttribute("value", "Media rule");
AddLabelToInfobox(gridrows, "Media list:", cssObject.media.mediaText, null, false);
AddLabelToInfobox(gridrows, "Number of rules:", cssObject.cssRules.length, null, false);
break;
case IMPORT_RULE:
gDialog.sheetInfoTabPanelTitle.setAttribute("value", "Import rule");
AddLabelToInfobox(gridrows, "URL:", cssObject.href, null, false);
AddLabelToInfobox(gridrows, "Media list:", cssObject.media.mediaText, null, false);
break;
// TO BE DONE : @charset and other exotic rules
}
}
// * updates the UI buttons with the given states
// param boolean importState
// param mediaState
// param boolean linkState
// param boolean styleState
// param boolean ruleState
// param boolean removeState
function UpdateButtons(importState, mediaState, linkState, styleState, ruleState, removeState)
{
if (!gDialog.expertMode) {
importState = false;
mediaState = false;
linkState = false;
styleState = false;
ruleState = true;
}
if (!gDialog.selectedObject) {
importState = false;
mediaState = false;
if (gDialog.selectedIndex != -1)
ruleState = false;
}
EnableUI(gDialog.atimportButton, importState);
EnableUI(gDialog.atmediaButton, mediaState);
EnableUI(gDialog.linkButton, linkState);
EnableUI(gDialog.styleButton, styleState);
EnableUI(gDialog.ruleButton, ruleState);
EnableUI(gDialog.removeButton, removeState);
EnableUI(gDialog.upButton, removeState);
EnableUI(gDialog.downButton, removeState);
}
// * adds a button to the given treerow
// param XULElement rows
// param String label
// param String callback
function AddSingleButtonToInfobox(rows, label, callback)
{
var row = document.createElementNS(XUL_NS, "row");
row.setAttribute("align", "center");
var spacer = document.createElementNS(XUL_NS, "spacer");
var hbox = document.createElementNS(XUL_NS, "hbox");
var button = document.createElementNS(XUL_NS, "button");
button.setAttribute("label", label);
button.setAttribute("class", "align-right");
button.setAttribute("oncommand", callback+"();");
row.appendChild(spacer);
hbox.appendChild(button);
row.appendChild(hbox);
rows.appendChild(row);
}
// * adds a textarea to the given treerow, allows to assign the
// initial value and specify that it should acquire the focus
// param XULElement rows
// param String label
// param String value
// param String callback
// param boolean focus
function AddEditableZoneToInfobox(rows, label, value, callback, focus)
{
var labelLabel = document.createElementNS(XUL_NS, "label");
var row = document.createElementNS(XUL_NS, "row");
row.setAttribute("align", "center");
labelLabel.setAttribute("value", label);
row.appendChild(labelLabel);
var textbox = document.createElementNS(XUL_NS, "textbox");
if (callback != "")
textbox.setAttribute("oninput", callback+"(this)");
textbox.setAttribute("value", value);
textbox.setAttribute("size", 20);
row.appendChild(textbox);
rows.appendChild(row);
if (focus)
SetTextboxFocus(textbox);
return textbox;
}
// * adds a radiogroup to the given treerow
// param XULElement rows
// param String label
function AddRadioGroupToInfoBox(rows, label)
{
var row = document.createElementNS(XUL_NS, "row");
row.setAttribute("align", "center");
var labelLabel = document.createElementNS(XUL_NS, "label");
labelLabel.setAttribute("value", label);
row.appendChild(labelLabel);
var radiogroup = document.createElementNS(XUL_NS, "radiogroup");
row.appendChild(radiogroup);
rows.appendChild(row);
return radiogroup;
}
// * adds a radio button to a previously created radiogroup
// param XULElement radiogroup
// param String label
// param String callback
// param integer selectorType
// param boolean selected
function AddRadioToRadioGroup(radiogroup, label, callback, selectorType, selected)
{
var radio = document.createElementNS(XUL_NS, "radio");
radio.setAttribute("label", label);
radio.setAttribute("oncommand", callback + "(" + selectorType + ");" );
if (selected)
radio.setAttribute("selected", "true");
radiogroup.appendChild(radio);
}
// * adds a label and a checkbox to a given treerows
// param XULElement rows
// param String label
// param String checkboxLabel
// param String value
// param String callback
function AddCheckboxToInfobox(rows, label, checkboxLabel, value, callback)
{
var row = document.createElementNS(XUL_NS, "row");
row.setAttribute("align", "center");
var labelLabel = document.createElementNS(XUL_NS, "label");
labelLabel.setAttribute("value", label);
row.appendChild(labelLabel);
var checkbox = document.createElementNS(XUL_NS, "checkbox");
checkbox.setAttribute("label", checkboxLabel);
checkbox.setAttribute("checked", value);
checkbox.setAttribute("oncommand", callback+"()");
row.appendChild(checkbox);
rows.appendChild(row);
}
// * adds a label to a given treerows; strong indicates a bold font
// param XULElement rows
// param String label
// param String value
// param boolean strong
function AddLabelToInfobox(rows, firstLabel, flexibleLabel, lastLabel, strong)
{
var labelLabel = document.createElementNS(XUL_NS, "label");
var row = document.createElementNS(XUL_NS, "row");
row.setAttribute("align", "center");
labelLabel.setAttribute("value", firstLabel);
if (strong) {
labelLabel.setAttribute("class", "titleLabel");
}
row.appendChild(labelLabel);
if (flexibleLabel) {
var valueLabel = document.createElementNS(XUL_NS, "label");
valueLabel.setAttribute("value", flexibleLabel);
if (strong) {
valueLabel.setAttribute("class", "titleLabel");
}
row.appendChild(valueLabel);
}
if (lastLabel) {
valueLabel = document.createElementNS(XUL_NS, "label");
valueLabel.setAttribute("value", lastLabel);
if (strong) {
valueLabel.setAttribute("class", "titleLabel");
}
row.appendChild(valueLabel);
}
rows.appendChild(row);
}
// * adds a declaration's importance to a given treerows
// param XULElement rows
// param String label
// param String value
// param String importance
function AddDeclarationToInfobox(rows, cssObject, i, importance)
{
var labelLabel = document.createElementNS(XUL_NS, "label");
var row = document.createElementNS(XUL_NS, "row");
row.setAttribute("align", "center");
labelLabel.setAttribute("value", "");
row.appendChild(labelLabel);
var valueLabel = document.createElementNS(XUL_NS, "label");
valueLabel.setAttribute("value", GetDeclarationText(cssObject, i));
row.appendChild(valueLabel);
var importanceLabel = document.createElementNS(XUL_NS, "checkbox");
if (GetDeclarationImportance(cssObject, i) == "important") {
importanceLabel.setAttribute("checked", true);
}
importanceLabel.setAttribute("oncommand", "TogglePropertyImportance(\"" + cssObject.style.item(i) + "\")" );
row.appendChild(importanceLabel);
rows.appendChild(row);
}
function TogglePropertyImportance(property)
{
var cssObject = gDialog.selectedObject;
dump("IMPORTANCE = " + cssObject.style.getPropertyPriority(property) + "\n");
var newImportance = (cssObject.style.getPropertyPriority(property) == "important") ? "" : "important" ;
dump("NEW IMPORTANCE = " + newImportance + "\n");
cssObject.style.setProperty(property, cssObject.style.getPropertyValue(property), newImportance);
}
// * retrieves the index-nth style declaration in a rule
// param DOMCSSRule styleRule
// param integer index
// return String
function GetDeclarationText(styleRule, index)
{
var pName = styleRule.style.item(index);
return pName + ": " + styleRule.style.getPropertyValue(pName);
}
// * retrieves the stylesheet containing the selected tree entry
// return integer
function GetSheetContainer()
{
var index = gDialog.selectedIndex;
while (index >= 0 && objectsArray[index].type != SHEET) {
index--;
}
return index;
}
// * declares that the stylesheet containing the selected tree entry
// has been modified
function SetModifiedFlagOnStylesheet()
{
var index = GetSheetContainer();
if (index != -1) {
objectsArray[index].modified = true;
gDialog.modified = true;
}
}
// * we are about to put some info about the selected entry into
// the Info tab
// return XULElement
function PrepareInfoGridForCreation()
{
gDialog.sheetTabbox.selectedTab = gDialog.sheetInfoTab;
var gridrows = gDialog.sheetInfoTabGridRows;
var grid = gDialog.sheetInfoTabGrid;
if (gridrows) {
grid.removeChild(gridrows);
}
gridrows = document.createElementNS(XUL_NS, "rows");
gDialog.sheetInfoTabGridRows = gridrows;
gridrows.setAttribute("id", "sheetInfoTabGridRows");
grid.appendChild(gridrows);
grid.removeAttribute("style");
return gridrows;
}
// * user wants to create a @import rule
function CreateNewAtimportRule()
{
var gridrows = PrepareInfoGridForCreation();
gDialog.newType = IMPORT_RULE;
gDialog.newMediaList = "";
gDialog.newURL = "";
gDialog.sheetInfoTabPanelTitle.setAttribute("value", "New Import At-rule");
AddEditableZoneToInfobox(gridrows, "URL:", gDialog.newURL, "onNewURLChange", true);
AddEditableZoneToInfobox(gridrows, "Media list:", gDialog.newMediaList, "onNewMediaListChange", false);
AddSingleButtonToInfobox(gridrows, "Create Import At-rule", "onConfirmCreateNewObject", false);
}
// * user wants to create a new style rule
function CreateNewStyleRule()
{
var gridrows = PrepareInfoGridForCreation();
gDialog.newExternal = false;
gDialog.newType = STYLE_RULE;
gDialog.newSelector = "";
gDialog.sheetInfoTabPanelTitle.setAttribute("value", "New Style Rule");
gDialog.newSelectorType = CLASS_SELECTOR;
var radiogroup = AddRadioGroupToInfoBox(gridrows, "Create a new:");
// offer choice between class selector and type element selector
AddRadioToRadioGroup(radiogroup, "named style (enter class name below)",
"onCreationStyleRuleTypeChange", CLASS_SELECTOR, true);
AddRadioToRadioGroup(radiogroup, "style applied to all elements of type (enter type below)",
"onCreationStyleRuleTypeChange", TYPE_ELEMENT_SELECTOR, false);
// oh, and in expert mode, allow of course any selector
if (gDialog.expertMode) {
AddRadioToRadioGroup(radiogroup, "style applied to all elements matching the following selector",
"onCreationStyleRuleTypeChange", GENERIC_SELECTOR, false);
}
AddEditableZoneToInfobox(gridrows, " ", gDialog.newSelector, "onNewSelectorChange", true);
AddSingleButtonToInfobox(gridrows, "Create Style Rule", "onConfirmCreateNewObject");
}
// * user changed the type of style rule (s)he wants to create
// param integer type
function onCreationStyleRuleTypeChange(type)
{
gDialog.newSelectorType = type;
}
// * user wants to create a new embedded stylesheet
function CreateNewStyleElement()
{
var gridrows = PrepareInfoGridForCreation();
gDialog.newExternal = false;
gDialog.newType = SHEET;
gDialog.newMediaList = "";
gDialog.newTitle = "";
gDialog.sheetInfoTabPanelTitle.setAttribute("value", "New Stylesheet");
AddLabelToInfobox(gridrows, "Type:", "text/css", null, false);
AddEditableZoneToInfobox(gridrows, "Media list:", gDialog.newMediaList, "onNewMediaListChange", true);
AddEditableZoneToInfobox(gridrows, "Title:", gDialog.newTitle, "onNewTitleChange", false);
AddSingleButtonToInfobox(gridrows, "Create Stylesheet", "onConfirmCreateNewObject")
}
// * user wants to attach an external stylesheet
function CreateNewLinkedSheet()
{
var gridrows = PrepareInfoGridForCreation();
gDialog.newExternal = true;
gDialog.newType = SHEET;
gDialog.newAlternate = false;
gDialog.newURL = "";
gDialog.newMediaList = "";
gDialog.newTitle = "";
gDialog.sheetInfoTabPanelTitle.setAttribute("value", "New Linked Stylesheet");
AddLabelToInfobox(gridrows, "Type:", "text/css", null, false);
// alternate stylesheet ?
AddCheckboxToInfobox(gridrows, "Alternate:", "check to create alternate stylesheet", gDialog.newAlternate,
"onNewAlternateChange");
gDialog.URLtextbox = AddEditableZoneToInfobox(gridrows, "URL:", gDialog.newURL, "onNewURLChange", true);
AddSingleButtonToInfobox(gridrows, "Choose file", "onChooseLocalFile")
AddEditableZoneToInfobox(gridrows, "Media list:", gDialog.newMediaList, "onNewMediaListChange", false);
AddEditableZoneToInfobox(gridrows, "Title:", gDialog.newTitle, "onNewTitleChange", false);
AddSingleButtonToInfobox(gridrows, "Create Stylesheet", "onConfirmCreateNewObject")
// the two following labels are unfortunately useful...
AddLabelToInfobox(gridrows, "", "(Warning : save document *before* attaching local stylesheet)", null, false);
AddLabelToInfobox(gridrows, "", "(use Refresh button if stylesheet is not immediately downloaded)", null, false);
}
// * forget about everything, and let's redo it
function Restart()
{
// delete all objects we keep track of
var l = objectsArray.length;
for (var i=0; i<l; i++) {
delete objectsArray[i];
}
delete objectsArray;
// now, let's clear the tree
var gridrows = gDialog.sheetInfoTabGridRows;
if (gridrows) {
var elt = gridrows.lastChild;
while (elt) {
var tmp = elt.previousSibling;
gridrows.removeChild(elt);
elt = tmp;
}
}
// switch to default Info tab
gDialog.selectedTab = GENERAL_TAB;
gDialog.sheetInfoTabPanelTitle.setAttribute("value", "");
// let's recreate the tree
InitSheetsTree(gDialog.sheetsTreechildren);
// and update the buttons
UpdateButtons(false, false, true, true, false, false);
}
// * does less than Restart(). We only regenerate the tree, keeping track
// of the selection
function Refresh()
{
var index = gDialog.selectedIndex;
Restart();
if (index != -1)
selectTreeItem(objectsArray[index].xulElt);
}
/* CALLBACKS */
// * user toggled the "Alternate Stylesheet" checkbox
function onNewAlternateChange()
{
gDialog.newAlternate = !gDialog.newAlternate;
}
// * user changed the URL of an external stylesheet; elt is the textarea
// param XULElement elt
function onNewURLChange(elt)
{
gDialog.newURL = elt.value;
}
// * user changed the selector for a rule; elt is the textarea
// param XULElement elt
function onNewSelectorChange(elt)
{
gDialog.newSelector = elt.value;
}
// * user changed the list of medias for a new rule; elt is the textarea
// param XULElement elt
function onNewMediaListChange(elt)
{
gDialog.newMediaList = elt.value;
}
// * user changed the title of a new stylesheet; elt is the textarea
// param XULElement elt
function onNewTitleChange(elt)
{
gDialog.newTitle = elt.value;
}
// * user disables/enabled the stylesheet
function onStylesheetDisabledChange()
{
gDialog.selectedObject.disabled = !gDialog.selectedObject.disabled;
}
// * user changed the title of an existing stylesheet; elt is the textarea
// param XULElement elt
function onStylesheetTitleChange(elt)
{
var titleText = elt.value;
gDialog.selectedObject.ownerNode.setAttribute("title", titleText);
SetModifiedFlagOnStylesheet();
}
// * user changed the list of medias of an existing stylesheet; elt is the textarea
// param XULElement elt
function onStylesheetMediaChange(elt)
{
var mediaText= elt.value;
gDialog.selectedObject.ownerNode.setAttribute("media", mediaText);
SetModifiedFlagOnStylesheet();
}
function GetSubtreeChildren(elt)
{
var subtreechildren = null;
if (!elt) return null;
if (elt.hasChildNodes()) {
subtreechildren = elt.lastChild;
while (subtreechildren && subtreechildren .nodeName.toLowerCase() != "treechildren") {
subtreechildren = subtreechildren .previousSibling;
}
}
if (!subtreechildren) {
subtreechildren = document.createElementNS(XUL_NS, "treechildren");
elt.appendChild(subtreechildren);
}
return subtreechildren;
}
// * here, we create a new sheet or rule
function onConfirmCreateNewObject()
{
// first, let's declare we modify the document
gDialog.modified = true;
var selector;
// if we are requested to create a style rule in expert mode,
// let's find the last embedded stylesheet
if (!gDialog.expertMode && gDialog.newType == STYLE_RULE) {
var indexLastEmbeddedStylesheet = -1;
for (var i = objectsArray.length-1; i >= 0 ; i--) {
if (objectsArray[i].type == SHEET && ! objectsArray[i].external) {
indexLastEmbeddedStylesheet = i;
break;
}
}
if (indexLastEmbeddedStylesheet != -1) {
gDialog.selectedIndex = indexLastEmbeddedStylesheet;
}
else {
// there is no stylesheet ! let's create one that will contain our rule
gDialog.newExternal = false;
gDialog.newMediaList = "";
gDialog.newTitle = "";
gDialog.newType = SHEET;
var selectorType = gDialog.newSelectorType;
selector = gDialog.newSelector;
onConfirmCreateNewObject();
// now, create the rule...
gDialog.newType = STYLE_RULE;
gDialog.newSelectorType = selectorType;
gDialog.newSelector = selector;
}
}
var containerIndex, sheetIndex;
var cssObject;
var l;
var ruleIndex;
var newSheetOwnerNode;
var headNode;
var newCssRule;
switch (gDialog.newType) {
case STYLE_RULE:
if (gDialog.newSelector != "") {
containerIndex = gDialog.selectedIndex;
while (objectsArray[containerIndex].type != SHEET &&
objectsArray[containerIndex].type != MEDIA_RULE)
containerIndex--;
switch (gDialog.newSelectorType) {
case TYPE_ELEMENT_SELECTOR:
case GENERIC_SELECTOR:
selector = gDialog.newSelector;
break;
case CLASS_SELECTOR:
selector = "." + gDialog.newSelector;
break;
}
cssObject = objectsArray[containerIndex].cssElt;
l = cssObject.cssRules.length;
cssObject.insertRule(selector + " { }", l);
if (cssObject.cssRules.length > l) {
// hmmm, there's always the bad case of a wrong rule, dropped by the
// parser ; that's why we need to check we really inserted something
/* find inserted rule's index in objectsArray */
var depth = objectsArray[containerIndex].depth;
var external = objectsArray[containerIndex].external;
ruleIndex = containerIndex + 1;
while (ruleIndex < objectsArray.length &&
objectsArray[ruleIndex].depth > depth) {
ruleIndex++;
}
var subtreechildren = GetSubtreeChildren(objectsArray[containerIndex].xulElt);
gInsertIndex = ruleIndex;
var subtreeitem = AddStyleRuleToTreeChildren(cssObject.cssRules[l], external, depth);
subtreechildren.appendChild(subtreeitem);
selectTreeItem(subtreeitem);
SetModifiedFlagOnStylesheet();
}
}
break;
case IMPORT_RULE:
if (gDialog.newURL != "") {
containerIndex = GetSheetContainer();
// **must** clear the selection before changing the tree
ClearTreeSelection(gDialog.sheetsTree);
var containerCssObject = objectsArray[containerIndex].cssElt;
var containerDepth = objectsArray[containerIndex].depth;
var containerExternal = objectsArray[containerIndex].external;
var cssRuleIndex = -1;
if (containerCssObject.cssRules)
for (i=0; i < containerCssObject.cssRules.length; i++)
if (containerCssObject.cssRules[i].type != CSSRule.IMPORT_RULE &&
containerCssObject.cssRules[i].type != CSSRule.CHARSET_RULE) {
cssRuleIndex = i;
break;
}
if (cssRuleIndex == -1) {
// no rule in the sheet for the moment or only charset and import rules
containerCssObject.insertRule('@import url("'+ gDialog.newURL + '") ' + gDialog.newMediaList + ";",
containerCssObject.cssRules.length);
newCssRule = containerCssObject.cssRules[containerCssObject.cssRules.length - 1];
subtreechildren = GetSubtreeChildren(objectsArray[containerIndex].xulElt);
gInsertIndex = ruleIndex;
subtreeitem = AddImportRuleToTreeChildren(newCssRule,
containerExternal,
containerDepth + 1);
subtreechildren.appendChild(subtreeitem);
ruleIndex = FindObjectIndexInObjectsArray(newCssRule);
}
else {
// cssRuleIndex is the index of the first not charset and not import rule in the sheet
ruleIndex = FindObjectIndexInObjectsArray(containerCssObject.cssRules[cssRuleIndex]);
// and ruleIndex represents the index of the corresponding object in objectsArray
var refObject = objectsArray[ruleIndex];
containerCssObject.insertRule('@import url("'+ gDialog.newURL + '") ' + gDialog.newMediaList + ";",
cssRuleIndex);
newCssRule = containerCssObject.cssRules[cssRuleIndex];
gInsertIndex = ruleIndex;
subtreeitem = AddImportRuleToTreeChildren(newCssRule, containerExternal, containerDepth + 1);
var refNode = refObject.xulElt;
refNode.parentNode.insertBefore(subtreeitem, refNode);
}
selectTreeItem(subtreeitem);
SetModifiedFlagOnStylesheet();
if (gAsyncLoadingTimerID)
clearTimeout(gAsyncLoadingTimerID);
if (!newCssRule.styleSheet)
gAsyncLoadingTimerID = setTimeout("sheetLoadedTimeoutCallback(" + ruleIndex + ")", kAsyncTimeout);
}
break;
case SHEET:
gInsertIndex = -1;
ClearTreeSelection(gDialog.sheetsTree);
if (gDialog.newExternal && gDialog.newURL != "") {
subtreeitem = document.createElementNS(XUL_NS, "treeitem");
var subtreerow = document.createElementNS(XUL_NS, "treerow");
var subtreecell = document.createElementNS(XUL_NS, "treecell");
subtreeitem.setAttribute("container", "true");
subtreerow.appendChild(subtreecell);
subtreeitem.appendChild(subtreerow);
gDialog.sheetsTreechildren.appendChild(subtreeitem);
newSheetOwnerNode = getCurrentEditor().document.createElement("link");
newSheetOwnerNode.setAttribute("type", "text/css");
newSheetOwnerNode.setAttribute("href", gDialog.newURL);
if (gDialog.newAlternate) {
newSheetOwnerNode.setAttribute("rel", "alternate stylesheet");
}
else {
newSheetOwnerNode.setAttribute("rel", "stylesheet");
}
if (gDialog.newMediaList != "") {
newSheetOwnerNode.setAttribute("media", gDialog.newMediaList);
}
if (gDialog.newTitle != "") {
newSheetOwnerNode.setAttribute("title", gDialog.newTitle);
}
headNode = GetHeadElement();
headNode.appendChild(newSheetOwnerNode);
subtreecell.setAttribute("label", gDialog.newURL);
external = true;
if ( /(\w*):.*/.test(gDialog.newURL) ) {
if (RegExp.$1 == "file") {
external = false;
}
}
if (external)
subtreecell.setAttribute("properties", "external");
if (!newSheetOwnerNode.sheet) {
/* hack due to asynchronous load of external stylesheet */
var o = newObject( subtreeitem, external, OWNER_NODE, newSheetOwnerNode, false, 0 );
PushInObjectsArray(o)
if (gAsyncLoadingTimerID)
clearTimeout(gAsyncLoadingTimerID);
sheetIndex = objectsArray.length - 1;
gAsyncLoadingTimerID = setTimeout("sheetLoadedTimeoutCallback(" + sheetIndex + ")", kAsyncTimeout);
}
else {
o = newObject( subtreeitem, external, SHEET, newSheetOwnerNode.sheet, false, 0 );
PushInObjectsArray(o)
AddRulesToTreechildren(subtreeitem, newSheetOwnerNode.sheet.cssRules, external, 1);
}
}
else if (!gDialog.newExternal) {
newSheetOwnerNode = getCurrentEditor().document.createElement("style");
newSheetOwnerNode.setAttribute("type", "text/css");
if (gDialog.newMediaList != "") {
newSheetOwnerNode.setAttribute("media", gDialog.newMediaList);
}
if (gDialog.newTitle != "") {
newSheetOwnerNode.setAttribute("title", gDialog.newTitle);
}
headNode = GetHeadElement();
headNode.appendChild(newSheetOwnerNode);
AddSheetEntryToTree(gDialog.sheetsTreechildren, newSheetOwnerNode);
selectTreeItem(objectsArray[objectsArray.length - 1].xulElt);
}
selectTreeItem(subtreeitem);
break;
}
}
// * we need that to refresh the tree after async sheet load
// param integer index
function sheetLoadedTimeoutCallback(index)
{
var subtreeitem = objectsArray[index].xulElt;
gInsertIndex = index+1;
ClearTreeSelection(gDialog.sheetsTree);
if (objectsArray[index].type == OWNER_NODE && objectsArray[index].cssElt.sheet != null) {
var sheet = objectsArray[index].cssElt.sheet;
AddRulesToTreechildren(subtreeitem , sheet.cssRules, objectsArray[index].external,
objectsArray[index].depth+1);
objectsArray[index].type = SHEET;
objectsArray[index].cssElt = sheet;
}
else if (objectsArray[index].type == IMPORT_RULE && objectsArray[index].cssElt.styleSheet != null) {
AddRulesToTreechildren(subtreeitem , objectsArray[index].cssElt.styleSheet.cssRules, true,
objectsArray[index].depth+1)
}
else
return;
selectTreeItem(subtreeitem);
}
// * gets the object's index corresponding to an entry in the tree
// param XULElement object
// return integer
function FindObjectIndexInObjectsArray(object)
{
var i, l = objectsArray.length;
for (i=0; i<l; i++)
if (objectsArray[i].cssElt == object)
return i;
return -1;
}
// * removes the selected entry from the tree and deletes the corresponding
// object ; does some magic if we remove a container like a stylesheet or
// an @import rule to remove all the contained rules
function RemoveObject()
{
GetSelectedItemData();
var objectIndex = gDialog.selectedIndex;
if (objectIndex == -1) return;
var depth = gDialog.depthObject;
var ruleIndex, i, ruleIndexInTree, toSplice;
switch (gDialog.selectedType) {
case SHEET:
var ownerNode = gDialog.selectedObject.ownerNode;
ownerNode.parentNode.removeChild(ownerNode);
for (i=objectIndex+1; i<objectsArray.length && objectsArray[i].depth > depth; i++);
toSplice = i - objectIndex;
break;
case IMPORT_RULE:
case MEDIA_RULE:
for (ruleIndexInTree=objectIndex-1; objectsArray[ruleIndexInTree].depth >= depth; ruleIndexInTree--);
objectsArray[ruleIndexInTree].modified = true;
ruleIndex = getRuleIndexInRulesList(gDialog.selectedObject.parentStyleSheet.cssRules,
gDialog.selectedObject);
if (ruleIndex != -1) {
gDialog.selectedObject.parentStyleSheet.deleteRule(ruleIndex);
}
for (i=objectIndex+1; i<objectsArray.length && objectsArray[i].depth > depth; i++);
toSplice = i - objectIndex;
break;
case STYLE_RULE:
for (ruleIndexInTree=objectIndex-1; objectsArray[ruleIndexInTree].depth; ruleIndexInTree--);
objectsArray[ruleIndexInTree].modified = true;
if (gDialog.selectedObject.parentRule) {
/* this style rule is contained in an at-rule */
/* need to remove the rule only from the at-rule listing it */
ruleIndex = getRuleIndexInRulesList(gDialog.selectedObject.parentRule.cssRules,
gDialog.selectedObject);
if (ruleIndex != -1) {
gDialog.selectedObject.parentRule.deleteRule(ruleIndex);
}
}
else {
ruleIndex = getRuleIndexInRulesList(gDialog.selectedObject.parentStyleSheet.cssRules,
gDialog.selectedObject);
if (ruleIndex != -1) {
gDialog.selectedObject.parentStyleSheet.deleteRule(ruleIndex);
}
}
toSplice = 1;
break;
}
// let's remove the whole treeitem
gDialog.treeItem.parentNode.removeChild(gDialog.treeItem);
// and then remove the objects from our array
objectsArray.splice(objectIndex, toSplice);
// can we select an item ?
if (objectsArray.length)
selectTreeItem(objectsArray[Math.min(objectIndex, objectsArray.length - 1)].xulElt);
}
// * moves a sheet/rule up in the tree
function MoveObjectUp()
{
GetSelectedItemData();
var index = gDialog.selectedIndex;
if (index <= 0) return;
var sheetIndex = GetSheetContainer();
switch (gDialog.selectedType) {
case SHEET:
var ownerNode = gDialog.selectedObject.ownerNode;
ClearTreeSelection(gDialog.sheetsTree)
index--;
while (index && objectsArray[index].type != SHEET)
index--;
if (index == -1) return;
ownerNode.parentNode.insertBefore(ownerNode, objectsArray[index].cssElt.ownerNode);
Restart();
selectTreeItem(objectsArray[index].xulElt);
gDialog.modified = true;
break;
case OWNER_NODE:
ownerNode = gDialog.selectedObject;
ClearTreeSelection(gDialog.sheetsTree)
index--;
while (index && objectsArray[index].type != SHEET)
index--;
if (index == -1) return;
ownerNode.parentNode.insertBefore(ownerNode, objectsArray[index].cssElt.ownerNode);
Restart();
selectTreeItem(objectsArray[index].xulElt);
gDialog.modified = true;
break;
case STYLE_RULE:
case PAGE_RULE:
var rule = gDialog.selectedObject;
objectsArray[sheetIndex].modified = true;
ClearTreeSelection(gDialog.sheetsTree)
var ruleText = rule.cssText;
var subtreeitem;
var newRule;
if (rule.parentRule) {
var ruleIndex = getRuleIndexInRulesList(rule.parentRule.cssRules, rule);
var parentRule = rule.parentRule;
if (ruleIndex == -1) return;
if (!ruleIndex) {
// we have to move the rule just before its parent rule
parentRule.deleteRule(0);
var parentRuleIndex;
parentRuleIndex = getRuleIndexInRulesList(parentRule.parentStyleSheet.cssRules, parentRule);
parentRule.parentStyleSheet.insertRule(ruleText, parentRuleIndex);
newRule = parentRule.parentStyleSheet.cssRules[parentRuleIndex];
}
else {
// we just move the rule in its parentRule
parentRule.deleteRule(ruleIndex);
parentRule.insertRule(ruleText, ruleIndex - 1);
newRule = parentRule.cssRules.item(ruleIndex - 1);
}
// remove the tree entry
objectsArray[index].xulElt.parentNode.removeChild(objectsArray[index].xulElt);
// delete the object
objectsArray.splice(index, 1);
// position the insertion index
gInsertIndex = index - 1;
subtreeitem = AddStyleRuleToTreeChildren(newRule,
objectsArray[index-1].external,
objectsArray[index-1].depth);
// make the new tree entry
objectsArray[index].xulElt.parentNode.insertBefore(subtreeitem,
objectsArray[index].xulElt);
selectTreeItem(subtreeitem);
}
else {
// standard case, the parent of the rule is the stylesheet itself
ruleIndex = getRuleIndexInRulesList(rule.parentStyleSheet.cssRules, rule);
var refStyleSheet = rule.parentStyleSheet;
if (ruleIndex == -1) return;
if (ruleIndex) {
// we just move the rule in the sheet
refStyleSheet.deleteRule(ruleIndex);
var targetRule = refStyleSheet.cssRules.item(ruleIndex - 1);
if (targetRule.type == CSSRule.MEDIA_RULE) {
targetRule.insertRule(ruleText, targetRule.cssRules.length);
var targetRuleIndex = FindObjectIndexInObjectsArray(targetRule);
newRule = targetRule.cssRules.item(targetRule.cssRules.length - 1);
var subtreechildren = GetSubtreeChildren(objectsArray[targetRuleIndex].xulElt);
// in this case, the target treeitem is at same location but one level deeper
// remove the tree entry
objectsArray[index].xulElt.parentNode.removeChild(objectsArray[index].xulElt);
// delete the object
objectsArray.splice(index, 1);
// position the insertion index
gInsertIndex = index;
subtreeitem = AddStyleRuleToTreeChildren(newRule,
objectsArray[targetRuleIndex].external,
objectsArray[targetRuleIndex].depth + 1);
// make the new tree entry
subtreechildren.appendChild(subtreeitem);
selectTreeItem(subtreeitem);
return;
}
else if (targetRule.type != CSSRule.IMPORT_RULE &&
targetRule.type != CSSRule.CHARSET_RULE) {
// we can move the rule before its predecessor only if that one is
// not an @import rule nor an @charset rule
refStyleSheet.insertRule(ruleText, ruleIndex - 1);
newRule = refStyleSheet.cssRules[ruleIndex - 1];
// remove the tree entry
objectsArray[index].xulElt.parentNode.removeChild(objectsArray[index].xulElt);
// delete the object
objectsArray.splice(index, 1);
// position the insertion index
gInsertIndex = index - 1;
subtreeitem = AddStyleRuleToTreeChildren(newRule,
objectsArray[index-1].external,
objectsArray[index-1].depth);
// make the new tree entry
objectsArray[index].xulElt.parentNode.insertBefore(subtreeitem,
objectsArray[index].xulElt);
selectTreeItem(subtreeitem);
return;
}
}
// At this point, we need to move the rule from one sheet to another one, if it
// exists...
// First, let's if there is another candidate stylesheet before the current one
// for the style rule's ownership
var refSheetIndex = FindObjectIndexInObjectsArray(refStyleSheet);
sheetIndex = refSheetIndex;
if (!sheetIndex) return; // early way out
sheetIndex--;
while (sheetIndex && (objectsArray[sheetIndex].type != SHEET ||
objectsArray[sheetIndex])) {
sheetIndex--;
}
if (sheetIndex == -1) return; // no embedded or local stylesheet available
var newStyleSheet = objectsArray[sheetIndex].cssElt;
// we need to check the type of the last rule in the sheet, if it exists
if (newStyleSheet.cssRules.length &&
newStyleSheet.cssRules[newStyleSheet.cssRules.length - 1].type == CSSRule.MEDIA_RULE) {
// this media rule is our target
var refMediaRule = newStyleSheet.cssRules[newStyleSheet.cssRules.length - 1];
var refMediaRuleIndex = FindObjectIndexInObjectsArray(refMediaRule );
var refRuleIndex = refMediaRuleIndex++;
while (refRuleIndex < objectsArray.length && objectsArray[refRuleIndex].depth > 1)
refRuleIndex++;
// refRuleIndex represents where we should insert the new object
}
else {
// we just append the rule to the list of rules of the sheet
}
}
break;
}
}
// * moves a sheet/rule down in the tree
function MoveObjectDown()
{
/* NOT YET IMPLEMENTED */
var objectIndex = FindObjectIndexInObjectsArray(gDialog.selectedObject);
if (objectIndex == -1) return;
}
// * opens a file picker and returns the file:/// URL in gDialog.newURL
function onChooseLocalFile() {
// Get a local file, converted into URL format
var fileName = getLocalFileURL(true);
if (fileName)
{
gDialog.URLtextbox.setAttribute("value", fileName);
gDialog.newURL = fileName;
}
}
// * opens a file picker for a *.css filename, exports the selected stylesheet
// in that file, and replace the selected embedded sheet by its external, but
// local to the filesystem, new counterpart
function onExportStylesheet() {
var fileName = getLocalFileURL(false);
SerializeExternalSheet(gDialog.selectedObject, fileName);
var ownerNode = gDialog.selectedObject.ownerNode;
var newSheetOwnerNode = ownerNode.ownerDocument.createElement("link");
newSheetOwnerNode.setAttribute("type", "text/css");
newSheetOwnerNode.setAttribute("href", fileName);
newSheetOwnerNode.setAttribute("rel", "stylesheet");
var mediaList = ownerNode.getAttribute("media");
if (mediaList && mediaList != "")
newSheetOwnerNode.setAttribute("media", mediaList);
ownerNode.parentNode.insertBefore(newSheetOwnerNode, ownerNode);
ownerNode.parentNode.removeChild(ownerNode);
// we still have to wait for async sheet loading's completion
if (gAsyncLoadingTimerID)
clearTimeout(gAsyncLoadingTimerID);
gAsyncLoadingTimerID = setTimeout("Refresh()", 500);
}
function getCurrentEditor() {
if (typeof window.InitEditorShell == "function")
return editorShell.editor;
else
return GetCurrentEditor();
}
function AddCSSLevelChoice(rows)
{
var labelLabel = document.createElementNS(XUL_NS, "label");
var row = document.createElementNS(XUL_NS, "row");
row.setAttribute("align", "center");
labelLabel.setAttribute("value", "CSS Level");
row.appendChild(labelLabel);
var level;
for (level = 1; level < 4; level++) {
var levelCheckbox = document.createElementNS(XUL_NS, "checkbox");
levelCheckbox.setAttribute("label", level);
row.appendChild(levelCheckbox);
}
rows.appendChild(row);
}