You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3264 lines
92 KiB
JavaScript

/*
* Copyright (C) 2008, 2009, 2010 Mihai Şucan
*
* This file is part of PaintWeb.
*
* PaintWeb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PaintWeb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PaintWeb. If not, see <http://www.gnu.org/licenses/>.
*
* $URL: http://code.google.com/p/paintweb $
* $Date: 2010-06-26 21:47:30 +0300 $
*/
/**
* @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
* @fileOverview The default PaintWeb interface code.
*/
/**
* @class The default PaintWeb interface.
*
* @param {PaintWeb} app Reference to the main paint application object.
*/
pwlib.gui = function (app) {
var _self = this,
config = app.config,
doc = app.doc,
lang = app.lang,
MathRound = Math.round,
pwlib = window.pwlib,
appEvent = pwlib.appEvent,
win = app.win;
this.app = app;
this.idPrefix = 'paintweb' + app.UID + '_',
this.classPrefix = 'paintweb_';
/**
* Holds references to DOM elements.
* @type Object
*/
this.elems = {};
/**
* Holds references to input elements associated to the PaintWeb configuration
* properties.
* @type Object
*/
this.inputs = {};
/**
* Holds references to DOM elements associated to configuration values.
* @type Object
*/
this.inputValues = {};
/**
* Holds references to DOM elements associated to color configuration
* properties.
*
* @type Object
* @see pwlib.guiColorInput
*/
this.colorInputs = {};
/**
* Holds references to DOM elements associated to each tool registered in the
* current PaintWeb application instance.
*
* @private
* @type Object
*/
this.tools = {};
/**
* Holds references to DOM elements associated to PaintWeb commands.
*
* @private
* @type Object
*/
this.commands = {};
/**
* Holds references to floating panels GUI components.
*
* @type Object
* @see pwlib.guiFloatingPanel
*/
this.floatingPanels = {zIndex_: 0};
/**
* Holds references to tab panel GUI components.
*
* @type Object
* @see pwlib.guiTabPanel
*/
this.tabPanels = {};
/**
* Holds an instance of the guiResizer object attached to the Canvas.
*
* @private
* @type pwlib.guiResizer
*/
this.canvasResizer = null;
/**
* Holds an instance of the guiResizer object attached to the viewport
* element.
*
* @private
* @type pwlib.guiResizer
*/
this.viewportResizer = null;
/**
* Holds tab configuration information for most drawing tools.
*
* @private
* @type Object
*/
this.toolTabConfig = {
bcurve: {
lineTab: true,
shapeType: true,
lineWidth: true,
lineWidthLabel: lang.inputs.borderWidth,
lineCap: true
},
ellipse: {
lineTab: true,
shapeType: true,
lineWidth: true,
lineWidthLabel: lang.inputs.borderWidth
},
rectangle: {
lineTab: true,
shapeType: true,
lineWidth: true,
lineWidthLabel: null,
lineJoin: true
},
polygon: {
lineTab: true,
shapeType: true,
lineWidth: true,
lineWidthLabel: lang.inputs.borderWidth,
lineJoin: true,
lineCap: true,
miterLimit: true
},
eraser: {
lineTab: true,
lineWidth: true,
lineWidthLabel: lang.inputs.eraserSize,
lineJoin: true,
lineCap: true,
miterLimit: true
},
pencil: {
lineTab: true,
lineWidth: true,
lineWidthLabel: lang.inputs.pencilSize,
lineJoin: true,
lineCap: true,
miterLimit: true
},
line: {
lineTab: true,
lineWidth: true,
lineWidthLabel: lang.inputs.line.lineWidth,
lineJoin: true,
lineCap: true,
miterLimit: true
},
text: {
lineTab: false,
lineTabLabel: lang.tabs.main.textBorder,
shapeType: true,
lineWidth: true,
lineWidthLabel: lang.inputs.borderWidth
}
};
/**
* Initialize the PaintWeb interface.
*
* @param {Document|String} markup The interface markup loaded and parsed as
* DOM Document object. Optionally, the value can be a string holding the
* interface markup (this is used when PaintWeb is packaged).
*
* @returns {Boolean} True if the initialization was successful, or false if
* not.
*/
this.init = function (markup) {
// Make sure the user nicely waits for PaintWeb to load, without seeing
// much.
var placeholder = config.guiPlaceholder,
placeholderStyle = placeholder.style;
placeholderStyle.display = 'none';
placeholderStyle.height = '1px';
placeholderStyle.overflow = 'hidden';
placeholderStyle.position = 'absolute';
placeholderStyle.visibility = 'hidden';
placeholder.className += ' ' + this.classPrefix + 'placeholder';
if (!placeholder.tabIndex || placeholder.tabIndex == -1) {
placeholder.tabIndex = 1;
}
if (!this.initImportDoc(markup)) {
app.initError(lang.guiMarkupImportFailed);
return false;
}
markup = null;
if (!this.initParseMarkup()) {
app.initError(lang.guiMarkupParseFailed);
return false;
}
if (!this.initCanvas() ||
!this.initImageZoom() ||
//!this.initSelectionTool() ||
//!this.initTextTool() ||
!this.initKeyboardShortcuts()) {
return false;
}
// Setup the main tabbed panel.
var panel = this.tabPanels.main;
if (!panel) {
app.initError(lang.noMainTabPanel);
return false;
}
// Hide the "Shadow" tab if the drawing of shadows is not supported.
if (!app.shadowSupported && 'shadow' in panel.tabs) {
panel.tabHide('shadow');
}
if (!('viewport' in this.elems)) {
app.initError(lang.missingViewport);
return false;
}
// Setup the GUI dimensions .
this.elems.viewport.style.height = config.viewportHeight;
placeholderStyle.width = config.viewportWidth;
// Setup the Canvas resizer.
var resizeHandle = this.elems.canvasResizer;
if (!resizeHandle) {
app.initError(lang.missingCanvasResizer);
return false;
}
resizeHandle.title = lang.guiCanvasResizer;
resizeHandle.replaceChild(doc.createTextNode(resizeHandle.title),
resizeHandle.firstChild);
resizeHandle.addEventListener('mouseover', this.item_mouseover, false);
resizeHandle.addEventListener('mouseout', this.item_mouseout, false);
this.canvasResizer = new pwlib.guiResizer(this, resizeHandle,
this.elems.canvasContainer);
this.canvasResizer.events.add('guiResizeStart', this.canvasResizeStart);
this.canvasResizer.events.add('guiResizeEnd', this.canvasResizeEnd);
// Setup the viewport resizer.
var resizeHandle = this.elems.viewportResizer;
if (!resizeHandle) {
app.initError(lang.missingViewportResizer);
return false;
}
resizeHandle.title = lang.guiViewportResizer;
resizeHandle.replaceChild(doc.createTextNode(resizeHandle.title),
resizeHandle.firstChild);
resizeHandle.addEventListener('mouseover', this.item_mouseover, false);
resizeHandle.addEventListener('mouseout', this.item_mouseout, false);
this.viewportResizer = new pwlib.guiResizer(this, resizeHandle,
this.elems.viewport);
this.viewportResizer.dispatchMouseMove = true;
this.viewportResizer.events.add('guiResizeMouseMove',
this.viewportResizeMouseMove);
this.viewportResizer.events.add('guiResizeEnd', this.viewportResizeEnd);
if ('statusMessage' in this.elems) {
this.elems.statusMessage._prevText = false;
}
// Update the version string in Help.
if ('version' in this.elems) {
this.elems.version.appendChild(doc.createTextNode(app.toString()));
}
// Update the image dimensions in the GUI.
var imageSize = this.elems.imageSize;
if (imageSize) {
imageSize.replaceChild(doc.createTextNode(app.image.width + 'x'
+ app.image.height), imageSize.firstChild);
}
// Add application-wide event listeners.
app.events.add('canvasSizeChange', this.canvasSizeChange);
app.events.add('commandRegister', this.commandRegister);
app.events.add('commandUnregister', this.commandUnregister);
app.events.add('configChange', this.configChangeHandler);
app.events.add('imageSizeChange', this.imageSizeChange);
app.events.add('imageZoom', this.imageZoom);
app.events.add('appInit', this.appInit);
app.events.add('shadowAllow', this.shadowAllow);
app.events.add('toolActivate', this.toolActivate);
app.events.add('toolRegister', this.toolRegister);
app.events.add('toolUnregister', this.toolUnregister);
// Make sure the historyUndo and historyRedo command elements are
// synchronized with the application history state.
if ('historyUndo' in this.commands && 'historyRedo' in this.commands) {
app.events.add('historyUpdate', this.historyUpdate);
}
app.commandRegister('about', this.commandAbout);
return true;
};
/**
* Initialize the Canvas elements.
*
* @private
* @returns {Boolean} True if the initialization was successful, or false if
* not.
*/
this.initCanvas = function () {
var canvasContainer = this.elems.canvasContainer,
layerCanvas = app.layer.canvas,
layerContext = app.layer.context,
layerStyle = layerCanvas.style,
bufferCanvas = app.buffer.canvas,
orginlCanvas = app.orginl.canvas;
if (!canvasContainer) {
app.initError(lang.missingCanvasContainer);
return false;
}
var containerStyle = canvasContainer.style;
canvasContainer.className = this.classPrefix + 'canvasContainer';
orginlCanvas.className = this.classPrefix + 'orginlCanvas';
layerCanvas.className = this.classPrefix + 'layerCanvas';
bufferCanvas.className = this.classPrefix + 'bufferCanvas';
containerStyle.width = layerStyle.width;
containerStyle.height = layerStyle.height;
if (!config.checkersBackground || pwlib.browser.olpcxo) {
containerStyle.backgroundImage = 'none';
}
canvasContainer.appendChild(orginlCanvas);
canvasContainer.appendChild(layerCanvas);
canvasContainer.appendChild(bufferCanvas);
// Make sure the selection transparency input checkbox is disabled if the
// putImageData and getImageData methods are unsupported.
if ('selection_transparent' in this.inputs && (!layerContext.putImageData ||
!layerContext.getImageData)) {
this.inputs.selection_transparent.disabled = true;
this.inputs.selection_transparent.checked = true;
}
return true;
};
/**
* Import the DOM nodes from the interface DOM document. All the nodes are
* inserted into the {@link PaintWeb.config.guiPlaceholder} element.
*
* <p>Elements which have the ID attribute will have the attribute renamed to
* <code>data-pwId</code>.
*
* <p>Input elements which have the ID attribute will have their attribute
* updated to be unique for the current PaintWeb instance.
*
* @private
*
* @param {Document|String} markup The source DOM document to import the nodes
* from. Optionally, this parameter can be a string which holds the interface
* markup.
*
* @returns {Boolean} True if the initialization was successful, or false if
* not.
*/
this.initImportDoc = function (markup) {
// I could use some XPath here, but for the sake of compatibility I don't.
var destElem = config.guiPlaceholder,
elType = app.ELEMENT_NODE,
elem, root, nodes, n, tag, isInput;
if (typeof markup === 'string') {
elem = doc.createElement('div');
elem.innerHTML = markup;
root = elem.firstChild;
} else {
root = markup.documentElement;
}
markup = null;
nodes = root.getElementsByTagName('*');
n = nodes.length;
// Change all the id attributes to be data-pwId attributes.
// Input elements have their ID updated to be unique for the current
// PaintWeb instance.
for (var i = 0; i < n; i++) {
elem = nodes[i];
if (elem.nodeType !== elType || !elem.tagName) {
continue;
}
tag = elem.tagName.toLowerCase();
isInput = tag === 'input' || tag === 'select' || tag === 'textarea';
if (elem.id) {
elem.setAttribute('data-pwId', elem.id);
if (isInput) {
elem.id = this.idPrefix + elem.id;
} else {
elem.removeAttribute('id');
}
}
// label elements have their "for" attribute updated as well.
if (tag === 'label' && elem.htmlFor) {
elem.htmlFor = this.idPrefix + elem.htmlFor;
}
}
// Import all the nodes.
n = root.childNodes.length;
for (var i = 0; i < n; i++) {
destElem.appendChild(doc.importNode(root.childNodes[i], true));
}
return true;
};
/**
* Parse the interface markup. The layout file can have custom
* PaintWeb-specific attributes.
*
* <p>Elements with the <code>data-pwId</code> attribute are added to the
* {@link pwlib.gui#elems} object.
*
* <p>Elements having the <code>data-pwCommand</code> attribute are added to
* the {@link pwlib.gui#commands} object.
*
* <p>Elements having the <code>data-pwTool</code> attribute are added to the
* {@link pwlib.gui#tools} object.
*
* <p>Elements having the <code>data-pwTabPanel</code> attribute are added to
* the {@link pwlib.gui#tabPanels} object. These become interactive GUI
* components (see {@link pwlib.guiTabPanel}).
*
* <p>Elements having the <code>data-pwFloatingPanel</code> attribute are
* added to the {@link pwlib.gui#floatingPanels} object. These become
* interactive GUI components (see {@link pwlib.guiFloatingPanel}).
*
* <p>Elements having the <code>data-pwConfig</code> attribute are added to
* the {@link pwlib.gui#inputs} object. These become interactive GUI
* components which allow users to change configuration options.
*
* <p>Elements having the <code>data-pwConfigValue</code> attribute are added
* to the {@link pwlib.gui#inputValues} object. These can only be child nodes
* of elements which have the <code>data-pwConfig</code> attribute. Each such
* element is considered an icon. Anchor elements are appended to ensure
* keyboard accessibility.
*
* <p>Elements having the <code>data-pwConfigToggle</code> attribute are added
* to the {@link pwlib.gui#inputs} object. These become interactive GUI
* components which toggle the boolean value of the configuration property
* they are associated to.
*
* <p>Elements having the <code>data-pwColorInput</code> attribute are added
* to the {@link pwlib.gui#colorInputs} object. These become color picker
* inputs which are associated to the configuration property given as the
* attribute value. (see {@link pwlib.guiColorInput})
*
* @returns {Boolean} True if the parsing was successful, or false if not.
*/
this.initParseMarkup = function () {
var nodes = config.guiPlaceholder.getElementsByTagName('*'),
elType = app.ELEMENT_NODE,
elem, tag, isInput, tool, tabPanel, floatingPanel, cmd, id, cfgAttr,
colorInput;
// Store references to important elements and parse PaintWeb-specific
// attributes.
for (var i = 0; i < nodes.length; i++) {
elem = nodes[i];
if (elem.nodeType !== elType) {
continue;
}
tag = elem.tagName.toLowerCase();
isInput = tag === 'input' || tag === 'select' || tag === 'textarea';
// Store references to commands.
cmd = elem.getAttribute('data-pwCommand');
if (cmd && !(cmd in this.commands)) {
elem.className += ' ' + this.classPrefix + 'command';
this.commands[cmd] = elem;
}
// Store references to tools.
tool = elem.getAttribute('data-pwTool');
if (tool && !(tool in this.tools)) {
elem.className += ' ' + this.classPrefix + 'tool';
this.tools[tool] = elem;
}
// Create tab panels.
tabPanel = elem.getAttribute('data-pwTabPanel');
if (tabPanel) {
this.tabPanels[tabPanel] = new pwlib.guiTabPanel(this, elem);
}
// Create floating panels.
floatingPanel = elem.getAttribute('data-pwFloatingPanel');
if (floatingPanel) {
this.floatingPanels[floatingPanel] = new pwlib.guiFloatingPanel(this,
elem);
}
cfgAttr = elem.getAttribute('data-pwConfig');
if (cfgAttr) {
if (isInput) {
this.initConfigInput(elem, cfgAttr);
} else {
this.initConfigIcons(elem, cfgAttr);
}
}
cfgAttr = elem.getAttribute('data-pwConfigToggle');
if (cfgAttr) {
this.initConfigToggle(elem, cfgAttr);
}
cfgAttr = elem.getAttribute('data-pwConfigButton');
if (cfgAttr) {
this.initConfigToggle(elem, cfgAttr, 'button');
}
// elem.hasAttribute() fails in webkit (tested with chrome and safari 4)
if (elem.getAttribute('data-pwColorInput')) {
colorInput = new pwlib.guiColorInput(this, elem);
this.colorInputs[colorInput.id] = colorInput;
}
id = elem.getAttribute('data-pwId');
if (id) {
elem.className += ' ' + this.classPrefix + id;
// Store a reference to the element.
if (isInput && !cfgAttr) {
this.inputs[id] = elem;
} else if (!isInput) {
this.elems[id] = elem;
}
}
}
return true;
};
/**
* Initialize an input element associated to a configuration property.
*
* @private
*
* @param {Element} elem The DOM element which is associated to the
* configuration property.
*
* @param {String} cfgAttr The configuration attribute. This tells the
* configuration group and property to which the DOM element is attached to.
*/
this.initConfigInput = function (input, cfgAttr) {
var cfgNoDots = cfgAttr.replace('.', '_'),
cfgArray = cfgAttr.split('.'),
cfgProp = cfgArray.pop(),
cfgGroup = cfgArray.join('.'),
cfgGroupRef = config,
langGroup = lang.inputs,
labelElem = input.parentNode;
for (var i = 0, n = cfgArray.length; i < n; i++) {
cfgGroupRef = cfgGroupRef[cfgArray[i]];
langGroup = langGroup[cfgArray[i]];
}
input._pwConfigProperty = cfgProp;
input._pwConfigGroup = cfgGroup;
input._pwConfigGroupRef = cfgGroupRef;
input.title = langGroup[cfgProp + 'Title'] || langGroup[cfgProp];
input.className += ' ' + this.classPrefix + 'cfg_' + cfgNoDots;
this.inputs[cfgNoDots] = input;
if (labelElem.tagName.toLowerCase() !== 'label') {
labelElem = labelElem.getElementsByTagName('label')[0];
}
if (input.type === 'checkbox' || labelElem.htmlFor) {
labelElem.replaceChild(doc.createTextNode(langGroup[cfgProp]),
labelElem.lastChild);
} else {
labelElem.replaceChild(doc.createTextNode(langGroup[cfgProp]),
labelElem.firstChild);
}
if (input.type === 'checkbox') {
input.checked = cfgGroupRef[cfgProp];
} else {
input.value = cfgGroupRef[cfgProp];
}
input.addEventListener('input', this.configInputChange, false);
input.addEventListener('change', this.configInputChange, false);
};
/**
* Initialize an HTML element associated to a configuration property, and all
* of its own sub-elements associated to configuration values. Each element
* that has the <var>data-pwConfigValue</var> attribute is considered an icon.
*
* @private
*
* @param {Element} elem The DOM element which is associated to the
* configuration property.
*
* @param {String} cfgAttr The configuration attribute. This tells the
* configuration group and property to which the DOM element is attached to.
*/
this.initConfigIcons = function (input, cfgAttr) {
var cfgNoDots = cfgAttr.replace('.', '_'),
cfgArray = cfgAttr.split('.'),
cfgProp = cfgArray.pop(),
cfgGroup = cfgArray.join('.'),
cfgGroupRef = config,
langGroup = lang.inputs;
for (var i = 0, n = cfgArray.length; i < n; i++) {
cfgGroupRef = cfgGroupRef[cfgArray[i]];
langGroup = langGroup[cfgArray[i]];
}
input._pwConfigProperty = cfgProp;
input._pwConfigGroup = cfgGroup;
input._pwConfigGroupRef = cfgGroupRef;
input.title = langGroup[cfgProp + 'Title'] || langGroup[cfgProp];
input.className += ' ' + this.classPrefix + 'cfg_' + cfgNoDots;
this.inputs[cfgNoDots] = input;
var labelElem = input.getElementsByTagName('p')[0];
labelElem.replaceChild(doc.createTextNode(langGroup[cfgProp]),
labelElem.firstChild);
var elem, anchor, val,
className = ' ' + this.classPrefix + 'configActive';
nodes = input.getElementsByTagName('*'),
elType = app.ELEMENT_NODE;
for (var i = 0; i < nodes.length; i++) {
elem = nodes[i];
if (elem.nodeType !== elType) {
continue;
}
val = elem.getAttribute('data-pwConfigValue');
if (!val) {
continue;
}
anchor = doc.createElement('a');
anchor.href = '#';
anchor.title = langGroup[cfgProp + '_' + val];
anchor.appendChild(doc.createTextNode(anchor.title));
elem.className += ' ' + this.classPrefix + cfgProp + '_' + val
+ ' ' + this.classPrefix + 'icon';
elem._pwConfigParent = input;
if (cfgGroupRef[cfgProp] == val) {
elem.className += className;
}
anchor.addEventListener('click', this.configValueClick, false);
anchor.addEventListener('mouseover', this.item_mouseover, false);
anchor.addEventListener('mouseout', this.item_mouseout, false);
elem.replaceChild(anchor, elem.firstChild);
this.inputValues[cfgGroup + '_' + cfgProp + '_' + val] = elem;
}
};
/**
* Initialize an HTML element associated to a boolean configuration property.
*
* @private
*
* @param {Element} elem The DOM element which is associated to the
* configuration property.
*
* @param {String} cfgAttr The configuration attribute. This tells the
* configuration group and property to which the DOM element is attached to.
*/
this.initConfigToggle = function (input, cfgAttr, isButton) {
var cfgNoDots = cfgAttr.replace('.', '_'),
cfgArray = cfgAttr.split('.'),
cfgProp = cfgArray.pop(),
cfgGroup = cfgArray.join('.'),
cfgGroupRef = config,
langGroup = lang.inputs;
for (var i = 0, n = cfgArray.length; i < n; i++) {
cfgGroupRef = cfgGroupRef[cfgArray[i]];
langGroup = langGroup[cfgArray[i]];
}
input._pwConfigProperty = cfgProp;
input._pwConfigGroup = cfgGroup;
input._pwConfigGroupRef = cfgGroupRef;
input.className += ' ' + this.classPrefix + 'cfg_' + cfgNoDots
+ ' ' + this.classPrefix + 'icon';
if (cfgGroupRef)
if (cfgGroupRef[cfgProp]) {
input.className += ' ' + this.classPrefix + 'configActive';
}
var anchor = doc.createElement('a');
anchor.href = '#';
anchor.title = langGroup[cfgProp + 'Title'] || langGroup[cfgProp];
anchor.appendChild(doc.createTextNode(langGroup[cfgProp]));
if (isButton)
anchor.addEventListener('click', this.configButtonClick, false);
else anchor.addEventListener('click', this.configToggleClick, false);
anchor.addEventListener('mouseover', this.item_mouseover, false);
anchor.addEventListener('mouseout', this.item_mouseout, false);
input.replaceChild(anchor, input.firstChild);
this.inputs[cfgNoDots] = input;
};
/**
* Initialize the image zoom input.
*
* @private
* @returns {Boolean} True if the initialization was successful, or false if
* not.
*/
this.initImageZoom = function () {
var input = this.inputs.imageZoom;
if (!input) {
return true; // allow layouts without the zoom input
}
//---------------------------------------------------------------------------------------
// FIXME : image size 조정값 zoom에 set
//---------------------------------------------------------------------------------------
console.log('initImageZoom>>>>>', app.image.width, app.image.height, app.image.zoom);
input.value = app.image.zoom * 100;
input._old_value = 100;
//---------------------------------------------------------------------------------------
//input.value = 100;
//input._old_value = 100;
// Override the attributes, based on the settings.
input.setAttribute('step', config.imageZoomStep * 100);
input.setAttribute('max', config.imageZoomMax * 100);
input.setAttribute('min', config.imageZoomMin * 100);
var changeFn = function () {
app.imageZoomTo(parseInt(this.value) / 100);
};
input.addEventListener('change', changeFn, false);
input.addEventListener('input', changeFn, false);
// Update some language strings
var label = input.parentNode;
if (label.tagName.toLowerCase() === 'label') {
label.replaceChild(doc.createTextNode(lang.imageZoomLabel),
label.firstChild);
}
var elem = this.elems.statusZoom;
if (!elem) {
return true;
}
elem.title = lang.imageZoomTitle;
return true;
};
/**
* Initialize GUI elements associated to selection tool options and commands.
*
* @private
* @returns {Boolean} True if the initialization was successful, or false if
* not.
*/
this.initSelectionTool = function () {
var classDisabled = ' ' + this.classPrefix + 'disabled',
cut = this.commands.selectionCut,
copy = this.commands.selectionCopy,
paste = this.commands.clipboardPaste;
if (paste) {
app.events.add('clipboardUpdate', this.clipboardUpdate);
paste.className += classDisabled;
}
if (cut && copy) {
app.events.add('selectionChange', this.selectionChange);
cut.className += classDisabled;
copy.className += classDisabled;
}
var selTab_cmds = ['selectionCut', 'selectionCopy', 'clipboardPaste'],
anchor, elem, cmd;
for (var i = 0, n = selTab_cmds.length; i < n; i++) {
cmd = selTab_cmds[i];
elem = this.elems['selTab_' + cmd];
if (!elem) {
continue;
}
anchor = doc.createElement('a');
anchor.title = lang.commands[cmd];
anchor.href = '#';
anchor.appendChild(doc.createTextNode(anchor.title));
anchor.addEventListener('click', this.commandClick, false);
elem.className += classDisabled + ' ' + this.classPrefix + 'command'
+ ' ' + this.classPrefix + 'cmd_' + cmd;
elem.setAttribute('data-pwCommand', cmd);
elem.replaceChild(anchor, elem.firstChild);
}
var selCrop = this.commands.selectionCrop,
selFill = this.commands.selectionFill,
selForNumPlt = this.commands.selectionForNumberPlate,
selDelete = this.commands.selectionDelete;
selCrop.className += classDisabled;
selFill.className += classDisabled;
selForNumPlt.className += classDisabled;
selDelete.className += classDisabled;
return true;
};
/**
* Initialize GUI elements associated to text tool options.
*
* @private
* @returns {Boolean} True if the initialization was successful, or false if
* not.
*/
this.initTextTool = function () {
/*if ('textString' in this.inputs) {
this.inputs.textString.value = lang.inputs.text.textString_value;
}*/
if (!('text_fontFamily' in this.inputs) || !('text' in config) ||
!('fontFamilies' in config.text)) {
return true;
}
var option, input = this.inputs.text_fontFamily;
for (var i = 0, n = config.text.fontFamilies.length; i < n; i++) {
option = doc.createElement('option');
option.value = config.text.fontFamilies[i];
option.appendChild(doc.createTextNode(option.value));
input.appendChild(option);
if (option.value === config.text.fontFamily) {
input.selectedIndex = i;
input.value = option.value;
}
}
option = doc.createElement('option');
option.value = '+';
option.appendChild(doc.createTextNode(lang.inputs.text.fontFamily_add));
input.appendChild(option);
var sizeArr = ["8", "10", "12", "14", "18", "24"];
input = this.inputs.fontSize;
for (var i = 0; i < sizeArr.length; i++) {
option = doc.createElement('option');
option.value = i+1;
option.appendChild(doc.createTextNode(sizeArr[i]+"pt"));
input.appendChild(option);
}
input.selectedIndex = 3;
return true;
};
/**
* Initialize the keyboard shortcuts. Basically, this updates various strings
* to ensure the user interface is informational.
*
* @private
* @returns {Boolean} True if the initialization was successful, or false if
* not.
*/
this.initKeyboardShortcuts = function () {
var kid = null, kobj = null;
for (kid in config.keys) {
kobj = config.keys[kid];
if ('toolActivate' in kobj && kobj.toolActivate in lang.tools) {
lang.tools[kobj.toolActivate] += ' [ ' + kid + ' ]';
}
if ('command' in kobj && kobj.command in lang.commands) {
lang.commands[kobj.command] += ' [ ' + kid + ' ]';
}
}
return true;
};
/**
* The <code>appInit</code> event handler. This method is invoked once
* PaintWeb completes all the loading.
*
* <p>This method dispatches the {@link pwlib.appEvent.guiShow} application
* event.
*
* @private
* @param {pwlib.appEvent.appInit} ev The application event object.
*/
this.appInit = function (ev) {
// Initialization was not successful ...
if (ev.state !== PaintWeb.INIT_DONE) {
return;
}
// Make sure the Hand tool is enabled/disabled as needed.
if ('hand' in _self.tools) {
app.events.add('canvasSizeChange', _self.toolHandStateChange);
app.events.add('viewportSizeChange', _self.toolHandStateChange);
_self.toolHandStateChange(ev);
}
// Make PaintWeb visible.
var placeholder = config.guiPlaceholder,
placeholderStyle = placeholder.style;
// We do not reset the display property. We leave this for the stylesheet.
placeholderStyle.height = '';
placeholderStyle.overflow = '';
placeholderStyle.position = '';
placeholderStyle.visibility = '';
var cs = win.getComputedStyle(placeholder, null);
// Do not allow the static positioning for the PaintWeb placeholder.
// Usually, the GUI requires absolute/relative positioning.
if (cs.position === 'static') {
placeholderStyle.position = 'relative';
}
try {
placeholder.focus();
} catch (err) { }
this.toolActivate("selection"); //영역 선택
app.events.dispatch(new appEvent.guiShow());
};
/**
* The <code>guiResizeStart</code> event handler for the Canvas resize
* operation.
* @private
*/
this.canvasResizeStart = function () {
this.resizeHandle.style.visibility = 'hidden';
// ugly...
this.timeout_ = setTimeout(function () {
_self.statusShow('guiCanvasResizerActive', true);
clearTimeout(_self.canvasResizer.timeout_);
delete _self.canvasResizer.timeout_;
}, 400);
};
/**
* The <code>guiResizeEnd</code> event handler for the Canvas resize
* operation.
*
* @private
* @param {pwlib.appEvent.guiResizeEnd} ev The application event object.
*/
this.canvasResizeEnd = function (ev) {
this.resizeHandle.style.visibility = '';
app.imageCrop(0, 0, MathRound(ev.width / app.image.canvasScale), MathRound(ev.height / app.image.canvasScale));
if (this.timeout_) {
clearTimeout(this.timeout_);
delete this.timeout_;
} else {
_self.statusShow(-1);
}
};
/**
* The <code>guiResizeMouseMove</code> event handler for the viewport resize
* operation.
*
* @private
* @param {pwlib.appEvent.guiResizeMouseMove} ev The application event object.
*/
this.viewportResizeMouseMove = function (ev) {
config.guiPlaceholder.style.width = ev.width + 'px';
};
/**
* The <code>guiResizeEnd</code> event handler for the viewport resize
* operation.
*
* @private
* @param {pwlib.appEvent.guiResizeEnd} ev The application event object.
*/
this.viewportResizeEnd = function (ev) {
_self.elems.viewport.style.width = '';
_self.resizeTo(ev.width + 'px', ev.height + 'px');
try {
config.guiPlaceholder.focus();
} catch (err) { }
};
/**
* The <code>mouseover</code> event handler for all tools, commands and icons.
* This simply shows the title / text content of the element in the GUI status
* bar.
*
* @see pwlib.gui#statusShow The method used for displaying the message in the
* GUI status bar.
*/
this.item_mouseover = function () {
if (this.title || this.textConent) {
_self.statusShow(this.title || this.textContent, true);
}
};
/**
* The <code>mouseout</code> event handler for all tools, commands and icons.
* This method simply resets the GUI status bar to the previous message it was
* displaying before the user hovered the current element.
*
* @see pwlib.gui#statusShow The method used for displaying the message in the
* GUI status bar.
*/
this.item_mouseout = function () {
_self.statusShow(-1);
};
/**
* Show a message in the status bar.
*
* @param {String|Number} msg The message ID you want to display. The ID
* should be available in the {@link PaintWeb.lang.status} object. If the
* value is -1 then the previous non-temporary message will be displayed. If
* the ID is not available in the language file, then the string is shown
* as-is.
*
* @param {Boolean} [temporary=false] Tells if the message is temporary or
* not.
*/
this.statusShow = function (msg, temporary) {
var elem = this.elems.statusMessage;
if (msg === -1 && elem._prevText === false) {
return false;
}
if (msg === -1) {
msg = elem._prevText;
}
if (msg in lang.status) {
msg = lang.status[msg];
}
if (!temporary) {
elem._prevText = msg;
}
if (elem.firstChild) {
elem.removeChild(elem.firstChild);
}
win.status = msg;
if (msg) {
elem.appendChild(doc.createTextNode(msg));
}
};
/**
* The "About" command. This method displays the "About" panel.
*/
this.commandAbout = function () {
_self.floatingPanels.about.toggle();
};
/**
* The <code>click</code> event handler for the tool DOM elements.
*
* @private
*
* @param {Event} ev The DOM Event object.
*
* @see PaintWeb#toolActivate to activate a drawing tool.
*/
this.toolClick = function (ev) {
app.toolActivate(this.parentNode.getAttribute('data-pwTool'), ev);
ev.preventDefault();
};
/**
* The <code>toolActivate</code> application event handler. This method
* provides visual feedback for the activation of a new drawing tool.
*
* @private
*
* @param {pwlib.appEvent.toolActivate} ev The application event object.
*
* @see PaintWeb#toolActivate the method which allows you to activate
* a drawing tool.
*/
this.toolActivate = function (ev) {
var tabAnchor,
tabActive = _self.tools[ev.id],
tabConfig = _self.toolTabConfig[ev.id] || {},
tabPanel = _self.tabPanels.main,
lineTab = tabPanel.tabs.line,
shapeType = _self.inputs.shapeType,
lineWidth = _self.inputs.line_lineWidth,
lineCap = _self.inputs.line_lineCap,
lineJoin = _self.inputs.line_lineJoin,
miterLimit = _self.inputs.line_miterLimit,
lineWidthLabel = null;
tabActive.className += ' ' + _self.classPrefix + 'toolActive';
try {
tabActive.firstChild.focus();
} catch (err) { }
if ((ev.id + 'Active') in lang.status) {
_self.statusShow(ev.id + 'Active');
}
// show/hide the shapeType input config.
if (shapeType) {
if (tabConfig.shapeType) {
shapeType.style.display = '';
} else {
shapeType.style.display = 'none';
}
}
if (ev.prevId) {
var prevTab = _self.tools[ev.prevId],
prevTabConfig = _self.toolTabConfig[ev.prevId] || {};
prevTab.className = prevTab.className.
replace(' ' + _self.classPrefix + 'toolActive', '');
// hide the line tab
if (prevTabConfig.lineTab && lineTab) {
tabPanel.tabHide('line');
lineTab.container.className = lineTab.container.className.
replace(' ' + _self.classPrefix + 'main_line_' + ev.prevId,
' ' + _self.classPrefix + 'main_line');
}
// hide the tab for the current tool.
if (ev.prevId in tabPanel.tabs) {
tabPanel.tabHide(ev.prevId);
}
}
// Change the label of the lineWidth input element.
if (tabConfig.lineWidthLabel) {
lineWidthLabel = lineWidth.parentNode;
lineWidthLabel.replaceChild(doc.createTextNode(tabConfig.lineWidthLabel),
lineWidthLabel.firstChild);
}
if (lineJoin) {
if (tabConfig.lineJoin) {
lineJoin.style.display = '';
} else {
lineJoin.style.display = 'none';
}
}
if (lineCap) {
if (tabConfig.lineCap) {
lineCap.style.display = '';
} else {
lineCap.style.display = 'none';
}
}
if (miterLimit) {
if (tabConfig.miterLimit) {
miterLimit.parentNode.parentNode.style.display = '';
} else {
miterLimit.parentNode.parentNode.style.display = 'none';
}
}
if (lineWidth) {
if (tabConfig.lineWidth) {
lineWidth.parentNode.parentNode.style.display = '';
} else {
lineWidth.parentNode.parentNode.style.display = 'none';
}
}
// show the line tab, if configured
if (tabConfig.lineTab && 'line' in tabPanel.tabs) {
tabAnchor = lineTab.button.firstChild;
tabAnchor.title = tabConfig.lineTabLabel || lang.tabs.main[ev.id];
tabAnchor.replaceChild(doc.createTextNode(tabAnchor.title),
tabAnchor.firstChild);
if (ev.id !== 'line') {
lineTab.container.className = lineTab.container.className.
replace(' ' + _self.classPrefix + 'main_line', ' ' + _self.classPrefix
+ 'main_line_' + ev.id);
}
tabPanel.tabShow('line');
}
// show the tab for the current tool, if there's one.
if (ev.id in tabPanel.tabs) {
tabPanel.tabShow(ev.id);
}
};
/**
* The <code>toolRegister</code> application event handler. This method adds
* the new tool into the GUI.
*
* @private
*
* @param {pwlib.appEvent.toolRegister} ev The application event object.
*
* @see PaintWeb#toolRegister the method which allows you to register new
* tools.
*/
this.toolRegister = function (ev) {
var attr = null, elem = null, anchor = null;
if (ev.id in _self.tools) {
elem = _self.tools[ev.id];
attr = elem.getAttribute('data-pwTool');
if (attr && attr !== ev.id) {
attr = null;
elem = null;
delete _self.tools[ev.id];
}
}
// Create a new element if there's none already associated to the tool ID.
if (!elem) {
elem = doc.createElement('li');
}
if (!attr) {
elem.setAttribute('data-pwTool', ev.id);
}
elem.className += ' ' + _self.classPrefix + 'tool_' + ev.id;
// Append an anchor element which holds the locale string.
anchor = doc.createElement('a');
anchor.title = lang.tools[ev.id];
anchor.href = '#';
anchor.appendChild(doc.createTextNode(anchor.title));
if (elem.firstChild) {
elem.replaceChild(anchor, elem.firstChild);
} else {
elem.appendChild(anchor);
}
anchor.addEventListener('click', _self.toolClick, false);
anchor.addEventListener('mouseover', _self.item_mouseover, false);
anchor.addEventListener('mouseout', _self.item_mouseout, false);
if (!(ev.id in _self.tools)) {
_self.tools[ev.id] = elem;
_self.elems.tools.appendChild(elem);
}
// Disable the text tool icon if the Canvas Text API is not supported.
if (ev.id === 'text' && !app.layer.context.fillText &&
!app.layer.context.mozPathText && elem) {
elem.className += ' ' + _self.classPrefix + 'disabled';
anchor.title = lang.tools.textUnsupported;
anchor.removeEventListener('click', _self.toolClick, false);
anchor.addEventListener('click', function (ev) {
ev.preventDefault();
}, false);
}
};
/**
* The <code>toolUnregister</code> application event handler. This method the
* tool element from the GUI.
*
* @param {pwlib.appEvent.toolUnregister} ev The application event object.
*
* @see PaintWeb#toolUnregister the method which allows you to unregister
* tools.
*/
this.toolUnregister = function (ev) {
if (ev.id in _self.tools) {
_self.elems.tools.removeChild(_self.tools[ev.id]);
delete _self.tools[ev.id];
} else {
return;
}
};
/**
* The <code>click</code> event handler for the command DOM elements.
*
* @private
*
* @param {Event} ev The DOM Event object.
*
* @see PaintWeb#commandRegister to register a new command.
*/
this.commandClick = function (ev) {
var cmd = this.parentNode.getAttribute('data-pwCommand');
if (cmd && cmd in app.commands) {
app.commands[cmd].call(this, ev);
}
ev.preventDefault();
try {
$(this).blur();
$(config.guiPlaceholder).focus();
} catch (err) { }
};
/**
* The <code>commandRegister</code> application event handler. GUI elements
* associated to commands are updated to ensure proper user interaction.
*
* @private
*
* @param {pwlib.appEvent.commandRegister} ev The application event object.
*
* @see PaintWeb#commandRegister the method which allows you to register new
* commands.
*/
this.commandRegister = function (ev) {
var elem = _self.commands[ev.id],
anchor = null;
if (!elem) {
return;
}
elem.className += ' ' + _self.classPrefix + 'cmd_' + ev.id;
if(elem.classList.contains('paintweb_buttonStyle')){
button = doc.createElement('button');
button.title = lang.commands[ev.id];
button.type = "button";
button.classList.add('btn');
button.classList.add('btn-outline-dark');
button.classList.add('p-1');
button.classList.add('w-px-100');
if(button.title.indexOf(" [") != -1){
var i = button.title.indexOf(" [");
var str1 = button.title.substr(0,i);
var str2 = button.title.substr(i+1);
button.appendChild(doc.createTextNode(str1));
} else {
button.appendChild(doc.createTextNode(button.title));
}
if (elem.firstChild) {
elem.removeChild(elem.firstChild);
}
elem.appendChild(button);
button.addEventListener('click', _self.commandClick, false);
} else {
anchor = doc.createElement('a');
anchor.title = lang.commands[ev.id];
anchor.href = '#';
anchor.appendChild(doc.createTextNode(anchor.title));
// Remove the text content and append the locale string associated to
// current command inside an anchor element (for better keyboard
// accessibility).
if (elem.firstChild) {
elem.removeChild(elem.firstChild);
}
elem.appendChild(anchor);
anchor.addEventListener('click', _self.commandClick, false);
anchor.addEventListener('mouseover', _self.item_mouseover, false);
anchor.addEventListener('mouseout', _self.item_mouseout, false);
}
};
/**
* The <code>commandUnregister</code> application event handler. This method
* simply removes all the user interactivity from the GUI element associated
* to the command being unregistered.
*
* @private
*
* @param {pwlib.appEvent.commandUnregister} ev The application event object.
*
* @see PaintWeb#commandUnregister the method which allows you to unregister
* commands.
*/
this.commandUnregister = function (ev) {
var elem = _self.commands[ev.id],
anchor = null;
if (!elem) {
return;
}
elem.className = elem.className.replace(' ' + _self.classPrefix + 'cmd_'
+ ev.id, '');
anchor = elem.firstChild;
anchor.removeEventListener('click', this.commands[ev.id], false);
anchor.removeEventListener('mouseover', _self.item_mouseover, false);
anchor.removeEventListener('mouseout', _self.item_mouseout, false);
elem.removeChild(anchor);
};
/**
* The <code>historyUpdate</code> application event handler. GUI elements
* associated to the <code>historyUndo</code> and to the
* <code>historyRedo</code> commands are updated such that they are either
* enabled or disabled, depending on the current history position.
*
* @private
*
* @param {pwlib.appEvent.historyUpdate} ev The application event object.
*
* @see PaintWeb#historyGoto the method which allows you to go to different
* history states.
*/
this.historyUpdate = function (ev) {
var undoElem = _self.commands.historyUndo,
undoState = false,
redoElem = _self.commands.historyRedo,
redoState = false,
className = ' ' + _self.classPrefix + 'disabled',
undoElemState = undoElem.className.indexOf(className) === -1,
redoElemState = redoElem.className.indexOf(className) === -1;
if (ev.currentPos > 1) {
undoState = true;
}
if (ev.currentPos < ev.states) {
redoState = true;
}
if (undoElemState !== undoState) {
if (undoState) {
undoElem.className = undoElem.className.replace(className, '');
} else {
undoElem.className += className;
}
}
if (redoElemState !== redoState) {
if (redoState) {
redoElem.className = redoElem.className.replace(className, '');
} else {
redoElem.className += className;
}
}
};
/**
* The <code>imageSizeChange</code> application event handler. The GUI element
* which displays the image dimensions is updated to display the new image
* size.
*
* <p>Image size refers strictly to the dimensions of the image being edited
* by the user, that's width and height.
*
* @private
* @param {pwlib.appEvent.imageSizeChange} ev The application event object.
*/
this.imageSizeChange = function (ev) {
var imageSize = _self.elems.imageSize;
if (imageSize) {
imageSize.replaceChild(doc.createTextNode(ev.width + 'x' + ev.height), imageSize.firstChild);
}
};
/**
* The <code>canvasSizeChange</code> application event handler. The Canvas
* container element dimensions are updated to the new values, and the image
* resize handle is positioned accordingly.
*
* <p>Canvas size refers strictly to the dimensions of the Canvas elements in
* the browser, changed with CSS style properties, width and height. Scaling
* of the Canvas elements is applied when the user zooms the image or when the
* browser changes the render DPI / zoom.
*
* @private
* @param {pwlib.appEvent.canvasSizeChange} ev The application event object.
*/
this.canvasSizeChange = function (ev) {
var canvasContainer = _self.elems.canvasContainer,
canvasResizer = _self.canvasResizer,
className = ' ' + _self.classPrefix + 'disabled',
resizeHandle = canvasResizer.resizeHandle;
// Update the Canvas container to be the same size as the Canvas elements.
canvasContainer.style.width = ev.width + 'px';
canvasContainer.style.height = ev.height + 'px';
resizeHandle.style.top = ev.height + 'px';
resizeHandle.style.left = ev.width + 'px';
};
/**
* The <code>imageZoom</code> application event handler. The GUI input element
* which displays the image zoom level is updated to display the new value.
*
* @private
* @param {pwlib.appEvent.imageZoom} ev The application event object.
*/
this.imageZoom = function (ev) {
var elem = _self.inputs.imageZoom,
val = MathRound(ev.zoom * 100);
if (elem && elem.value != val) {
elem.value = val;
}
};
/**
* The <code>configChange</code> application event handler. This method
* ensures the GUI input elements stay up-to-date when some PaintWeb
* configuration is modified.
*
* @private
* @param {pwlib.appEvent.configChange} ev The application event object.
*/
this.configChangeHandler = function (ev) {
var cfg = '', input;
if (ev.group) {
cfg = ev.group.replace('.', '_') + '_';
}
cfg += ev.config;
input = _self.inputs[cfg];
// Handle changes for color inputs.
if (!input && (input = _self.colorInputs[cfg])) {
var color = ev.value.replace(/\s+/g, '').
replace(/^rgba\(/, '').replace(/\)$/, '');
color = color.split(',');
input.updateColor({
red: color[0] / 255,
green: color[1] / 255,
blue: color[2] / 255,
alpha: color[3]
});
return;
}
if (!input) {
return;
}
var tag = input.tagName.toLowerCase(),
isInput = tag === 'select' || tag === 'input' || tag === 'textarea';
if (isInput) {
if (input.type === 'checkbox' && input.checked !== ev.value) {
input.checked = ev.value;
}
if (input.type !== 'checkbox' && input.value !== ev.value) {
input.value = ev.value;
}
return;
}
var classActive = ' ' + _self.className + 'configActive';
if (input.hasAttribute('data-pwConfigToggle')) {
var inputActive = input.className.indexOf(classActive) !== -1;
if (ev.value && !inputActive) {
input.className += classActive;
} else if (!ev.value && inputActive) {
input.className = input.className.replace(classActive, '');
}
}
var classActive = ' ' + _self.className + 'configActive',
prevValElem = _self.inputValues[cfg + '_' + ev.previousValue],
valElem = _self.inputValues[cfg + '_' + ev.value];
if (prevValElem && prevValElem.className.indexOf(classActive) !== -1) {
prevValElem.className = prevValElem.className.replace(classActive, '');
}
if (valElem && valElem.className.indexOf(classActive) === -1) {
valElem.className += classActive;
}
};
/**
* The <code>click</code> event handler for DOM elements associated to
* PaintWeb configuration values. These elements rely on parent elements which
* are associated to configuration properties.
*
* <p>This method dispatches the {@link pwlib.appEvent.configChange} event.
*
* @private
* @param {Event} ev The DOM Event object.
*/
this.configValueClick = function (ev) {
var pNode = this.parentNode,
input = pNode._pwConfigParent,
val = pNode.getAttribute('data-pwConfigValue');
if (!input || !input._pwConfigProperty) {
return;
}
ev.preventDefault();
var className = ' ' + _self.classPrefix + 'configActive',
groupRef = input._pwConfigGroupRef,
group = input._pwConfigGroup,
prop = input._pwConfigProperty,
prevVal = groupRef[prop],
prevValElem = _self.inputValues[group.replace('.', '_') + '_' + prop
+ '_' + prevVal];
if (prevVal == val) {
return;
}
if (prevValElem && prevValElem.className.indexOf(className) !== -1) {
prevValElem.className = prevValElem.className.replace(className, '');
}
groupRef[prop] = val;
if (pNode.className.indexOf(className) === -1) {
pNode.className += className;
}
app.events.dispatch(new appEvent.configChange(val, prevVal, prop, group,
groupRef));
};
/**
* The <code>change</code> event handler for input elements associated to
* PaintWeb configuration properties.
*
* <p>This method dispatches the {@link pwlib.appEvent.configChange} event.
*
* @private
*/
this.configInputChange = function () {
if (!this._pwConfigProperty) {
return;
}
var val = this.type === 'checkbox' ? this.checked : this.value,
groupRef = this._pwConfigGroupRef,
group = this._pwConfigGroup,
prop = this._pwConfigProperty,
prevVal = groupRef[prop];
if (this.getAttribute('type') === 'number') {
val = parseInt(val);
if (val != this.value) {
this.value = val;
}
}
if (val == prevVal) {
return;
}
groupRef[prop] = val;
app.events.dispatch(new appEvent.configChange(val, prevVal, prop, group,
groupRef));
};
/**
* The <code>click</code> event handler for DOM elements associated to boolean
* configuration properties. These elements only toggle the true/false value
* of the configuration property.
*
* <p>This method dispatches the {@link pwlib.appEvent.configChange} event.
*
* @private
* @param {Event} ev The DOM Event object.
*/
this.configToggleClick = function (ev) {
var className = ' ' + _self.classPrefix + 'configActive',
pNode = this.parentNode,
groupRef = pNode._pwConfigGroupRef,
group = pNode._pwConfigGroup,
prop = pNode._pwConfigProperty,
elemActive = pNode.className.indexOf(className) !== -1;
ev.preventDefault();
groupRef[prop] = !groupRef[prop];
if (groupRef[prop] && !elemActive) {
pNode.className += className;
} else if (!groupRef[prop] && elemActive) {
pNode.className = pNode.className.replace(className, '');
}
app.events.dispatch(new appEvent.configChange(groupRef[prop],
!groupRef[prop], prop, group, groupRef));
};
/**
* It's same with configToggleClick().
* just remove code for Toggle.
*/
this.configButtonClick = function (ev) {
var className = ' ' + _self.classPrefix + 'configActive',
pNode = this.parentNode,
groupRef = pNode._pwConfigGroupRef,
group = pNode._pwConfigGroup,
prop = pNode._pwConfigProperty,
elemActive = pNode.className.indexOf(className) !== -1;
ev.preventDefault();
groupRef[prop] = !groupRef[prop];
app.events.dispatch(new appEvent.configChange(groupRef[prop],
!groupRef[prop], prop, group, groupRef));
};
/**
* The <code>shadowAllow</code> application event handler. This method
* shows/hide the shadow tab when shadows are allowed/disallowed.
*
* @private
* @param {pwlib.appEvent.shadowAllow} ev The application event object.
*/
this.shadowAllow = function (ev) {
if ('shadow' in _self.tabPanels.main.tabs) {
if (ev.allowed) {
_self.tabPanels.main.tabShow('shadow');
} else {
_self.tabPanels.main.tabHide('shadow');
}
}
};
/**
* The <code>clipboardUpdate</code> application event handler. The GUI element
* associated to the <code>clipboardPaste</code> command is updated to be
* disabled/enabled depending on the event.
*
* @private
* @param {pwlib.appEvent.clipboardUpdate} ev The application event object.
*/
this.clipboardUpdate = function (ev) {
var classDisabled = ' ' + _self.classPrefix + 'disabled',
elem, elemEnabled,
elems = [_self.commands.clipboardPaste,
_self.elems.selTab_clipboardPaste];
for (var i = 0, n = elems.length; i < n; i++) {
elem = elems[i];
if (!elem) {
continue;
}
elemEnabled = elem.className.indexOf(classDisabled) === -1;
if (!ev.data && elemEnabled) {
elem.className += classDisabled;
} else if (ev.data && !elemEnabled) {
elem.className = elem.className.replace(classDisabled, '');
}
}
};
/**
* The <code>selectionChange</code> application event handler. The GUI
* elements associated to the <code>selectionCut</code> and
* <code>selectionCopy</code> commands are updated to be disabled/enabled
* depending on the event.
*
* @private
* @param {pwlib.appEvent.selectionChange} ev The application event object.
*/
this.selectionChange = function (ev) {
var classDisabled = ' ' + _self.classPrefix + 'disabled',
elem, elemEnabled,
elems = [_self.commands.selectionCut, _self.commands.selectionCopy,
_self.elems.selTab_selectionCut, _self.elems.selTab_selectionCopy,
_self.commands.selectionDelete, _self.commands.selectionFill,
_self.commands.selectionCrop];
for (var i = 0, n = elems.length; i < n; i++) {
elem = elems[i];
if (!elem) {
continue;
}
elemEnabled = elem.className.indexOf(classDisabled) === -1;
if (ev.state === ev.STATE_NONE && elemEnabled) {
elem.className += classDisabled;
} else if (ev.state === ev.STATE_SELECTED && !elemEnabled) {
elem.className = elem.className.replace(classDisabled, '');
}
}
};
/**
* Show the graphical user interface.
*
* <p>This method dispatches the {@link pwlib.appEvent.guiShow} application
* event.
*/
this.show = function () {
var placeholder = config.guiPlaceholder,
className = this.classPrefix + 'placeholder',
re = new RegExp('\\b' + className);
if (!re.test(placeholder.className)) {
placeholder.className += ' ' + className;
}
try {
placeholder.focus();
} catch (err) { }
app.events.dispatch(new appEvent.guiShow());
};
/**
* Hide the graphical user interface.
*
* <p>This method dispatches the {@link pwlib.appEvent.guiHide} application
* event.
*/
this.hide = function () {
var placeholder = config.guiPlaceholder,
re = new RegExp('\\b' + this.classPrefix + 'placeholder', 'g');
placeholder.className = placeholder.className.replace(re, '');
app.events.dispatch(new appEvent.guiHide());
};
/**
* The application destroy event handler. This method is invoked by the main
* PaintWeb application when the instance is destroyed, for the purpose of
* cleaning-up the GUI-related things from the document add by the current
* instance.
*
* @private
*/
this.destroy = function () {
var placeholder = config.guiPlaceholder;
while(placeholder.hasChildNodes()) {
placeholder.removeChild(placeholder.firstChild);
}
};
/**
* Resize the PaintWeb graphical user interface.
*
* <p>This method dispatches the {@link pwlib.appEvent.configChange} event for
* the "viewportWidth" and "viewportHeight" configuration properties. Both
* properties are updated to hold the new values you give.
*
* <p>Once the GUI is resized, the {@link pwlib.appEvent.viewportSizeChange}
* event is also dispatched.
*
* @param {String} width The new width you want. Make sure the value is a CSS
* length, like "50%", "450px" or "30em".
*
* @param {String} height The new height you want.
*/
this.resizeTo = function (width, height) {
if (!width || !height) {
return;
}
var width_old = config.viewportWidth,
height_old = config.viewportHeight;
config.viewportWidth = width;
config.viewportHeight = height;
app.events.dispatch(new appEvent.configChange(width, width_old,
'viewportWidth', '', config));
app.events.dispatch(new appEvent.configChange(height, height_old,
'viewportHeight', '', config));
config.guiPlaceholder.style.width = config.viewportWidth;
this.elems.viewport.style.height = config.viewportHeight;
app.events.dispatch(new appEvent.viewportSizeChange(width, height));
};
/**
* The state change event handler for the Hand tool. This function
* enables/disables the Hand tool by checking if the current image fits into
* the viewport or not.
*
* <p>This function is invoked when one of the following application events is
* dispatched: <code>viewportSizeChange</code>, <code>canvasSizeChange</code>
* or <code>appInit</code.
*
* @private
* @param
* {pwlib.appEvent.viewportSizeChange|pwlib.appEvent.canvasSizeChange|pwlib.appEvent.appInit}
* [ev] The application event object.
*/
this.toolHandStateChange = function (ev) {
var cwidth = 0,
cheight = 0,
className = ' ' + _self.classPrefix + 'disabled',
hand = _self.tools.hand,
viewport = _self.elems.viewport;
if (!hand) {
return;
}
if (ev.type === 'canvasSizeChange') {
cwidth = ev.width;
cheight = ev.height;
} else {
var containerStyle = _self.elems.canvasContainer.style;
cwidth = parseInt(containerStyle.width);
cheight = parseInt(containerStyle.height);
}
// FIXME: it should be noted that when PaintWeb loads, the entire GUI is
// hidden, and win.getComputedStyle() style tells that the viewport
// width/height is 0.
cs = win.getComputedStyle(viewport, null);
var vwidth = parseInt(cs.width),
vheight = parseInt(cs.height),
enableHand = false,
handState = hand.className.indexOf(className) === -1;
if (vheight < cheight || vwidth < cwidth) {
enableHand = true;
}
if (enableHand && !handState) {
hand.className = hand.className.replace(className, '');
} else if (!enableHand && handState) {
hand.className += className;
}
if (!enableHand && app.tool && app.tool._id === 'hand' && 'prevTool' in
app.tool) {
app.toolActivate(app.tool.prevTool, ev);
}
};
};
/**
* @class A floating panel GUI element.
*
* @private
*
* @param {pwlib.gui} gui Reference to the PaintWeb GUI object.
*
* @param {Element} container Reference to the DOM element you want to transform
* into a floating panel.
*/
pwlib.guiFloatingPanel = function (gui, container) {
var _self = this,
appEvent = pwlib.appEvent,
cStyle = container.style,
doc = gui.app.doc,
guiPlaceholder = gui.app.config.guiPlaceholder,
lang = gui.app.lang,
panels = gui.floatingPanels,
win = gui.app.win,
zIndex_step = 200;
// These hold the mouse starting location during the drag operation.
var mx, my;
// These hold the panel starting location during the drag operation.
var ptop, pleft;
/**
* Panel state: hidden.
* @constant
*/
this.STATE_HIDDEN = 0;
/**
* Panel state: visible.
* @constant
*/
this.STATE_VISIBLE = 1;
/**
* Panel state: minimized.
* @constant
*/
this.STATE_MINIMIZED = 3;
/**
* Panel state: the user is dragging the floating panel.
* @constant
*/
this.STATE_DRAGGING = 4;
/**
* Tells the state of the floating panel: hidden/minimized/visible or if it's
* being dragged.
* @type Number
*/
this.state = -1;
/**
* Floating panel ID. This is the ID used in the
* <var>data-pwFloatingPanel</var> element attribute.
* @type String
*/
this.id = null;
/**
* Reference to the floating panel element.
* @type Element
*/
this.container = container;
/**
* The viewport element. This element is the first parent element which has
* the style.overflow set to "auto" or "scroll".
* @type Element
*/
this.viewport = null;
/**
* Custom application events interface.
* @type pwlib.appEvents
*/
this.events = null;
/**
* The panel content element.
* @type Element
*/
this.content = null;
// The initial viewport scroll position.
var vScrollLeft = 0, vScrollTop = 0,
btn_close = null, btn_minimize = null;
/**
* Initialize the floating panel.
* @private
*/
function init () {
_self.events = new pwlib.appEvents(_self);
_self.id = _self.container.getAttribute('data-pwFloatingPanel');
var ttl = _self.container.getElementsByTagName('h1')[0],
content = _self.container.getElementsByTagName('div')[0],
cs = win.getComputedStyle(_self.container, null),
zIndex = parseInt(cs.zIndex);
cStyle.zIndex = cs.zIndex;
if (zIndex > panels.zIndex_) {
panels.zIndex_ = zIndex;
}
_self.container.className += ' ' + gui.classPrefix + 'floatingPanel ' +
gui.classPrefix + 'floatingPanel_' + _self.id;
// the content
content.className += ' ' + gui.classPrefix + 'floatingPanel_content';
_self.content = content;
// setup the title element
ttl.className += ' ' + gui.classPrefix + 'floatingPanel_title';
ttl.replaceChild(doc.createTextNode(lang.floatingPanels[_self.id]),
ttl.firstChild);
ttl.addEventListener('mousedown', ev_mousedown, false);
// allow auto-hide for the panel
if (_self.container.getAttribute('data-pwPanelHide') === 'true') {
_self.hide();
} else {
_self.state = _self.STATE_VISIBLE;
}
// Find the viewport parent element.
var pNode = _self.container.parentNode,
found = null;
while (!found && pNode) {
if (pNode.nodeName.toLowerCase() === 'html') {
found = pNode;
break;
}
cs = win.getComputedStyle(pNode, null);
if (cs && (cs.overflow === 'scroll' || cs.overflow === 'auto')) {
found = pNode;
} else {
pNode = pNode.parentNode;
}
}
_self.viewport = found;
// add the panel minimize button.
btn_minimize = doc.createElement('a');
btn_minimize.href = '#';
btn_minimize.title = lang.floatingPanelMinimize;
btn_minimize.className = gui.classPrefix + 'floatingPanel_minimize';
btn_minimize.addEventListener('click', ev_minimize, false);
btn_minimize.appendChild(doc.createTextNode(btn_minimize.title));
_self.container.insertBefore(btn_minimize, content);
// add the panel close button.
btn_close = doc.createElement('a');
btn_close.href = '#';
btn_close.title = lang.floatingPanelClose;
btn_close.className = gui.classPrefix + 'floatingPanel_close';
btn_close.addEventListener('click', ev_close, false);
btn_close.appendChild(doc.createTextNode(btn_close.title));
_self.container.insertBefore(btn_close, content);
// setup the panel resize handle.
if (_self.container.getAttribute('data-pwPanelResizable') === 'true') {
var resizeHandle = doc.createElement('div');
resizeHandle.className = gui.classPrefix + 'floatingPanel_resizer';
_self.container.appendChild(resizeHandle);
_self.resizer = new pwlib.guiResizer(gui, resizeHandle, _self.container);
}
};
/**
* The <code>click</code> event handler for the panel Minimize button element.
*
* <p>This method dispatches the {@link
* pwlib.appEvent.guiFloatingPanelStateChange} application event.
*
* @private
* @param {Event} ev The DOM Event object.
*/
function ev_minimize (ev) {
ev.preventDefault();
try {
this.focus();
} catch (err) { }
var classMinimized = ' ' + gui.classPrefix + 'floatingPanel_minimized';
if (_self.state === _self.STATE_MINIMIZED) {
_self.state = _self.STATE_VISIBLE;
this.title = lang.floatingPanelMinimize;
this.className = gui.classPrefix + 'floatingPanel_minimize';
this.replaceChild(doc.createTextNode(this.title), this.firstChild);
if (_self.container.className.indexOf(classMinimized) !== -1) {
_self.container.className
= _self.container.className.replace(classMinimized, '');
}
} else if (_self.state === _self.STATE_VISIBLE) {
_self.state = _self.STATE_MINIMIZED;
this.title = lang.floatingPanelRestore;
this.className = gui.classPrefix + 'floatingPanel_restore';
this.replaceChild(doc.createTextNode(this.title), this.firstChild);
if (_self.container.className.indexOf(classMinimized) === -1) {
_self.container.className += classMinimized;
}
}
_self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state));
_self.bringOnTop();
};
/**
* The <code>click</code> event handler for the panel Close button element.
* This hides the floating panel.
*
* <p>This method dispatches the {@link
* pwlib.appEvent.guiFloatingPanelStateChange} application event.
*
* @private
* @param {Event} ev The DOM Event object.
*/
function ev_close (ev) {
ev.preventDefault();
_self.hide();
try {
guiPlaceholder.focus();
} catch (err) { }
};
/**
* The <code>mousedown</code> event handler. This is invoked when you start
* dragging the floating panel.
*
* <p>This method dispatches the {@link
* pwlib.appEvent.guiFloatingPanelStateChange} application event.
*
* @private
* @param {Event} ev The DOM Event object.
*/
function ev_mousedown (ev) {
_self.state = _self.STATE_DRAGGING;
mx = ev.clientX;
my = ev.clientY;
var cs = win.getComputedStyle(_self.container, null);
ptop = parseInt(cs.top);
pleft = parseInt(cs.left);
if (_self.viewport) {
vScrollLeft = _self.viewport.scrollLeft;
vScrollTop = _self.viewport.scrollTop;
}
_self.bringOnTop();
doc.addEventListener('mousemove', ev_mousemove, false);
doc.addEventListener('mouseup', ev_mouseup, false);
_self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state));
if (ev.preventDefault) {
ev.preventDefault();
}
};
/**
* The <code>mousemove</code> event handler. This performs the actual move of
* the floating panel.
*
* @private
* @param {Event} ev The DOM Event object.
*/
function ev_mousemove (ev) {
var x = pleft + ev.clientX - mx,
y = ptop + ev.clientY - my;
if (_self.viewport) {
if (_self.viewport.scrollLeft !== vScrollLeft) {
x += _self.viewport.scrollLeft - vScrollLeft;
}
if (_self.viewport.scrollTop !== vScrollTop) {
y += _self.viewport.scrollTop - vScrollTop;
}
}
cStyle.left = x + 'px';
cStyle.top = y + 'px';
};
/**
* The <code>mouseup</code> event handler. This ends the panel drag operation.
*
* <p>This method dispatches the {@link
* pwlib.appEvent.guiFloatingPanelStateChange} application event.
*
* @private
* @param {Event} ev The DOM Event object.
*/
function ev_mouseup (ev) {
if (_self.container.className.indexOf(' ' + gui.classPrefix
+ 'floatingPanel_minimized') !== -1) {
_self.state = _self.STATE_MINIMIZED;
} else {
_self.state = _self.STATE_VISIBLE;
}
doc.removeEventListener('mousemove', ev_mousemove, false);
doc.removeEventListener('mouseup', ev_mouseup, false);
try {
guiPlaceholder.focus();
} catch (err) { }
_self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state));
};
/**
* Bring the panel to the top. This method makes sure the current floating
* panel is visible.
*/
this.bringOnTop = function () {
panels.zIndex_ += zIndex_step;
cStyle.zIndex = panels.zIndex_;
};
/**
* Hide the panel.
*
* <p>This method dispatches the {@link
* pwlib.appEvent.guiFloatingPanelStateChange} application event.
*/
this.hide = function () {
cStyle.display = 'none';
_self.state = _self.STATE_HIDDEN;
_self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state));
};
/**
* Show the panel.
*
* <p>This method dispatches the {@link
* pwlib.appEvent.guiFloatingPanelStateChange} application event.
*/
this.show = function () {
if (_self.state === _self.STATE_VISIBLE) {
return;
}
cStyle.display = 'block';
_self.state = _self.STATE_VISIBLE;
var classMinimized = ' ' + gui.classPrefix + 'floatingPanel_minimized';
if (_self.container.className.indexOf(classMinimized) !== -1) {
_self.container.className
= _self.container.className.replace(classMinimized, '');
btn_minimize.className = gui.classPrefix + 'floatingPanel_minimize';
btn_minimize.title = lang.floatingPanelMinimize;
btn_minimize.replaceChild(doc.createTextNode(btn_minimize.title),
btn_minimize.firstChild);
}
_self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state));
_self.bringOnTop();
};
/**
* Toggle the panel visibility.
*
* <p>This method dispatches the {@link
* pwlib.appEvent.guiFloatingPanelStateChange} application event.
*/
this.toggle = function () {
if (_self.state === _self.STATE_VISIBLE || _self.state ===
_self.STATE_MINIMIZED) {
_self.hide();
} else {
_self.show();
}
};
init();
};
/**
* @class The state change event for the floating panel. This event is fired
* when the floating panel changes its state. This event is not cancelable.
*
* @augments pwlib.appEvent
*
* @param {Number} state The floating panel state.
*/
pwlib.appEvent.guiFloatingPanelStateChange = function (state) {
/**
* Panel state: hidden.
* @constant
*/
this.STATE_HIDDEN = 0;
/**
* Panel state: visible.
* @constant
*/
this.STATE_VISIBLE = 1;
/**
* Panel state: minimized.
* @constant
*/
this.STATE_MINIMIZED = 3;
/**
* Panel state: the user is dragging the floating panel.
* @constant
*/
this.STATE_DRAGGING = 4;
/**
* The current floating panel state.
* @type Number
*/
this.state = state;
pwlib.appEvent.call(this, 'guiFloatingPanelStateChange');
};
/**
* @class Resize handler.
*
* @private
*
* @param {pwlib.gui} gui Reference to the PaintWeb GUI object.
*
* @param {Element} resizeHandle Reference to the resize handle DOM element.
* This is the element users will be able to drag to achieve the resize effect
* on the <var>container</var> element.
*
* @param {Element} container Reference to the container DOM element. This is
* the element users will be able to resize using the <var>resizeHandle</var>
* element.
*/
pwlib.guiResizer = function (gui, resizeHandle, container) {
var _self = this,
cStyle = container.style,
doc = gui.app.doc,
guiResizeEnd = pwlib.appEvent.guiResizeEnd,
guiResizeMouseMove = pwlib.appEvent.guiResizeMouseMove,
guiResizeStart = pwlib.appEvent.guiResizeStart,
win = gui.app.win;
/**
* Custom application events interface.
* @type pwlib.appEvents
*/
this.events = null;
/**
* The resize handle DOM element.
* @type Element
*/
this.resizeHandle = resizeHandle;
/**
* The container DOM element. This is the element that's resized by the user
* when he/she drags the resize handle.
* @type Element
*/
this.container = container;
/**
* The viewport element. This element is the first parent element which has
* the style.overflow set to "auto" or "scroll".
* @type Element
*/
this.viewport = null;
/**
* Tells if the GUI resizer should dispatch the {@link
* pwlib.appEvent.guiResizeMouseMove} application event when the user moves
* the mouse during the resize operation.
*
* @type Boolean
* @default false
*/
this.dispatchMouseMove = false;
/**
* Tells if the user resizing the container now.
*
* @type Boolean
* @default false
*/
this.resizing = false;
// The initial position of the mouse.
var mx = 0, my = 0;
// The initial container dimensions.
var cWidth = 0, cHeight = 0;
// The initial viewport scroll position.
var vScrollLeft = 0, vScrollTop = 0;
/**
* Initialize the resize functionality.
* @private
*/
function init () {
_self.events = new pwlib.appEvents(_self);
resizeHandle.addEventListener('mousedown', ev_mousedown, false);
// Find the viewport parent element.
var cs, pNode = _self.container.parentNode,
found = null;
while (!found && pNode) {
if (pNode.nodeName.toLowerCase() === 'html') {
found = pNode;
break;
}
cs = win.getComputedStyle(pNode, null);
if (cs && (cs.overflow === 'scroll' || cs.overflow === 'auto')) {
found = pNode;
} else {
pNode = pNode.parentNode;
}
}
_self.viewport = found;
};
/**
* The <code>mousedown</code> event handler. This starts the resize operation.
*
* <p>This function dispatches the {@link pwlib.appEvent.guiResizeStart}
* event.
*
* @private
* @param {Event} ev The DOM Event object.
*/
function ev_mousedown (ev) {
mx = ev.clientX;
my = ev.clientY;
var cs = win.getComputedStyle(_self.container, null);
cWidth = parseInt(cs.width);
cHeight = parseInt(cs.height);
var cancel = _self.events.dispatch(new guiResizeStart(mx, my, cWidth,
cHeight));
if (cancel) {
return;
}
if (_self.viewport) {
vScrollLeft = _self.viewport.scrollLeft;
vScrollTop = _self.viewport.scrollTop;
}
_self.resizing = true;
doc.addEventListener('mousemove', ev_mousemove, false);
doc.addEventListener('mouseup', ev_mouseup, false);
if (ev.preventDefault) {
ev.preventDefault();
}
if (ev.stopPropagation) {
ev.stopPropagation();
}
};
/**
* The <code>mousemove</code> event handler. This performs the actual resizing
* of the <var>container</var> element.
*
* @private
* @param {Event} ev The DOM Event object.
*/
function ev_mousemove (ev) {
var w = cWidth + ev.clientX - mx,
h = cHeight + ev.clientY - my;
if (_self.viewport) {
if (_self.viewport.scrollLeft !== vScrollLeft) {
w += _self.viewport.scrollLeft - vScrollLeft;
}
if (_self.viewport.scrollTop !== vScrollTop) {
h += _self.viewport.scrollTop - vScrollTop;
}
}
cStyle.width = w + 'px';
cStyle.height = h + 'px';
if (_self.dispatchMouseMove) {
_self.events.dispatch(new guiResizeMouseMove(ev.clientX, ev.clientY, w,
h));
}
};
/**
* The <code>mouseup</code> event handler. This ends the resize operation.
*
* <p>This function dispatches the {@link pwlib.appEvent.guiResizeEnd} event.
*
* @private
* @param {Event} ev The DOM Event object.
*/
function ev_mouseup (ev) {
var cancel = _self.events.dispatch(new guiResizeEnd(ev.clientX, ev.clientY,
parseInt(cStyle.width), parseInt(cStyle.height)));
if (cancel) {
return;
}
_self.resizing = false;
doc.removeEventListener('mousemove', ev_mousemove, false);
doc.removeEventListener('mouseup', ev_mouseup, false);
};
init();
};
/**
* @class The GUI element resize start event. This event is cancelable.
*
* @augments pwlib.appEvent
*
* @param {Number} x The mouse location on the x-axis.
* @param {Number} y The mouse location on the y-axis.
* @param {Number} width The element width.
* @param {Number} height The element height.
*/
pwlib.appEvent.guiResizeStart = function (x, y, width, height) {
/**
* The mouse location on the x-axis.
* @type Number
*/
this.x = x;
/**
* The mouse location on the y-axis.
* @type Number
*/
this.y = y;
/**
* The element width.
* @type Number
*/
this.width = width;
/**
* The element height.
* @type Number
*/
this.height = height;
pwlib.appEvent.call(this, 'guiResizeStart', true);
};
/**
* @class The GUI element resize end event. This event is cancelable.
*
* @augments pwlib.appEvent
*
* @param {Number} x The mouse location on the x-axis.
* @param {Number} y The mouse location on the y-axis.
* @param {Number} width The element width.
* @param {Number} height The element height.
*/
pwlib.appEvent.guiResizeEnd = function (x, y, width, height) {
/**
* The mouse location on the x-axis.
* @type Number
*/
this.x = x;
/**
* The mouse location on the y-axis.
* @type Number
*/
this.y = y;
/**
* The element width.
* @type Number
*/
this.width = width;
/**
* The element height.
* @type Number
*/
this.height = height;
pwlib.appEvent.call(this, 'guiResizeEnd', true);
};
/**
* @class The GUI element resize mouse move event. This event is not cancelable.
*
* @augments pwlib.appEvent
*
* @param {Number} x The mouse location on the x-axis.
* @param {Number} y The mouse location on the y-axis.
* @param {Number} width The element width.
* @param {Number} height The element height.
*/
pwlib.appEvent.guiResizeMouseMove = function (x, y, width, height) {
/**
* The mouse location on the x-axis.
* @type Number
*/
this.x = x;
/**
* The mouse location on the y-axis.
* @type Number
*/
this.y = y;
/**
* The element width.
* @type Number
*/
this.width = width;
/**
* The element height.
* @type Number
*/
this.height = height;
pwlib.appEvent.call(this, 'guiResizeMouseMove');
};
/**
* @class The tabbed panel GUI component.
*
* @private
*
* @param {pwlib.gui} gui Reference to the PaintWeb GUI object.
*
* @param {Element} panel Reference to the panel DOM element.
*/
pwlib.guiTabPanel = function (gui, panel) {
var _self = this,
appEvent = pwlib.appEvent,
doc = gui.app.doc,
lang = gui.app.lang;
/**
* Custom application events interface.
* @type pwlib.appEvents
*/
this.events = null;
/**
* Panel ID. The ID is the same as the data-pwTabPanel attribute value of the
* panel DOM element .
*
* @type String.
*/
this.id = null;
/**
* Holds references to the DOM element of each tab and tab button.
* @type Object
*/
this.tabs = {};
/**
* Reference to the tab buttons DOM element.
* @type Element
*/
this.tabButtons = null;
/**
* The panel container DOM element.
* @type Element
*/
this.container = panel;
/**
* Holds the ID of the currently active tab.
* @type String
*/
this.tabId = null;
/**
* Holds the ID of the previously active tab.
*
* @private
* @type String
*/
var prevTabId_ = null;
/**
* Initialize the toolbar functionality.
* @private
*/
function init () {
_self.events = new pwlib.appEvents(_self);
_self.id = _self.container.getAttribute('data-pwTabPanel');
// Add two class names, the generic .paintweb_tabPanel and another class
// name specific to the current tab panel: .paintweb_tabPanel_id.
_self.container.className += ' ' + gui.classPrefix + 'tabPanel'
+ ' ' + gui.classPrefix + 'tabPanel_' + _self.id;
var tabButtons = doc.createElement('ul'),
tabButton = null,
tabDefault = _self.container.getAttribute('data-pwTabDefault') || null,
childNodes = _self.container.childNodes,
type = gui.app.ELEMENT_NODE,
elem = null,
tabId = null,
anchor = null;
tabButtons.className = gui.classPrefix + 'tabsList';
// Find all the tabs in the current panel container element.
for (var i = 0; elem = childNodes[i]; i++) {
if (elem.nodeType !== type) {
continue;
}
// A tab is any element with a given data-pwTab attribute.
tabId = elem.getAttribute('data-pwTab');
if (!tabId) {
continue;
}
// two class names, the generic .paintweb_tab and the tab-specific class
// name .paintweb_tabPanelId_tabId.
elem.className += ' ' + gui.classPrefix + 'tab ' + gui.classPrefix
+ _self.id + '_' + tabId;
tabButton = doc.createElement('li');
tabButton._pwTab = tabId;
anchor = doc.createElement('a');
anchor.href = '#';
anchor.addEventListener('click', ev_tabClick, false);
if (_self.id in lang.tabs) {
anchor.title = lang.tabs[_self.id][tabId + 'Title'] ||
lang.tabs[_self.id][tabId];
anchor.appendChild(doc.createTextNode(lang.tabs[_self.id][tabId]));
}
if ((tabDefault && tabId === tabDefault) ||
(!tabDefault && !_self.tabId)) {
_self.tabId = tabId;
tabButton.className = gui.classPrefix + 'tabActive';
} else {
prevTabId_ = tabId;
elem.style.display = 'none';
}
// automatically hide the tab
if (elem.getAttribute('data-pwTabHide') === 'true') {
tabButton.style.display = 'none';
}
_self.tabs[tabId] = {container: elem, button: tabButton};
tabButton.appendChild(anchor);
tabButtons.appendChild(tabButton);
}
_self.tabButtons = tabButtons;
_self.container.appendChild(tabButtons);
};
/**
* The <code>click</code> event handler for tab buttons. This function simply
* activates the tab the user clicked.
*
* @private
* @param {Event} ev The DOM Event object.
*/
function ev_tabClick (ev) {
ev.preventDefault();
_self.tabActivate(this.parentNode._pwTab);
};
/**
* Activate a tab by ID.
*
* <p>This method dispatches the {@link pwlib.appEvent.guiTabActivate} event.
*
* @param {String} tabId The ID of the tab you want to activate.
* @returns {Boolean} True if the tab has been activated successfully, or
* false if not.
*/
this.tabActivate = function (tabId) {
if (!tabId || !(tabId in this.tabs)) {
return false;
} else if (tabId === this.tabId) {
return true;
}
var ev = new appEvent.guiTabActivate(tabId, this.tabId),
cancel = this.events.dispatch(ev),
elem = null,
tabButton = null;
if (cancel) {
return false;
}
// Deactivate the currently active tab.
if (this.tabId in this.tabs) {
elem = this.tabs[this.tabId].container;
elem.style.display = 'none';
tabButton = this.tabs[this.tabId].button;
tabButton.className = '';
prevTabId_ = this.tabId;
}
// Activate the new tab.
elem = this.tabs[tabId].container;
elem.style.display = '';
tabButton = this.tabs[tabId].button;
tabButton.className = gui.classPrefix + 'tabActive';
tabButton.style.display = ''; // make sure the tab is not hidden
this.tabId = tabId;
try {
tabButton.firstChild.focus();
} catch (err) { }
return true;
};
/**
* Hide a tab by ID.
*
* @param {String} tabId The ID of the tab you want to hide.
* @returns {Boolean} True if the tab has been hidden successfully, or false
* if not.
*/
this.tabHide = function (tabId) {
if (!(tabId in this.tabs)) {
return false;
}
if (this.tabId === tabId) {
this.tabActivate(prevTabId_);
}
this.tabs[tabId].button.style.display = 'none';
return true;
};
/**
* Show a tab by ID.
*
* @param {String} tabId The ID of the tab you want to show.
* @returns {Boolean} True if the tab has been displayed successfully, or
* false if not.
*/
this.tabShow = function (tabId) {
if (!(tabId in this.tabs)) {
return false;
}
this.tabs[tabId].button.style.display = '';
return true;
};
init();
};
/**
* @class The GUI tab activation event. This event is cancelable.
*
* @augments pwlib.appEvent
*
* @param {String} tabId The ID of the tab being activated.
* @param {String} prevTabId The ID of the previously active tab.
*/
pwlib.appEvent.guiTabActivate = function (tabId, prevTabId) {
/**
* The ID of the tab being activated.
* @type String
*/
this.tabId = tabId;
/**
* The ID of the previously active tab.
* @type String
*/
this.prevTabId = prevTabId;
pwlib.appEvent.call(this, 'guiTabActivate', true);
};
/**
* @class The color input GUI component.
*
* @private
*
* @param {pwlib.gui} gui Reference to the PaintWeb GUI object.
*
* @param {Element} input Reference to the DOM input element. This can be
* a span, a div, or any other tag.
*/
pwlib.guiColorInput = function (gui, input) {
var _self = this,
colormixer = null,
config = gui.app.config,
doc = gui.app.doc,
MathRound = Math.round,
lang = gui.app.lang;
/**
* Color input ID. The ID is the same as the data-pwColorInput attribute value
* of the DOM input element .
*
* @type String.
*/
this.id = null;
/**
* The color input element DOM reference.
*
* @type Element
*/
this.input = input;
/**
* The configuration property to which this color input is attached to.
* @type String
*/
this.configProperty = null;
/**
* The configuration group to which this color input is attached to.
* @type String
*/
this.configGroup = null;
/**
* Reference to the configuration object which holds the color input value.
* @type String
*/
this.configGroupRef = null;
/**
* Holds the current color displayed by the input.
*
* @type Object
*/
this.color = {red: 0, green: 0, blue: 0, alpha: 0};
/**
* Initialize the color input functionality.
* @private
*/
function init () {
var cfgAttr = _self.input.getAttribute('data-pwColorInput'),
cfgNoDots = cfgAttr.replace('.', '_'),
cfgArray = cfgAttr.split('.'),
cfgProp = cfgArray.pop(),
cfgGroup = cfgArray.join('.'),
cfgGroupRef = config,
langGroup = lang.inputs,
labelElem = _self.input.parentNode,
anchor = doc.createElement('a'),
color;
for (var i = 0, n = cfgArray.length; i < n; i++) {
cfgGroupRef = cfgGroupRef[cfgArray[i]];
langGroup = langGroup[cfgArray[i]];
}
_self.configProperty = cfgProp;
_self.configGroup = cfgGroup;
_self.configGroupRef = cfgGroupRef;
_self.id = cfgNoDots;
_self.input.className += ' ' + gui.classPrefix + 'colorInput'
+ ' ' + gui.classPrefix + _self.id;
labelElem.replaceChild(doc.createTextNode(langGroup[cfgProp]),
labelElem.firstChild);
color = _self.configGroupRef[_self.configProperty];
color = color.replace(/\s+/g, '').replace(/^rgba\(/, '').replace(/\)$/, '');
color = color.split(',');
_self.color.red = color[0] / 255;
_self.color.green = color[1] / 255;
_self.color.blue = color[2] / 255;
_self.color.alpha = color[3];
anchor.style.backgroundColor = 'rgb(' + color[0] + ',' + color[1] + ','
+ color[2] + ')';
anchor.style.opacity = color[3];
anchor.href = '#';
anchor.title = langGroup[cfgProp + 'Title'] || langGroup[cfgProp];
anchor.appendChild(doc.createTextNode(lang.inputs.colorInputAnchorContent));
anchor.addEventListener('click', ev_input_click, false);
_self.input.replaceChild(anchor, _self.input.firstChild);
};
/**
* The <code>click</code> event handler for the color input element. This
* function shows/hides the Color Mixer panel.
*
* @private
* @param {Event} ev The DOM Event object.
*/
function ev_input_click (ev) {
ev.preventDefault();
if (!colormixer) {
colormixer = gui.app.extensions.colormixer;
}
if (!colormixer.targetInput || colormixer.targetInput.id !== _self.id) {
colormixer.show({
id: _self.id,
configProperty: _self.configProperty,
configGroup: _self.configGroup,
configGroupRef: _self.configGroupRef,
show: colormixer_show,
hide: colormixer_hide
}, _self.color);
} else {
colormixer.hide();
}
};
/**
* The color mixer <code>show</code> event handler. This function is invoked
* when the color mixer is shown.
* @private
*/
function colormixer_show () {
var classActive = ' ' + gui.classPrefix + 'colorInputActive',
elemActive = _self.input.className.indexOf(classActive) !== -1;
if (!elemActive) {
_self.input.className += classActive;
}
};
/**
* The color mixer <code>hide</code> event handler. This function is invoked
* when the color mixer is hidden.
* @private
*/
function colormixer_hide () {
var classActive = ' ' + gui.classPrefix + 'colorInputActive',
elemActive = _self.input.className.indexOf(classActive) !== -1;
if (elemActive) {
_self.input.className = _self.input.className.replace(classActive, '');
}
};
/**
* Update color. This method allows the change of the color values associated
* to the current color input.
*
* <p>This method is used by the color picker tool and by the global GUI
* <code>configChange</code> application event handler.
*
* @param {Object} color The new color values. The object must have four
* properties: <var>red</var>, <var>green</var>, <var>blue</var> and
* <var>alpha</var>. All values must be between 0 and 1.
*/
this.updateColor = function (color) {
var anchor = _self.input.firstChild.style;
anchor.opacity = color.alpha;
anchor.backgroundColor = 'rgb(' + MathRound(color.red * 255) + ',' +
MathRound(color.green * 255) + ',' +
MathRound(color.blue * 255) + ')';
_self.color.red = color.red;
_self.color.green = color.green;
_self.color.blue = color.blue;
_self.color.alpha = color.alpha;
};
init();
};
// vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: