1/*
2 * Copyright (C) 2012 Google 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 are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @param {WebInspector.TimelinePanel} timelinePanel
33 * @param {WebInspector.TimelineModel} model
34 * @param {number} sidebarWidth
35 * @constructor
36 */
37WebInspector.MemoryStatistics = function(timelinePanel, model, sidebarWidth)
38{
39    this._timelinePanel = timelinePanel;
40    this._counters = [];
41
42    model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this);
43    model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this);
44
45    this._containerAnchor = timelinePanel.element.lastChild;
46    this._memorySidebarView = new WebInspector.SidebarView(WebInspector.SidebarView.SidebarPosition.Start, undefined, sidebarWidth);
47    this._memorySidebarView.sidebarElement.addStyleClass("sidebar");
48    this._memorySidebarView.element.id = "memory-graphs-container";
49
50    this._memorySidebarView.addEventListener(WebInspector.SidebarView.EventTypes.Resized, this._sidebarResized.bind(this));
51
52    this._canvasContainer = this._memorySidebarView.mainElement;
53    this._canvasContainer.id = "memory-graphs-canvas-container";
54    this._createCurrentValuesBar();
55    this._canvas = this._canvasContainer.createChild("canvas");
56    this._canvas.id = "memory-counters-graph";
57    this._lastMarkerXPosition = 0;
58
59    this._canvas.addEventListener("mouseover", this._onMouseOver.bind(this), true);
60    this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this), true);
61    this._canvas.addEventListener("mouseout", this._onMouseOut.bind(this), true);
62    this._canvas.addEventListener("click", this._onClick.bind(this), true);
63    // We create extra timeline grid here to reuse its event dividers.
64    this._timelineGrid = new WebInspector.TimelineGrid();
65    this._canvasContainer.appendChild(this._timelineGrid.dividersElement);
66
67    // Populate sidebar
68    this._memorySidebarView.sidebarElement.createChild("div", "sidebar-tree sidebar-tree-section").textContent = WebInspector.UIString("COUNTERS");
69    this._counterUI = this._createCounterUIList();
70}
71
72/**
73 * @constructor
74 * @param {number} time
75 */
76WebInspector.MemoryStatistics.Counter = function(time)
77{
78    this.time = time;
79}
80
81/**
82 * @constructor
83 * @extends {WebInspector.Object}
84 */
85WebInspector.SwatchCheckbox = function(title, color)
86{
87    this.element = document.createElement("div");
88    this._swatch = this.element.createChild("div", "swatch");
89    this.element.createChild("span", "title").textContent = title;
90    this._color = color;
91    this.checked = true;
92
93    this.element.addEventListener("click", this._toggleCheckbox.bind(this), true);
94}
95
96WebInspector.SwatchCheckbox.Events = {
97    Changed: "Changed"
98}
99
100WebInspector.SwatchCheckbox.prototype = {
101    get checked()
102    {
103        return this._checked;
104    },
105
106    set checked(v)
107    {
108        this._checked = v;
109        if (this._checked)
110            this._swatch.style.backgroundColor = this._color;
111        else
112            this._swatch.style.backgroundColor = "";
113    },
114
115    _toggleCheckbox: function(event)
116    {
117        this.checked = !this.checked;
118        this.dispatchEventToListeners(WebInspector.SwatchCheckbox.Events.Changed);
119    },
120
121    __proto__: WebInspector.Object.prototype
122}
123
124/**
125 * @constructor
126 */
127WebInspector.CounterUIBase = function(memoryCountersPane, title, graphColor, valueGetter)
128{
129    this._memoryCountersPane = memoryCountersPane;
130    this.valueGetter = valueGetter;
131    var container = memoryCountersPane._memorySidebarView.sidebarElement.createChild("div", "memory-counter-sidebar-info");
132    var swatchColor = graphColor;
133    this._swatch = new WebInspector.SwatchCheckbox(WebInspector.UIString(title), swatchColor);
134    this._swatch.addEventListener(WebInspector.SwatchCheckbox.Events.Changed, this._toggleCounterGraph.bind(this));
135    container.appendChild(this._swatch.element);
136
137    this._value = null;
138    this.graphColor =graphColor;
139    this.strokeColor = graphColor;
140    this.graphYValues = [];
141}
142
143WebInspector.CounterUIBase.prototype = {
144    _toggleCounterGraph: function(event)
145    {
146        if (this._swatch.checked)
147            this._value.removeStyleClass("hidden");
148        else
149            this._value.addStyleClass("hidden");
150        this._memoryCountersPane.refresh();
151    },
152
153    updateCurrentValue: function(countersEntry)
154    {
155        this._value.textContent = Number.bytesToString(this.valueGetter(countersEntry));
156    },
157
158    clearCurrentValueAndMarker: function(ctx)
159    {
160        this._value.textContent = "";
161    },
162
163    get visible()
164    {
165        return this._swatch.checked;
166    },
167}
168
169WebInspector.MemoryStatistics.prototype = {
170    _createCurrentValuesBar: function()
171    {
172        throw new Error("Not implemented");
173    },
174
175    _createCounterUIList: function()
176    {
177        throw new Error("Not implemented");
178    },
179
180    _onRecordsCleared: function()
181    {
182        this._counters = [];
183    },
184
185    /**
186     * @param {WebInspector.TimelineGrid} timelineGrid
187     */
188    setMainTimelineGrid: function(timelineGrid)
189    {
190        this._mainTimelineGrid = timelineGrid;
191    },
192
193    /**
194     * @param {number} top
195     */
196     setTopPosition: function(top)
197    {
198        this._memorySidebarView.element.style.top = top + "px";
199        this._updateSize();
200    },
201
202    /**
203     * @param {number} width
204     */
205    setSidebarWidth: function(width)
206    {
207        if (this._ignoreSidebarResize)
208            return;
209        this._ignoreSidebarResize = true;
210        this._memorySidebarView.setSidebarWidth(width);
211        this._ignoreSidebarResize = false;
212    },
213
214    /**
215     * @param {WebInspector.Event} event
216     */
217    _sidebarResized: function(event)
218    {
219        if (this._ignoreSidebarResize)
220            return;
221        this._ignoreSidebarResize = true;
222        this._timelinePanel.splitView.setSidebarWidth(event.data);
223        this._ignoreSidebarResize = false;
224    },
225
226    _canvasHeight: function()
227    {
228        throw new Error("Not implemented");
229    },
230
231    _updateSize: function()
232    {
233        var width = this._mainTimelineGrid.dividersElement.offsetWidth + 1;
234        this._canvasContainer.style.width = width + "px";
235
236        var height = this._canvasHeight();
237        this._canvas.width = width;
238        this._canvas.height = height;
239    },
240
241    /**
242     * @param {WebInspector.Event} event
243     */
244    _onRecordAdded: function(event)
245    {
246        throw new Error("Not implemented");
247    },
248
249    _draw: function()
250    {
251        this._calculateVisibleIndexes();
252        this._calculateXValues();
253        this._clear();
254
255        this._setVerticalClip(10, this._canvas.height - 20);
256    },
257
258    _calculateVisibleIndexes: function()
259    {
260        var calculator = this._timelinePanel.calculator;
261        var start = calculator.minimumBoundary() * 1000;
262        var end = calculator.maximumBoundary() * 1000;
263        var firstIndex = 0;
264        var lastIndex = this._counters.length - 1;
265        for (var i = 0; i < this._counters.length; i++) {
266            var time = this._counters[i].time;
267            if (time <= start) {
268                firstIndex = i;
269            } else {
270                if (end < time)
271                    break;
272                lastIndex = i;
273            }
274        }
275        // Maximum index of element whose time <= start.
276        this._minimumIndex = firstIndex;
277
278        // Maximum index of element whose time <= end.
279        this._maximumIndex = lastIndex;
280
281        // Current window bounds.
282        this._minTime = start;
283        this._maxTime = end;
284    },
285
286    /**
287     * @param {MouseEvent} event
288     */
289     _onClick: function(event)
290    {
291        var x = event.x - event.target.offsetParent.offsetLeft;
292        var i = this._recordIndexAt(x);
293        var counter = this._counters[i];
294        if (counter)
295            this._timelinePanel.revealRecordAt(counter.time / 1000);
296    },
297
298    /**
299     * @param {MouseEvent} event
300     */
301     _onMouseOut: function(event)
302    {
303        delete this._markerXPosition;
304
305        var ctx = this._canvas.getContext("2d");
306        this._clearCurrentValueAndMarker(ctx);
307    },
308
309    /**
310     * @param {CanvasRenderingContext2D} ctx
311     */
312    _clearCurrentValueAndMarker: function(ctx)
313    {
314        for (var i = 0; i < this._counterUI.length; i++)
315            this._counterUI[i].clearCurrentValueAndMarker(ctx);
316    },
317
318    /**
319     * @param {MouseEvent} event
320     */
321     _onMouseOver: function(event)
322    {
323        this._onMouseMove(event);
324    },
325
326    /**
327     * @param {MouseEvent} event
328     */
329     _onMouseMove: function(event)
330    {
331        var x = event.x - event.target.offsetParent.offsetLeft
332        this._markerXPosition = x;
333        this._refreshCurrentValues();
334    },
335
336    _refreshCurrentValues: function()
337    {
338        if (!this._counters.length)
339            return;
340        if (this._markerXPosition === undefined)
341            return;
342        if (this._maximumIndex === -1)
343            return;
344        var i = this._recordIndexAt(this._markerXPosition);
345
346        this._updateCurrentValue(this._counters[i]);
347
348        this._highlightCurrentPositionOnGraphs(this._markerXPosition, i);
349    },
350
351    _updateCurrentValue: function(counterEntry)
352    {
353        for (var j = 0; j < this._counterUI.length; j++)
354            this._counterUI[j].updateCurrentValue(counterEntry);
355    },
356
357    _recordIndexAt: function(x)
358    {
359        var i;
360        for (i = this._minimumIndex + 1; i <= this._maximumIndex; i++) {
361            var statX = this._counters[i].x;
362            if (x < statX)
363                break;
364        }
365        i--;
366        return i;
367    },
368
369    _highlightCurrentPositionOnGraphs: function(x, index)
370    {
371        var ctx = this._canvas.getContext("2d");
372        this._restoreImageUnderMarker(ctx);
373        this._drawMarker(ctx, x, index);
374    },
375
376    _restoreImageUnderMarker: function(ctx)
377    {
378        throw new Error("Not implemented");
379    },
380
381    _drawMarker: function(ctx, x, index)
382    {
383        throw new Error("Not implemented");
384    },
385
386    visible: function()
387    {
388        return this._memorySidebarView.isShowing();
389    },
390
391    show: function()
392    {
393        var anchor = /** @type {Element|null} */ (this._containerAnchor.nextSibling);
394        this._memorySidebarView.show(this._timelinePanel.element, anchor);
395        this._updateSize();
396        this._refreshDividers();
397        setTimeout(this._draw.bind(this), 0);
398    },
399
400    refresh: function()
401    {
402        this._updateSize();
403        this._refreshDividers();
404        this._draw();
405        this._refreshCurrentValues();
406    },
407
408    hide: function()
409    {
410        this._memorySidebarView.detach();
411    },
412
413    _refreshDividers: function()
414    {
415        this._timelineGrid.updateDividers(this._timelinePanel.calculator);
416    },
417
418    _setVerticalClip: function(originY, height)
419    {
420        this._originY = originY;
421        this._clippedHeight = height;
422    },
423
424    _calculateXValues: function()
425    {
426        if (!this._counters.length)
427            return;
428
429        var width = this._canvas.width;
430        var xFactor = width / (this._maxTime - this._minTime);
431
432        this._counters[this._minimumIndex].x = 0;
433        for (var i = this._minimumIndex + 1; i < this._maximumIndex; i++)
434             this._counters[i].x = xFactor * (this._counters[i].time - this._minTime);
435        this._counters[this._maximumIndex].x = width;
436    },
437
438    _clear: function() {
439        var ctx = this._canvas.getContext("2d");
440        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
441        this._discardImageUnderMarker();
442    },
443
444    _discardImageUnderMarker: function()
445    {
446        throw new Error("Not implemented");
447    }
448}
449
450