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

Source Code for Module pyjamas.ui.RichTextToolbar

   1  """ 
   2  * Copyright 2007 Google Inc. 
   3  * 
   4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
   5  * use this file except in compliance with the License. You may obtain a copy of 
   6  * the License at 
   7  * 
   8  * http:#www.apache.org/licenses/LICENSE-2.0 
   9  * 
  10  * Unless required by applicable law or agreed to in writing, software 
  11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
  12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
  13  * License for the specific language governing permissions and limitations under 
  14  * the License. 
  15  """ 
  16  #package com.google.gwt.sample.kitchensink.client 
  17   
  18   
  19  from pyjamas.ui.PopupPanel import PopupPanel 
  20  from pyjamas.ui.VerticalPanel import VerticalPanel 
  21  from pyjamas.ui.Label import Label 
  22  from pyjamas.ui.CheckBox import CheckBox 
  23  from pyjamas.ui.Button import Button 
  24  from pyjamas.ui.TextBox import TextBox 
  25   
  26  from __pyjamas__ import doc 
  27   
  28  from pyjamas import DOM 
  29  from pyjamas import Window 
  30  from pyjamas.ui.Image import Image 
  31  from pyjamas.ui.ChangeListener import ChangeHandler 
  32  from pyjamas.ui.ClickListener import ClickHandler 
  33  from pyjamas.ui.Composite import Composite 
  34  from pyjamas.ui.HorizontalPanel import HorizontalPanel 
  35  from pyjamas.ui.ListBox import ListBox 
  36  from pyjamas.ui.PushButton import PushButton 
  37  from pyjamas.ui import RichTextAreaConsts 
  38  from pyjamas.ui.ToggleButton import ToggleButton 
  39  from pyjamas.ui.VerticalPanel import VerticalPanel 
  40  from pyjamas.ui.Widget import Widget 
  41   
  42  from pyjamas.Timer import Timer 
  43   
  44  from pyjamas.selection.RangeEndPoint import RangeEndPoint 
  45  from pyjamas.selection.Range import Range 
  46  from pyjamas.selection.RangeUtil import getAdjacentTextElement 
  47  from pyjamas.selection import Selection 
  48   
  49  import string 
  50  import traceback 
  51   
  52   
  57   
