Package pyjamas :: Package dnd :: Module DNDHelper
[hide private]
[frames] | no frames]

Source Code for Module pyjamas.dnd.DNDHelper

  1  # Copyright (C) 2010 Jim Washington 
  2  # 
  3  # Licensed under the Apache License, Version 2.0 (the "License"); 
  4  # you may not use this file except in compliance with the License. 
  5  # You may obtain a copy of the License at 
  6  # 
  7  #     http://www.apache.org/licenses/LICENSE-2.0 
  8  # 
  9  # Unless required by applicable law or agreed to in writing, software 
 10  # distributed under the License is distributed on an "AS IS" BASIS, 
 11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 12  # See the License for the specific language governing permissions and 
 13  # limitations under the License. 
 14   
 15  import time 
 16  from __pyjamas__ import wnd, doc 
 17  from pyjamas import DOM 
 18  from pyjamas import Window 
 19  from pyjamas.ui import GlassWidget 
 20  from pyjamas.ui.RootPanel import RootPanel 
 21  from pyjamas.ui import Event 
 22  from pyjamas.Timer import Timer 
 23  from pyjamas.dnd.utils import DraggingWidget, isCanceled, \ 
 24       findDraggable, eventCoordinates, \ 
 25       getElementUnderMouse 
 26  from pyjamas.dnd.DataTransfer import DataTransfer, DragDataStore 
 27  from pyjamas.dnd.DragEvent import DragEvent 
 28  from pyjamas.dnd import READ_ONLY, READ_WRITE, PROTECTED 
 29   
 30  ACTIVELY_DRAGGING = 3 
 31  DRAGGING_NO_MOVEMENT_YET = 2 
 32  NOT_DRAGGING = 1 
 33   
