[PyKDE] scrollview

Andrew Dalke adalke at mindspring.com
Wed May 8 12:04:00 BST 2002


Hey all,

  I'm trying to use the QScrollView and I'm having some problems.
The problem is in my fundamental understanding of how that widget
works, and how Qt works, so I'll start be describing what I want to
do.  I've attached the code I'm working with if you want to go ahead
and look at it.  I've also attached the image showing what's wrong
(it's only 30K so I figured it was okay to include it in this mail
rather than pointing to an external URL).

  I'm writing a sequence alignment program, for biological sequences
like DNA.  These look like

 Name1   AATGCA--ACGTTT----AT--
 Name2   --TG-A--ACGTTTAACTATGC
 Name3   AATTGATTACGTTTGGGGAT--

The sequences can be very long, even into the tens of thousands of
bases (each letter is a DNA "base").  There are a couple of ways to
handle that.  One is to use a horizontal scrollbar to slide things
back and forth, as in

         _________________
 Name1  |AATGCA--ACGTTT---|   ]
 Name2  |--TG-A--ACGTTTAAC|   ]-- viewport doesn't show the last 5
characters
 Name3  |AATTGATTACGTTTGGG|   ]
        |[=============--]|   <--- scrollbar all the way to the left

         _________________
 Name1  |A--ACGTTT----AT--|   ]
 Name2  |A--ACGTTTAACTATGC|   ]-- viewport doesn't show the first 5
characters
 Name3  |ATTACGTTTGGGGAT--|   ]
        |[--=============]|   <--- scrollbar all the way to the right

Each base (letter) might be drawn differently (eg, fg/bg color, italic,
underline) but for now the font name and size is the same for everything.
These would indicate the base name (eg, "G" is drawn with a yellow
background) or amount of consensus (eg, if >90% of the letters in a given
column are the same then draw it in bold).


To implement this I made an AlignmentView, which derives from QScrollView.
This draws the viewport as shown above.

The AlignmentView contains a list of SequenceView instances.  A SequenceView
mostly works to define the widget size, break the display up into
base-sized segments, and (eventually) allow selection and other sorts of
interaction.

A SequenceView contains a SequenceBaseView.  The SequenceBaseView is
in charge of painting a base in a cell, and can set colors, etc. as
appropriate.  The SequenceView also understands paint events so that
only visible bases are painted, to help with performance.

But what I did doesn't work.  Here are the problems I see:

  - only a few of the bases are drawn -- the paint event isn't as wide
     as the viewport, so only a few characters are shown.  For the first
     line it's enough, but for the other lines it only shows the first
     three characters.

  - the scrollbar size is too large.  The effective width of the scroll
      area is somewhat larger than what I told it.

  - the paint height for the first line is 35 pixels while for the
      other lines is 30 pixels.  That leaves gaps between the lines when
      there should be no gaps.

I don't know how to fix these.  I looked in Boudewijn Rempt's book and
in Dalheimer's book but couldn't find relevant pointers.

Suggestions?

                    Andrew
                    dalke at dalkescientific.com

-------------- next part --------------
from qt import *
import sys, operator

class SequenceBaseView:
    def __init__(self, seq):
        self.seq = seq

    def __len__(self):
        return len(self.seq)

    def paintBase(self, paint, index, rect, ascent):
        assert index >= 0
        try:
            c = self.seq[index]
        except IndexError:
            return
        paint.drawText(rect, Qt.AlignHCenter, c)
#        paint.drawRect(rect)

