1/*
2 * Copyright (C) 2014 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26WebInspector.CodeMirrorGradientEditingController = function(codeMirror, marker)
27{
28    WebInspector.CodeMirrorEditingController.call(this, codeMirror, marker);
29
30    if (!WebInspector.CodeMirrorGradientEditingController.GradientTypes) {
31        WebInspector.CodeMirrorGradientEditingController.GradientTypes = {
32            "linear-gradient": {
33                type: WebInspector.LinearGradient,
34                label: WebInspector.UIString("Linear Gradient"),
35                repeats: false
36            },
37
38            "radial-gradient": {
39                type: WebInspector.RadialGradient,
40                label: WebInspector.UIString("Radial Gradient"),
41                repeats: false
42            },
43
44            "repeating-linear-gradient": {
45                type: WebInspector.LinearGradient,
46                label: WebInspector.UIString("Repeating Linear Gradient"),
47                repeats: true
48            },
49
50            "repeating-radial-gradient": {
51                type: WebInspector.RadialGradient,
52                label: WebInspector.UIString("Repeating Radial Gradient"),
53                repeats: true
54            }
55        };
56    }
57}
58
59WebInspector.CodeMirrorGradientEditingController.StyleClassName = "gradient-editing-controller";
60WebInspector.CodeMirrorGradientEditingController.EditsColorClassName = "edits-color";
61WebInspector.CodeMirrorGradientEditingController.RadialGradientClassName = "radial-gradient";
62
63// Lazily populated in the WebInspector.CodeMirrorGradientEditingController constructor.
64// It needs to be lazy to use UIString after Main.js and localizedStrings.js loads.
65WebInspector.CodeMirrorGradientEditingController.GradientTypes = null;
66
67WebInspector.CodeMirrorGradientEditingController.prototype = {
68    constructor: WebInspector.CodeMirrorGradientEditingController,
69    __proto__: WebInspector.CodeMirrorEditingController.prototype,
70
71    // Public
72
73    get initialValue()
74    {
75        return WebInspector.Gradient.fromString(this.text);
76    },
77
78    get cssClassName()
79    {
80        return "gradient";
81    },
82
83    get popoverPreferredEdges()
84    {
85        // Since the gradient editor can resize to be quite tall, let's avoid displaying the popover
86        // above the edited value so that it may not change which edge it attaches to upon editing a stop.
87        return [WebInspector.RectEdge.MIN_X, WebInspector.RectEdge.MAX_Y, WebInspector.RectEdge.MAX_X];
88    },
89
90    popoverTargetFrameWithRects: function(rects)
91    {
92        // If a gradient is defined across several lines, we probably want to use the first line only
93        // as a target frame for the editor since we may reformat the gradient value to fit on a single line.
94        return rects[0];
95    },
96
97    popoverWillPresent: function(popover)
98    {
99        this._container = document.createElement("div");
100        this._container.className = WebInspector.CodeMirrorGradientEditingController.StyleClassName;
101
102        this._gradientTypePicker = this._container.appendChild(document.createElement("select"));
103        for (var type in WebInspector.CodeMirrorGradientEditingController.GradientTypes) {
104            var option = this._gradientTypePicker.appendChild(document.createElement("option"));
105            option.value = type;
106            option.innerText = WebInspector.CodeMirrorGradientEditingController.GradientTypes[type].label;
107        }
108        this._gradientTypePicker.addEventListener("change", this);
109
110        this._gradientSlider = new WebInspector.GradientSlider;
111        this._container.appendChild(this._gradientSlider.element);
112
113        this._colorPicker = new WebInspector.ColorPicker;
114        this._colorPicker.colorWheel.dimension = 190;
115        this._colorPicker.addEventListener(WebInspector.ColorPicker.Event.ColorChanged, this._colorPickerColorChanged, this);
116
117        var angleLabel = this._container.appendChild(document.createElement("label"));
118        angleLabel.textContent = WebInspector.UIString("Angle");
119
120        this._angleInput = document.createElement("input");
121        this._angleInput.type = "text";
122        this._angleInput.size = 3;
123        this._angleInput.addEventListener("input", this);
124        angleLabel.appendChild(this._angleInput);
125
126        var dragToAdjustController = new WebInspector.DragToAdjustController(this);
127        dragToAdjustController.element = angleLabel;
128        dragToAdjustController.enabled = true;
129
130        this._updateCSSClassForGradientType();
131
132        popover.content = this._container;
133    },
134
135    popoverDidPresent: function(popover)
136    {
137        this._gradientSlider.stops = this.value.stops;
138
139        if (this.value instanceof WebInspector.LinearGradient) {
140            this._gradientTypePicker.value = this.value.repeats ? "repeating-linear-gradient" : "linear-gradient";
141            this._angleInput.value = this.value.angle + "\u00B0";
142        } else
143            this._gradientTypePicker.value = this.value.repeats ? "repeating-radial-gradient" : "radial-gradient"
144
145        this._gradientSlider.delegate = this;
146    },
147
148    // Protected
149
150    handleEvent: function(event)
151    {
152        if (event.type === "input")
153            this._handleInputEvent(event);
154        else if (event.type === "change")
155            this._handleChangeEvent(event);
156    },
157
158    gradientSliderStopsDidChange: function(gradientSlider)
159    {
160        this.text = this.value.toString();
161    },
162
163    gradientSliderStopWasSelected: function(gradientSlider, stop)
164    {
165        var selectedStop = gradientSlider.selectedStop;
166
167        if (selectedStop && !this._container.classList.contains(WebInspector.CodeMirrorGradientEditingController.EditsColorClassName)) {
168            this._container.appendChild(this._colorPicker.element);
169            this._container.classList.add(WebInspector.CodeMirrorGradientEditingController.EditsColorClassName);
170            this._colorPicker.color = selectedStop.color;
171        } else if (!selectedStop) {
172            this._colorPicker.element.remove();
173            this._container.classList.remove(WebInspector.CodeMirrorGradientEditingController.EditsColorClassName);
174        }
175
176        // Ensure the angle input is not focused since, if it were, it'd make a scrollbar appear as we
177        // animate the popover's frame to fit its new content.
178        this._angleInput.blur();
179
180        this.popover.update();
181    },
182
183    dragToAdjustControllerWasAdjustedByAmount: function(dragToAdjustController, amount)
184    {
185        var angle = parseFloat(this._angleInput.value) + amount;
186        if (Math.round(angle) !== angle)
187            angle = angle.toFixed(1);
188
189        this._angleInput.value = angle;
190        this._angleInputValueDidChange(angle);
191    },
192
193    // Private
194
195    _handleInputEvent: function(event)
196    {
197        var angle = parseFloat(this._angleInput.value);
198        if (isNaN(angle))
199            return;
200
201        this._angleInputValueDidChange(angle);
202    },
203
204    _angleInputValueDidChange: function(angle)
205    {
206        this.value.angle = angle;
207        this.text = this.value.toString();
208
209        var matches = this._angleInput.value.match(/\u00B0/g);
210        if (!matches || matches.length !== 1) {
211            var selectionStart = this._angleInput.selectionStart;
212            this._angleInput.value = angle + "\u00B0";
213            this._angleInput.selectionStart = selectionStart;
214            this._angleInput.selectionEnd = selectionStart;
215        }
216    },
217
218    _handleChangeEvent: function(event)
219    {
220        var descriptor = WebInspector.CodeMirrorGradientEditingController.GradientTypes[this._gradientTypePicker.value];
221        if (!(this.value instanceof descriptor.type)) {
222            if (descriptor.type === WebInspector.LinearGradient) {
223                this.value = new WebInspector.LinearGradient(180, this.value.stops);
224                this._angleInput.value = "180\u00B0";
225            } else
226                this.value = new WebInspector.RadialGradient("", this.value.stops);
227
228            this._updateCSSClassForGradientType();
229            this.popover.update();
230        }
231        this.value.repeats = descriptor.repeats;
232        this.text = this.value.toString();
233    },
234
235    _colorPickerColorChanged: function(event)
236    {
237        this._gradientSlider.selectedStop.color = event.target.color;
238        this._gradientSlider.stops = this.value.stops;
239        this.text = this.value.toString();
240    },
241
242    _updateCSSClassForGradientType: function()
243    {
244        if (this.value instanceof WebInspector.LinearGradient)
245            this._container.classList.remove(WebInspector.CodeMirrorGradientEditingController.RadialGradientClassName);
246        else
247            this._container.classList.add(WebInspector.CodeMirrorGradientEditingController.RadialGradientClassName);
248    }
249}
250