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
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
54 child = parent.firstChild
55 while child:
56 child = child.nextSibling
57
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
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
102
104
106 self.fontname = fontname
107 self.doc = doc
108
113
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
123
125 self.stylename = stylename
126 self.doc = doc
127
132
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
142
144 self.heading = heading
145 self.doc = doc
146
148 element = self.doc.createElement(self.heading)
149 return element
150
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 """
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
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
298
312
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
346
352
358
371
393
444
445 - def onKeyDown(self, sender, keyCode, modifiers):
447
448 - def onKeyPress(self, sender, keyCode, modifiers):
450
451 - def onKeyUp(self, sender, keyCode, modifiers):
459
462
465
468
471
474
476 print "focus"
477 pass
478
482
489
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
502
505
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
535
538
541
544
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
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
624
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
636
638 rng = self.getRange()
639 if rng is None or rng.isCursor():
640 return
641 rng.deleteContents()
642 refresh()
643
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
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
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
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
681
682
683
684 return iFrameWin
685
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
702
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
716
719
721 if self.lastRange is None:
722 self.getSelection()
723 return Selection.getRange()
724 else:
725 return self.lastRange
726
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
737 self.richText.setHTML(text)
738 self.lastText = text
739
742
745
748
749
750 LABEL_WIDTH = 85
751 ROW_HEIGHT = 24
752
762
764
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
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
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
875 self.m_range.setRange(self.m_origAnchorStart, self.m_origAnchorEnd)
876
877
878
879 Selection.setRange(self.m_range)
880
881 targetText = self.m_targetText.getText()
882
883 if self.m_range.isCursor():
884
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
902 startNode.splitText(offset)
903
904 parentEle.insertAfter(newEle, startNode)
905
906 Selection.setRange(Range(newEle))
907
908 elif targetText != self.m_origTargetText:
909
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
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
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
967 res = 0
968 idx = href.index("#event=")
969 if idx > 0:
970 try:
971 res = href[idx+7:]
972 except:
973 pass
974
975 return res
976
977
979 return "#event=" + id
980
981
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
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
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
1032
1035
1038