1# ----------------------------------------------------------------------
2# $Id: qrview.py 2640 2014-08-12 02:04:01Z thomas-sturm $
3# ----------------------------------------------------------------------
4# (c) 2009 T. Sturm, 2010 T. Sturm, C. Zengler, 2011-2014 T. Sturm
5# ----------------------------------------------------------------------
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9#
10#    * Redistributions of source code must retain the relevant
11#      copyright notice, this list of conditions and the following
12#      disclaimer.
13#    * Redistributions in binary form must reproduce the above
14#      copyright notice, this list of conditions and the following
15#      disclaimer in the documentation and/or other materials provided
16#      with the distribution.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29#
30
31from types import IntType
32from types import BooleanType
33
34from PySide.QtCore import Qt
35from PySide.QtCore import Signal
36from PySide.QtCore import QSettings
37
38from PySide.QtGui import QColor
39from PySide.QtGui import QFont
40from PySide.QtGui import QFontDatabase
41from PySide.QtGui import QFontInfo
42from PySide.QtGui import QFrame
43from PySide.QtGui import QMessageBox
44from PySide.QtGui import QPainter
45from PySide.QtGui import qRgb
46from PySide.QtGui import QTextCursor
47from PySide.QtGui import QTextEdit
48
49from qrlogging import fontLogger
50from qrlogging import signalLogger
51from qrlogging import traceLogger
52
53from qrdefaults import QtReduceDefaults
54
55from qrformats import QtReduceFormat
56from qrformats import QtReduceInput
57from qrformats import QtReduceResult
58from qrformats import QtReduceNoResult
59from qrformats import QtReduceError
60from qrformats import QtReduceRowFormat
61
62
63class QtReduceTextEdit(QTextEdit):
64
65    computationRequest = Signal(IntType)
66    modified = Signal(BooleanType)
67
68    def __init__(self,parent=None):
69        super(QtReduceTextEdit,self).__init__(parent)
70        self.setupFont(int(QSettings().value("worksheet/fontsize",
71                                         QtReduceDefaults.FONTSIZE)))
72        self.textChanged.connect(self.textChangedHandler)
73        self.setCursorWidth(1)
74
75    def __nextGoodFontSize(self,font,size,step):
76        info = QFontInfo(font)
77        family = info.family()
78        fontDatabase = QFontDatabase()
79        styleStr = fontDatabase.styleString(font)
80        fontLogger.debug("family=%s, style=%s" % (family,styleStr))
81        if fontDatabase.isSmoothlyScalable(family,styleStr):
82            sizes = QFontDatabase.standardSizes()
83        else:
84            sizes = fontDatabase.smoothSizes(family,styleStr)
85        fontLogger.debug("looking for %s in %s step %d" % (size,sizes,step))
86        nSize = size
87        while nSize not in sizes and sizes[0] <= nSize and nSize <= sizes[-1]:
88            nSize += step
89        if nSize < sizes[0]:
90            fontLogger.debug("out of range - returning %s" % sizes[0])
91            return sizes[0]
92        if sizes[-1] < nSize:
93            fontLogger.debug("out of range - returning %s" % sizes[-1])
94            return sizes[-1]
95        fontLogger.debug("found %s" % nSize)
96        return nSize
97
98    def setupFont(self,size=None,step=1):
99        traceLogger.debug("size=%s, step=%s" % (size,step))
100        if not size:
101            size = self.font().pointSize()
102        font = self.font()
103        font.setFamily(QSettings().value("worksheet/fontfamily",
104                                         QtReduceDefaults.FONTFAMILY))
105        font.setFixedPitch(True)
106        font.setKerning(0)
107        font.setWeight(QFont.Normal)
108        font.setItalic(False)
109        font.setPointSize(self.__nextGoodFontSize(font,size,step))
110        self.setFont(font)
111
112    def currentFontChangedHandler(self,newFont):
113        QSettings().setValue("worksheet/fontfamily",newFont.family())
114        self.setupFont()
115        self.ensureCursorVisible()
116
117    def currentSizeChangedHandler(self,newSize,fullScreen):
118        newSize = int(newSize)
119        traceLogger.debug("newSize=%s (%s)" % (newSize,type(newSize)))
120        QSettings().setValue("worksheet/fontsize",newSize)
121        if not fullScreen:
122            self.setupFont(newSize)
123
124    def currentSizeChangedHandlerFs(self,newSize,fullScreen):
125        newSize = int(newSize)
126        traceLogger.debug("newSize=%s (%s)" % (newSize,type(newSize)))
127        QSettings().setValue("worksheet/fontsizefs",newSize)
128        if fullScreen:
129            self.setupFont(newSize)
130
131    def textChangedHandler(self):
132        if self.insertingFrames:
133            return
134        self.modified.emit(True)
135
136    def zoomDef(self,fullScreen):
137        if fullScreen:
138            fs = int(QSettings().value("worksheet/fontsizefs",
139                                       QtReduceDefaults.FONTSIZEFS))
140        else:
141            fs = int(QSettings().value("worksheet/fontsize",
142                                       QtReduceDefaults.FONTSIZE))
143        self.setupFont(fs)
144
145    def zoomIn(self):
146        currentSize = self.font().pointSize()
147        self.setupFont(currentSize + 1)
148
149    def zoomOut(self):
150        currentSize = self.font().pointSize()
151        self.setupFont(currentSize - 1,step=-1)
152
153
154class QtReduceFrameView(QtReduceTextEdit):
155    Aborted = u'Aborted'
156    Evaluated = u'\u2713'.encode("utf-8")
157    NotEvaluated = ''
158    leftRow = Signal(IntType)
159
160    def __init__(self,parent=None):
161        super(QtReduceFrameView,self).__init__(parent)
162        self.setUndoRedoEnabled(False)
163        self.selectionChanged.connect(self.pruneSelection)
164        self.insertingFrames = False
165
166    def atEnd(self):
167        return self.textCursor().atEnd()
168
169    def atStart(self):
170        return self.textCursor().atStart()
171
172    def gotoNextBlock(self):
173        cursor = self.textCursor()
174        if not cursor.atEnd():
175            cursor.movePosition(QTextCursor.NextBlock)
176            self.setTextCursor(cursor)
177
178    def gotoNextInputPosition(self):
179        cursor = self.textCursor()
180        if not cursor.atEnd():
181            cursor.movePosition(QTextCursor.NextBlock)
182            while not cursor.atEnd() and not self.__isInputPosition(cursor):
183                cursor.movePosition(QTextCursor.NextBlock)
184            self.setTextCursor(cursor)
185
186    def gotoNextOtherPosition(self):
187        cursor = self.textCursor()
188        if not cursor.atEnd():
189            ff = cursor.currentFrame().frameFormat()
190            cursor.movePosition(QTextCursor.NextBlock)
191            while not cursor.atEnd() \
192                      and cursor.currentFrame().frameFormat() == ff:
193                cursor.movePosition(QTextCursor.NextBlock)
194            self.setTextCursor(cursor)
195
196    def gotoPreviousBlock(self):
197        cursor = self.textCursor()
198        if not cursor.atStart():
199            cursor.movePosition(QTextCursor.PreviousBlock)
200            self.setTextCursor(cursor)
201
202    def gotoPreviousInputPosition(self):
203        cursor = self.textCursor()
204        if not cursor.atStart():
205            cursor.movePosition(QTextCursor.PreviousBlock)
206            while not cursor.atStart() and not self.__isInputPosition(cursor):
207                cursor.movePosition(QTextCursor.PreviousBlock)
208            self.setTextCursor(cursor)
209
210    def gotoPreviousOtherPosition(self):
211        cursor = self.textCursor()
212        if cursor.atStart():
213            return
214        cursor.movePosition(QTextCursor.Left)
215        cf = cursor.currentFrame()
216        while not cursor.atStart() and cursor.currentFrame() == cf:
217            cursor.movePosition(QTextCursor.Left)
218        if cursor.currentFrame() != cf:
219            cursor.movePosition(QTextCursor.Right)
220        self.setTextCursor(cursor)
221
222    def gotoRow(self,row):
223        rows = self.document().rootFrame().childFrames()
224        if row >= len(rows):
225            traceLogger.critical("invalid row %d > %d" % (row,len(rows)-1))
226            row = len(rows)
227        cursor = QTextCursor(rows[row].lastCursorPosition())
228        cursor.movePosition(QTextCursor.NextBlock)
229        self.setTextCursor(cursor)
230        self.ensureCursorVisible()
231        cursor = QTextCursor(rows[row].childFrames()[0])
232        self.setTextCursor(cursor)
233        self.ensureCursorVisible()
234
235    def insertRow(self,row):
236        self.insertingFrames = True
237        document = self.document()
238        rootFrame = document.rootFrame()
239        rows = rootFrame.childFrames()
240        if row > len(rows):
241            traceLogger.critical("invalid row %d > %d" % (row,len(rows)))
242            row = len(rows)
243        if row == len(rows):
244            cursor = QTextCursor(document)
245            cursor.movePosition(QTextCursor.End)
246        elif rows:
247            cursor = QTextCursor(rows[row].firstCursorPosition())
248            cursor.movePosition(QTextCursor.PreviousBlock)
249        cursor.insertFrame(QtReduceRowFormat())
250        cursor.insertFrame(QtReduceInput().frameFormat)
251        position = cursor.position()
252        cursor.clearSelection()
253        cursor.setBlockFormat(QtReduceInput().blockFormat)
254        cursor.setBlockCharFormat(QtReduceInput().charFormat)
255        cursor.movePosition(QTextCursor.NextBlock)
256        cursor.insertFrame(QtReduceNoResult().frameFormat)
257        cursor.setBlockFormat(QtReduceNoResult().blockFormat)
258        cursor.setBlockCharFormat(QtReduceNoResult().charFormat)
259        cursor.insertText(QtReduceFrameView.NotEvaluated)
260        cursor.setPosition(position)
261        self.insertingFrames = False
262
263    def insertRows(self,start,end):
264        for row in range(start, end + 1):
265            self.insertRow(row)
266
267    def input(self,row):
268        rows = self.document().rootFrame().childFrames()
269        if row >= len(rows):
270            traceLogger.critical("invalid row %d > %d" % (row,len(rows)-1))
271            row = len(rows)
272        inputFrame = rows[row].childFrames()[0]
273        cursor = QTextCursor(inputFrame)
274        cursor.setPosition(inputFrame.lastPosition(),QTextCursor.KeepAnchor)
275        command = cursor.selectedText()
276        command = command.replace(u'\u2028',u'\n')
277        command = command.replace(u'\u2029',u'\n')
278        if command != '' and not command[-1] in [';','$']:
279            command += ';'
280        return command
281
282    def currentField(self,row):
283        rows = self.document().rootFrame().childFrames()
284        if row >= len(rows):
285            traceLogger.critical("invalid row %d > %d" % (row,len(rows)-1))
286            row = len(rows)
287        inputFrame = rows[row].childFrames()[0]
288        cursor = QTextCursor(inputFrame)
289        cursor.setPosition(inputFrame.lastPosition(),QTextCursor.KeepAnchor)
290        command = cursor.selectedText()
291        command = command.replace(u'\u2028',u'\n')
292        command = command.replace(u'\u2029',u'\n')
293        if command != '' and not command[-1] in [';','$']:
294            command += ';'
295        return command
296
297    def keyPressEvent(self,e):
298        if e.key() == Qt.Key_Tab:
299            self.gotoNextOtherPosition()
300            return
301        if e.key() == Qt.Key_Backtab:
302            self.gotoPreviousOtherPosition()
303            return
304        if self.__isReadOnlyPosition():
305            if e.key() not in [Qt.Key_Left,Qt.Key_Right,Qt.Key_Up,Qt.Key_Down]:
306                return
307        if self.__isInputPosition():
308            if (e.key() == Qt.Key_Return or e.key() == Qt.Key_Enter) and \
309                   not (e.modifiers() & Qt.ShiftModifier):
310                signalLogger.debug("emitting computationRequest")
311                self.computationRequest.emit(self.row())
312                return
313        super(QtReduceTextEdit,self).keyPressEvent(e)
314
315    def paintEvent(self,e):
316        painter = QPainter(self.viewport())
317        pen = painter.pen()
318        pen.setCosmetic(True)
319        pen.setWidth(0.0)
320        painter.setPen(pen)
321        painter.setRenderHint(QPainter.Antialiasing, False)
322        groups = self.document().rootFrame().childFrames()
323        for group in groups:
324            h = QtReduceRowFormat().leftMargin()
325            hoff = int(0.4*h)
326            self.__drawFrameBracket(painter,group,hoff,
327                                    QColor(qRgb(0x80,0x80,0x80)),xt=2)
328            childFrames = group.childFrames()
329            hoff = int(0.7*h)
330            self.__drawFrameBracket(painter,childFrames[0],hoff,
331                                    QColor(qRgb(0xb0,0x70,0x70)),xt=2)
332            self.__drawFrameBracket(painter,childFrames[1],hoff,
333                                    QColor(qRgb(0x70,0x70,0xb0)),xt=2)
334        painter.end()
335        super(QtReduceFrameView,self).paintEvent(e)
336
337    def pruneSelection(self):
338        cursor = self.textCursor()
339        if not cursor.hasSelection():
340            return
341        if cursor.hasComplexSelection():
342            cursor.clearSelection()
343            self.setTextCursor(cursor)
344            return
345        s = cursor.selectionStart()
346        e = cursor.selectionEnd()
347        sp = self.__positionPair(s)
348        ep = self.__positionPair(e)
349        traceLogger.debug("start=%d in %s, end=%d in %s" % (s,sp,e,ep))
350        if sp == ep:
351            return
352        c = cursor.position()
353        cp = self.__positionPair(c)
354        traceLogger.debug("cursor=%d in %s" % (c,cp))
355        cursor.clearSelection()
356        self.setTextCursor(cursor)
357        return
358
359    def rowOrNextRow(self,cursor=None):
360        if not cursor:
361            cursor = self.textCursor()
362        row = self.row(cursor)
363        if row >= 0:
364            return row
365        if cursor.atEnd():
366            return -1
367        cursor.movePosition(QTextCursor.Right)
368        row = self.row(cursor)
369        if row >= 0:
370            return row
371        traceLogger.critical("unexpected negative row index")
372
373    def rowOrPreviousRow(self,cursor=None):
374        if not cursor:
375            cursor = self.textCursor()
376        row = self.row(cursor)
377        if row >= 0:
378            return row
379        if cursor.atStart():
380            return -1
381        cursor.movePosition(QTextCursor.Left)
382        row = self.row(cursor)
383        if row >= 0:
384            return row
385        traceLogger.critical("unexpected negative row index")
386
387    def removeRows(self,start,end):
388        document = self.document()
389        rootFrame = document.rootFrame()
390        rows = rootFrame.childFrames()
391        if end >= len(rows):
392            traceLogger.critical("invalid row %d >= %d" % (end,len(rows)))
393        cursor = rows[start].firstCursorPosition()
394        cursor.setPosition(rows[end].lastPosition()+1,QTextCursor.KeepAnchor)
395        cursor.deleteChar()
396
397    def row(self,cursor=None):
398        if not cursor:
399            cursor = self.textCursor()
400        rootFrame = self.document().rootFrame()
401        frames = rootFrame.childFrames()
402        frame = cursor.currentFrame()
403        if frame == rootFrame:
404            return -1
405        p = frame.parentFrame()
406        if p != rootFrame:
407            frame = p
408        return frames.index(frame)
409
410    def rowCount(self):
411        return len(self.document().rootFrame().childFrames())
412
413    def setError(self,row,text):
414        self.__setOutput(row,text,QtReduceError())
415
416    def setCell(self,row,c1,input,c2,output,c3):
417        rows = self.document().rootFrame().childFrames()
418        if row >= len(rows):
419            traceLogger.critical("invalid row %d > %d" % (row,len(rows)-1))
420            return
421        cell = rows[row]
422        rowFrames = cell.childFrames()
423        if len(rowFrames) != 2:
424            traceLogger.critical("%d subframes in row %d", len(rowFrames), row)
425            return
426        inputFrame = rowFrames[0]
427        outputFrame = rowFrames[1]
428        # C1:
429        cursor = cell.firstCursorPosition()
430        cursor.setPosition(inputFrame.firstPosition(),QTextCursor.KeepAnchor)
431        cursor.movePosition(QTextCursor.Left,QTextCursor.KeepAnchor)
432        cursor.insertText(c1)
433        # Input:
434        cursor.setPosition(inputFrame.firstPosition())
435        cursor.setPosition(inputFrame.lastPosition(),QTextCursor.KeepAnchor)
436        cursor.setBlockFormat(QtReduceInput().blockFormat)
437        #cursor.insertText(input,QtReduceInput().charFormat)
438        cursor.insertText(input)
439        # C2:
440        cursor.setPosition(inputFrame.lastPosition())
441        cursor.movePosition(QTextCursor.Right)
442        cursor.setPosition(outputFrame.firstPosition(),QTextCursor.KeepAnchor)
443        cursor.movePosition(QTextCursor.Left,QTextCursor.KeepAnchor)
444        cursor.insertText(c2)
445        # Output:
446        cursor.setPosition(outputFrame.firstPosition())
447        cursor.setPosition(outputFrame.lastPosition(),QTextCursor.KeepAnchor)
448        # ...
449        # C3
450        cursor.setPosition(outputFrame.lastPosition())
451        cursor.movePosition(QTextCursor.Right)
452        cursor.setPosition(cell.lastPosition(),QTextCursor.KeepAnchor)
453        cursor.insertText(c3)
454
455    def setInput(self,row,text):
456        rows = self.document().rootFrame().childFrames()
457        if row >= len(rows):
458            traceLogger.critical("invalid row %d > %d" % (row,len(rows)-1))
459            return
460        rowFrames = rows[row].childFrames()
461        if len(rowFrames) != 2:
462            traceLogger.critical("%d subframes in row %d", len(rowFrames), row)
463            return
464        #text = text.rstrip(';')
465        inputFrame = rowFrames[0]
466        cursor = inputFrame.firstCursorPosition()
467        cursor.setPosition(inputFrame.lastPosition(),QTextCursor.KeepAnchor)
468        cursor.setBlockFormat(QtReduceInput().blockFormat)
469        cursor.insertText(text,QtReduceInput().charFormat)
470
471    def setNoResult(self,row):
472        self.__setOutput(row,QtReduceFrameView.Evaluated,QtReduceNoResult())
473
474    def setAborted(self,row):
475        self.__setOutput(row,QtReduceFrameView.Aborted,QtReduceError())
476
477    def setNotEvaluated(self,row):
478        self.__setOutput(row,self.tr(QtReduceFrameView.NotEvaluated),QtReduceNoResult())
479
480    def setResult(self,row,text):
481        self.__setOutput(row,text,QtReduceResult())
482
483    def subCell(self):
484        rootFrame = self.document().rootFrame()
485        rows = rootFrame.childFrames()
486        cursor = self.textCursor()
487        pos = cursor.position()
488        frame = cursor.currentFrame()
489        if frame == rootFrame:
490            # pos is outside any group
491            return SubCell(-1,pos,pos,SubCell.Root,'')
492        p = frame.parentFrame()
493        if p != rootFrame:
494            # frame is an input or output frame containing pos
495            row = rows.index(p)
496            i = p.childFrames().index(frame)
497            if i == 0:
498                typ = SubCell.Input
499            elif i == 1:
500                typ = SubCell.Output
501            else:
502                traceLogger.critical("bad subframe %d" % i)
503            startPos = frame.firstPosition()
504            endPos = frame.lastPosition()
505        else:
506            # frame is a group frame containing pos in a comment position
507            row = rows.index(frame)
508            localFrames = frame.childFrames()
509            inputStart = localFrames[0].firstPosition()
510            if pos < inputStart:
511                typ = SubCell.C1
512                startPos = frame.firstPosition()
513                endPos = inputStart-1
514            else:
515                inputEnd = localFrames[0].lastPosition()
516                outputStart = localFrames[1].firstPosition()
517                if inputEnd < pos and pos < outputStart:
518                    typ = SubCell.C2
519                    startPos = inputEnd+1
520                    endPos = outputStart-1
521                else:
522                    outputEnd = localFrames[1].lastPosition()
523                    if pos <= outputEnd:
524                        traceLogger.critical("pos could not be identified %d" %
525                                             pos)
526                    typ = SubCell.C3
527                    startPos = outputEnd+1
528                    endPos = frame.lastPosition()
529        cursor.setPosition(startPos)
530        cursor.setPosition(endPos,QTextCursor.KeepAnchor)
531        content = cursor.selection().toPlainText()
532        return SubCell(row,startPos,endPos,typ,content)
533
534    def __drawFrameBracket(self,painter,frame,hoff,color,xt=0,width=2):
535        pen = painter.pen()
536        pen.setColor(color)
537        painter.setPen(pen)
538        top = self.cursorRect(frame.firstCursorPosition()).top()
539        bot = self.cursorRect(frame.lastCursorPosition()).bottom() + 1
540        top -= xt
541        bot += xt
542        painter.drawLine(hoff,top,hoff+width,top)
543        painter.drawLine(hoff,top,hoff,bot)
544        painter.drawLine(hoff,bot,hoff+width,bot)
545
546    def __isInputPosition(self,cursor=None):
547        if not cursor:
548            cursor = self.textCursor()
549        if cursor.block().blockFormat() == QtReduceInput().blockFormat:
550            return True
551        else:
552            return False
553
554    def __isOutputPosition(self,cursor=None):
555        if not cursor:
556            cursor = self.textCursor()
557        if cursor.block().blockFormat() in \
558               [QtReduceResult().blockFormat, QtReduceNoResult().blockFormat,
559                QtReduceError().blockFormat]:
560            return True
561        return False
562
563    def __isReadOnlyPosition(self,cursor=None):
564        if self.__isOutputPosition(cursor) or self.__isRootPosition(cursor):
565            return True
566        return False
567
568    def __isRootPosition(self,cursor=None):
569        if not cursor:
570            cursor = self.textCursor()
571        if cursor.currentFrame() == self.document().rootFrame():
572            return True
573        return False
574
575    def __positionPair(self,pos):
576        cursor = QTextCursor(self.document())
577        cursor.setPosition(pos)
578        rootFrame = self.document().rootFrame()
579        frames = rootFrame.childFrames()
580        frame = cursor.currentFrame()
581        if frame == rootFrame:
582            # pos is outside any group
583            return [pos,pos]
584        p = frame.parentFrame()
585        if p != rootFrame:
586            # frame is an input or output frame containing pos
587            return [frame.firstPosition(),frame.lastPosition()]
588        # frame is a group frame containing pos in a comment position
589        localFrames = frame.childFrames()
590        inputStart = localFrames[0].firstPosition()
591        if pos < inputStart:
592            return [frame.firstPosition(),inputStart-1]
593        inputEnd = localFrames[0].lastPosition()
594        outputStart = localFrames[1].firstPosition()
595        if inputEnd < pos and pos < outputStart:
596            return [inputEnd+1,outputStart-1]
597        outputEnd = localFrames[1].lastPosition()
598        if outputEnd < pos:
599            return [outputEnd+1,frame.lastPosition()]
600        traceLogger.critical("bad position %d (%d %d) (%d %d)" %
601                             (pos,
602                              ifr.firstPosition(), ifr.lastPosition(),
603                              ofr.firstPosition(), ofr.lastPosition()))
604        return [-1,-1]
605
606    def __setOutput(self,row,text,reduceBlockFormat):
607        rows = self.document().rootFrame().childFrames()
608        if row >= len(rows):
609            traceLogger.critical("invalid row %d > %d" % (row,len(rows)-1))
610            return
611        rowFrames = rows[row].childFrames()
612        if len(rowFrames) != 2:
613            traceLogger.critical("%d subframes in row %d", len(rowFrames), row)
614            return
615        outputFrame = rowFrames[1]
616        cursor = outputFrame.firstCursorPosition()
617        cursor.setPosition(outputFrame.lastPosition(),QTextCursor.KeepAnchor)
618        cursor.setBlockFormat(reduceBlockFormat.blockFormat)
619        traceLogger.warning("text=%s" % text)
620        cursor.insertText(text.decode('utf-8'),reduceBlockFormat.charFormat)
621#        cursor.insertText(text,reduceBlockFormat.charFormat)
622
623
624class SubCell(object):
625    Root = 0
626    C1 = 1
627    Input = 2
628    C2 = 3
629    Output = 4
630    C3 = 5
631
632    def __init__(self,row,startPos,endPos,typ,content):
633        self.row = row
634        self.startPos = startPos
635        self.endPos = endPos
636        self.type = typ
637        self.content = content
638
639    def __repr__(self):
640        return str([self.row,[self.startPos,self.endPos],
641                    self.type,self.content])
642