58 -def remove_node(doc, element):
59 """ removes a specific node, adding its children in its place 60 """ 61 fragment = doc.createDocumentFragment() 62 while element.firstChild: 63 fragment.appendChild(element.firstChild) 64 65 parent = element.parentNode 66 parent.insertBefore(fragment, element) 67 parent.removeChild(element)
68
69 -def remove_editor_styles(doc, csm, tree):
70 """ removes all other <span> nodes with an editor style 71 """ 72 73 element = tree.lastChild 74 while element: 75 if not csm.identify(element): 76 element = element.previousSibling 77 continue 78 prev_el = element 79 remove_editor_styles(doc, csm, prev_el) 80 element = element.previousSibling 81 remove_node(doc, prev_el)
82
83 -class Images(object):
84 bold = "bold.gif" 85 italic = "italic.gif" 86 underline = "underline.gif" 87 subscript = "subscript.gif" 88 superscript = "superscript.gif" 89 justifyLeft = "justifyLeft.gif" 90 justifyCenter = "justifyCenter.gif" 91 justifyRight = "justifyRight.gif" 92 strikeThrough = "strikeThrough.gif" 93 indent = "indent.gif" 94 outdent = "outdent.gif" 95 hr = "hr.gif" 96 ol = "ol.gif" 97 ul = "ul.gif" 98 insertImage = "insertImage.gif" 99 createLink = "createLink.gif" 100 removeLink = "removeLink.gif" 101 removeFormat = "removeFormat.gif"
102
103 -class FontFamilyManager:
104
105 - def __init__(self, doc, fontname):
106 self.fontname = fontname 107 self.doc = doc
108
109 - def create(self):
110 element = self.doc.createElement("span") 111 DOM.setStyleAttribute(element, "font-family", self.fontname) 112 return element
113
114 - def identify(self, element):
115 if element.nodeType != 1: 116 return False 117 if str(string.lower(element.tagName)) != 'span': 118 return False 119 style = DOM.getStyleAttribute(element, "font-family") 120 return style is not None
121
122 -class CustomStyleManager:
123
124 - def __init__(self, doc, stylename):
125 self.stylename = stylename 126 self.doc = doc
127
128 - def create(self):
129 element = self.doc.createElement("span") 130 DOM.setAttribute(element, "className", self.stylename) 131 return element
132
133 - def identify(self, element):
134 if element.nodeType != 1: 135 return False 136 if str(string.lower(element.tagName)) != 'span': 137 return False 138 style = DOM.getAttribute(element, "className") 139 return style and style.startswith("editor-")
140
141 -class HeadingStyleManager:
142
143 - def __init__(self, doc, heading):
144 self.heading = heading 145 self.doc = doc
146
147 - def create(self):
148 element = self.doc.createElement(self.heading) 149 return element
150
151 - def identify(self, element):
152 if element.nodeType != 1: 153 return False 154 tag = str(string.lower(element.tagName)) 155 return len(tag) == 2 and tag[0] == "h"
156 157 158 """* 159 * A sample toolbar for use with {@link RichTextArea}. It provides a simple UI 160 * for all rich text formatting, dynamically displayed only for the available 161 * functionality. 162 """
163 -class RichTextToolbar(Composite, ClickHandler, ChangeHandler):
164 165 fontSizesConstants = [ 166 RichTextAreaConsts.XX_SMALL, RichTextAreaConsts.X_SMALL, 167 RichTextAreaConsts.SMALL, RichTextAreaConsts.MEDIUM, 168 RichTextAreaConsts.LARGE, RichTextAreaConsts.X_LARGE, 169 RichTextAreaConsts.XX_LARGE 170 ] 171 172 """* 173 * Creates a toolbar that drives the given rich text area. 174 * 175 * @param richText the rich text area to be controlled 176 """
177 - def __init__(self, richText, _parent, **kwargs):
178 179 self.isInText = False 180 self.lastText = "" 181 self.trigger = False 182 self.lastRange = None 183 self._parent = _parent 184 185 # Timer for trying real time selection change stuff 186 self.timerRange = None 187 self.selTimer = Timer(notify=self.run) 188 189 self.outer = VerticalPanel() 190 self.topPanel = HorizontalPanel(BorderWidth=1) 191 self.bottomPanel = HorizontalPanel() 192 193 self.richText = richText 194 self.basic = richText.getBasicFormatter() 195 self.extended = richText.getExtendedFormatter() 196 197 self.outer.add(self.topPanel) 198 self.outer.add(self.bottomPanel) 199 self.topPanel.setWidth("100%") 200 self.topPanel.setHeight("20px") 201 self.bottomPanel.setWidth("100%") 202 203 kwargs['StyleName'] = kwargs.get('StyleName', "gwt-RichTextToolbar") 204 205 Composite.__init__(self, self.outer, **kwargs) 206 ClickHandler.__init__(self) 207 ChangeHandler.__init__(self) 208 209 if self.basic is not None: 210 self.bold = self.createToggleButton(Images.bold, 211 "bold") 212 self.italic = self.createToggleButton(Images.italic, 213 "italic") 214 self.underline = self.createToggleButton(Images.underline, 215 "underline") 216 self.subscript = self.createToggleButton(Images.subscript, 217 "subscript") 218 self.superscript = self.createToggleButton(Images.superscript, 219 "superscript") 220 self.justifyLeft = self.createPushButton(Images.justifyLeft, 221 "justify left") 222 self.justifyCenter = self.createPushButton(Images.justifyCenter, 223 "justify centre") 224 self.justifyRight = self.createPushButton(Images.justifyRight, 225 "justify right") 226 self.topPanel.add(self.bold) 227 self.topPanel.add(self.italic) 228 self.topPanel.add(self.underline) 229 self.topPanel.add(self.subscript) 230 self.topPanel.add(self.superscript) 231 self.topPanel.add(self.justifyLeft) 232 self.topPanel.add(self.justifyCenter) 233 self.topPanel.add(self.justifyRight) 234 235 if self.extended is not None: 236 self.strikethrough = self.createToggleButton(Images.strikeThrough, 237 "strikethrough") 238 self.indent = self.createPushButton(Images.indent, 239 "indent") 240 self.outdent = self.createPushButton(Images.outdent, 241 "outdent") 242 self.hr = self.createPushButton(Images.hr, 243 "hr") 244 self.ol = self.createPushButton(Images.ol, 245 "ordered list") 246 self.ul = self.createPushButton(Images.ul, 247 "unordered list") 248 self.insertImage = self.createPushButton(Images.insertImage, 249 "insert image") 250 self.createLink = self.createPushButton(Images.createLink, 251 "create link") 252 self.removeLink = self.createPushButton(Images.removeLink, 253 "remove link") 254 self.removeFormat = self.createPushButton(Images.removeFormat, 255 "remove formatting") 256 257 self.topPanel.add(self.strikethrough) 258 self.topPanel.add(self.indent) 259 self.topPanel.add(self.outdent) 260 self.topPanel.add(self.hr) 261 self.topPanel.add(self.ol) 262 self.topPanel.add(self.ul) 263 self.topPanel.add(self.insertImage) 264 self.topPanel.add(self.createLink) 265 self.topPanel.add(self.removeLink) 266 self.topPanel.add(self.removeFormat) 267 268 if self.basic is not None: 269 self.hstyles = self.createHeadingStyleList("Headings") 270 self.backColors = self.createColorList("Background") 271 self.foreColors = self.createColorList("Foreground") 272 self.fonts = self.createFontList() 273 self.fontSizes = self.createFontSizes() 274 self.bottomPanel.add(self.hstyles) 275 self.bottomPanel.add(self.backColors) 276 self.bottomPanel.add(self.foreColors) 277 self.bottomPanel.add(self.fonts) 278 self.bottomPanel.add(self.fontSizes) 279 280 self.richText.addKeyboardListener(self) 281 self.richText.addClickListener(self) 282 self.richText.addFocusListener(self) 283 self.richText.addMouseListener(self)
284
285 - def createHeadingStyleList(self, caption):
286 lb = ListBox() 287 lb.addChangeListener(self) 288 lb.setVisibleItemCount(1) 289 290 lb.addItem(caption) 291 lb.addItem("Heading1", "h1") 292 lb.addItem("Heading2", "h2") 293 lb.addItem("Heading3", "h3") 294 lb.addItem("Heading4", "h4") 295 lb.addItem("Heading5", "h5") 296 297 return lb
298
299 - def createColorList(self, caption):
300 lb = ListBox() 301 lb.addChangeListener(self) 302 lb.setVisibleItemCount(1) 303 304 lb.addItem(caption) 305 lb.addItem("White", "white") 306 lb.addItem("Black", "black") 307 lb.addItem("Red", "red") 308 lb.addItem("Green", "green") 309 lb.addItem("Yellow", "yellow") 310 lb.addItem("Blue", "blue") 311 return lb
312
313 - def createFontList(self):
314 lb = ListBox() 315 lb.addChangeListener(self) 316 lb.setVisibleItemCount(1) 317 318 lb.addItem("Font", "") 319 lb.addItem("Normal", "") 320 lb.addItem("Times New Roman", "Times New Roman") 321 lb.addItem("Arial", "Arial") 322 lb.addItem("Aramanth", "Aramanth") 323 lb.addItem("Calibri", "Calibri") 324 lb.addItem("Courier New", "Courier New") 325 lb.addItem("Georgia", "Georgia") 326 lb.addItem("Helvetica", "Helvetica") 327 lb.addItem("Symbol", "Symbol") 328 lb.addItem("Trebuchet", "Trebuchet") 329 lb.addItem("Verdana", "Verdana") 330 return lb
331
332 - def createFontSizes(self):
333 lb = ListBox() 334 lb.addChangeListener(self) 335 lb.setVisibleItemCount(1) 336 337 lb.addItem("Size") 338 lb.addItem("XXsmall") 339 lb.addItem("Xsmall") 340 lb.addItem("small") 341 lb.addItem("medium") 342 lb.addItem("large") 343 lb.addItem("Xlarge") 344 lb.addItem("XXlarge") 345 return lb
346
347 - def createPushButton(self, img, tip):
348 img = Image(img) 349 pb = PushButton(img, img, self) 350 pb.setTitle(tip) 351 return pb
352
353 - def createToggleButton(self, img, tip):
354 img = Image(img) 355 tb = ToggleButton(img, img, self) 356 tb.setTitle(tip) 357 return tb
358
359 - def updateStatus(self):
360 """* Updates the status of all the stateful buttons. 361 """ 362 if self.basic is not None: 363 self.bold.setDown(self.basic.isBold()) 364 self.italic.setDown(self.basic.isItalic()) 365 self.underline.setDown(self.basic.isUnderlined()) 366 self.subscript.setDown(self.basic.isSubscript()) 367 self.superscript.setDown(self.basic.isSuperscript()) 368 369 if self.extended is not None: 370 self.strikethrough.setDown(self.extended.isStrikethrough())
371
372 - def onChange(self, sender):
373 if sender == self.hstyles: 374 bc = self.hstyles.getValue(self.hstyles.getSelectedIndex()) 375 self._surround(HeadingStyleManager, bc) 376 self.backColors.setSelectedIndex(0) 377 if sender == self.backColors: 378 bc = self.backColors.getValue(self.backColors.getSelectedIndex()) 379 self.basic.setBackColor(bc) 380 self.backColors.setSelectedIndex(0) 381 elif sender == self.foreColors: 382 fc = self.foreColors.getValue(self.foreColors.getSelectedIndex()) 383 self.basic.setForeColor(fc) 384 self.foreColors.setSelectedIndex(0) 385 elif sender == self.fonts: 386 fname = self.fonts.getValue(self.fonts.getSelectedIndex()) 387 self.basic.setFontName(fname) 388 self.fonts.setSelectedIndex(0) 389 elif sender == self.fontSizes: 390 fs = self.fontSizesConstants[self.fontSizes.getSelectedIndex() - 1] 391 self.basic.setFontSize(fs) 392 self.fontSizes.setSelectedIndex(0)
393
394 - def onClick(self, sender):
395 396 if sender == self.bold: 397 self.basic.toggleBold() 398 elif sender == self.italic: 399 self.basic.toggleItalic() 400 elif sender == self.underline: 401 self.basic.toggleUnderline() 402 elif sender == self.subscript: 403 self.basic.toggleSubscript() 404 elif sender == self.superscript: 405 self.basic.toggleSuperscript() 406 elif sender == self.strikethrough: 407 self.extended.toggleStrikethrough() 408 elif sender == self.indent: 409 self.extended.rightIndent() 410 elif sender == self.outdent: 411 self.extended.leftIndent() 412 elif sender == self.justifyLeft: 413 self.basic.setJustification(RichTextAreaConsts.LEFT) 414 elif sender == self.justifyCenter: 415 self.basic.setJustification(RichTextAreaConsts.CENTER) 416 elif sender == self.justifyRight: 417 self.basic.setJustification(RichTextAreaConsts.RIGHT) 418 elif sender == self.insertImage: 419 url = Window.prompt("Enter an image URL:", "http:#") 420 if url is not None: 421 self.extended.insertImage(url) 422 423 elif sender == self.createLink: 424 EventLinkPopup_open(self) 425 426 elif sender == self.removeLink: 427 self.extended.removeLink() 428 elif sender == self.hr: 429 self.extended.insertHorizontalRule() 430 elif sender == self.ol: 431 self.extended.insertOrderedList() 432 elif sender == self.ul: 433 self.extended.insertUnorderedList() 434 elif sender == self.removeFormat: 435 self.extended.removeFormat() 436 elif sender == self.richText: 437 # We use the RichTextArea's onKeyUp event to update the 438 # toolbar status. This will catch any cases where the 439 # user moves the cursor using the keyboard, or uses one of 440 # the browser's built-in keyboard shortcuts. 441 self.updateStatus() 442 443 self.checkForChange()
444
445 - def onKeyDown(self, sender, keyCode, modifiers):
446 pass
447
448 - def onKeyPress(self, sender, keyCode, modifiers):
449 pass
450
451 - def onKeyUp(self, sender, keyCode, modifiers):
452 if sender == self.richText: 453 # We use the RichTextArea's onKeyUp event to update the 454 # toolbar status. This will catch any cases where the user 455 # moves the cursor using the keyboard, or uses one of 456 # the browser's built-in keyboard shortcuts. 457 self.updateStatus() 458 self.checkForChange()
459
460 - def onMouseLeave(self, event):
461 pass
462
463 - def onMouseEnter(self, event):
464 pass
465
466 - def onMouseUp(self, event, x, y):
467 pass
468
469 - def onMouseMove(self, event, x, y):
470 pass
471
472 - def onMouseDown(self, event, x, y):
473 self.trigger = True
474
475 - def onFocus(self, event):
476 print "focus" 477 pass
478
479 - def onLostFocus(self, event):
480 "lost focus" 481 self.checkForChange()
482
483 - def onMouseOut(self, sender):
484 print "mouse out" 485 if self.isInText and self.isOnTextBorder(sender): 486 self.isInText = False 487 self.captureSelection() 488 self.endSelTimer()
489
490 - def onMouseOver(self, sender):
491 print "mouse over" 492 if not self.isInText: 493 self.isInText = True 494 self.richText.setFocus(True) 495 self.lastRange = None 496 self.startSelTimer()
497 498
499 - def setFocus(self, wid):
500 self._wid = wid # hack 501 DeferredCommand.add(getattr(self, "execute_set_focus"))
502
503 - def execute_set_focus(self):
504 self._wid.setFocus(True)
505
506 - def findNodeByNumber(self, num):
507 508 doc = self.getDocument() 509 res = getAdjacentTextElement(doc, True) 510 while (res is not None) and (num > 0): 511 num -= 1 512 res = getAdjacentTextElement(res, True) 513 514 return res
515
516 - def selectNodes(self, startNode, startOffset, endNode=None, endOffset=None):
517 518 startText = self.findNodeByNumber(startNode) 519 if endNode is not None: 520 endText = self.findNodeByNumber(endNode) 521 else: 522 endText = startText 523 endOffset = startOffset 524 525 rng = Range(RangeEndPoint(startText, startOffset), 526 RangeEndPoint(endText, endOffset)) 527 528 self.getSelection() 529 Selection.setRange(rng) 530 531 self.refresh()
532
533 - def font1(self):
534 self._surround(FontFamilyManager, "Times New Roman")
535
536 - def font2(self):
537 self._surround(FontFamilyManager, "Arial")
538
539 - def surround1(self):
540 self._surround(CustomStyleManager, "editor-cls1")
541
542 - def surround2(self):
543 self._surround(CustomStyleManager, "editor-cls2")
544
545 - def _surround(self, kls, cls):
546 """ this is possibly one of the most truly dreadful bits of code 547 for manipulating DOM ever written. its purpose is to add only 548 the editor class required, and no more. unfortunately, DOM gets 549 chopped up by the range thing, and a bit more besides. so we 550 have to: 551 552 * extract the range contents 553 * clean up removing any blank text nodes that got created above 554 * slap a span round it 555 * clean up removing any blank text nodes that got created above 556 * remove any prior editor styles on the range contents 557 * go hunting through the entire document for stacked editor styles 558 559 this latter is funfunfun because only "spans with editor styles 560 which themselves have no child elements but a single span with 561 an editor style" must be removed. e.g. if an outer editor span 562 has another editor span and also some text, the outer span must 563 be left alone. 564 """ 565 rng = self.getRange() 566 if (rng is None) or rng.isCursor(): 567 return 568 569 csm = kls(rng.m_document, cls) 570 571 rng.ensureRange() 572 dfrag = rng.m_range.extractContents() 573 remove_editor_styles(rng.m_document, csm, dfrag) 574 element = csm.create() 575 DOM.appendChild(element, dfrag) 576 rng.m_range.insertNode(element) 577 578 it = DOM.IterWalkChildren(element, True) 579 while True: 580 try: 581 node = it.next() 582 except StopIteration: 583 break 584 if node.nodeType == 3 and unicode(node.data) == u'': 585 DOM.removeChild(node.parentNode, node) 586 587 rng.setRange(element) 588 589 it = DOM.IterWalkChildren(rng.m_document, True) 590 while True: 591 try: 592 node = it.next() 593 except StopIteration: 594 break 595 if node.nodeType == 3 and unicode(node.data) == u'': 596 DOM.removeChild(node.parentNode, node) 597 598 # clears out all nodes with no children. 599 it = DOM.IterWalkChildren(rng.m_document) 600 while True: 601 try: 602 node = it.next() 603 except StopIteration: 604 break 605 if node.firstChild or not csm.identify(node): 606 continue 607 DOM.removeChild(node.parentNode, node) 608 609 it = DOM.IterWalkChildren(rng.m_document, True) 610 while True: 611 try: 612 node = it.next() 613 except StopIteration: 614 break 615 if not csm.identify(node): 616 continue 617 if node.firstChild is None: 618 continue 619 if not csm.identify(node.firstChild): 620 continue 621 if node.firstChild.nextSibling: 622 continue 623 # remove the *outer* one because the range was added to 624 # the inner, and the inner one overrides anyway 625 626 remove_node(rng.m_document, node) 627 628 doc = self.getDocument() 629 630 self.getSelection() 631 Selection.setRange(rng) 632 self.refresh()
633
634 - def refresh(self):
635 self._parent.refresh()
636
637 - def delete(self):
638 rng = self.getRange() 639 if rng is None or rng.isCursor(): 640 return 641 rng.deleteContents() 642 refresh()
643
644 - def toCursor(self, start):
645 rng = self.getRange() 646 if rng is None or rng.isCursor(): 647 return 648 rng.collapse(start) 649 self.getSelection() 650 Selection.setRange(rng) 651 self.refresh()
652
653 - def run(self):
654 try: 655 self.getSelection() 656 rng = Selection.getRange() 657 if (self.timerRange is None) or (not self.timerRange.equals(rng)): 658 self.onSelectionChange(rng) 659 self.timerRange = rng 660 661 except: 662 GWT.log("Error in timer selection", ex)
663
664 - def getSelection(self):
665 res = None 666 try: 667 window = self.getWindow() 668 Selection.getSelection(window) 669 670 except: 671 print "Error getting the selection" 672 traceback.print_exc()
673
674 - def getWindow(self, iFrame=None):
675 if iFrame is None: 676 iFrame = self.richText.getElement() 677 iFrameWin = iFrame.contentWindow or iFrame.contentDocument 678 679 if not iFrameWin.document: 680 iFrameWin = iFrameWin.parentNode # FBJS version of parentNode 681 682 #print "getWindow", iFrameWin, dir(iFrameWin) 683 684 return iFrameWin
685
686 - def captureSelection(self):
687 """ This captures the selection when the mouse leaves the RTE, 688 because in IE the selection indicating the cursor position 689 is lost once another widget gains focus. 690 Could be implemented for IE only. 691 """ 692 try: 693 self.getSelection() 694 self.lastRange = Selection.getRange() 695 696 except: 697 GWT.log("Error capturing selection for IE", ex)
698 699 # Gets run every time the selection is changed
700 - def onSelectionChange(self, sel):
701 pass
702
703 - def isOnTextBorder(self, sender):
704 twX = self.richText.getAbsoluteLeft() 705 twY = self.richText.getAbsoluteTop() 706 x = event.getClientX() - twX 707 y = event.getClientY() - twY 708 width = self.richText.getOffsetWidth() 709 height = self.richText.getOffsetHeight() 710 return ((sender == self.richText) and 711 ((x <= 0) or (x >= width) or 712 (y <= 0) or (y >= height)))
713
714 - def startSelTimer(self):
715 self.selTimer.scheduleRepeating(250)
716
717 - def endSelTimer(self):
718 self.selTimer.cancel()
719
720 - def getRange(self):
721 if self.lastRange is None: 722 self.getSelection() 723 return Selection.getRange() 724 else: 725 return self.lastRange
726
727 - def checkForChange(self):
728 text = self.richText.getHTML() 729 if text == self.lastText: 730 return 731 nEvt = doc().createEvent("HTMLEvents") 732 nEvt.initEvent("change", False, True) 733 self.getElement().dispatchEvent(nEvt) 734 self.lastText = text
735
736 - def setHtml(self, text):
737 self.richText.setHTML(text) 738 self.lastText = text
739
740 - def getHtml(self):
741 return self.richText.getHTML()
742
743 - def getDocument(self):
744 return Selection.getDocument(self.getWindow())
745
746 - def getFormatter(self):
747 return self.richText.getExtendedFormatter()
748 749 750 LABEL_WIDTH = 85 751 ROW_HEIGHT = 24 752
753 -def EventLinkPopup_open(editor):
754 popup = EventLinkPopup(editor) 755 if popup.refresh(): 756 popup.center() 757 popup.show() 758 else: 759 popup = None 760 761 return popup
762
763 -class EventLinkPopup(PopupPanel):
764
765 - def __init__(self, editor):
766 PopupPanel.__init__(self, glass=True) 767 768 self.m_origAnchorStart = None 769 self.m_origAnchorEnd = None 770 self.m_origTargetText = "" 771 self.m_editor = editor 772 773 vpanel = VerticalPanel() 774 vpanel.setWidth("350px") 775 776 self.m_webPageText = TextBox() 777 self.m_webPageText.setText("http:#") 778 self.m_webPageText.setWidth("320px") 779 780 vpanel.add(self.m_webPageText) 781 782 lbl = Label("Display:") 783 784 self.m_targetText = TextBox() 785 self.m_targetText.setWidth("100%") 786 787 lpanel = HorizontalPanel() 788 lpanel.add(lbl) 789 lpanel.add(self.m_targetText) 790 791 vpanel.add(lpanel) 792 793 self.m_fillOutCB = CheckBox("Change entire link") 794 self.m_fillOutCB.setVisible(False) 795 self.m_fillOutCB.addClickListener(self) 796 vpanel.add(self.m_fillOutCB) 797 798 self.m_okBut = Button("Ok", self) 799 self.m_okBut.addStyleName("float-left") 800 801 self.m_cancelBut = Button("Cancel", self) 802 self.m_cancelBut.addStyleName("float-left") 803 804 hpanel = HorizontalPanel() 805 hpanel.add(self.m_okBut) 806 hpanel.add(self.m_cancelBut) 807 808 vpanel.add(hpanel) 809 810 self.add(vpanel) 811 self.setStyleName("gwt-DialogBox")
812
813 - def refresh(self):
814 try: 815 self.m_editor.getSelection() 816 self.m_range = self.m_editor.getRange() 817 if self.m_range is None: 818 return False 819 else: 820 self.m_selTexts = self.m_range.getSelectedTextElements() 821 if self.m_selTexts is None: 822 return False 823 else: 824 self.m_origTargetText = self.m_range.getText() 825 self.m_targetText.setText(self.m_origTargetText) 826 827 anchor = self.getAnchor(self.m_selTexts) 828 if anchor is not None: 829 href = anchor.getHref().strip() 830 if href: 831 self.m_webPageText.setText(href) 832 833 self.m_origAnchorStart = self.getAnchorLimit( 834 self.m_range.getStartPoint().getTextNode(), 835 anchor, False) 836 self.m_origAnchorEnd = self.getAnchorLimit( 837 self.m_range.getStartPoint().getTextNode(), 838 anchor, True) 839 840 if self.m_range.getStartPoint().equals(self.m_origAnchorStart) and self.m_range.getStartPoint().equals(self.m_origAnchorEnd): 841 self.m_origAnchorStart = None 842 self.m_origAnchorEnd = None 843 844 else: 845 self.m_fillOutCB.setVisible(True) 846 self.m_fillOutCB.setValue(True) 847 848 self.m_origTargetText = self.fetchStringFromTexts( 849 self.m_origAnchorStart, self.m_origAnchorEnd) 850 self.m_targetText.setText(self.m_origTargetText) 851 852 853 854 855 856 except: 857 print "exception" 858 traceback.print_exc() 859 return False 860 861 return True
862 863
864 - def _apply(self):
865 formatter = self.m_editor.getFormatter() 866 867 link = self.m_webPageText.getText().strip() 868 if not link: 869 return False 870 871 print self.m_origAnchorStart, self.m_origAnchorEnd 872 873 if (self.m_origAnchorStart is not None) and self.m_fillOutCB.getValue(): 874 # Expand selection to these bounds 875 self.m_range.setRange(self.m_origAnchorStart, self.m_origAnchorEnd) 876 877 # Ensure the selection hasn't changed, or at least changes to the 878 # expanded bounds we want 879 Selection.setRange(self.m_range) 880 881 targetText = self.m_targetText.getText() 882 883 if self.m_range.isCursor(): 884 # Insert into a single cursor location 885 newEle = DOM.createAnchor() 886 newEle.setHref(link) 887 newEle.setInnerText(targetText) 888 889 sp = self.m_range.getStartPoint() 890 startNode = sp.getTextNode() 891 offset = sp.getOffset() 892 print "sp", sp, startNode, offset 893 parentEle = startNode.parentElement 894 text = startNode.data 895 896 if offset == 0: 897 parentEle.insertBefore(newEle, startNode) 898 899 else: 900 if offset < text.length(): 901 # Split this in two and insert the node between 902 startNode.splitText(offset) 903 904 parentEle.insertAfter(newEle, startNode) 905 906 Selection.setRange(Range(newEle)) 907 908 elif targetText != self.m_origTargetText: 909 # Replace whatever was selected with this text 910 ele = self.m_range.surroundContents() 911 newEle = DOM.createAnchor() 912 newEle.href = link 913 newEle.innerText = targetText 914 ele.parentElement.replaceChild(newEle, ele) 915 916 Selection.setRange(Range(newEle)) 917 else: 918 formatter.createLink(link) 919 920 921 return True
922 923
924 - def getAnchor(self, node):
925 res = None 926 if isinstance(node, list): 927 nodes = node 928 for node in nodes: 929 res = self.getAnchor(node) 930 if res is not None: 931 break 932 933 return res 934 935 ele = node.parentElement 936 while ele is not None: 937 tag = ele.tagName 938 if tag.lower == "a": 939 res = ele 940 break 941 942 ele = ele.parentElement 943 944 return res
945 946
947 - def getAnchorLimit(self, node, anchor, forward):
948 href = anchor.href 949 while True: 950 prevNode = node 951 node = Range.getAdjacentTextElement(prevNode, forward) 952 if node is not None: 953 cmpAnchor = self.getAnchor(node) 954 if (cmpAnchor is None) or not href == cmpAnchor.href: 955 break 956 957 if node is None: 958 break 959 960 res = RangeEndPoint() 961 res.setTextNode(prevNode) 962 res.setOffset(forward and prevNode.getData().length() or 0) 963 return res
964 965 976 977 980 981
982 - def fetchStringFromTexts(self, startPoint, endPoint):
983 res = None 984 texts = Range.getSelectedTextElements( 985 startPoint.getTextNode(), endPoint.getTextNode()) 986 if texts is not None: 987 res = self.fetchStringFromTexts(texts, startPoint, endPoint) 988 989 return res
990 991
992 - def fetchStringFromTexts(self, allTexts, startPoint, endPoint):
993 selText = "" 994 for node in allTexts: 995 val = node.getData() 996 if node == startPoint.getTextNode(): 997 if node == endPoint.getTextNode(): 998 val = val.substring[startPoint.getOffset(): 999 endPoint.getOffset()] 1000 1001 else: 1002 val = val[startPoint.getOffset():] 1003 1004 1005 elif node == endPoint.getTextNode(): 1006 val = val[:endPoint.getOffset()] 1007 1008 selText += val 1009 1010 return selText
1011
1012 - def onClick(self, sender):
1013 if sender == self.m_cancelBut: 1014 self.hide() 1015 1016 elif sender == self.m_okBut: 1017 if self._apply(): 1018 self.hide() 1019 1020 elif sender == self.m_fillOutCB: 1021 if self.m_fillOutCB.getValue(): 1022 self.m_origTargetText = fetchStringFromTexts(self.m_origAnchorStart, 1023 self.m_origAnchorEnd) 1024 self.m_targetText.setValue(self.m_origTargetText) 1025 1026 else: 1027 self.m_origTargetText = self.m_range.getText() 1028 self.m_targetText.setValue(self.m_origTargetText)
1029
1030 - def checkSuggestValid(self):
1031 self.m_okBut.setEnabled(True)
1032
1033 - def execute(self):
1034 self.checkSuggestValid()
1035
1036 - def deferredCheckValid(self):
1037 DeferredCommand.addCommand(self)
1038