/*================================================== * Detailed Event Painter *================================================== */ // Note: a number of features from original-painter // are not yet implemented in detailed painter. // Eg classname, id attributes for icons, labels, tapes Timeline.DetailedEventPainter = function(params) { this._params = params; this._onSelectListeners = []; this._filterMatcher = null; this._highlightMatcher = null; this._frc = null; this._eventIdToElmt = {}; }; Timeline.DetailedEventPainter.prototype.initialize = function(band, timeline) { this._band = band; this._timeline = timeline; this._backLayer = null; this._eventLayer = null; this._lineLayer = null; this._highlightLayer = null; this._eventIdToElmt = null; }; Timeline.DetailedEventPainter.prototype.getType = function() { return 'detailed'; }; Timeline.DetailedEventPainter.prototype.addOnSelectListener = function(listener) { this._onSelectListeners.push(listener); }; Timeline.DetailedEventPainter.prototype.removeOnSelectListener = function(listener) { for (var i = 0; i < this._onSelectListeners.length; i++) { if (this._onSelectListeners[i] == listener) { this._onSelectListeners.splice(i, 1); break; } } }; Timeline.DetailedEventPainter.prototype.getFilterMatcher = function() { return this._filterMatcher; }; Timeline.DetailedEventPainter.prototype.setFilterMatcher = function(filterMatcher) { this._filterMatcher = filterMatcher; }; Timeline.DetailedEventPainter.prototype.getHighlightMatcher = function() { return this._highlightMatcher; }; Timeline.DetailedEventPainter.prototype.setHighlightMatcher = function(highlightMatcher) { this._highlightMatcher = highlightMatcher; }; Timeline.DetailedEventPainter.prototype.paint = function() { var eventSource = this._band.getEventSource(); if (eventSource == null) { return; } this._eventIdToElmt = {}; this._prepareForPainting(); var eventTheme = this._params.theme.event; var trackHeight = Math.max(eventTheme.track.height, this._frc.getLineHeight()); var metrics = { trackOffset: Math.round(this._band.getViewWidth() / 2 - trackHeight / 2), trackHeight: trackHeight, trackGap: eventTheme.track.gap, trackIncrement: trackHeight + eventTheme.track.gap, icon: eventTheme.instant.icon, iconWidth: eventTheme.instant.iconWidth, iconHeight: eventTheme.instant.iconHeight, labelWidth: eventTheme.label.width } var minDate = this._band.getMinDate(); var maxDate = this._band.getMaxDate(); var filterMatcher = (this._filterMatcher != null) ? this._filterMatcher : function(evt) { return true; }; var highlightMatcher = (this._highlightMatcher != null) ? this._highlightMatcher : function(evt) { return -1; }; var iterator = eventSource.getEventReverseIterator(minDate, maxDate); while (iterator.hasNext()) { var evt = iterator.next(); if (filterMatcher(evt)) { this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt)); } } this._highlightLayer.style.display = "block"; this._lineLayer.style.display = "block"; this._eventLayer.style.display = "block"; // update the band object for max number of tracks in this section of the ether this._band.updateEventTrackInfo(this._lowerTracks.length + this._upperTracks.length, metrics.trackIncrement); }; Timeline.DetailedEventPainter.prototype.softPaint = function() { }; Timeline.DetailedEventPainter.prototype._prepareForPainting = function() { var band = this._band; if (this._backLayer == null) { this._backLayer = this._band.createLayerDiv(0, "timeline-band-events"); this._backLayer.style.visibility = "hidden"; var eventLabelPrototype = document.createElement("span"); eventLabelPrototype.className = "timeline-event-label"; this._backLayer.appendChild(eventLabelPrototype); this._frc = SimileAjax.Graphics.getFontRenderingContext(eventLabelPrototype); } this._frc.update(); this._lowerTracks = []; this._upperTracks = []; if (this._highlightLayer != null) { band.removeLayerDiv(this._highlightLayer); } this._highlightLayer = band.createLayerDiv(105, "timeline-band-highlights"); this._highlightLayer.style.display = "none"; if (this._lineLayer != null) { band.removeLayerDiv(this._lineLayer); } this._lineLayer = band.createLayerDiv(110, "timeline-band-lines"); this._lineLayer.style.display = "none"; if (this._eventLayer != null) { band.removeLayerDiv(this._eventLayer); } this._eventLayer = band.createLayerDiv(110, "timeline-band-events"); this._eventLayer.style.display = "none"; }; Timeline.DetailedEventPainter.prototype.paintEvent = function(evt, metrics, theme, highlightIndex) { if (evt.isInstant()) { this.paintInstantEvent(evt, metrics, theme, highlightIndex); } else { this.paintDurationEvent(evt, metrics, theme, highlightIndex); } }; Timeline.DetailedEventPainter.prototype.paintInstantEvent = function(evt, metrics, theme, highlightIndex) { if (evt.isImprecise()) { this.paintImpreciseInstantEvent(evt, metrics, theme, highlightIndex); } else { this.paintPreciseInstantEvent(evt, metrics, theme, highlightIndex); } } Timeline.DetailedEventPainter.prototype.paintDurationEvent = function(evt, metrics, theme, highlightIndex) { if (evt.isImprecise()) { this.paintImpreciseDurationEvent(evt, metrics, theme, highlightIndex); } else { this.paintPreciseDurationEvent(evt, metrics, theme, highlightIndex); } } Timeline.DetailedEventPainter.prototype.paintPreciseInstantEvent = function(evt, metrics, theme, highlightIndex) { var doc = this._timeline.getDocument(); var text = evt.getText(); var startDate = evt.getStart(); var startPixel = Math.round(this._band.dateToPixelOffset(startDate)); var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2); var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2); var labelSize = this._frc.computeSize(text); var iconTrack = this._findFreeTrackForSolid(iconRightEdge, startPixel); var iconElmtData = this._paintEventIcon(evt, iconTrack, iconLeftEdge, metrics, theme); var labelLeft = iconRightEdge + theme.event.label.offsetFromLine; var labelTrack = iconTrack; var iconTrackData = this._getTrackData(iconTrack); if (Math.min(iconTrackData.solid, iconTrackData.text) >= labelLeft + labelSize.width) { // label on the same track, to the right of icon iconTrackData.solid = iconLeftEdge; iconTrackData.text = labelLeft; } else { // label on a different track, below icon iconTrackData.solid = iconLeftEdge; labelLeft = startPixel + theme.event.label.offsetFromLine; labelTrack = this._findFreeTrackForText(iconTrack, labelLeft + labelSize.width, function(t) { t.line = startPixel - 2; }); this._getTrackData(labelTrack).text = iconLeftEdge; this._paintEventLine(evt, startPixel, iconTrack, labelTrack, metrics, theme); } var labelTop = Math.round( metrics.trackOffset + labelTrack * metrics.trackIncrement + metrics.trackHeight / 2 - labelSize.height / 2); var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme); var self = this; var clickHandler = function(elmt, domEvt, target) { return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt); }; SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler); SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler); this._createHighlightDiv(highlightIndex, iconElmtData, theme); this._eventIdToElmt[evt.getID()] = iconElmtData.elmt; }; Timeline.DetailedEventPainter.prototype.paintImpreciseInstantEvent = function(evt, metrics, theme, highlightIndex) { var doc = this._timeline.getDocument(); var text = evt.getText(); var startDate = evt.getStart(); var endDate = evt.getEnd(); var startPixel = Math.round(this._band.dateToPixelOffset(startDate)); var endPixel = Math.round(this._band.dateToPixelOffset(endDate)); var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2); var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2); var labelSize = this._frc.computeSize(text); var iconTrack = this._findFreeTrackForSolid(endPixel, startPixel); var tapeElmtData = this._paintEventTape(evt, iconTrack, startPixel, endPixel, theme.event.instant.impreciseColor, theme.event.instant.impreciseOpacity, metrics, theme); var iconElmtData = this._paintEventIcon(evt, iconTrack, iconLeftEdge, metrics, theme); var iconTrackData = this._getTrackData(iconTrack); iconTrackData.solid = iconLeftEdge; var labelLeft = iconRightEdge + theme.event.label.offsetFromLine; var labelRight = labelLeft + labelSize.width; var labelTrack; if (labelRight < endPixel) { labelTrack = iconTrack; } else { labelLeft = startPixel + theme.event.label.offsetFromLine; labelRight = labelLeft + labelSize.width; labelTrack = this._findFreeTrackForText(iconTrack, labelRight, function(t) { t.line = startPixel - 2; }); this._getTrackData(labelTrack).text = iconLeftEdge; this._paintEventLine(evt, startPixel, iconTrack, labelTrack, metrics, theme); } var labelTop = Math.round( metrics.trackOffset + labelTrack * metrics.trackIncrement + metrics.trackHeight / 2 - labelSize.height / 2); var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme); var self = this; var clickHandler = function(elmt, domEvt, target) { return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt); }; SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler); SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler); SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler); this._createHighlightDiv(highlightIndex, iconElmtData, theme); this._eventIdToElmt[evt.getID()] = iconElmtData.elmt; }; Timeline.DetailedEventPainter.prototype.paintPreciseDurationEvent = function(evt, metrics, theme, highlightIndex) { var doc = this._timeline.getDocument(); var text = evt.getText(); var startDate = evt.getStart(); var endDate = evt.getEnd(); var startPixel = Math.round(this._band.dateToPixelOffset(startDate)); var endPixel = Math.round(this._band.dateToPixelOffset(endDate)); var labelSize = this._frc.computeSize(text); var tapeTrack = this._findFreeTrackForSolid(endPixel); var color = evt.getColor(); color = color != null ? color : theme.event.duration.color; var tapeElmtData = this._paintEventTape(evt, tapeTrack, startPixel, endPixel, color, 100, metrics, theme); var tapeTrackData = this._getTrackData(tapeTrack); tapeTrackData.solid = startPixel; var labelLeft = startPixel + theme.event.label.offsetFromLine; var labelTrack = this._findFreeTrackForText(tapeTrack, labelLeft + labelSize.width, function(t) { t.line = startPixel - 2; }); this._getTrackData(labelTrack).text = startPixel - 2; this._paintEventLine(evt, startPixel, tapeTrack, labelTrack, metrics, theme); var labelTop = Math.round( metrics.trackOffset + labelTrack * metrics.trackIncrement + metrics.trackHeight / 2 - labelSize.height / 2); var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme); var self = this; var clickHandler = function(elmt, domEvt, target) { return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt); }; SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler); SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler); this._createHighlightDiv(highlightIndex, tapeElmtData, theme); this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt; }; Timeline.DetailedEventPainter.prototype.paintImpreciseDurationEvent = function(evt, metrics, theme, highlightIndex) { var doc = this._timeline.getDocument(); var text = evt.getText(); var startDate = evt.getStart(); var latestStartDate = evt.getLatestStart(); var endDate = evt.getEnd(); var earliestEndDate = evt.getEarliestEnd(); var startPixel = Math.round(this._band.dateToPixelOffset(startDate)); var latestStartPixel = Math.round(this._band.dateToPixelOffset(latestStartDate)); var endPixel = Math.round(this._band.dateToPixelOffset(endDate)); var earliestEndPixel = Math.round(this._band.dateToPixelOffset(earliestEndDate)); var labelSize = this._frc.computeSize(text); var tapeTrack = this._findFreeTrackForSolid(endPixel); var color = evt.getColor(); color = color != null ? color : theme.event.duration.color; var impreciseTapeElmtData = this._paintEventTape(evt, tapeTrack, startPixel, endPixel, theme.event.duration.impreciseColor, theme.event.duration.impreciseOpacity, metrics, theme); var tapeElmtData = this._paintEventTape(evt, tapeTrack, latestStartPixel, earliestEndPixel, color, 100, metrics, theme); var tapeTrackData = this._getTrackData(tapeTrack); tapeTrackData.solid = startPixel; var labelLeft = latestStartPixel + theme.event.label.offsetFromLine; var labelTrack = this._findFreeTrackForText(tapeTrack, labelLeft + labelSize.width, function(t) { t.line = latestStartPixel - 2; }); this._getTrackData(labelTrack).text = latestStartPixel - 2; this._paintEventLine(evt, latestStartPixel, tapeTrack, labelTrack, metrics, theme); var labelTop = Math.round( metrics.trackOffset + labelTrack * metrics.trackIncrement + metrics.trackHeight / 2 - labelSize.height / 2); var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme); var self = this; var clickHandler = function(elmt, domEvt, target) { return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt); }; SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler); SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler); this._createHighlightDiv(highlightIndex, tapeElmtData, theme); this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt; }; Timeline.DetailedEventPainter.prototype._findFreeTrackForSolid = function(solidEdge, softEdge) { for (var i = 0; true; i++) { if (i < this._lowerTracks.length) { var t = this._lowerTracks[i]; if (Math.min(t.solid, t.text) > solidEdge && (!(softEdge) || t.line > softEdge)) { return i; } } else { this._lowerTracks.push({ solid: Number.POSITIVE_INFINITY, text: Number.POSITIVE_INFINITY, line: Number.POSITIVE_INFINITY }); return i; } if (i < this._upperTracks.length) { var t = this._upperTracks[i]; if (Math.min(t.solid, t.text) > solidEdge && (!(softEdge) || t.line > softEdge)) { return -1 - i; } } else { this._upperTracks.push({ solid: Number.POSITIVE_INFINITY, text: Number.POSITIVE_INFINITY, line: Number.POSITIVE_INFINITY }); return -1 - i; } } }; Timeline.DetailedEventPainter.prototype._findFreeTrackForText = function(fromTrack, edge, occupiedTrackVisitor) { var extendUp; var index; var firstIndex; var result; if (fromTrack < 0) { extendUp = true; firstIndex = -fromTrack; index = this._findFreeUpperTrackForText(firstIndex, edge); result = -1 - index; } else if (fromTrack > 0) { extendUp = false; firstIndex = fromTrack + 1; index = this._findFreeLowerTrackForText(firstIndex, edge); result = index; } else { var upIndex = this._findFreeUpperTrackForText(0, edge); var downIndex = this._findFreeLowerTrackForText(1, edge); if (downIndex - 1 <= upIndex) { extendUp = false; firstIndex = 1; index = downIndex; result = index; } else { extendUp = true; firstIndex = 0; index = upIndex; result = -1 - index; } } if (extendUp) { if (index == this._upperTracks.length) { this._upperTracks.push({ solid: Number.POSITIVE_INFINITY, text: Number.POSITIVE_INFINITY, line: Number.POSITIVE_INFINITY }); } for (var i = firstIndex; i < index; i++) { occupiedTrackVisitor(this._upperTracks[i]); } } else { if (index == this._lowerTracks.length) { this._lowerTracks.push({ solid: Number.POSITIVE_INFINITY, text: Number.POSITIVE_INFINITY, line: Number.POSITIVE_INFINITY }); } for (var i = firstIndex; i < index; i++) { occupiedTrackVisitor(this._lowerTracks[i]); } } return result; }; Timeline.DetailedEventPainter.prototype._findFreeLowerTrackForText = function(index, edge) { for (; index < this._lowerTracks.length; index++) { var t = this._lowerTracks[index]; if (Math.min(t.solid, t.text) >= edge) { break; } } return index; }; Timeline.DetailedEventPainter.prototype._findFreeUpperTrackForText = function(index, edge) { for (; index < this._upperTracks.length; index++) { var t = this._upperTracks[index]; if (Math.min(t.solid, t.text) >= edge) { break; } } return index; }; Timeline.DetailedEventPainter.prototype._getTrackData = function(index) { return (index < 0) ? this._upperTracks[-index - 1] : this._lowerTracks[index]; }; Timeline.DetailedEventPainter.prototype._paintEventLine = function(evt, left, startTrack, endTrack, metrics, theme) { var top = Math.round(metrics.trackOffset + startTrack * metrics.trackIncrement + metrics.trackHeight / 2); var height = Math.round(Math.abs(endTrack - startTrack) * metrics.trackIncrement); var lineStyle = "1px solid " + theme.event.label.lineColor; var lineDiv = this._timeline.getDocument().createElement("div"); lineDiv.style.position = "absolute"; lineDiv.style.left = left + "px"; lineDiv.style.width = theme.event.label.offsetFromLine + "px"; lineDiv.style.height = height + "px"; if (startTrack > endTrack) { lineDiv.style.top = (top - height) + "px"; lineDiv.style.borderTop = lineStyle; } else { lineDiv.style.top = top + "px"; lineDiv.style.borderBottom = lineStyle; } lineDiv.style.borderLeft = lineStyle; this._lineLayer.appendChild(lineDiv); }; Timeline.DetailedEventPainter.prototype._paintEventIcon = function(evt, iconTrack, left, metrics, theme) { var icon = evt.getIcon(); icon = icon != null ? icon : metrics.icon; var middle = metrics.trackOffset + iconTrack * metrics.trackIncrement + metrics.trackHeight / 2; var top = Math.round(middle - metrics.iconHeight / 2); var img = SimileAjax.Graphics.createTranslucentImage(icon); var iconDiv = this._timeline.getDocument().createElement("div"); iconDiv.style.position = "absolute"; iconDiv.style.left = left + "px"; iconDiv.style.top = top + "px"; iconDiv.appendChild(img); iconDiv.style.cursor = "pointer"; if(evt._title != null) iconDiv.title = evt._title this._eventLayer.appendChild(iconDiv); return { left: left, top: top, width: metrics.iconWidth, height: metrics.iconHeight, elmt: iconDiv }; }; Timeline.DetailedEventPainter.prototype._paintEventLabel = function(evt, text, left, top, width, height, theme) { var doc = this._timeline.getDocument(); var labelBackgroundDiv = doc.createElement("div"); labelBackgroundDiv.style.position = "absolute"; labelBackgroundDiv.style.left = left + "px"; labelBackgroundDiv.style.width = width + "px"; labelBackgroundDiv.style.top = top + "px"; labelBackgroundDiv.style.height = height + "px"; labelBackgroundDiv.style.backgroundColor = theme.event.label.backgroundColor; SimileAjax.Graphics.setOpacity(labelBackgroundDiv, theme.event.label.backgroundOpacity); this._eventLayer.appendChild(labelBackgroundDiv); var labelDiv = doc.createElement("div"); labelDiv.style.position = "absolute"; labelDiv.style.left = left + "px"; labelDiv.style.width = width + "px"; labelDiv.style.top = top + "px"; labelDiv.innerHTML = text; labelDiv.style.cursor = "pointer"; if(evt._title != null) labelDiv.title = evt._title; var color = evt.getTextColor(); if (color == null) { color = evt.getColor(); } if (color != null) { labelDiv.style.color = color; } this._eventLayer.appendChild(labelDiv); return { left: left, top: top, width: width, height: height, elmt: labelDiv }; }; Timeline.DetailedEventPainter.prototype._paintEventTape = function( evt, iconTrack, startPixel, endPixel, color, opacity, metrics, theme) { var tapeWidth = endPixel - startPixel; var tapeHeight = theme.event.tape.height; var middle = metrics.trackOffset + iconTrack * metrics.trackIncrement + metrics.trackHeight / 2; var top = Math.round(middle - tapeHeight / 2); var tapeDiv = this._timeline.getDocument().createElement("div"); tapeDiv.style.position = "absolute"; tapeDiv.style.left = startPixel + "px"; tapeDiv.style.width = tapeWidth + "px"; tapeDiv.style.top = top + "px"; tapeDiv.style.height = tapeHeight + "px"; tapeDiv.style.backgroundColor = color; tapeDiv.style.overflow = "hidden"; tapeDiv.style.cursor = "pointer"; if(evt._title != null) tapeDiv.title = evt._title; SimileAjax.Graphics.setOpacity(tapeDiv, opacity); this._eventLayer.appendChild(tapeDiv); return { left: startPixel, top: top, width: tapeWidth, height: tapeHeight, elmt: tapeDiv }; } Timeline.DetailedEventPainter.prototype._createHighlightDiv = function(highlightIndex, dimensions, theme) { if (highlightIndex >= 0) { var doc = this._timeline.getDocument(); var eventTheme = theme.event; var color = eventTheme.highlightColors[Math.min(highlightIndex, eventTheme.highlightColors.length - 1)]; var div = doc.createElement("div"); div.style.position = "absolute"; div.style.overflow = "hidden"; div.style.left = (dimensions.left - 2) + "px"; div.style.width = (dimensions.width + 4) + "px"; div.style.top = (dimensions.top - 2) + "px"; div.style.height = (dimensions.height + 4) + "px"; div.style.background = color; this._highlightLayer.appendChild(div); } }; Timeline.DetailedEventPainter.prototype._onClickInstantEvent = function(icon, domEvt, evt) { var c = SimileAjax.DOM.getPageCoordinates(icon); this._showBubble( c.left + Math.ceil(icon.offsetWidth / 2), c.top + Math.ceil(icon.offsetHeight / 2), evt ); this._fireOnSelect(evt.getID()); domEvt.cancelBubble = true; SimileAjax.DOM.cancelEvent(domEvt); return false; }; Timeline.DetailedEventPainter.prototype._onClickDurationEvent = function(target, domEvt, evt) { if ("pageX" in domEvt) { var x = domEvt.pageX; var y = domEvt.pageY; } else { var c = SimileAjax.DOM.getPageCoordinates(target); var x = domEvt.offsetX + c.left; var y = domEvt.offsetY + c.top; } this._showBubble(x, y, evt); this._fireOnSelect(evt.getID()); domEvt.cancelBubble = true; SimileAjax.DOM.cancelEvent(domEvt); return false; }; Timeline.DetailedEventPainter.prototype.showBubble = function(evt) { var elmt = this._eventIdToElmt[evt.getID()]; if (elmt) { var c = SimileAjax.DOM.getPageCoordinates(elmt); this._showBubble(c.left + elmt.offsetWidth / 2, c.top + elmt.offsetHeight / 2, evt); } }; Timeline.DetailedEventPainter.prototype._showBubble = function(x, y, evt) { var div = document.createElement("div"); var themeBubble = this._params.theme.event.bubble; evt.fillInfoBubble(div, this._params.theme, this._band.getLabeller()); SimileAjax.WindowManager.cancelPopups(); SimileAjax.Graphics.createBubbleForContentAndPoint(div, x, y, themeBubble.width, null, themeBubble.maxHeight); }; Timeline.DetailedEventPainter.prototype._fireOnSelect = function(eventID) { for (var i = 0; i < this._onSelectListeners.length; i++) { this._onSelectListeners[i](eventID); } };