1"use strict";
2/*
3 * Copyright (C) 2012 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26var global = {
27    argumentsReceived: false,
28    params: null
29};
30
31/**
32 * @param {Event} event
33 */
34function handleMessage(event) {
35    initialize(JSON.parse(event.data));
36    global.argumentsReceived = true;
37}
38
39/**
40 * @param {!Object} args
41 */
42function initialize(args) {
43    global.params = args;
44    var main = $("main");
45    main.innerHTML = "";
46    var errorString = validateArguments(args);
47    if (errorString) {
48        main.textContent = "Internal error: " + errorString;
49        resizeWindow(main.offsetWidth, main.offsetHeight);
50    } else
51        new ColorPicker(main, args);
52}
53
54// The DefaultColorPalette is used when the list of values are empty.
55var DefaultColorPalette = ["#000000", "#404040", "#808080", "#c0c0c0",
56    "#ffffff", "#980000", "#ff0000", "#ff9900", "#ffff00", "#00ff00", "#00ffff",
57    "#4a86e8", "#0000ff", "#9900ff", "#ff00ff"];
58
59function handleArgumentsTimeout() {
60    if (global.argumentsReceived)
61        return;
62    var args = {
63        values : DefaultColorPalette,
64        otherColorLabel: "Other..."
65    };
66    initialize(args);
67}
68
69/**
70 * @param {!Object} args
71 * @return {?string} An error message, or null if the argument has no errors.
72 */
73function validateArguments(args) {
74    if (!args.values)
75        return "No values.";
76    if (!args.otherColorLabel)
77        return "No otherColorLabel.";
78    return null;
79}
80
81function ColorPicker(element, config) {
82    Picker.call(this, element, config);
83    this._config = config;
84    if (this._config.values.length === 0)
85        this._config.values = DefaultColorPalette;
86    this._container = null;
87    this._layout();
88    document.body.addEventListener("keydown", this._handleKeyDown.bind(this));
89    this._element.addEventListener("mousemove", this._handleMouseMove.bind(this));
90    this._element.addEventListener("mousedown", this._handleMouseDown.bind(this));
91}
92ColorPicker.prototype = Object.create(Picker.prototype);
93
94var SwatchBorderBoxWidth = 24; // keep in sync with CSS
95var SwatchBorderBoxHeight = 24; // keep in sync with CSS
96var SwatchesPerRow = 5;
97var SwatchesMaxRow = 4;
98
99ColorPicker.prototype._layout = function() {
100    var container = createElement("div", "color-swatch-container");
101    container.addEventListener("click", this._handleSwatchClick.bind(this), false);
102    for (var i = 0; i < this._config.values.length; ++i) {
103        var swatch = createElement("button", "color-swatch");
104        swatch.dataset.index = i;
105        swatch.dataset.value = this._config.values[i];
106        swatch.title = this._config.values[i];
107        swatch.style.backgroundColor = this._config.values[i];
108        container.appendChild(swatch);
109    }
110    var containerWidth = SwatchBorderBoxWidth * SwatchesPerRow;
111    if (this._config.values.length > SwatchesPerRow * SwatchesMaxRow)
112        containerWidth += getScrollbarWidth();
113    container.style.width = containerWidth + "px";
114    container.style.maxHeight = (SwatchBorderBoxHeight * SwatchesMaxRow) + "px";
115    this._element.appendChild(container);
116    var otherButton = createElement("button", "other-color", this._config.otherColorLabel);
117    otherButton.addEventListener("click", this.chooseOtherColor.bind(this), false);
118    this._element.appendChild(otherButton);
119    this._container = container;
120    this._otherButton = otherButton;
121    var elementWidth = this._element.offsetWidth;
122    var elementHeight = this._element.offsetHeight;
123    resizeWindow(elementWidth, elementHeight);
124};
125
126ColorPicker.prototype.selectColorAtIndex = function(index) {
127    index = Math.max(Math.min(this._container.childNodes.length - 1, index), 0);
128    this._container.childNodes[index].focus();
129};
130
131ColorPicker.prototype._handleMouseMove = function(event) {
132    if (event.target.classList.contains("color-swatch"))
133        event.target.focus();
134};
135
136ColorPicker.prototype._handleMouseDown = function(event) {
137    // Prevent blur.
138    if (event.target.classList.contains("color-swatch"))
139        event.preventDefault();
140};
141
142ColorPicker.prototype._handleKeyDown = function(event) {
143    var key = event.keyIdentifier;
144    if (key === "U+001B") // ESC
145        this.handleCancel();
146    else if (key == "Left" || key == "Up" || key == "Right" || key == "Down") {
147        var selectedElement = document.activeElement;
148        var index = 0;
149        if (selectedElement.classList.contains("other-color")) {
150            if (key != "Right" && key != "Up")
151                return;
152            index = this._container.childNodes.length - 1;
153        } else if (selectedElement.classList.contains("color-swatch")) {
154            index = parseInt(selectedElement.dataset.index, 10);
155            switch (key) {
156            case "Left":
157                index--;
158                break;
159            case "Right":
160                index++;
161                break;
162            case "Up":
163                index -= SwatchesPerRow;
164                break;
165            case "Down":
166                index += SwatchesPerRow;
167                break;
168            }
169            if (index > this._container.childNodes.length - 1) {
170                this._otherButton.focus();
171                return;
172            }
173        }
174        this.selectColorAtIndex(index);
175    }
176    event.preventDefault();
177};
178
179ColorPicker.prototype._handleSwatchClick = function(event) {
180    if (event.target.classList.contains("color-swatch"))
181        this.submitValue(event.target.dataset.value);
182};
183
184if (window.dialogArguments) {
185    initialize(dialogArguments);
186} else {
187    window.addEventListener("message", handleMessage, false);
188    window.setTimeout(handleArgumentsTimeout, 1000);
189}
190