c7391.htm

来自「GUI Programming with Python」· HTM 代码 · 共 962 行 · 第 1/2 页

HTM
962
字号
        light-gray pen. The distance is computed by letting the        <TTCLASS="FUNCTION">range</TT> function use the height of the        widget divided by the number of rows we want as a        stepsize.</P><P>If you wish to use several different        painter objects, you might want to use the        <TTCLASS="FUNCTION">begin()</TT> and <TTCLASS="FUNCTION">end()</TT>        methods of the <TTCLASS="CLASSNAME">QPainter</TT> class. In        normal use, as here, the <TTCLASS="FUNCTION">begin()</TT> function        is called when the <TTCLASS="CLASSNAME">QPainter</TT> is created,        and <TTCLASS="FUNCTION">end()</TT> when it is destroyed. However,        because the reference goes out of scope,        <TTCLASS="FUNCTION">end()</TT> is called automatically, so you        won't have to call <TTCLASS="FUNCTION">end()</TT> yourself.</P><PRECLASS="PROGRAMLISTING">    def text(self):        return QString(str(self.count))      </PRE><P>The function <TTCLASS="FUNCTION">text()</TT>        simply returns a <TTCLASS="CLASSNAME">QString</TT> object        containing the last plotted value. We will use this to set the        caption of the chart window.</P><PRECLASS="PROGRAMLISTING">    def update(self, count):        """        Called periodically by a timer to update the count.        """        self.count = count        h = self.height()        w = self.width()        p = QPainter(self)        p.setBackgroundColor(QColor("white"))        p.setBrush(QColor("black"))                if self.col &#62;= w:            self.col = w            # move one pixel to the left            pixmap = QPixmap(w, h)            pixmap.fill(QColor("white"))            bitBlt(pixmap, 0, 0,                   self, BARWIDTH, 0, w - BARWIDTH, h)                        bitBlt(self, 0, 0, pixmap, 0, 0, w, h)            for i in range(1, h, h/5):                p.setPen(QColor("lightgray"))                p.drawLine(self.col - BARWIDTH , i, w, i)        else:            self.col += BARWIDTH        y = float(self.scale) * float(self.count)        # to avoid ZeroDivisionError        if y == 0: y = 1        # Draw gradient        minV = 255        H = 0        S = 255        vStep = float(float(128)/float(y))        for i in range(y):            color = QColor()            color.setHsv(H, S, 100 + int(vStep * i))            p.setPen(QPen(color))            p.drawLine(self.col - BARWIDTH, h-i, self.col, h-i)      </PRE><P>The <TTCLASS="FUNCTION">update()</TT>        function is where the real meat of the charting pixmap is. It        draws a gradiented bar that scrolls left when the right side        is reached (that is, if the current column has arrived at or        gone beyond the width of the pixmap).</P><P>The scrolling is done by creating a new,        empty <TTCLASS="CLASSNAME">QPixmap</TT> and blitting the right        hand part of the old pixmap onto it. When writing this code, I        noticed that you cannot blit a pixmap onto itself. So, after        we've created a pixmap that contains the old pixmap minus the        first few vertical lines, we blit it back, and add the grid to        the now empty right hand side of the pixmap.</P><P>The height of the bar we want to draw is        computed by multiplying the value        (<TTCLASS="VARNAME">self.count</TT>) with the scale of the chart.        If the result is 0, we make it 1.</P><P>We draw the bar in steps, with each step        having a subtly differing color from the one before it. The        color gradient is determined by going along the        <SPAN><ICLASS="EMPHASIS">value</I></SPAN> range of a hue-saturation-value        color model. Value determines darkness, with 0 being        completely dark, and 255 completely light. We don't use the        complete range, but step directly from 100 (fairly dark) to        228 (quite bright). The step is computed by dividing the value        range we want (128) by the height of the bar. Every bar is        going from 100 to 228.</P><P>Then we step through the computed height        of the bar, drawing a horizontal line with the length of the        bar thickness &#8212; <TTCLASS="VARNAME">BARWIDTH</TT>.</P><P>Computing gradients is fairly costly,        but it is still possible to type comfortably when this chart        is running: a testimony to the efficient design of        <TTCLASS="CLASSNAME">QPainter</TT>. If your needs are more        complicated, then <TTCLASS="CLASSNAME">QPainter</TT> offers a        host of sophisticated drawing primitives (and not so        primitives, like shearing, scaling, resizing and the drawing        of quad beziers).</P><P>The <TTCLASS="CLASSNAME">TypoGraph</TT> is        completely generic: it draws a nicely gradiented graph of any        values that you feed the update function. There's some testing        code included that uses a simple timer to update the chart        with a random value.</P><DIVCLASS="MEDIAOBJECT"><P><DIVCLASS="CAPTION"><P>A stand-alone chart</P></DIV></P></DIV><P>More application-specific is the        <TTCLASS="CLASSNAME">TypoMeter</TT> widget, which keeps track of        all open <SPANCLASS="APPLICATION">Kalam</SPAN> documents, and shows        the right chart for the currently active document.</P><PRECLASS="PROGRAMLISTING">class TypoMeter(QWidget):    def __init__(self, docmanager, workspace, w, h, *args):        apply(QWidget.__init__, (self,) + args)        self.docmanager = docmanager        self.workspace = workspace        self.resize(w, h)        self.setMinimumSize(w,h)        self.setMaximumSize(w,h)        self.h = h        self.w = w        self.connect(self.docmanager,                     PYSIGNAL("sigNewDocument"),                     self.addGraph)        self.connect(self.workspace,                     PYSIGNAL("sigViewActivated"),                     self.changeGraph)        self.graphMap = {}        self.addGraph(self.docmanager.activeDocument(),                      self.workspace.activeWindow())        self.timer = QTimer(self)        self.connect(self.timer,                     SIGNAL("timeout()"),                     self.updateGraph)        self.timer.start(FIVE_SECONDS, FALSE)      </PRE><P>In order to implement this feature, some        new signals had to be added to the document manager and the        workspace classes. Note also the use of the        <TTCLASS="CLASSNAME">QTimer</TT> class. A timer is created with        the current object as its parent; a slot is connected to the        <TTCLASS="FUNCTION">timeout()</TT> signal, and the timer is        started with a certain interval. The <TTCLASS="VARNAME">FALSE</TT>        parameter means that the timer is supposed to keep running,        instead of firing once, when the timeout is reached.</P><PRECLASS="PROGRAMLISTING">    def addGraph(self, document, view):        self.currentGraph = TypoGraph(0,                                      self.h,                                      self.w)        self.graphMap[document] = (self.currentGraph, 0)        self.currentDocument = document    def changeGraph(self, view):        self.currentGraph = self.graphMap[view.document()][0]        self.currentDocument = view.document()        bitBlt(self, 0, 0,               self.currentGraph,               0, 0,               self.w,               self.h)    def updateGraph(self):        prevCount = self.graphMap[self.currentDocument][1]        newCount = self.currentDocument.text().length()        self.graphMap[self.currentDocument] = (self.currentGraph, newCount)        delta = newCount - prevCount        if delta &#60; 0: delta = 0 # no negative productivity        self.currentGraph.update(delta)        bitBlt(self, 0, 0,               self.currentGraph,               0, 0,               self.w,               self.h)        self.setCaption(self.currentGraph.text())      </PRE><P>The actual keeping track of the        type-rate is done in this class, not in the        <TTCLASS="CLASSNAME">TypoChart</TT> class. In making good use of        Python's ability to form tuples on the fly, a combination of        the <TTCLASS="CLASSNAME">TypoChart</TT> instance and the last        count is kept in a dictionary, indexed by the document.</P><P>Using the last count and the current        length of the text, the <SPAN><ICLASS="EMPHASIS">delta</I></SPAN> (the        difference) is computed and fed to the chart. This updates the        chart, and the chart is then blitted onto the widget &#8212; a        <TTCLASS="CLASSNAME">QWidget</TT> is a paintdevice, after        all.</P><PRECLASS="PROGRAMLISTING">    def paintEvent(self, ev):        p = QPainter(self)        bitBlt(self, 0, 0,               self.currentGraph,               0, 0,               self.w,               self.h)class TestWidget(QWidget):    def __init__(self, *args):        apply(QWidget.__init__, (self,) + args)        self.setGeometry(10, 10, 50, 250)        self.pixmap = TypoGraph(0, self.width(), self.height())        self.timer = self.startTimer(100)    def paintEvent(self, ev):        bitBlt(self, 0, 0, self.pixmap, 0, 0, self.width(), self.height())    def timerEvent(self, ev):        self.pixmap.update(whrandom.randrange(0, 300))        bitBlt(self, 0, 0, self.pixmap, 0, 0, self.width(), self.height())if __name__ == '__main__':    a = QApplication(sys.argv)    QObject.connect(a,SIGNAL('lastWindowClosed()'),a,SLOT('quit()'))    w = TestWidget()    a.setMainWidget(w)    w.show()    a.exec_loop()      </PRE><P>Finally, this is some testing code, not        for the <TTCLASS="CLASSNAME">TypoMeter</TT> class, which can only        work together with <SPANCLASS="APPLICATION">Kalam</SPAN>, but for        the <TTCLASS="CLASSNAME">TypoChart</TT> class. It is difficult to        use the unit testing framework from <AHREF="c5064.htm">Chapter 14</A>        here&#8212; after all, in the case of graphics work, the proof        of the pudding is in the eating, and it's difficult to assert        things about pixels on the screen.</P><P>The code to show the type-o-meter on        screen is interesting, since it shows how you can        destructively delete a widget. The        <TTCLASS="CLASSNAME">QAction</TT> that provides the menu option        "show type-o-meter" is a toggle action, and changing the        toggle emits the <TTCLASS="FUNCTION">toggled(bool)</TT> signal.        This is connected to the following function (in        <TTCLASS="FILENAME">kalamapp.py</TT>: </P><PRECLASS="PROGRAMLISTING">    def slotSettingsTypometer(self, toggle):        if toggle:            self.typowindow = TypoMeter(self.docManager,                                        self.workspace,                                        100,                                        100,                                        self,                                        "type-o-meter",                                        Qt.WType_TopLevel or Qt.WDestructiveClose)            self.typowindow.setCaption("Type-o-meter")            self.typowindow.show()        else:            self.typowindow.close(TRUE)      </PRE><P>Destroying this popup-window is        important, because you don't want to waste processing power on        a widget that still exists and is merely hidden. The character        picker popup we will create in the next section will be        hidden, not destroyed.</P></DIV></DIV></DIV><DIVCLASS="NAVFOOTER"><HRALIGN="LEFT"WIDTH="100%"><TABLESUMMARY="Footer navigation table"WIDTH="100%"BORDER="0"CELLPADDING="0"CELLSPACING="0"><TR><TDWIDTH="33%"ALIGN="left"VALIGN="top"><AHREF="x7388.htm"ACCESSKEY="P">Prev</A></TD><TDWIDTH="34%"ALIGN="center"VALIGN="top"><AHREF="book1.htm"ACCESSKEY="H">Home</A></TD><TDWIDTH="33%"ALIGN="right"VALIGN="top"><AHREF="x7601.htm"ACCESSKEY="N">Next</A></TD></TR><TR><TDWIDTH="33%"ALIGN="left"VALIGN="top">Conclusion</TD><TDWIDTH="34%"ALIGN="center"VALIGN="top"><AHREF="p4627.htm"ACCESSKEY="U">Up</A></TD><TDWIDTH="33%"ALIGN="right"VALIGN="top">QCanvas</TD></TR></TABLE></DIV></BODY></HTML>

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?