Browse files

possibly the most devious horrible code ever added, allows styles to …

…be added to richtext and importantly replaced again
  • Loading branch information...
1 parent a655d4a commit 77066c195a342bdd276c6663a4906f77a8db2a1b Luke Leighton committed Apr 24, 2012
View
5 CHANGELOG
@@ -4,6 +4,11 @@
Changes made to Pyjamas since 0.8pre1
---------------------------------
+ * completed the port of RichTextArea, added an example RichTextToolbar,
+ and added a port of gwt-selection which can be used to highlight
+ links, add span styles around blocks etc. unsurprisingly an additional
+ example named "richtext" has been added.
+
* modified DisclosurePanel and Tree widgets to allow use of
alternative sets of images. Issue #715. (Phil Charlesworth)
View
2 examples/richtext/RichTextEditor.py
@@ -298,7 +298,7 @@ def getWindow(self, iFrame=None):
if not iFrameWin.document:
iFrameWin = iFrameWin.parentNode # FBJS version of parentNode
- print "getWindow", iFrameWin, dir(iFrameWin)
+ #print "getWindow", iFrameWin, dir(iFrameWin)
return iFrameWin
View
198 examples/richtext/SelectionTest.py
@@ -9,13 +9,69 @@
from pyjamas.ui.TextArea import TextArea
from pyjamas.ui import RootPanel
+from pyjamas import DOM
+
from RichTextEditor import RichTextEditor
from pyjamas.selection.RangeEndPoint import RangeEndPoint
from pyjamas.selection.Range import Range
from pyjamas.selection.RangeUtil import getAdjacentTextElement
from pyjamas.selection import Selection
+import string
+
+def print_tree(parent):
+ if parent.nodeType == 1:
+ print "parent", parent, parent.tagName, parent.innerHTML
+ else:
+ print "parent", parent, parent.nodeName
+ child = parent.firstChild
+ while child:
+ print "child", child,
+ if child.nodeType == 1:
+ print child.tagName, repr(child.innerHTML)
+ else:
+ print repr(child.data)
+ child = child.nextSibling
+
+def remove_node(doc, element):
+ """ removes a specific node, adding its children in its place
+ """
+ fragment = doc.createDocumentFragment()
+ while element.firstChild:
+ fragment.appendChild(element.firstChild)
+
+ parent = element.parentNode
+ parent.insertBefore(fragment, element)
+ parent.removeChild(element)
+
+ print_tree(parent)
+ #print "element", element, element.tagName, element.innerHTML
+
+def remove_editor_styles(doc, tree):
+ """ removes all other <span> nodes with an editor style
+ """
+
+ element = tree.lastChild
+ while element:
+ if element.nodeType != 1:
+ element = element.previousSibling
+ continue
+ if string.lower(element.tagName) != 'span':
+ element = element.previousSibling
+ continue
+ style = DOM.getAttribute(element, "className")
+ print "span", style, element, element.innerHTML
+ if not style or not style.startswith("editor-"):
+ element = element.previousSibling
+ continue
+ prev_el = element
+ remove_editor_styles(doc, prev_el)
+ element = element.previousSibling
+ remove_node(doc, prev_el)
+ print "post-remove"
+ print_tree(tree)
+
"""*
* Entry point classes define <code>onModuleLoad()</code>.
"""
@@ -40,7 +96,8 @@ def onModuleLoad(self):
self.m_toSCursor.setTitle("Set the selection to be a cursor at the beginning of the current selection")
self.m_toECursor = Button("To Cursor >", self)
self.m_toECursor.setTitle("Set the selection to be a cursor at the end of the current selection")
- self.m_surround = Button("Surround", self)
+ self.m_surround1 = Button("Surround1", self)
+ self.m_surround2 = Button("Surround2", self)
grid = Grid(2, 2)
self.m_startNode = self.createTextBox(1)
@@ -63,7 +120,8 @@ def onModuleLoad(self):
buts.add(self.m_setHtml)
buts.add(self.m_toSCursor)
buts.add(self.m_toECursor)
- buts.add(self.m_surround)
+ buts.add(self.m_surround1)
+ buts.add(self.m_surround2)
buts.add(grid)
buts.add(self.m_select)
buts.add(self.m_cursor)
@@ -146,13 +204,130 @@ def toCursor(self, start):
Selection.setRange(rng)
self.refresh()
- def surround(self):
+ def _surround(self, cls):
+ """ this is possibly one of the most truly dreadful bits of code
+ for manipulating DOM ever written. its purpose is to add only
+ the editor class required, and no more. unfortunately, DOM gets
+ chopped up by the range thing, and a bit more besides. so we
+ have to:
+
+ * extract the range contents
+ * clean up removing any blank text nodes that got created above
+ * slap a span round it
+ * clean up removing any blank text nodes that got created above
+ * remove any prior editor styles on the range contents
+ * go hunting through the entire document for stacked editor styles
+
+ this latter is funfunfun because only "spans with editor styles
+ which themselves have no child elements but a single span with
+ an editor style" must be removed. e.g. if an outer editor span
+ has another editor span and also some text, the outer span must
+ be left alone.
+ """
rng = self.m_rte.getRange()
- if (rng is not None) and not rng.isCursor():
- rng.surroundContents()
- self.m_rte.getSelection()
- Selection.setRange(rng)
- self.refresh()
+ if (rng is None) or rng.isCursor():
+ return
+
+ rng.ensureRange()
+ dfrag = rng.m_range.extractContents()
+ print "doc pre remove"
+ print_tree(rng.m_document)
+ remove_editor_styles(rng.m_document, dfrag)
+ print "dfrag post remove styles"
+ print_tree(dfrag)
+ print "doc after dfrag remove"
+ print_tree(rng.m_document)
+ print "extract", dfrag, dir(dfrag)
+ element = rng.m_document.createElement("span")
+ DOM.setAttribute(element, "className", cls)
+ DOM.appendChild(element, dfrag)
+ rng.m_range.insertNode(element)
+
+ it = DOM.IterWalkChildren(element, True)
+ while True:
+ try:
+ node = it.next()
+ except StopIteration:
+ break
+ print node, node.nodeType
+ if node.nodeType == 3 and unicode(node.data) == u'':
+ print "removing blank text"
+ DOM.removeChild(node.parentNode, node)
+
+ rng.setRange(element)
+
+ it = DOM.IterWalkChildren(rng.m_document, True)
+ while True:
+ try:
+ node = it.next()
+ except StopIteration:
+ break
+ print node, node.nodeType
+ if node.nodeType == 3 and unicode(node.data) == u'':
+ print "removing blank text"
+ DOM.removeChild(node.parentNode, node)
+
+ it = DOM.IterWalkChildren(rng.m_document)
+ while True:
+ try:
+ node = it.next()
+ except StopIteration:
+ break
+ style = DOM.getAttribute(node, "className")
+ print "node", node.firstChild, node.tagName, style
+ if node.firstChild:
+ continue
+ if str(string.lower(node.tagName)) != 'span':
+ continue
+ print "node", node.firstChild, node.tagName, style
+ if not style or not style.startswith("editor-"):
+ continue
+ it.remove()
+
+ it = DOM.IterWalkChildren(rng.m_document, True)
+ while True:
+ try:
+ node = it.next()
+ except StopIteration:
+ break
+ if node.nodeType != 1:
+ continue
+ if node.firstChild is None:
+ continue
+ style = DOM.getAttribute(node, "className")
+ print "double-node", node.tagName, style, node.firstChild.nextSibling
+ print_tree(node)
+ if node.firstChild.nextSibling:
+ continue
+ if node.firstChild.nodeType != 1:
+ continue
+ if str(string.lower(node.tagName)) != 'span':
+ continue
+ if str(string.lower(node.firstChild.tagName)) != 'span':
+ continue
+ if not style or not style.startswith("editor-"):
+ continue
+ style = DOM.getAttribute(node.firstChild, "className")
+ if not style or not style.startswith("editor-"):
+ continue
+ # remove the *outer* one because the range was added to
+ # the inner, and the inner one overrides anyway
+ print "remove overlapping styles", node
+ remove_node(rng.m_document, node)
+
+ doc = self.m_rte.getDocument()
+ print "doc pre", doc, doc.body.innerHTML
+
+ self.m_rte.getSelection()
+ Selection.setRange(rng)
+ self.refresh()
+ print "doc", doc, doc.body.innerHTML
+
+ def surround1(self):
+ self._surround("editor-cls1")
+
+ def surround2(self):
+ self._surround("editor-cls2")
def findNodeByNumber(self, num):
@@ -208,8 +383,11 @@ def onClick(self, wid):
elif wid == self.m_toSCursor:
self.toCursor(True)
- elif wid == self.m_surround:
- self.surround()
+ elif wid == self.m_surround1:
+ self.surround1()
+
+ elif wid == self.m_surround2:
+ self.surround2()
elif wid == self.m_setHtml:
self.toHtml()
View
334 examples/richtext/public/SelectionTest.css
@@ -0,0 +1,334 @@
+body {
+ background-color: white;
+ color: black;
+ font-family: Arial, sans-serif;
+ font-size: smaller;
+ margin: 20px 20px 20px 20px;
+}
+
+code {
+ font-size: small;
+}
+
+a {
+ color: darkblue;
+}
+
+a:visited {
+ color: darkblue;
+}
+
+.gwt-BorderedPanel {
+}
+
+.gwt-Button {
+}
+
+.gwt-Canvas {
+}
+
+.gwt-CheckBox {
+ font-size: smaller;
+}
+
+.gwt-DialogBox {
+ sborder: 8px solid #C3D9FF;
+ border: 2px outset;
+ background-color: white;
+}
+
+.gwt-DialogBox .Caption {
+ background-color: #C3D9FF;
+ padding: 3px;
+ margin: 2px;
+ font-weight: bold;
+ cursor: default;
+}
+
+.gwt-DialogBox .WindowCaption {
+ background-color: #C3D9FF;
+ padding: 0px;
+ margin: 2px;
+ font-weight: bold;
+ cursor: default;
+}
+
+.gwt-DialogBox .Controls td {
+ padding: 1px;
+}
+
+.gwt-DialogBox .Minimize {
+ width: 18px;
+ height: 22px;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: transparent url(images/window_minimize.gif) no-repeat center top;
+ text-indent: -1000em;
+ display: block;
+}
+
+.gwt-DialogBox .Maximize {
+ width: 18px;
+ height: 22px;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: transparent url(images/window_maximize.gif) no-repeat center top;
+ text-indent: -1000em;
+ display: block;
+}
+
+.gwt-DialogBox .Close {
+ width: 18px;
+ height: 22px;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: transparent url(images/window_close.gif) no-repeat center top;
+ text-indent: -1000em;
+ display: block;
+}
+
+.gwt-PopupPanelGlass {
+ background-color: #000;
+ opacity: 0.3;
+ filter: alpha(opacity=30);
+}
+
+.gwt-FileUpload {
+}
+
+.gwt-Frame {
+}
+
+.gwt-HorizontalSplitter .Bar {
+ width: 8px;
+ background-color: #C3D9FF;
+}
+
+.gwt-VerticalSplitter .Bar {
+ height: 8px;
+ background-color: #C3D9FF;
+}
+
+.gwt-HTML {
+ font-size: smaller;
+}
+
+.gwt-Hyperlink {
+}
+
+.gwt-Image {
+}
+
+.gwt-Label {
+ font-size: smaller;
+}
+
+.gwt-ListBox {
+}
+
+.gwt-MenuBar {
+ background-color: #C3D9FF;
+ border: 1px solid #87B3FF;
+ cursor: default;
+}
+
+.gwt-MenuBar .gwt-MenuItem {
+ padding: 1px 4px 1px 4px;
+ font-size: smaller;
+ cursor: default;
+}
+
+.gwt-MenuBar .gwt-MenuItem-selected {
+ background-color: #E8EEF7;
+}
+
+.gwt-PasswordTextBox {
+}
+
+.gwt-RadioButton {
+ font-size: smaller;
+}
+
+.gwt-TabPanel {
+}
+
+.gwt-TabPanelBottom {
+ border-left: 1px solid #87B3FF;
+}
+
+.gwt-TabBar {
+ background-color: #C3D9FF;
+ font-size: smaller;
+}
+
+.gwt-TabBar .gwt-TabBarFirst {
+ height: 100%;
+ border-bottom: 1px solid #87B3FF;
+ padding-left: 3px;
+}
+
+.gwt-TabBar .gwt-TabBarRest {
+ border-bottom: 1px solid #87B3FF;
+ padding-right: 3px;
+}
+
+.gwt-TabBar .gwt-TabBarItem {
+ border-top: 1px solid #C3D9FF;
+ border-bottom: 1px solid #87B3FF;
+ padding: 2px;
+ cursor: pointer;
+ cursor: hand;
+}
+
+.gwt-TabBar .gwt-TabBarItem-selected {
+ font-weight: bold;
+ background-color: #E8EEF7;
+ border-top: 1px solid #87B3FF;
+ border-left: 1px solid #87B3FF;
+ border-right: 1px solid #87B3FF;
+ border-bottom: 1px solid #E8EEF7;
+ padding: 2px;
+ cursor: default;
+}
+
+.gwt-TextArea {
+}
+
+.gwt-TextBox {
+}
+
+.gwt-Tree {
+}
+
+.gwt-Tree .gwt-TreeItem {
+ font-size: smaller;
+}
+
+.gwt-Tree .gwt-TreeItem-selected {
+ background-color: #C3D9FF;
+}
+
+.gwt-StackPanel {
+}
+
+.gwt-StackPanel .gwt-StackPanelItem {
+ background-color: #C3D9FF;
+ cursor: pointer;
+ cursor: hand;
+}
+
+.gwt-StackPanel .gwt-StackPanelItem-selected {
+}
+
+/* -------------------------------------------------------------------------- */
+.ks-Sink {
+ border: 8px solid #C3D9FF;
+ background-color: #E8EEF7;
+ width: 100%;
+ height: 24em;
+}
+
+.ks-Info {
+ background-color: #C3D9FF;
+ padding: 10px 10px 2px 10px;
+ font-size: smaller;
+}
+
+.ks-List {
+ margin-top: 8px;
+ margin-bottom: 8px;
+ font-size: smaller;
+}
+
+.ks-List .ks-SinkItem {
+ width: 100%;
+ padding: 0.3em;
+ padding-right: 16px;
+ cursor: pointer;
+ cursor: hand;
+}
+
+.ks-List .ks-SinkItem-selected {
+ background-color: #C3D9FF;
+}
+
+.ks-images-Image {
+ margin: 8px;
+}
+
+.ks-images-Button {
+ margin: 8px;
+ cursor: pointer;
+ cursor: hand;
+}
+
+.ks-layouts {
+ margin: 8px;
+}
+
+.ks-layouts-Label {
+ background-color: #C3D9FF;
+ font-weight: bold;
+ margin-top: 1em;
+ padding: 2px 0px 2px 0px;
+ width: 100%;
+}
+
+.ks-layouts-Scroller {
+ height: 128px;
+ border: 2px solid #C3D9FF;
+ padding: 8px;
+ margin: 8px;
+}
+
+.ks-popups-Popup {
+ background-color: white;
+ border: 1px solid #87B3FF;
+ padding: 4px;
+}
+
+.infoProse {
+ margin: 8px;
+}
+
+/******************************************************
+* DisclosurePanel
+******************************************************/
+.gwt-DisclosurePanel
+{
+ background-color : #ddf;
+ border : 1px solid #009;
+}
+.gwt-DisclosurePanel .header
+{
+ font-size : 70%;
+ cursor : pointer;
+ cursor : hand;
+}
+.gwt-DisclosurePanel-closed
+{
+}
+.gwt-DisclosurePanel-closed .header
+{
+}
+.gwt-DisclosurePanel-open
+{
+}
+.gwt-DisclosurePanel-open .header
+{
+}
+.gwt-DisclosurePanel-open .content
+{
+}
+
+.editor-cls1 {
+ font-family: Helvetica, sans-serif;
+}
+
+.editor-cls2 {
+ font-family: Times-Roman;
+}
+
View
1 examples/richtext/public/SelectionTest.html
@@ -1,6 +1,7 @@
<html>
<head>
<title>SelectionTest</title>
+ <link rel='stylesheet' href='SelectionTest.css'>
</head>
<body style="background-color:white">
</body>
View
16 library/pyjamas/DOM.py
@@ -712,18 +712,26 @@ def iterChildren(elem):
class IterWalkChildren:
- def __init__(self, elem):
+ def __init__(self, elem, all_nodes=False):
self.parent = elem
- self.child = getFirstChild(elem)
+ self.all_nodes = all_nodes
+ if all_nodes:
+ self.child = elem.firstChild
+ else:
+ self.child = getFirstChild(elem)
self.lastChild = None
self.stack = []
def next(self):
if not self.child:
raise StopIteration
self.lastChild = self.child
- firstChild = getFirstChild(self.child)
- nextSibling = getNextSibling(self.child)
+ if self.all_nodes:
+ firstChild = self.child.firstChild
+ nextSibling = self.child.nextSibling
+ else:
+ firstChild = getFirstChild(self.child)
+ nextSibling = getNextSibling(self.child)
if firstChild is not None:
if nextSibling is not None:
self.stack.append((nextSibling, self.parent))
View
17 library/pyjamas/selection/Range.py
@@ -166,10 +166,10 @@ def fillRangePoints(fillRange):
startNode = jsRange.startContainer
startOffset = jsRange.startOffset
- print "jsRange", jsRange
- print "startNode", startNode
- print "startOffset", startOffset
- print dir(jsRange)
+ #print "jsRange", jsRange
+ #print "startNode", startNode
+ #print "startOffset", startOffset
+ #print dir(jsRange)
startPoint = findTextPoint(startNode, startOffset)
endNode = jsRange.endContainer
@@ -278,7 +278,7 @@ def getSelectedTextElements(self, startNode=None, endNode=None):
res = []
- print "getSelectedTextElements", startNode, endNode
+ #print "getSelectedTextElements", startNode, endNode
current = startNode
while (current is not None) and (not DOM.compare(current, endNode)):
@@ -321,8 +321,8 @@ def getSelectedTextElements(self, startNode=None, endNode=None):
* @param rangeObj
"""
def __init__(self, arg1, arg2=None):
- print "range", arg1, arg2
- print dir(arg1)
+ #print "range", arg1, arg2
+ #print dir(arg1)
self.m_startPoint = None
self.m_endPoint = None
self.m_range = None
@@ -661,6 +661,7 @@ def setStartPoint(self, startPoint):
* them in the DOM after this operation
"""
def surroundContents(self, copyInto=None):
+
if copyInto is None:
copyInto = self.m_document.createElement("span")
self.ensureRange()
@@ -704,7 +705,7 @@ def rangeNeedsUpdate(self):
def setupLastEndpoints(self):
self.m_lastStartPoint = RangeEndPoint(self.m_startPoint)
self.m_lastEndPoint = RangeEndPoint(self.m_endPoint)
- print "setupLastEndpoints:", self.m_lastStartPoint, self.m_lastEndPoint
+ #print "setupLastEndpoints:", self.m_lastStartPoint, self.m_lastEndPoint
"""*
View
4 library/pyjamas/selection/RangeUtil.py
@@ -40,7 +40,7 @@ def getAdjacentTextElement(current, topMostNode, forward=None, traversingUp=Fals
res = None
- print "getAdjacentTextElement", current, topMostNode, forward, traversingUp
+ #print "getAdjacentTextElement", current, topMostNode, forward, traversingUp
# If traversingUp, then the children have already been processed
if not traversingUp:
@@ -68,7 +68,7 @@ def getAdjacentTextElement(current, topMostNode, forward=None, traversingUp=Fals
if DOM.getNodeType(node) == DOM.TEXT_NODE:
res = node
else:
- print node, DOM.getNodeType(node), node.innerHTML
+ #print node, DOM.getNodeType(node), node.innerHTML
# Depth first traversal, the recursive call deals with
# siblings
res = getAdjacentTextElement(node, topMostNode,
View
4 library/pyjamas/selection/Selection.py
@@ -86,9 +86,9 @@ def clear():
"""
def getDocument(window=None):
if window:
- print "getDocument", window, window.document
+ #print "getDocument", window, window.document
return window.document
- print "getDocument", m_document
+ #print "getDocument", m_document
return m_document
"""*

0 comments on commit 77066c1

Please sign in to comment.