1/*
2 * Copyright (C) 2013 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.RulesStyleDetailsPanel = function()
27{
28    WebInspector.StyleDetailsPanel.call(this, WebInspector.RulesStyleDetailsPanel.StyleClassName, "rules", WebInspector.UIString("Rules"));
29
30    this._sections = [];
31};
32
33WebInspector.RulesStyleDetailsPanel.StyleClassName = "rules";
34WebInspector.RulesStyleDetailsPanel.LabelElementStyleClassName = "label";
35WebInspector.RulesStyleDetailsPanel.NewRuleElementStyleClassName = "new-rule";
36
37WebInspector.RulesStyleDetailsPanel.prototype = {
38    constructor: WebInspector.RulesStyleDetailsPanel,
39
40    // Public
41
42    refresh: function(significantChange)
43    {
44        // We only need to do a rebuild on significant changes. Other changes are handled
45        // by the sections and text editors themselves.
46        if (!significantChange)
47            return;
48
49        var newSections = [];
50        var newDOMFragment = document.createDocumentFragment();
51
52        var previousMediaList = [];
53        var previousSection = null;
54        var previousFocusedSection = null;
55
56        function mediaListsEqual(a, b)
57        {
58            a = a || [];
59            b = b || [];
60
61            if (a.length !== b.length)
62                return false;
63
64            for (var i = 0; i < a.length; ++i) {
65                var aMedia = a[i];
66                var bMedia = b[i];
67
68                if (aMedia.type !== bMedia.type)
69                    return false;
70
71                if (aMedia.text !== bMedia.text)
72                    return false;
73
74                if (!aMedia.sourceCodeLocation && bMedia.sourceCodeLocation)
75                    return false;
76
77                if (aMedia.sourceCodeLocation && !aMedia.sourceCodeLocation.isEqual(bMedia.sourceCodeLocation))
78                    return false;
79            }
80
81            return true;
82        }
83
84        function filteredMediaList(mediaList)
85        {
86            if (!mediaList)
87                return [];
88
89            // Exclude the basic "screen" query since it's very common and just clutters things.
90            return mediaList.filter(function(media) {
91                return media.text !== "screen";
92            });
93        }
94
95        function appendStyleSection(style)
96        {
97            var section = style.__rulesSection;
98            if (section && section.focused && !previousFocusedSection)
99                previousFocusedSection = section;
100
101            if (!section) {
102                section = new WebInspector.CSSStyleDeclarationSection(style);
103                style.__rulesSection = section;
104            } else
105                section.refresh();
106
107            if (this._focusNextNewInspectorRule && style.ownerRule && style.ownerRule.type === WebInspector.CSSRule.Type.Inspector) {
108                previousFocusedSection = section;
109                delete this._focusNextNewInspectorRule;
110            }
111
112            // Reset lastInGroup in case the order/grouping changed.
113            section.lastInGroup = false;
114
115            newDOMFragment.appendChild(section.element);
116            newSections.push(section);
117
118            previousSection = section;
119        }
120
121        function addNewRuleButton()
122        {
123            if (previousSection)
124                previousSection.lastInGroup = true;
125
126            var newRuleButton = document.createElement("div");
127            newRuleButton.className = WebInspector.RulesStyleDetailsPanel.NewRuleElementStyleClassName;
128            newRuleButton.addEventListener("click", this._newRuleClicked.bind(this));
129
130            newRuleButton.appendChild(document.createElement("img"));
131            newRuleButton.appendChild(document.createTextNode(WebInspector.UIString("New Rule")));
132
133            newDOMFragment.appendChild(newRuleButton);
134
135            addedNewRuleButton = true;
136        }
137
138        var pseudoElements = this.nodeStyles.pseudoElements;
139        for (var pseudoIdentifier in pseudoElements) {
140            var pseudoElement = pseudoElements[pseudoIdentifier];
141            for (var i = 0; i < pseudoElement.orderedStyles.length; ++i) {
142                var style = pseudoElement.orderedStyles[i];
143                appendStyleSection.call(this, style);
144            }
145
146            if (previousSection)
147                previousSection.lastInGroup = true;
148        }
149
150        var addedNewRuleButton = false;
151
152        var orderedStyles = this.nodeStyles.orderedStyles;
153        for (var i = 0; i < orderedStyles.length; ++i) {
154            var style = orderedStyles[i];
155
156            if (style.type === WebInspector.CSSStyleDeclaration.Type.Rule && !addedNewRuleButton)
157                addNewRuleButton.call(this);
158
159            if (previousSection && previousSection.style.node !== style.node) {
160                previousSection.lastInGroup = true;
161
162                var prefixElement = document.createElement("strong");
163                prefixElement.textContent = WebInspector.UIString("Inherited From: ");
164
165                var inheritedLabel = document.createElement("div");
166                inheritedLabel.className = WebInspector.RulesStyleDetailsPanel.LabelElementStyleClassName;
167                inheritedLabel.appendChild(prefixElement);
168                inheritedLabel.appendChild(WebInspector.linkifyNodeReference(style.node));
169                newDOMFragment.appendChild(inheritedLabel);
170            }
171
172            // Only include the media list if it is different from the previous media list shown.
173            var currentMediaList = filteredMediaList(style.ownerRule && style.ownerRule.mediaList);
174            if (!mediaListsEqual(previousMediaList, currentMediaList)) {
175                previousMediaList = currentMediaList;
176
177                // Break the section group even if the media list is empty. That way the user knows
178                // the previous displayed media list does not apply to the next section.
179                if (previousSection)
180                    previousSection.lastInGroup = true;
181
182                for (var j = 0; j < currentMediaList.length; ++j) {
183                    var media = currentMediaList[j];
184
185                    var prefixElement = document.createElement("strong");
186                    prefixElement.textContent = WebInspector.UIString("Media: ");
187
188                    var mediaLabel = document.createElement("div");
189                    mediaLabel.className = WebInspector.RulesStyleDetailsPanel.LabelElementStyleClassName;
190                    mediaLabel.appendChild(prefixElement);
191                    mediaLabel.appendChild(document.createTextNode(media.text));
192
193                    if (media.sourceCodeLocation) {
194                        mediaLabel.appendChild(document.createTextNode(" \u2014 "));
195                        mediaLabel.appendChild(WebInspector.createSourceCodeLocationLink(media.sourceCodeLocation, true));
196                    }
197
198                    newDOMFragment.appendChild(mediaLabel);
199                }
200            }
201
202            appendStyleSection.call(this, style);
203        }
204
205        if (!addedNewRuleButton)
206            addNewRuleButton.call(this);
207
208        if (previousSection)
209            previousSection.lastInGroup = true;
210
211        this.element.removeChildren();
212        this.element.appendChild(newDOMFragment);
213
214        this._sections = newSections;
215
216        for (var i = 0; i < this._sections.length; ++i)
217            this._sections[i].updateLayout();
218
219        if (previousFocusedSection) {
220            previousFocusedSection.focus();
221
222            function scrollToFocusedSection()
223            {
224                previousFocusedSection.element.scrollIntoViewIfNeeded(true);
225            }
226
227            // Do the scroll on a timeout since StyleDetailsPanel restores scroll position
228            // after the refresh, and we might not need to scroll after the restore.
229            setTimeout(scrollToFocusedSection, 0);
230        }
231    },
232
233    // Protected
234
235    shown: function()
236    {
237        WebInspector.StyleDetailsPanel.prototype.shown.call(this);
238
239        // Associate the style and section objects so they can be reused.
240        // Also update the layout in case we changed widths while hidden.
241        for (var i = 0; i < this._sections.length; ++i) {
242            var section = this._sections[i];
243            section.style.__rulesSection = section;
244            section.updateLayout();
245        }
246    },
247
248    hidden: function()
249    {
250        WebInspector.StyleDetailsPanel.prototype.hidden.call(this);
251
252        // Disconnect the style and section objects so they have a chance
253        // to release their objects when this panel is not visible.
254        for (var i = 0; i < this._sections.length; ++i)
255            delete this._sections[i].style.__rulesSection;
256    },
257
258    widthDidChange: function()
259    {
260        for (var i = 0; i < this._sections.length; ++i)
261            this._sections[i].updateLayout();
262    },
263
264    // Private
265
266    _newRuleClicked: function(event)
267    {
268        this._focusNextNewInspectorRule = true;
269        this.nodeStyles.addRule();
270    }
271};
272
273WebInspector.RulesStyleDetailsPanel.prototype.__proto__ = WebInspector.StyleDetailsPanel.prototype;
274