class SequenceView(QWidget):
    def __init__(self, baseview, *args):
        QWidget.__init__(self, *args)
        self._baseView = baseview

    def sizeHint(self):
        metrics = self.fontMetrics()
        h = metrics.height()
        w = metrics.maxWidth()
        return QSize(w * 40, h)

    def minimumSizeHint(self):
        metrics = self.fontMetrics()
        h = metrics.height()
        w = metrics.maxWidth()
        return QSize(w * 10, h)

    def maximumSizeHint(self):
        metrics = self.fontMetrics()
        h = metrics.height()
        w = metrics.maxWidth()
        return QSize(w * len(self._baseView), h)
        

    def setFont(self, font):
        QWidget.setFont(self, font)
        self.update()

    def setBaseView(self, obj):
        self._baseView = obj

    def baseView(self):
        return self._baseView

    def paintEvent(self, event):
        paint = QPainter(self)
        metrics = paint.fontMetrics()
        ascent = metrics.ascent()
        char_w = metrics.maxWidth()
        char_h = metrics.height()

        rect = event.rect()
        x = rect.x()
        y = rect.y()
        width = rect.width()
        height = rect.height()

        if y > char_h or y + height < 0:
            return
        i = x / char_w
        j = (x + width) / char_w + 1

        text_x = i * char_w
        text_y = ascent
        for index in range(i, j):
            self._baseView.paintBase(paint, index,
                                     QRect(text_x, 0, char_w, char_h),
                                     text_y)
            text_x += char_w

class AlignmentView(QScrollView):
    def __init__(self, *args):
        QScrollView.__init__(self, *args)
        self._sequences = []
        f = QFont("Helvetica", 48, QFont.Black)
        self.setFont(f)

    def addSequenceBaseView(self, baseview):
        v = SequenceView(baseview, self.viewport())
        font = self.font()
        v.setFont(font)

        hintsizes = [obj.maximumSizeHint() for obj in self._sequences]
        widths = [size.width() for size in hintsizes]
        heights = [size.height() for size in hintsizes]
        height = reduce(operator.add, [size.height() for size in hintsizes], 0)

        self.addChild(v, 0, height)
        size = v.maximumSizeHint()
        widths.append(size.width())
        width = max(widths)
        height += size.height()

        self._sequences.append(v)
        self.resizeContents(width, height)

    def setFont(self, font):
        QVBox.setFont(self, font)
        # Move children!!!
        for x in self._sequences:
            x.setFont(font)

class MainWindow(QWidget):
    def __init__(self, alignment, *args):
        QWidget.__init__(self, *args)

        filemenu = self._filemenu = QPopupMenu()
        filemenu.insertItem("&Open")
        filemenu.insertItem("&Save")
        filemenu.insertSeparator()
        filemenu.insertItem("&Quit", qApp, SLOT("quit()"))

        editmenu = self._editmenu = QPopupMenu()
        editmenu.insertItem("Delete")

        alignmenu = self._alignment = QPopupMenu()
        alignmenu.insertItem("Everything")

        menubar = self._menubar = QMenuBar(self)
        menubar.insertItem("&File", filemenu)
        menubar.insertItem("&Edit", editmenu)
        menubar.insertItem("&Align", alignmenu)

        av = self._alignment_view = AlignmentView(self)
        av.addSequenceBaseView(SequenceBaseView("ANDREW"))
        av.addSequenceBaseView(SequenceBaseView("PETER"))
        av.addSequenceBaseView(SequenceBaseView("DALKE"))
        av.addSequenceBaseView(SequenceBaseView("Four"))
        av.addSequenceBaseView(SequenceBaseView("Five"))
        av.addSequenceBaseView(SequenceBaseView("Six"))
        av.addSequenceBaseView(SequenceBaseView(
            ("".join([chr(i) for i in range(32, 128)])) * 5))

        av.setGeometry(0, menubar.height(),
                       self.width(), self.height() - menubar.height())

    def resizeEvent(self, event):
        QWidget.resizeEvent(self, event)
        av = self._alignment_view
        menubar = self._menubar
        av.setGeometry(0, menubar.height(),
                       self.width(), self.height() - menubar.height())


def main():
    app = QApplication(sys.argv)
    mywidget = MainWindow([])
    mywidget.setGeometry(50, 50, 400, 400)

    app.setMainWidget(mywidget)
    mywidget.show()
    app.exec_loop()

if __name__ == "__main__":
    main()

-------------- next part --------------
A non-text attachment was scrubbed...
Name: align.jpg
Type: image/jpeg
Size: 29921 bytes
Desc: not available
Url : http://www.riverbankcomputing.com/pipermail/pyqt/attachments/20020508/66ed00e7/align.jpg


More information about the PyQt mailing list