"""
Vertical Split Panel: Top and Bottom layouts with a movable splitter.
/*
* Copyright 2008 Google Inc.
* Copyright (C) 2008 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
*
* Licensed under the Apache License, Version 2.0 (the "License") you may not
* use self file except in compliance with the License. You may obtain a copy of
* the License at
*
* http:#www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
"""
from pyjamas.splitpanel import SplitPanel
import DOM
from DeferredCommand import DeferredCommand
from Timer import Timer
TOP = 0
BOTTOM = 1
class ImplVerticalSplitPanel:
""" Provides a base implementation for splitter layout that relies on CSS
positioned layout.
"""
def __init__(self, panel):
self.panel = panel
DOM.setStyleAttribute(panel.getElement(), "position", "relative")
topElem = panel.getWidgetElement(TOP)
bottomElem = panel.getWidgetElement(BOTTOM)
self.expandToFitParentHorizontally(topElem)
self.expandToFitParentHorizontally(bottomElem)
self.expandToFitParentHorizontally(panel.getSplitElement())
self.panel.expandToFitParentUsingCssOffsets(panel.container)
# Snap the bottom wrapper to the bottom side.
DOM.setStyleAttribute(bottomElem, "bottom", "0")
def expandToFitParentHorizontally(self, elem):
self.panel.addAbsolutePositoning(elem)
DOM.setStyleAttribute(elem, "left", "0")
DOM.setStyleAttribute(elem, "right", "0")
def onAttach(self):
pass
def onDetach(self):
pass
def onSplitterResize(self, px):
self.setSplitPosition(px)
def setSplitPosition(self, px):
splitElem = self.panel.getSplitElement()
rootElemHeight = self.panel.getOffsetHeight(self.panel.container)
splitElemHeight = self.panel.getOffsetHeight(splitElem)
# layout not settled, set height to what it _should_ be... yuk.
if splitElemHeight == 0:
splitElemHeight = 7
if rootElemHeight < splitElemHeight:
return
newBottomHeight = rootElemHeight - px - splitElemHeight
if px < 0:
px = 0
newBottomHeight = rootElemHeight - splitElemHeight
elif newBottomHeight < 0:
px = rootElemHeight - splitElemHeight
newBottomHeight = 0
self.updateElements(self.panel.getWidgetElement(TOP),
splitElem,
self.panel.getWidgetElement(BOTTOM),
px, px + splitElemHeight, newBottomHeight)
def updateElements(self, topElem, splitElem,
bottomElem, topHeight, bottomTop, bottomHeight):
self.panel.setElemHeight(topElem, "%dpx" % topHeight)
self.panel.setTop(splitElem, "%dpx" % topHeight)
self.panel.setTop(bottomElem, "%dpx" % bottomTop)
# bottom's height is handled by CSS.
class ImplIE6VerticalSplitPanel:
""" Provides an implementation for IE6/7 that relies on 100% length in CSS.
"""
def __init__(self, panel):
self.panel = panel
self.isResizeInProgress = False
self.isTopHidden = False
self.isBottomHidden = False
elem = panel.getElement()
# Prevents inherited text-align settings from interfering with the
# panel's layout.
DOM.setStyleAttribute(elem, "textAlign", "left")
DOM.setStyleAttribute(elem, "position", "relative")
topElem = panel.getWidgetElement(TOP)
bottomElem = panel.getWidgetElement(BOTTOM)
self.expandToFitParentHorizontally(topElem)
self.expandToFitParentHorizontally(bottomElem)
self.expandToFitParentHorizontally(panel.getSplitElement())
self.expandToFitParentUsingPercentages(panel.container)
def expandToFitParentHorizontally(self, elem):
self.addAbsolutePositoning(elem)
self.setLeft(elem, "0")
self.setElemWidth(elem, "100%")
def onAttach(self):
self.addResizeListener(self.panel.container)
self.onResize()
def onDetach(self):
DOM.setElementProperty(self.panel.container, "onresize", None)
def onSplitterResize(self, px):
""" IE6/7 has event priority issues that will prevent
the repaints from happening quickly enough causing the
interaction to seem unresponsive. The following is simply
a poor man's mouse event coalescing.
"""
resizeUpdatePeriod = 20 # ms
if not self.isResizeInProgress:
self.isResizeInProgress = True
Timer(resizeUpdatePeriod, self)
self.splitPosition = px
def onTimer(self, t):
self.setSplitPosition(splitPosition)
self.isResizeInProgress = False
def updateElements(topElem, splitElem, bottomElem,
topHeight, bottomTop, bottomHeight):
""" IE6/7 has a quirk where a zero height element with
non-zero height children will expand larger than 100%. To
prevent self, the width is explicitly set to zero when
height is zero.
"""
if topHeight == 0:
self.setWidth(topElem, "0px")
self.isTopHidden = True
elif self.isTopHidden:
self.setWidth(topElem, "100%")
self.isTopHidden = False
if bottomHeight == 0:
self.setElemWidth(bottomElem, "0px")
self.isBottomHidden = True
elif self.isBottomHidden:
self.setElemWidth(bottomElem, "100%")
self.isBottomHidden = False
self.panel.setElemHeight(topElem, "%dpx" % topHeight)
self.panel.setTop(splitElem, "%dpx" % topHeight)
self.panel.setTop(bottomElem, "%dpx" % bottomTop)
# IE6/7 cannot update properly with CSS alone.
self.panel.setElemHeight(bottomElem, bottomHeight + "px")
def addResizeListener(self, container):
JS("""
this.container.onresize = function() {
__ImplIE6VerticalSplitPanel_onResize();
}
""")
def onResize(self):
self.setSplitPosition(self.panel.getOffsetHeight(self.panel.getWidgetElement(TOP)))
class VerticalSplitPanel(SplitPanel):
""" A panel that arranges two widgets in a single vertical
column and allows the user to interactively
change the proportion of the height dedicated to
each of the two widgets. Widgets contained within a
<code>VerticalSplitterPanel</code> will be automatically
decorated with scrollbars when necessary.
"""
def __init__(self):
""" Creates an empty vertical split panel.
"""
SplitPanel.__init__(self, DOM.createDiv(),
DOM.createDiv(),
self.preventBoxStyles(DOM.createDiv()),
self.preventBoxStyles(DOM.createDiv()))
self.container = self.preventBoxStyles(DOM.createDiv())
self.buildDOM()
self.setStyleName("gwt-VerticalSplitPanel")
self.impl = ImplVerticalSplitPanel(self)
self.setSplitPosition("50%")
# Captures the height of the top container when drag resizing starts.
self.initialTopHeight = 0
# Captures the offset of a user's mouse pointer during drag resizing.
self.initialThumbPos = 0
self.lastSplitPosition = ""
def getBottomWidget(self):
""" Gets the widget in the bottom of the panel.
@return the widget, <code>None</code> if there is not one
"""
return self.getWidget(BOTTOM)
def getTopWidget(self):
""" Gets the widget in the top of the panel.
@return the widget, <code>None</code> if there is not one
"""
return self.getWidget(TOP)
def setBottomWidget(self, w):
""" Sets the widget in the bottom of the panel.
@param w the widget
"""
self.setWidget(BOTTOM, w)
def setSplitPosition(self, pos):
self.lastSplitPosition = pos
topElem = self.getWidgetElement(TOP)
self.setElemHeight(topElem, pos)
self.impl.setSplitPosition(self.getOffsetHeight(topElem))
def setTopWidget(self, w):
""" Sets the widget in the top of the panel.
@param w the widget
"""
self.setWidget(TOP, w)
def onLoad(self):
self.impl.onAttach()
# Set the position realizing it might not work until
# after layout runs. This first call is simply to try
# to avoid a jitter effect if possible.
self.setSplitPosition(self.lastSplitPosition)
DeferredCommand().add(self)
def execute(self):
self.setSplitPosition(self.lastSplitPosition)
def onUnload(self):
self.impl.onDetach()
def onSplitterResize(self, x, y):
self.impl.onSplitterResize(self.initialTopHeight + y -
self.initialThumbPos)
def onSplitterResizeStarted(self, x, y):
self.initialThumbPos = y
self.initialTopHeight = self.getOffsetHeight(self.getWidgetElement(TOP))
def buildDOM(self):
topDiv = self.getWidgetElement(TOP)
bottomDiv = self.getWidgetElement(BOTTOM)
splitDiv = self.getSplitElement()
DOM.appendChild(self.getElement(), self.container)
DOM.appendChild(self.container, topDiv)
DOM.appendChild(self.container, splitDiv)
DOM.appendChild(self.container, bottomDiv)
# The style name is placed on the table rather than splitElem
# to allow the splitter to be styled without interfering
# with layout.
thumb_html = '<img src="splitPanelThumb.png" />'
DOM.setInnerHTML(splitDiv, "<div class='vsplitter' " +
"style='text-align:center'>" +
thumb_html + "</div>")
self.addScrolling(topDiv)
self.addScrolling(bottomDiv)