Package pyjamas :: Package ui :: Module HorizontalSplitPanel
[hide private]
[frames] | no frames]

Source Code for Module pyjamas.ui.HorizontalSplitPanel

  1  """ 
  2      Horizontal Split Panel: Left and Right layouts with a movable splitter. 
  3   
  4  /* 
  5   * Copyright 2008 Google Inc. 
  6   * Copyright 2009 Luke Kenneth Casson Leighton <lkcl@lkcl.net> 
  7   *  
  8   * Licensed under the Apache License, Version 2.0 (the "License") you may not 
  9   * use this file except in compliance with the License. You may obtain a copy of 
 10   * the License at 
 11   *  
 12   * http:#www.apache.org/licenses/LICENSE-2.0 
 13   *  
 14   * Unless required by applicable law or agreed to in writing, software 
 15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
 17   * License for the specific language governing permissions and limitations under 
 18   * the License. 
 19   */ 
 20  """ 
 21   
 22  from __pyjamas__ import JS 
 23  from pyjamas import Factory 
 24  from SplitPanel import SplitPanel 
 25  from pyjamas import DOM 
 26  from pyjamas import DeferredCommand 
 27   
28 -class ImplHorizontalSplitPanel:
29 """ The standard implementation for horizontal split panels. 30 """
31 - def __init__(self, panel):
32 self.panel = panel 33 34 DOM.setStyleAttribute(panel.getElement(), "position", "relative") 35 36 self.expandToFitParentHorizontally(panel.getWidgetElement(0)) 37 self.expandToFitParentHorizontally(panel.getWidgetElement(1)) 38 self.expandToFitParentHorizontally(panel.getSplitElement()) 39 40 self.panel.expandToFitParentUsingCssOffsets(panel.container) 41 42 # Right now, both panes are stacked on top of each other 43 # on either the left side or the right side of the containing 44 # panel. This happens because both panes have position:absolute 45 # and no left/top values. The panes will be on the left side 46 # if the directionality is LTR, and on the right side if the 47 # directionality is RTL. In the LTR case, we need to snap the 48 # right pane to the right of the container, and in the RTL case, 49 # we need to snap the left pane to the left of the container. 50 51 if True: # TODO: (LocaleInfo.getCurrentLocale().isRTL()): 52 self.panel.setLeft(self.panel.getWidgetElement(0), "0px") 53 else: 54 self.panel.setRight(self.panel.getWidgetElement(1), "0px")
55
56 - def expandToFitParentHorizontally(self, elem):
57 self.panel.addAbsolutePositoning(elem) 58 zeroSize = "0px" 59 self.panel.setTop(elem, zeroSize) 60 self.panel.setBottom(elem, zeroSize)
61 62
63 - def onAttach(self):
64 pass
65 - def onDetach(self):
66 pass
67
68 - def onTimer(self, sender):
69 pass
70 - def execute(self):
71 pass
72 - def addResizeListener(self, container):
73 pass
74 - def onResize(self):
75 pass
76
77 - def onSplitterResize(self, px):
79
80 - def setSplitPosition(self, pos):
81 leftElem = self.panel.getWidgetElement(0) 82 self.panel.setElemWidth(leftElem, pos) 83 self.setSplitPositionUsingPixels(DOM.getOffsetWidth(leftElem))
84
85 - def setSplitPositionUsingPixels(self, px):
87
88 - def _setSplitPositionUsingPixels(self, px):
89 """ Set the splitter's position in units of pixels. 90 91 px represents the splitter's position as a distance 92 of px pixels from the left edge of the container. This is 93 true even in a bidi environment. Callers of this method 94 must be aware of this constraint. 95 """ 96 splitElem = self.panel.getSplitElement() 97 98 rootElemWidth = DOM.getOffsetWidth(self.panel.container) 99 splitElemWidth = DOM.getOffsetWidth(splitElem) 100 101 # This represents an invalid state where layout is incomplete. This 102 # typically happens before DOM attachment, but I leave it here as a 103 # precaution because negative width/height style attributes produce 104 # errors on IE. 105 if (rootElemWidth < splitElemWidth): 106 return 107 108 # Compute the new right side width. 109 newRightWidth = rootElemWidth - px - splitElemWidth 110 111 # Constrain the dragging to the physical size of the panel. 112 if (px < 0): 113 px = 0 114 newRightWidth = rootElemWidth - splitElemWidth 115 elif (newRightWidth < 0): 116 px = rootElemWidth - splitElemWidth 117 newRightWidth = 0 118 119 rightElem = self.panel.getWidgetElement(1) 120 121 # Set the width of the left side. 122 self.panel.setElemWidth(self.panel.getWidgetElement(0), "%dpx" % px) 123 124 # Move the splitter to the right edge of the left element. 125 self.panel.setLeft(splitElem, "%dpx" % px) 126 127 # Move the right element to the right of the splitter. 128 self.panel.setLeft(rightElem, "%dpx" % (px + splitElemWidth)) 129 130 self.updateRightWidth(rightElem, newRightWidth)
131 132
133 - def updateRightWidth(self, rightElem, newRightWidth):
134 # No need to update the width of the right side this will be 135 # recomputed automatically by CSS. This is helpful, as we do not 136 # have to worry about watching for resize events and adjusting the 137 # right-side width. 138 pass
139
140 -class HorizontalSplitPanel(SplitPanel):
141 """ A panel that arranges two widgets in a single horizontal row 142 and allows the user to interactively change the proportion 143 of the width dedicated to each of the two widgets. Widgets 144 contained within a <code>HorizontalSplitPanel</code> will 145 be automatically decorated with scrollbars when necessary. 146 147 Default layout behaviour of HorizontalSplitPanels is to 100% fill 148 its parent vertically and horizontally [this is NOT normal!] 149 """ 150
151 - def __init__(self, **kwargs):
152 """ Creates an empty horizontal split panel. 153 """ 154 155 if not kwargs.has_key('StyleName'): kwargs['StyleName']="gwt-HorizontalSplitPanel" 156 157 if kwargs.has_key('Element'): 158 element = kwargs.pop('Element') 159 else: 160 element = DOM.createDiv() 161 SplitPanel.__init__(self, element, 162 DOM.createDiv(), 163 self.preventBoxStyles(DOM.createDiv()), 164 self.preventBoxStyles(DOM.createDiv()), 165 **kwargs) 166 167 self.container = self.preventBoxStyles(DOM.createDiv()) 168 169 self.buildDOM() 170 171 self.impl = ImplHorizontalSplitPanel(self) 172 173 # By default the panel will fill its parent vertically and horizontally. 174 # The horizontal case is covered by the fact that the top level div is 175 # block display. 176 self.setHeight("100%") 177 178 self.lastSplitPosition = "50%" 179 self.initialLeftWidth = 0 180 self.initialThumbPos = 0
181
182 - def add(self, w):
183 """ 184 * Adds a widget to a pane in the HorizontalSplitPanel. The method 185 * will first attempt to add the widget to the left pane. If a 186 * widget is already in that position, it will attempt to add the 187 * widget to the right pane. If a widget is already in that position, 188 * an exception will be thrown, as a HorizontalSplitPanel can 189 * contain at most two widgets. 190 * 191 * Note that this method is bidi-sensitive. In an RTL environment, 192 * this method will first attempt to add the widget to the right pane, 193 * and if a widget is already in that position, it will attempt to add 194 * the widget to the left pane. 195 * 196 * @param w the widget to be added 197 * @throws IllegalStateException 198 """ 199 if self.getStartOfLineWidget() is None: 200 self.setStartOfLineWidget(w) 201 elif self.getEndOfLineWidget() is None: 202 self.setEndOfLineWidget(w) 203 else: 204 return
205 # TODO throw new IllegalStateException( 206 # "A Splitter can only contain two Widgets.") 207
208 - def getEndOfLineWidget(self):
209 """ 210 * Gets the widget in the pane that is at the end of the line 211 * direction for the layout. That is, in an RTL layout, gets 212 * the widget in the left pane, and in an LTR layout, gets 213 * the widget in the right pane. 214 * 215 * @return the widget, <code>null</code> if there is not one. 216 """ 217 return self.getWidget(self.getEndOfLinePos())
218
219 - def getLeftWidget(self):
220 """ 221 * Gets the widget in the left side of the panel. 222 * 223 * @return the widget, <code>null</code> if there is not one. 224 """ 225 return self.getWidget(0)
226
227 - def getRightWidget(self):
228 """ 229 * Gets the widget in the right side of the panel. 230 * 231 * @return the widget, <code>null</code> if there is not one. 232 """ 233 return self.getWidget(1)
234
235 - def getStartOfLineWidget(self):
236 """ 237 * Gets the widget in the pane that is at the start of the line 238 * direction for the layout. That is, in an RTL environment, gets 239 * the widget in the right pane, and in an LTR environment, gets 240 * the widget in the left pane. 241 * 242 * @return the widget, <code>null</code> if there is not one. 243 """ 244 return self.getWidget(self.getStartOfLinePos())
245
246 - def setEndOfLineWidget(self, w):
247 """ 248 * Sets the widget in the pane that is at the end of the line direction 249 * for the layout. That is, in an RTL layout, sets the widget in 250 * the left pane, and in and RTL layout, sets the widget in the 251 * right pane. 252 * 253 * @param w the widget 254 """ 255 self.setWidget(self.getEndOfLinePos(), w)
256
257 - def setLeftWidget(self, w):
258 """ 259 * Sets the widget in the left side of the panel. 260 * 261 * @param w the widget 262 """ 263 self.setWidget(0, w)
264
265 - def setRightWidget(self, w):
266 """ 267 * Sets the widget in the right side of the panel. 268 * 269 * @param w the widget 270 """ 271 self.setWidget(1, w)
272
273 - def setSplitPosition(self, pos):
274 """ 275 * Moves the position of the splitter. 276 * 277 * This method is not bidi-sensitive. The size specified is always 278 * the size of the left region, regardless of directionality. 279 * 280 * @param pos the new size of the left region in CSS units (e.g. "10px", 281 * "1em") 282 """ 283 self.lastSplitPosition = pos 284 self.impl.setSplitPosition(pos)
285
286 - def setStartOfLineWidget(self, w):
287 """ 288 * Sets the widget in the pane that is at the start of the line direction 289 * for the layout. That is, in an RTL layout, sets the widget in 290 * the right pane, and in and RTL layout, sets the widget in the 291 * left pane. 292 * 293 * @param w the widget 294 """ 295 self.setWidget(self.getStartOfLinePos(), w)
296
297 - def onLoad(self):
298 self.impl.onAttach() 299 # Set the position realizing it might not work until 300 # after layout runs. This first call is simply to try 301 # to avoid a jitter effect if possible. 302 self.setSplitPosition(self.lastSplitPosition) 303 DeferredCommand.add(self)
304
305 - def execute(self):
306 self.setSplitPosition(self.lastSplitPosition)
307
308 - def onUnload(self):
309 self.impl.onDetach()
310
311 - def onSplitterResize(self, x, y):
312 self.lastSplitPosition = x 313 self.impl.onSplitterResize(self.initialLeftWidth + x - 314 self.initialThumbPos)
315
316 - def onSplitterResizeStarted(self, x, y):
317 self.initialThumbPos = x 318 self.initialLeftWidth = DOM.getOffsetWidth(self.getWidgetElement(0))
319 320
321 - def buildDOM(self):
322 323 leftDiv = self.getWidgetElement(0) 324 rightDiv = self.getWidgetElement(1) 325 splitDiv = self.getSplitElement() 326 327 DOM.appendChild(self.getElement(), self.container) 328 329 DOM.appendChild(self.container, leftDiv) 330 DOM.appendChild(self.container, splitDiv) 331 DOM.appendChild(self.container, rightDiv) 332 333 # Sadly, this is the only way I've found to get vertical 334 # centering in this case. The usually CSS hacks (display: 335 # table-cell, vertical-align: middle) don't work in an 336 # absolute positioned DIV. 337 DOM.setInnerHTML(splitDiv, 338 "<table class='hsplitter' height='100%' cellpadding='0' " + 339 "cellspacing='0'><tr><td align='center' valign='middle'>" + 340 self.getThumbImageHTML() + 341 "</td></tr></table>") 342 343 self.addScrolling(leftDiv) 344 self.addScrolling(rightDiv)
345
346 - def getEndOfLinePos(self):
347 return 0
348 # TODO: return (LocaleInfo.getCurrentLocale().isRTL() ? 0 : 1) 349
350 - def getStartOfLinePos(self):
351 return 1
352 # TODO: return (LocaleInfo.getCurrentLocale().isRTL() ? 1 : 0) 353 354 Factory.registerClass('pyjamas.ui.HorizontalSplitPanel', HorizontalSplitPanel) 355