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.CodeMirrorEditingController = function(codeMirror, marker)
27{
28    WebInspector.Object.call(this);
29
30    this._codeMirror = codeMirror;
31    this._marker = marker;
32    this._delegate = null;
33
34    this._range = marker.range;
35
36    // The value must support .toString() and .copy() methods.
37    this._value = this.initialValue;
38
39    this._keyboardShortcutEsc = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Escape);
40}
41
42WebInspector.CodeMirrorEditingController.prototype = {
43    constructor: WebInspector.CodeMirrorEditingController,
44    __proto__: WebInspector.Object.prototype,
45
46    // Public
47
48    get marker()
49    {
50        return this._marker;
51    },
52
53    get range()
54    {
55        return this._range;
56    },
57
58    get value()
59    {
60        return this._value;
61    },
62
63    set value(value)
64    {
65        this.text = value.toString();
66        this._value = value;
67    },
68
69    get delegate()
70    {
71        return this._delegate;
72    },
73
74    set delegate(delegate)
75    {
76        this._delegate = delegate;
77    },
78
79    get text()
80    {
81        var from = {line: this._range.startLine, ch: this._range.startColumn};
82        var to = {line: this._range.endLine, ch: this._range.endColumn};
83        return this._codeMirror.getRange(from, to);
84    },
85
86    set text(text)
87    {
88        var from = {line: this._range.startLine, ch: this._range.startColumn};
89        var to = {line: this._range.endLine, ch: this._range.endColumn};
90        this._codeMirror.replaceRange(text, from, to);
91
92        var lines = text.split("\n");
93        var endLine = this._range.startLine + lines.length - 1;
94        var endColumn = lines.length > 1 ? lines.lastValue.length : this._range.startColumn + text.length;
95        this._range = new WebInspector.TextRange(this._range.startLine, this._range.startColumn, endLine, endColumn);
96    },
97
98    get initialValue()
99    {
100        // Implemented by subclasses.
101        return this.text;
102    },
103
104    get cssClassName()
105    {
106        // Implemented by subclasses.
107        return "";
108    },
109
110    get popover()
111    {
112        return this._popover;
113    },
114
115    get popoverPreferredEdges()
116    {
117        // Best to display the popover to the left or above the edited range since its end position may change, but not its start
118        // position. This way we minimize the chances of overlaying the edited range as it changes.
119        return [WebInspector.RectEdge.MIN_X, WebInspector.RectEdge.MIN_Y, WebInspector.RectEdge.MAX_Y, WebInspector.RectEdge.MAX_X];
120    },
121
122    popoverTargetFrameWithRects: function(rects)
123    {
124        return WebInspector.Rect.unionOfRects(rects);
125    },
126
127    presentHoverMenu: function()
128    {
129        this._hoverMenu = new WebInspector.HoverMenu(this);
130        this._hoverMenu.element.classList.add(this.cssClassName);
131        this._rects = this._marker.rects;
132        this._hoverMenu.present(this._rects);
133    },
134
135    dismissHoverMenu: function(discrete)
136    {
137        this._hoverMenu.dismiss(discrete);
138    },
139
140    popoverWillPresent: function(popover)
141    {
142        // Implemented by subclasses.
143    },
144
145    popoverDidPresent: function(popover)
146    {
147        // Implemented by subclasses.
148    },
149
150    // Protected
151
152    handleKeydownEvent: function(event)
153    {
154        if (!this._keyboardShortcutEsc.matchesEvent(event) || !this._popover.visible)
155            return false;
156
157        this.value = this._originalValue;
158        this._popover.dismiss();
159
160        return true;
161    },
162
163    hoverMenuButtonWasPressed: function(hoverMenu)
164    {
165        this._popover = new WebInspector.Popover(this);
166        this.popoverWillPresent(this._popover);
167        this._popover.present(this.popoverTargetFrameWithRects(this._rects).pad(2), this.popoverPreferredEdges);
168        this.popoverDidPresent(this._popover);
169
170        WebInspector.addWindowKeydownListener(this);
171
172        hoverMenu.dismiss();
173
174        if (this._delegate && typeof this._delegate.editingControllerDidStartEditing === "function")
175            this._delegate.editingControllerDidStartEditing(this);
176
177        this._originalValue = this._value.copy();
178    },
179
180    didDismissPopover: function(popover)
181    {
182        delete this._popover;
183        delete this._originalValue;
184
185        WebInspector.removeWindowKeydownListener(this);
186
187        if (this._delegate && typeof this._delegate.editingControllerDidFinishEditing === "function")
188            this._delegate.editingControllerDidFinishEditing(this);
189    }
190}
191