34 -class DNDHelper(object):
35 """ 36 DNDHelper is a singleton drag and drop agent. 37 38 It acts as dragging/dropping agent for platforms that do not support html5 39 drag and drop. 40 """ 41
42 - def __init__(self):
43 self.dropTargets = [] 44 self.dragging = NOT_DRAGGING 45 self.dragBusy = False 46 self._currentTargetElement = None 47 self.previousDropTarget = None 48 self.draggingImage = None 49 self.origMouseX = 0 50 self.origMouseY = 0 51 self.currentDragOperation = 'none' 52 self.data = None 53 self.returnTimer = Timer(notify=self.onReturningWidget) 54 self.mouseEvent = None 55 self.dragDataStore = None
56
57 - def setCurrentTargetElement(self, element):
58 if self._currentTargetElement is not None: 59 if not DOM.compare(self._currentTargetElement, element): 60 # leave_event = self.makeDragEvent(self.mouseEvent, 'dragleave', 61 # self.currentTargetElement) 62 self.fireDNDEvent('dragleave', self.currentTargetElement, 63 self.currentDropWidget) 64 # self.currentDropWidget.onDragLeave(leave_event) 65 # self.finalize(leave_event) 66 self._currentTargetElement = element
67
68 - def getCurrentTargetElement(self):
69 return self._currentTargetElement
70 71 currentTargetElement = property(getCurrentTargetElement, 72 setCurrentTargetElement) 73
74 - def getElement(self):
75 """ 76 ie6 GlassWidget impl needs this 77 """ 78 return self.dragWidget.getElement()
79
80 - def updateDropEffect(self, dataTransfer, event_type):
81 """ 82 http://dev.w3.org/html5/spec/dnd.html#dragevent 83 """ 84 # default for dragstart, drag, dragleave 85 dropEffect='none' 86 87 if event_type in ['dragover', 'dragenter']: 88 ea = dataTransfer.getEffectAllowed() 89 if ea == 'none': 90 dropEffect = 'none' 91 elif ea.startswith('copy') or ea == 'all': 92 dropEffect = 'copy' 93 elif ea.startswith('link'): 94 dropEffect = 'link' 95 elif ea == 'move': 96 dropEffect = 'move' 97 else: 98 dropEffect = 'copy' 99 elif event_type in ['drop', 'dragend']: 100 dropEffect = self.currentDragOperation 101 dataTransfer.dropEffect = dropEffect
102
103 - def updateDragOperation(self, event):
104 """ 105 http://dev.w3.org/html5/spec/dnd.html 106 """ 107 dataTransfer = event.dataTransfer 108 ea = dataTransfer.effectAllowed 109 de = dataTransfer.dropEffect 110 if (de == 'copy' and ea in 111 ['uninitialized', 'copy','copyLink', 'copyMove', 'all']): 112 self.currentDragOperation = 'copy' 113 elif (de == 'link' and ea in 114 ['uninitialized', 'link', 'copyLink', 'linkMove', 'all']): 115 self.currentDragOperation = 'link' 116 elif (de == 'move' and ea in 117 ['uninitialized', 'move', 'copyMove', 'linkMove', 'all']): 118 self.currentDragOperation = 'move' 119 else: 120 self.currentDragOperation = 'none'
121
122 - def updateAllowedEffects(self, drag_event):
123 dt = drag_event.dataTransfer 124 self.dragDataStore.allowed_effects_state = dt.effectAllowed
125
126 - def registerTarget(self, target):
127 """ 128 Rather than searching the entire document for drop target widgets and 129 maybe drop targets within widgets, this implementation holds a list of 130 widgets and searches only within this list for potential drop targets. 131 """ 132 if not target in self.dropTargets: 133 self.dropTargets.append(target)
134
135 - def unregisterTarget(self, target):
136 """ 137 I dont know why, but a widget may no longer want to be registered 138 as a drop target. 139 """ 140 while target in self.dropTargets: 141 self.dropTargets.remove(target)
142 143
144 - def setDragImage(self, element, x, y):
145 position_absolute = DOM.getStyleAttribute(element, 146 'position') == 'absolute' 147 if position_absolute: 148 self.dragLeftOffset = x + DOM.getAbsoluteLeft( 149 element.offsetParent) 150 self.dragTopOffset = y + DOM.getAbsoluteTop( 151 element.offsetParent) 152 else: 153 self.dragLeftOffset = x 154 self.dragTopOffset = y 155 if element.tagName.lower().endswith('img'): 156 src = DOM.getAttribute(element,'src') 157 element = DOM.createElement('img') 158 DOM.setAttribute(element, 'src', src) 159 if not self.draggingImage: 160 self.createDraggingImage(element) 161 else: 162 self.draggingImage.setImage(element)
163
164 - def addFeedbackElement(self, element):
165 """ 166 This is called from DataTransfer 167 """ 168 if self.draggingImage: 169 self.draggingImage.addElement(element) 170 else: 171 self.createDraggingImage(element)
172
173 - def createDraggingImage(self, element):
174 self.draggingImage = DraggingWidget(element) 175 return self.draggingImage
176
177 - def setDragImageLocation(self, x, y):
178 """ 179 Move the dragging image around. 180 """ 181 elt_top = y - self.dragTopOffset 182 elt_left = x - self.dragLeftOffset 183 # if self.absParent: 184 # ap = self.absParent 185 # elt_top -= int(self.absTop) 186 # elt_left -= int(self.absLeft) 187 188 self.draggingImage.setStyleAttribute('top', elt_top ) 189 self.draggingImage.setStyleAttribute('left', elt_left)
190
191 - def getAbsoluteLeft(self):
192 """ 193 GlassWidget wants this 194 """ 195 # return 0 196 # if self.absParent: 197 # return self.absParent.getAbsoluteLeft() 198 return self.dragWidget.getAbsoluteLeft()
199 #return self.origLeft 200
201 - def getAbsoluteTop(self):
202 """ 203 GlassWidget wants this 204 """ 205 # return 0 206 # if self.absParent: 207 # return self.absParent.getAbsoluteTop() 208 return self.dragWidget.getAbsoluteTop()
209 #return self.origTop
210 - def makeDragEvent(self, event, type, target=None):
211 dt = DataTransfer(self.dragDataStore) 212 self.updateDropEffect(dt, type) 213 drag_event = DragEvent(event, type, dt, target) 214 return drag_event
215
216 - def finalize(self, event):
217 self.dragDataStore.allowed_effects_state = \ 218 event.dataTransfer.effectAllowed 219 if event.type in ['dragstart', 'drop']: 220 self.dragDataStore.setMode(PROTECTED) 221 event.dataTransfer.dataStore = None
222
223 - def fireDNDEvent(self, name, target, widget):
224 if name == 'dragstart': 225 self.dragDataStore.setMode(READ_WRITE) 226 elif name == 'drop': 227 self.dragDataStore.setMode(READ_ONLY) 228 event = self.makeDragEvent(self.mouseEvent, name, target) 229 widget.onBrowserEvent(event) 230 self.finalize(event) 231 return event
232
233 - def initFeedbackImage(self):
234 ds = self.dragDataStore 235 x = 0 236 y = 0 237 if ds.bitmap is not None: 238 if ds.hotspot_coordinate is not None: 239 offset = ds.hotspot_coordinate 240 x = offset[0] 241 y = offset[1] 242 self.setDragImage(ds.bitmap, x, y) 243 return 244 if self.dragDataStore.elements: 245 for element in self.dragDataStore.elements: 246 self.addFeedbackElement(element)
247 248
249 - def onMouseMove(self, sender, x, y):
250 event = DOM.eventGetCurrentEvent() 251 self.mouseEvent = event 252 button = DOM.eventGetButton(event) 253 if not button == Event.BUTTON_LEFT: 254 return 255 ## The following commented code lets the native dnd happen in IE. sucks. 256 ## But it may enable dragging our widgets out of IE into other apps. 257 # else: 258 # try: 259 # self.dragWidget.getElement().dragDrop() 260 # return 261 # except: 262 # pass 263 264 # Adjust x and y to absolute coordinates. 265 x, y = eventCoordinates(event) 266 267 if self.dragging == DRAGGING_NO_MOVEMENT_YET: 268 self.origMouseX = x 269 self.origMouseY = y 270 self.currentDragOperation = 'none' 271 fromElement = self.dragWidget.getElement() 272 # Is the widget itself draggable? 273 try: 274 draggable = fromElement.draggable 275 except: 276 draggable = False 277 # if not, find the draggable element at (x, y) in the widget 278 if not draggable: 279 fromElement = findDraggable(sender.getElement(), 280 self.origMouseX, self.origMouseY) 281 # Nothing draggable found. return. 282 if fromElement is None: 283 self.dragging = NOT_DRAGGING 284 return 285 # Get the location for the dragging widget 286 287 #self.absParent = None 288 289 #self.absParent = self.dragWidget.getParent() 290 #self.absLeft = DOM.getStyleAttribute(fromElement, 'left') 291 292 #print self.absLeft 293 #self.absTop = DOM.getStyleAttribute(fromElement, 'top') 294 #print self.absTop 295 #self.origTop = DOM.getAbsoluteTop(fromElement) + parent.getAbsoluteTop() 296 #self.origLeft = DOM.getAbsoluteLeft(fromElement) + parent.getAbsoluteLeft() 297 self.origTop = DOM.getAbsoluteTop(fromElement) 298 self.origLeft = DOM.getAbsoluteLeft(fromElement) 299 #self.glassTop = DOM.getAbsoluteTop(fromElement.offsetParent) 300 #self.glassLeft = DOM.getAbsoluteTop(fromElement.offsetParent) 301 position_absolute = DOM.getStyleAttribute(fromElement, 302 'position') == 'absolute' 303 if position_absolute: 304 self.dragLeftOffset = (self.origMouseX - 305 DOM.getAbsoluteLeft(fromElement.offsetParent)) 306 self.dragTopOffset = (self.origMouseY - 307 DOM.getAbsoluteTop(fromElement.offsetParent)) 308 else: 309 self.dragLeftOffset = self.origMouseX - self.origLeft 310 self.dragTopOffset = self.origMouseY - self.origTop 311 312 # self.setDragImage(fromElement, 313 # self.origMouseX - self.origLeft, 314 # self.origMouseY - self.origTop) 315 self.dragDataStore.elements = [fromElement] 316 dragStartEvent = self.fireDNDEvent('dragstart', None, 317 self.dragWidget) 318 if not isCanceled(dragStartEvent): 319 self.initFeedbackImage() 320 RootPanel().add(self.draggingImage) 321 self.setDragImageLocation(x, y) 322 self.dragging = ACTIVELY_DRAGGING 323 GlassWidget.show(self) 324 elif self.dragging == ACTIVELY_DRAGGING: 325 try: 326 doc().selection.empty() 327 except: 328 wnd().getSelection().removeAllRanges() 329 330 self.setDragImageLocation(x, y) 331 332 # If we are still working on the previous iteration, or if we have 333 # done this recently, we'll wait for the next event. 334 if self.dragBusy or time.time() - self.drag_time < 0.25: 335 return 336 337 self.doDrag(event, x, y) 338 self.drag_time = time.time()
339 340 341
342 - def doDrag(self, event, x, y):
343 self.dragBusy = True 344 #self.dragDataStore.dropEffect = 'none' 345 drag_event = self.fireDNDEvent('drag', None, self.dragWidget) 346 # drag event was not canceled 347 if not isCanceled(drag_event): 348 target = None 349 widget = None 350 # Find the most specific element under the cursor and the widget 351 # with the drop listener for it. 352 for widget in self.dropTargets: 353 target = getElementUnderMouse(widget, x, y) 354 if target is not None: 355 break 356 if target: 357 drop_widget = widget 358 drop_element = target 359 if (not self.currentTargetElement or 360 not DOM.compare(drop_element, self.currentTargetElement)): 361 # enter_event = self.makeDragEvent(event,'dragenter', 362 # drop_element) 363 enter_event = self.fireDNDEvent('dragenter', drop_element, 364 drop_widget) 365 # drop_widget.onDragEnter(enter_event) 366 # self.finalize(enter_event) 367 if isCanceled(enter_event): 368 self.currentTargetElement = drop_element 369 self.currentDropWidget = drop_widget 370 371 if self.currentTargetElement is not None: 372 # disable dropping if over event is not canceled 373 # over_event = self.makeDragEvent(event, 'dragover', 374 # drop_element) 375 over_event = self.fireDNDEvent('dragover', drop_element, 376 self.currentDropWidget) 377 # self.currentDropWidget.onDragOver(over_event) 378 # self.finalize(over_event) 379 if isCanceled(over_event): 380 self.updateDragOperation(over_event) 381 else: 382 self.currentDragOperation = 'none' 383 self.draggingImage.updateCursor(self.currentDragOperation) 384 else: 385 self.currentTargetElement = None 386 387 else: 388 self.currentDragOperation = 'none' 389 self.dragBusy = False
390
391 - def onMouseDown(self, sender, x, y):
392 self.dragWidget = sender 393 event = DOM.eventGetCurrentEvent() 394 self.mouseEvent = event 395 button = DOM.eventGetButton(event) 396 if button != Event.BUTTON_LEFT: 397 return 398 # x, y = eventCoordinates(event) 399 # self.origMouseX = x 400 # self.origMouseY = y 401 self.dragging = DRAGGING_NO_MOVEMENT_YET 402 self.drag_time = time.time() 403 self.dragDataStore = DragDataStore()
404
405 - def onMouseUp(self, sender, x, y):
406 # event = DOM.eventGetCurrentEvent() 407 self.dragging = NOT_DRAGGING 408 if self.draggingImage: 409 GlassWidget.hide() 410 if (self.currentDragOperation == 'none' 411 or not self.currentTargetElement): 412 if self.currentTargetElement: 413 # leave_event = self.makeDragEvent(event, 'dragleave', 414 # self.currentTargetElement) 415 self.fireDNDEvent('dragleave', self.currentTargetElement, 416 self.currentDropWidget) 417 # self.currentDropWidget.onDragLeave(leave_event) 418 # self.finalize(leave_event) 419 else: 420 self.currentDragOperation = 'none' 421 self.returnDrag() 422 else: 423 # self.dragDataStore.mode = READ_ONLY 424 # drop_event = self.makeDragEvent(event, 'drop', 425 # self.currentTargetElement) 426 drop_event = self.fireDNDEvent('drop', self.currentTargetElement, 427 self.currentDropWidget) 428 #self.dropEffect = self.currentDragOperation 429 # self.currentDropWidget.onDrop(drop_event) 430 # self.finalize(drop_event) 431 if isCanceled(drop_event): 432 self.currentDragOperation = drop_event.dataTransfer.dropEffect 433 else: 434 self.currentDragOperation = 'none' 435 self.zapDragImage() 436 437 #self.dropEffect = self.currentDragOperation 438 self.fireDNDEvent('dragend', None, self.dragWidget)
439 # dragEnd_event = self.makeDragEvent(event, 'dragend') 440 441 # self.dragWidget.onDragEnd(dragEnd_event) 442 # self.finalize(dragEnd_event) 443
444 - def zapDragImage(self):
445 RootPanel().remove(self.draggingImage) 446 self.draggingImage = None
447
448 - def returnDrag(self):
449 self.moveItemTo(self.draggingImage,self.origLeft, self.origTop)
450
451 - def returnXY(self, start, destination, count):
452 start_x, start_y = start 453 destination_x, destination_y = destination 454 diff_x = (start_x - destination_x) / count 455 diff_y = (start_y - destination_y) / count 456 while (abs(start_x - destination_x) > 10 457 or abs(start_y - destination_y) > 10): 458 start_x -= diff_x 459 start_y -= diff_y 460 yield start_x, start_y 461 raise StopIteration
462
463 - def onReturningWidget(self, timer):
464 try: 465 next_loc = self.return_iterator.next() 466 except StopIteration: 467 self.zapDragImage() 468 return 469 x, y = next_loc 470 self.draggingImage.setStyleAttribute('top', str(y)) 471 self.draggingImage.setStyleAttribute('left', str(x)) 472 self.returnTimer.schedule(50)
473
474 - def moveItemTo(self, widget, x, y):
475 self.returnWidget = widget 476 returnWidgetDestination = x, y 477 widgetStart = widget.getAbsoluteLeft(), widget.getAbsoluteTop() 478 self.return_iterator = self.returnXY(widgetStart, 479 returnWidgetDestination, 10) 480 self.returnTimer.schedule(50)
481
482 - def onMouseEnter(self, sender):
483 pass
484
485 - def onMouseLeave(self, sender):
486 if self.dragging == DRAGGING_NO_MOVEMENT_YET: 487 self.dragging = NOT_DRAGGING
488
489 - def onMouseGlassEnter(self, sender):
490 pass
491
492 - def onMouseGlassLeave(self, sender):
493 pass
494 495 dndHelper = None 496
497 -def initDNDHelper():
498 global dndHelper 499 if dndHelper is None: 500 dndHelper = DNDHelper()
501 502 initDNDHelper() 503