Changeset 486

Show
Ignore:
Timestamp:
09/04/08 15:44:34 (10 months ago)
Author:
stefan
Message:

initial changes

Location:
nodebox/branches/try-qt-graphics-view/nodebox
Files:
6 added
3 modified

Legend:

Unmodified
Added
Removed
  • nodebox/branches/try-qt-graphics-view/nodebox/graphics/bezier.py

    r155 r486  
    147147    if segments == None: 
    148148        segments = path.segmentlengths(relative=True) 
    149          
     149 
    150150    if len(segments) == 0: 
    151151        raise NodeBoxError, "The given path is empty" 
  • nodebox/branches/try-qt-graphics-view/nodebox/graphics/qt.py

    r286 r486  
    33from random import choice, shuffle 
    44 
    5 from PyQt4.QtGui import QPainterPath, QColor, QTransform, QBrush, QPen, QImage, QPrinter, QPainter, QFontMetrics, QFont 
    6 from PyQt4.QtCore import Qt, QSize, QPointF, QRectF 
     5from PyQt4.QtGui import * 
     6from PyQt4.QtCore import Qt, QSize, QSizeF, QPointF, QRectF 
    77from PyQt4.QtSvg import QSvgGenerator 
    88 
    99from nodebox.util import _copy_attr, _copy_attrs 
     10from nodebox.graphics.qgraphics import * 
    1011 
    1112__all__ = [ 
     
    3637CORNER = "corner" 
    3738 
    38 MOVETO = 0 
    39 LINETO = 1 
    40 CURVETO = 2 
    41 CLOSE = 3 
     39MOVETO = QPainterPath.MoveToElement 
     40LINETO = QPainterPath.LineToElement 
     41CURVETO = QPainterPath.CurveToElement 
     42CLOSE = "close" 
    4243 
    4344LEFT = 0 
     
    6768    '_lineheight':    'lineheight', 
    6869} 
    69  
    70 #def _save(): 
    71 #    NSGraphicsContext.currentContext().saveGraphicsState() 
    72  
    73 #def _restore(): 
    74 #    NSGraphicsContext.currentContext().restoreGraphicsState() 
    7570 
    7671class NodeBoxError(Exception): pass 
     
    201196    strokewidth = property(_get_strokewidth, _set_strokewidth) 
    202197 
     198 
    203199class BezierPath(Grob, TransformMixin, ColorMixin): 
    204200    """A BezierPath provides a wrapper around QPainterPath.""" 
     
    212208        ColorMixin.__init__(self, **kwargs) 
    213209        self._segment_cache = None 
     210        self._qpath_segment_cache = None 
    214211        if path is None: 
    215212            self._qpath = QPainterPath() 
     
    237234    def moveto(self, x, y): 
    238235        self._segment_cache = None 
     236        self._qpath_segment_cache = None 
    239237        self._qpath.moveTo(x, y) 
    240238 
    241239    def lineto(self, x, y): 
    242240        self._segment_cache = None 
     241        self._qpath_segment_cache = None 
    243242        self._qpath.lineTo(x, y) 
    244243 
    245244    def curveto(self, x1, y1, x2, y2, x3, y3): 
    246245        self._segment_cache = None 
     246        self._qpath_segment_cache = None 
    247247        self._qpath.cubicTo(x1, y1, x2, y2, x3, y3) 
    248248 
    249249    def closepath(self): 
    250250        self._segment_cache = None 
     251        self._qpath_segment_cache = None 
    251252        self._qpath.closeSubpath() # XXX: Is this correct? 
    252253 
     
    272273    def rect(self, x, y, width, height): 
    273274        self._segment_cache = None 
     275        self._qpath_segment_cache = None 
    274276        self._qpath.addRect(x, y, width, height) 
    275277         
    276278    def oval(self, x, y, width, height): 
    277279        self._segment_cache = None 
     280        self._qpath_segment_cache = None 
    278281        self._qpath.addEllipse(x, y, width, height) 
    279282         
    280283    def line(self, x1, y1, x2, y2): 
    281284        self._segment_cache = None 
     285        self._qpath_segment_cache = None 
    282286        self._qpath.moveTo(x1, y1) 
    283287        self._qpath.lineTo(x2, y2) 
     
    286290 
    287291    def __getitem__(self, index): 
    288         el = self._qpath.elementAt(index) 
    289         return PathElement(el) 
     292        if self._qpath_segment_cache == None: 
     293            self._set_qpath_segment_cache() 
     294        cmd, el = self._qpath_segment_cache[index] 
     295        return PathElement(cmd, el) 
    290296 
    291297    def __iter__(self): 
     
    294300 
    295301    def __len__(self): 
    296         return self._qpath.elementCount() 
     302        if self._qpath_segment_cache == None: 
     303            self._set_qpath_segment_cache() 
     304        return len(self._qpath_segment_cache) 
     305 
     306    def _set_qpath_segment_cache(self): 
     307        self._qpath_segment_cache = [] 
     308        length = self._qpath.elementCount() 
     309        temp = [] 
     310        for i in range(length): 
     311            el = self._qpath.elementAt(i) 
     312            if el.type in [LINETO, MOVETO, CURVETO, CLOSE]: 
     313                temp.append(i) 
     314        for i in temp: 
     315            el = self._qpath.elementAt(i) 
     316            if el.type in [LINETO, MOVETO, CLOSE]: 
     317                self._qpath_segment_cache.append((el.type, ((el.x, el.y),))) 
     318            elif el.type == CURVETO: 
     319                ctrl1 = self._qpath.elementAt(i+1) 
     320                ctrl2 = self._qpath.elementAt(i+2) 
     321                self._qpath_segment_cache.append((el.type, ((el.x, el.y), (ctrl1.x, ctrl1.y), (ctrl2.x, ctrl2.y)))) 
    297322 
    298323    def extend(self, pathElements): 
    299324        self._segment_cache = None 
     325        self._qpath_segment_cache = None 
    300326        for el in pathElements: 
    301327            if isinstance(el, (list, tuple)): 
     
    313339    def append(self, el): 
    314340        self._segment_cache = None 
     341        self._qpath_segment_cache = None 
    315342        if el.cmd == MOVETO: 
    316343            self.moveto(el.x, el.y) 
     
    344371    transform = property(_get_transform) 
    345372 
    346     def _draw(self, painter): 
    347         painter.save() 
    348         self.transform.concat(painter) 
     373    def _draw(self): 
     374        item = GraphicsPathItem() 
     375        self.transform.concat(item) 
     376        item.setPath(self._qpath) 
    349377        if self._fillcolor: 
    350             painter.fillPath(self._qpath, QBrush(self._fillcolor._rgb)) 
     378            item.setBrush(QBrush(self._fillcolor._rgb)) 
    351379        if self._strokecolor: 
    352380            p = QPen(QBrush(self._strokecolor._rgb), self._strokewidth) 
    353             painter.setPen(p) 
    354             painter.drawPath(self._qpath) 
    355         painter.restore() 
     381            item.setPen(p) 
     382        else: 
     383            item.setPen(QPen(Qt.NoPen)) 
     384        self._scene.addItem(item) 
     385         
     386    def fit(self, x=None, y=None, width=None, height=None, stretch=False): 
     387 
     388        """Fits this path to the specified bounds. 
     389     
     390        All parameters are optional; if no parameters are specified, nothing will happen. 
     391        Specifying a parameter will constrain its value: 
     392     
     393        - x: The path will be positioned at the specified x value 
     394        - y: The path will be positioned at the specified y value 
     395        - width: The path will be of the specified width 
     396        - height: The path will be of the specified height 
     397        - stretch: If both width and height are defined, either stretch the path or 
     398                keep the aspect ratio. 
     399        """ 
     400 
     401        (px, py), (pw, ph) = self.bounds 
     402        t = Transform() 
     403        if x is not None and y is None: 
     404            t.translate(x, py) 
     405        elif x is None and y is not None: 
     406            t.translate(px, y) 
     407        elif x is not None and y is not None: 
     408            t.translate(x, y) 
     409        else: 
     410            t.translate(px, py) 
     411        if width is not None and height is None: 
     412            t.scale(width / pw) 
     413        elif width is None and height is not None: 
     414            t.scale(height / ph) 
     415        elif width is not None and height is not None: 
     416            if stretch: 
     417                t.scale(width /pw, height / ph) 
     418            else: 
     419                t.scale(min(width /pw, height / ph)) 
     420        t.translate(-px, -py) 
     421        self._qpath = t.transformBezierPath(self)._qpath 
     422        self._qpath_segment_cache = None 
    356423         
    357424    ### Mathematics ### 
     
    394461    def addpoint(self, t): 
    395462        import bezier 
    396         self._nsBezierPath = bezier.insert_point(self, t)._nsBezierPath 
     463        self._qpath = bezier.insert_point(self, t)._qpath 
    397464        self._segment_cache = None 
     465        self._qpath_segment_cache = None 
     466 
     467    ### Clipping operations ### 
     468 
     469    def intersects(self, other): 
     470        return self._qpath.intersects(other._qpath) 
     471 
     472    def union(self, other, flatness=0.6): 
     473        return BezierPath(self._ctx, self._qpath.united(other._qpath)) 
     474 
     475    def intersect(self, other, flatness=0.6): 
     476        return BezierPath(self._ctx, self._qpath.intersected(other._qpath)) 
     477 
     478    def difference(self, other, flatness=0.6): 
     479        return BezierPath(self._ctx, self._qpath.subtracted(other._qpath)) 
     480 
     481    def xor(self, other, flatness=0.6): 
     482        union = self._qpath.united(other._qpath) 
     483        intersection = self._qpath.intersected(other._qpath) 
     484        return BezierPath(self._ctx, union.subtracted(intersection)) 
    398485 
    399486class PathElement(object): 
     
    446533        return not self.__eq__(other) 
    447534 
     535 
    448536class ClippingPath(Grob): 
    449537 
     
    457545         
    458546    def _draw(self): 
    459         _save() 
    460547        cp = self.path.transform.transformBezierPath(self.path) 
    461         cp._nsBezierPath.addClip() 
     548        item = GraphicsClipItem(self._scene) 
     549        self._scene.addItem(item) 
     550        item.setPath(cp._qpath)         
     551 
    462552        for grob in self._grobs: 
     553            grob._scene = item 
    463554            grob._draw() 
    464         _restore() 
    465555 
    466556class Rect(BezierPath): 
     
    468558    def __init__(self, ctx, x, y, width, height, **kwargs): 
    469559        warnings.warn("Rect is deprecated. Use BezierPath's rect method.", DeprecationWarning, stacklevel=2) 
    470         r = (x,y), (width,height) 
    471         super(Rect, self).__init__(ctx, NSBezierPath.bezierPathWithRect_(r), **kwargs) 
     560        p=QPainterPath() 
     561        p.addRect(x, y, width, height) 
     562        super(Rect, self).__init__(ctx, p, **kwargs) 
    472563 
    473564    def copy(self): 
     
    478569    def __init__(self, ctx, x, y, width, height, **kwargs): 
    479570        warnings.warn("Oval is deprecated. Use BezierPath's oval method.", DeprecationWarning, stacklevel=2) 
    480         r = (x,y), (width,height) 
    481         super(Oval, self).__init__(ctx, NSBezierPath.bezierPathWithOvalInRect_(r), **kwargs) 
     571        p=QPainterPath() 
     572        p.addEllipse(x, y, width, height) 
     573        super(Oval, self).__init__(ctx, p, **kwargs) 
    482574 
    483575    def copy(self): 
    484576        raise NotImplementedError, "Please don't use Oval anymore" 
    485  
     577         
    486578class Color(object): 
    487579 
     
    519611            args = self._normalizeList(args) 
    520612            h, s, b = args 
     613            if h == 1.0: 
     614                h = .99998 
    521615            clr = QColor.fromHsvF(h, s, b, 1) 
    522616        elif params == 4 and self._ctx._colormode == RGB: # RGB and alpha 
     
    527621            args = self._normalizeList(args) 
    528622            h, s, b, a = args 
     623            if h == 1.0: 
     624                h = .99998 
    529625            clr = QColor.fromHsvF(h, s, b, a) 
    530626        elif params == 4 and self._ctx._colormode == CMYK: # CMYK, no alpha 
     
    557653    qColor = property(_get_qColor) 
    558654         
    559  
    560655    def copy(self): 
    561656        new = self.__class__(self._ctx) 
    562657        new._rgb = QColor(self._rgb) 
    563         new._updateCmyk() 
     658#        new._updateCmyk() 
    564659        return new 
    565660 
     
    573668 
    574669    def _get_hue(self): 
    575         return self._rgb.hueF() 
     670        hue = self._rgb.hueF() 
     671        if round(hue, 4) == 1.0: 
     672            return 1.0 
     673        if hue < 0.0: 
     674            return 0.0 
     675        return hue 
    576676    def _set_hue(self, val): 
    577677        val = self._normalize(val) 
    578         c = self._rgb 
    579         h, s, b, a = c.hueF(), c.saturationF(), c.valueF(), c.alpha() 
     678        if val == 1.0: 
     679           val = .99998 
     680        h, s, b, a = self.hsba 
    580681        self._rgb.setHsvF(val, s, b, a) 
    581682        self._updateCmyk() 
    582683    h = hue = property(_get_hue, _set_hue, doc="the hue of the color") 
     684 
     685    def _get_saturation(self): 
     686        return self._rgb.saturationF() 
     687    def _set_saturation(self, val): 
     688        val = self._normalize(val) 
     689        h, s, b, a = self.hsba 
     690        self._rgb.setHsvF(h, val, b, a) 
     691        self._updateCmyk() 
     692    s = saturation = property(_get_saturation, _set_saturation, doc="the saturation of the color") 
     693 
     694    def _get_brightness(self): 
     695        return self._rgb.valueF() 
     696    def _set_brightness(self, val): 
     697        val = self._normalize(val) 
     698        h, s, b, a = self.hsba 
     699        self._rgb.setHsvF(h, s, val, a) 
     700        self._updateCmyk() 
     701    v = brightness = property(_get_brightness, _set_brightness, doc="the saturation of the color") 
     702 
     703    def _get_hsba(self): 
     704        c = self._rgb 
     705        return c.hueF(), c.saturationF(), c.valueF(), c.alphaF() 
     706    def _set_hsba(self, values): 
     707        values = self._normalizeList(values) 
     708        h, s, b, a = values 
     709        self._rgb.setHsvF(h, s, b, a) 
     710    hsba = property(_get_hsba, _set_hsba, doc="the hue, saturation, brightness and alpha of the color") 
     711 
     712    def _get_red(self): 
     713        return self._rgb.redF() 
     714    def _set_red(self, val): 
     715        val = self._normalize(val) 
     716        r, g, b, a = self.rgba 
     717        self._rgb.setRgbF(val, g, b, a) 
     718        self._updateCmyk()         
     719    r = red = property(_get_red, _set_red, doc="the red component of the color") 
     720 
     721    def _get_green(self): 
     722        return self._rgb.greenF() 
     723    def _set_green(self, val): 
     724        val = self._normalize(val) 
     725        r, g, b, a = self.rgba 
     726        self._rgb.setRgbF(r, val, b, a) 
     727        self._updateCmyk()         
     728    g = green = property(_get_green, _set_green, doc="the green component of the color") 
     729 
     730    def _get_blue(self): 
     731        return self._rgb.blueF() 
     732    def _set_blue(self, val): 
     733        val = self._normalize(val) 
     734        r, g, b, a = self.rgba 
     735        self._rgb.setRgbF(r, g, val, a) 
     736        self._updateCmyk()         
     737    b = blue = property(_get_blue, _set_blue, doc="the blue component of the color") 
     738 
     739    def _get_alpha(self): 
     740        return self._rgb.alphaF() 
     741    def _set_alpha(self, val): 
     742        val = self._normalize(val) 
     743        r, g, b, a = self.rgba 
     744        self._rgb.setRgbF(r, g, b, val) 
     745        self._updateCmyk()         
     746    a = alpha = property(_get_alpha, _set_alpha, doc="the alpha component of the color") 
     747    
     748    def _get_rgba(self): 
     749        c = self._rgb 
     750        return c.redF(), c.greenF(), c.blueF(), c.alphaF() 
     751    def _set_rgba(self, values): 
     752        values = self._normalizeList(values) 
     753        r, g, b, a = values 
     754        self._rgb.setRgbF(r, g, b, a) 
     755    rgba = property(_get_rgba, _set_rgba, doc="the red, green, blue and alpha values of the color") 
     756 
     757    def _get_cyan(self): 
     758        return self._rgb.cyanF() 
     759    def _set_cyan(self, val): 
     760        val = self._normalize(val) 
     761        c, m, y, k, a = self.cmyka 
     762        self._rgb.setCmykF(val, m, y, k, a) 
     763        self._updateRgb()         
     764    c = cyan = property(_get_cyan, _set_cyan, doc="the cyan component of the color") 
     765 
     766    def _get_magenta(self): 
     767        return self._rgb.magentaF() 
     768    def _set_magenta(self, val): 
     769        val = self._normalize(val) 
     770        c, m, y, k, a = self.cmyka 
     771        self._rgb.setCmykF(c, val, y, k, a) 
     772        self._updateRgb()         
     773    m = magenta = property(_get_magenta, _set_magenta, doc="the magenta component of the color") 
     774 
     775    def _get_yellow(self): 
     776        return self._rgb.yellowF() 
     777    def _set_yellow(self, val): 
     778        val = self._normalize(val) 
     779        c, m, y, k, a = self.cmyka 
     780        self._rgb.setCmykF(c, m, val, k, a) 
     781        self._updateRgb()         
     782    y = yellow = property(_get_yellow, _set_yellow, doc="the yellow component of the color") 
     783 
     784    def _get_black(self): 
     785        return self._rgb.blackF() 
     786    def _set_black(self, val): 
     787        val = self._normalize(val) 
     788        c, m, y, k, a = self.cmyka 
     789        self._rgb.setCmykF(c, m, y, val, a) 
     790        self._updateRgb()         
     791    k = black = property(_get_black, _set_black, doc="the black component of the color") 
     792 
     793    def _get_cmyka(self): 
     794        c = self._rgb 
     795        return c.cyanF(), c.magentaF(), c.yellowF(), c.blackF(), c.alphaF() 
     796    cmyka = property(_get_cmyka, doc="a tuple containing the CMYKA values for this color") 
    583797 
    584798    def _normalize(self, v): 
     
    604818        elif isinstance(transform, (list, tuple)): 
    605819            matrix = tuple(transform) 
    606             transform = QTransform() 
    607             transform.setMatrix(*matrix) 
     820            transform = QTransform(*matrix) 
    608821        elif isinstance(transform, QTransform): 
    609822            pass 
     
    622835    def concat(self, painter): 
    623836        trans = painter.transform() 
    624         painter.setTransform(trans * self._qtransform) 
     837        painter.setTransform(self._qtransform * trans) 
    625838 
    626839    def copy(self): 
     
    637850    def _get_matrix(self): 
    638851        q = self._qtransform 
    639         return (q.m11(), q.m12(), q.m13(), q.m21(), q.m22(), q.m23(), q.m31(), q.m32(), q.m33()) 
     852        return (q.m11(), q.m12(), q.m21(), q.m22(), q.m31(), q.m32()) 
    640853    def _set_matrix(self, value): 
    641         self._qtransform.setMatrix(value) 
     854        self._qtransform = QTransform(*value) 
    642855    matrix = property(_get_matrix, _set_matrix) 
    643856 
     
    657870 
    658871    def skew(self, x=0, y=0): 
    659         self._qtransform.shear(x, y) 
     872        import math 
     873        x = math.pi * x / 180. 
     874        y = math.pi * y / 180. 
     875        t = Transform() 
     876        t.matrix = 1, math.tan(y), -math.tan(x), 1, 0, 0 
     877        self.prepend(t) 
    660878 
    661879    def invert(self): 
     
    711929        TransformMixin.__init__(self) 
    712930        if data is not None: 
    713             self._nsImage = QImage(data) 
     931            self._qimage = QImage(data) 
    714932            if self._qimage is None: 
    715933                raise NodeBoxError, "can't read image %r" % path 
     
    731949            if image is None: 
    732950                image = QImage(path) 
     951                pxm = QPixmap.fromImage(image) 
    733952                if image is None: 
    734953                    raise NodeBoxError, "Can't read image %r" % path 
     
    753972 
    754973    def getSize(self): 
    755         return self._nsImage.size() 
     974        return self._qimage.width(), self._qimage.height() 
    756975 
    757976    size = property(getSize) 
    758977 
    759     def _draw(self, painter): 
     978    def _draw(self): 
    760979        """Draw an image on the given coordinates.""" 
    761980 
    762         srcW, srcH = self._qimage.width(), self._qimage.height() 
     981        srcW, srcH = float(self._qimage.width()), float(self._qimage.height()) 
    763982        srcRect = ((0, 0), (srcW, srcH)) 
     983 
     984        if self.debugImage: 
     985            item = QGraphicsRectItem() 
     986        else: 
     987            item = GraphicsImageItem(self._qimage, self.alpha) 
    764988 
    765989        # Width or height given 
     
    771995            elif self.height is not None: 
    772996                factor = self.height / srcH 
    773             painter.save() 
    774997 
    775998            # Center-mode transforms: translate to image center 
     
    7771000                # This is the hardest case: center-mode transformations with given width or height. 
    7781001                # Order is very important in this code. 
    779  
    7801002                # Set the position first, before any of the scaling or transformations are done. 
    7811003                # Context transformations might change the translation, and we don't want that. 
    7821004                t = Transform() 
    7831005                t.translate(self.x, self.y) 
    784                 t.concat(painter) 
    785  
     1006                t.concat(item) 
     1007                 
    7861008                # Set new width and height factors. Note that no scaling is done yet: they're just here 
    787                 # to set the new center of the image according to the scaling factors. 
     1009                # to set the new center of the image according to the scaling factors 
    7881010                srcW = srcW * factor 
    7891011                srcH = srcH * factor 
    7901012 
    791                 # Move image to newly calculated center. 
     1013                # Move image to newly calculated center.                 
    7921014                dX = srcW / 2 
    7931015                dY = srcH / 2 
    7941016                t = Transform() 
    7951017                t.translate(dX, dY) 
    796                 t.concat(painter) 
    797  
     1018                t.concat(item) 
     1019                 
    7981020                # Do current transformation. 
    799                 self._transform.concat(painter) 
    800  
    801                 # Move back to the previous position. 
     1021                self._transform.concat(item) 
     1022 
     1023                # Move back to the previous position.                 
    8021024                t = Transform() 
    8031025                t.translate(-dX, -dY) 
    804                 t.concat(painter) 
    805  
    806                 # Finally, scale the image according to the factors. 
     1026                t.concat(item) 
     1027 
     1028                # Finally, scale the image according to the factors.                 
    8071029                t = Transform() 
    8081030                t.scale(factor) 
    809                 t.concat(painter) 
     1031                t.concat(item)                 
    8101032            else: 
    8111033                # Do current transformation 
    812                 #self._transform.concat() 
     1034                self._transform.concat(item) 
    8131035                # Scale according to width or height factor 
    8141036                t = Transform() 
    8151037                t.translate(self.x, self.y) # Here we add the positioning of the image. 
    8161038                t.scale(factor) 
    817                 t.concat(painter) 
     1039                t.concat(item) 
    8181040 
    8191041            # A debugImage draws a black rectangle instead of an image. 
    8201042            if self.debugImage: 
    821                 painter.setBrush(QBrush(Qt.SolidPattern)) 
    822                 painter.fillRect(QRectF(0, 0, srcW / factor, srcH / factor)) 
    823             else: 
    824                 # TODO: Stuff with composition modes to allow for alpha transparency 
    825                 painter.drawImage(QPointF(0, 0), self._qimage, QRectF(0, 0, srcW, srcH)) 
    826             painter.restore() 
     1043                item.setRect(0, 0, srcW / factor, srcH / factor) 
     1044                item.setPen(QPen(Qt.NoPen)) 
     1045                item.setBrush(QBrush(Qt.black)) 
     1046 
     1047            self._scene.addItem(item)             
    8271048        # No width or height given 
    8281049        else: 
    829             painter.save() 
    8301050            x,y = self.x, self.y 
    8311051            # Center-mode transforms: translate to image center 
     
    8351055                t = Transform() 
    8361056                t.translate(x+deltaX, y+deltaY) 
    837                 t.concat(painter) 
     1057                t.concat(item) 
    8381058                x = -deltaX 
    8391059                y = -deltaY 
    8401060            # Do current transformation 
    841             self._transform.concat(painter) 
     1061            self._transform.concat(item) 
    8421062            # A debugImage draws a black rectangle instead of an image. 
    8431063            if self.debugImage: 
    844                 painter.setBrush(QBrush(Qt.SolidPattern)) 
    845                 painter.fillRect(QRectF(0, 0, srcW, srcH)) 
     1064                item.setRect(x, y, srcW, srcH) 
     1065                item.setPen(QPen(Qt.NoPen)) 
     1066                item.setBrush(QBrush(Qt.black)) 
    8461067            else: 
    847                 # TODO: Stuff with composition modes to allow for alpha transparency 
    848                 painter.drawImage(QPointF(0, 0), self._qimage, QRectF(0, 0, srcW, srcH)) 
    849             painter.restore() 
     1068                t = Transform() 
     1069                t.translate(x, y) 
     1070                t.concat(item) 
     1071            self._scene.addItem(item) 
    8501072 
    8511073class Text(Grob, TransformMixin, ColorMixin): 
     
    8551077 
    8561078    __dummy_color = QColor() 
    857     __alignMap = { LEFT: Qt.AlignLeft, RIGHT: Qt.AlignRight, CENTER: Qt.AlignCenter, JUSTIFY: Qt.AlignJustify } 
     1079    __alignMap = { LEFT: Qt.AlignLeft, RIGHT: Qt.AlignRight, CENTER: Qt.AlignHCenter, JUSTIFY: Qt.AlignJustify } 
    8581080     
    8591081    def __init__(self, ctx, text, x=0, y=0, width=None, height=None, **kwargs): 
     
    8851107 
    8861108    def _get_font(self): 
    887         return QFont(self._fontname, self._fontsize) 
     1109        f = QFont(self._fontname) 
     1110        f.setPointSizeF(self._fontsize / textScaleFactor) 
     1111        return f 
    8881112    _qfont = property(_get_font) 
    8891113 
    890     def _draw(self, painter): 
    891         if self.width is None: 
    892             w = 100000 
    893         else: 
    894             w = self.width 
    895         if self.height is None: 
    896             h = 100000 
    897         else: 
    898             h = self.height 
    899         fm = painter.fontMetrics() 
    900         flags = self.__alignMap[self._align] 
    901         r = fm.boundingRect(self.x, self.y, w, h, flags, self.text) 
    902  
     1114    def _draw(self): 
    9031115        if self._fillcolor is None: return 
    904         x,y = r.x(), r.y() 
     1116        flags = self.__alignMap[self._align] | Qt.TextWordWrap 
     1117        fm = QFontMetricsF(self._qfont)                 
     1118        w = self.width or 100000 
     1119        h = self.height or 100000 
     1120        preferredWidth, preferredHeight = w, h 
     1121        r = fm.boundingRect(QRectF(self.x, self.y, w, h), flags, self.text) 
     1122        x, y = r.x(), r.y() 
    9051123        w, h = r.width(), r.height() 
    906         preferredWidth, preferredHeight = r.width(), r.height() 
    9071124        if self.width is not None: 
    9081125            if self._align == RIGHT: 
     
    9111128                x += preferredWidth/2 - w/2 
    9121129 
    913         painter.save() 
    914         painter.setFont(self._qfont) 
    915         self._fillcolor._set(painter) 
    916         #painter.setBrush(QBrush(self._fillcolor)) 
     1130        textLayout = QTextLayout() 
     1131        textLayout.setFont(self._qfont) 
     1132        textLayout.setText(self.text) 
     1133        opt = QTextOption() 
     1134        opt.setAlignment(self.__alignMap[self._align]) 
     1135        opt.setWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere) 
     1136        textLayout.setTextOption(opt) 
     1137        textLayout.beginLayout() 
     1138        lineSpacing = (fm.ascent() + fm.descent() + fm.leading()) * self._lineheight 
     1139 
     1140        advance = 0 
     1141        while True: 
     1142            line = textLayout.createLine() 
     1143            if not line.isValid(): 
     1144                break 
     1145            line.setLineWidth(w) 
     1146            line.setPosition(QPointF(0, advance)) 
     1147            advance += lineSpacing 
     1148        textLayout.endLayout() 
     1149 
     1150        item = GraphicsTextItem(textLayout, x, y, w, h) 
     1151        item.setPen(QPen(QBrush(self._fillcolor._rgb), 0)) 
     1152 
    9171153        # Center-mode transforms: translate to image center 
    9181154        if self._transformmode == CENTER: 
     1155            lc = textLayout.lineCount()*lineSpacing 
    9191156            deltaX = w / 2 
    9201157            deltaY = h / 2 
    9211158            t = Transform() 
    922             #t.translate(x+deltaX, y+fm.ascent()+deltaY) 
    923             t.concat(painter) 
    924             self._transform.concat(painter) 
    925             painter.drawText(x, y, w, h, flags, self.text) 
    926             #layoutManager.drawGlyphsForGlyphRange_atPoint_(glyphRange, (-deltaX-dx,-deltaY-dy)) 
    927         else: 
    928             self._transform.concat() 
    929             painter.drawText(x, y, w, h, flags, self.text) 
    930             #layoutManager.drawGlyphsForGlyphRange_atPoint_(glyphRange, (x-dx,y-dy-self.font.defaultLineHeightForFont())) 
    931         painter.restore() 
     1159            t.translate(deltaX,lc/2) 
     1160            t.concat(item) 
     1161            self._transform.concat(item) 
     1162            t = Transform() 
     1163            t.translate(-deltaX,-lc/2) 
     1164            t.concat(item) 
     1165        else: 
     1166            self._transform.concat(item) 
     1167        self._scene.addItem(item) 
    9321168        return (w, h) 
    9331169 
    9341170    def _get_metrics(self): 
    9351171        # TODO: Measure using boundingRect 
    936         flags = self.__alignMap[self._align] 
    937         fm = QFontMetrics(self._qfont) 
    938          
    939         if self.width is None: 
    940             w = 100000 
    941         else: 
    942             w = self.width 
    943         if self.height is None: 
    944             h = 100000 
    945         else: 
    946             h = self.height 
    947  
    948         r = fm.boundingRect(self.x, self.y, w, h, flags, self.text) 
     1172        flags = self.__alignMap[self._align] | Qt.TextWordWrap 
     1173        fm = QFontMetricsF(self._qfont) 
     1174        w = self.width or 100000 
     1175        h = self.height or 100000 
     1176        r = fm.boundingRect(QRectF(self.x, self.y, w, h), flags, self.text) 
    9491177        return r.width(), r.height() 
    9501178    metrics = property(_get_metrics) 
     
    10241252        self.speed = None 
    10251253        self.mousedown = False 
     1254        self._scene = None 
    10261255        self.clear() 
    10271256 
     
    10591288            raise NodeBoxError, "pop: too many canvas pops!" 
    10601289 
    1061     def draw(self, painter): 
     1290    def _setTextScaleFactor(self, factor): 
     1291        global textScaleFactor 
     1292        textScaleFactor = factor 
     1293         
     1294    def draw(self): 
     1295        self._scene.setSceneRect(0,0,self.width,self.height) 
    10621296        if self.background is not None: 
    1063             painter.fillRect(0,0, self.width, self.height, self.background._rgb) 
     1297            self._scene.addRect(QRectF(0,0,self.width,self.height), QPen(Qt.NoPen), QBrush(self.background._rgb)) 
     1298            self._scene.num += 1 
    10641299        for grob in self._grobs: 
    1065             grob._draw(painter) 
     1300            grob._scene = self._scene 
     1301            grob._draw() 
    10661302 
    10671303    def save(self, fname, format=None): 
     
    10731309            svgGen.setFileName(fname) 
    10741310            svgGen.setSize(QSize(self.width, self.height)) 
    1075             painter = QPainter(svgGen) 
    1076             self.draw(painter) 
     1311            painter = QPainter() 
     1312            painter.begin(svgGen) 
     1313            if self._scene: 
     1314                self._scene.render(painter) 
    10771315            painter.end() 
    10781316        elif format == "pdf": 
     
    10811319            printer.setOutputFileName(fname) 
    10821320            printer.setFullPage(True) 
    1083             #printer.setPageSize(QPrinter.Custom) 
    1084             painter = QPainter(printer) 
    1085             self.draw(painter) 
     1321            printer.setPaperSize(QSizeF(self.width, self.height), QPrinter.Point) 
     1322            painter = QPainter() 
     1323            painter.begin(printer) 
     1324            if self._scene: 
     1325                self._scene.render(painter) 
    10861326            painter.end() 
    10871327        elif format in ("png", "tiff", "jpg", "jpeg"): 
    10881328            img = QImage(self.width, self.height, QImage.Format_ARGB32) 
    1089             painter = QPainter(img) 
    1090             painter.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing) 
    1091             self.draw(painter) 
     1329            painter = QPainter() 
     1330            painter.begin(img) 
     1331            painter.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform) 
     1332            if format in ("jpg", "jpeg"): 
     1333                painter.fillRect(0, 0, self.width, self.height, Qt.white) 
     1334            if self._scene: 
     1335                self._scene.render(painter) 
    10921336            painter.end() 
    10931337            img.save(fname, None, 100) 
  • nodebox/branches/try-qt-graphics-view/nodebox/gui/qt/__init__.py

    r286 r486  
    77import random 
    88 
    9 from PyQt4.QtGui import QApplication, QWidget, QColor, QPainter, QMenuBar, QMenu, QKeySequence, QTextEdit, QSplitter, QGridLayout, QDialog, QScrollArea, QFont, QTextCharFormat, QFileDialog, QTextCursor, QPixmap, QPrinter, QImage, QMainWindow 
    10 from PyQt4.QtCore import Qt, SIGNAL, SLOT, pyqtSignature, QTimer, QRectF, QSize, QCoreApplication, QSettings, QVariant 
     9from PyQt4.QtGui import * 
     10from PyQt4.QtCore import * 
    1111from PyQt4.QtSvg import QSvgGenerator 
    12  
    13 from nodebox.gui.qt.editor import PythonHighlighter, CodeEdit, loadConfig 
    14  
    15 MAGICVAR = "__magic_var__" 
     12from PyQt4.QtOpenGL import QGLWidget, QGLFormat, QGL 
     13 
     14from nodebox.gui.qt.ValueLadder import MAGICVAR 
     15from nodebox.gui.qt.pytextview import PythonHighlighter, PyTextView, loadConfig, Config 
     16from nodebox.gui.qt.dashboard import DashboardController 
    1617 
    1718from nodebox import graphics 
     
    3233        self.data.append((self.isErr, data)) 
    3334 
    34 class NodeBoxGraphicsView(QWidget): 
    35  
     35class GraphicsScene(QGraphicsScene): 
    3636    def __init__(self, parent=None): 
    37         QWidget.__init__(self, parent) 
    38         self.setMinimumSize(300, 300) 
     37        QGraphicsScene.__init__(self, parent) 
     38        self.num = 0 
     39 
     40    def addItem(self, item): 
     41        item.setZValue(self.num) 
     42        QGraphicsScene.addItem(self, item) 
     43        self.num += 1 
     44 
     45    def removeItem(self, item): 
     46        QGraphicsScene.removeItem(self, item) 
     47        self.num -= 1 
     48 
     49    def clear(self): 
     50        try: 
     51            QGraphicsScene.clear(self) 
     52        except AttributeError: 
     53            for item in self.items(): 
     54                QGraphicsScene.removeItem(self, item)  
     55        self.num = 0 
     56         
     57    def drawItems(self, painter, items, options, widget): 
     58        painter.setClipRect(self.sceneRect()) 
     59        QGraphicsScene.drawItems(self, painter, items, options, widget) 
     60         
     61class NodeBoxGraphicsView(QGraphicsView): 
     62    zoomLevels = [0.1, 0.25, 0.5, 0.75] 
     63    zoom = 1.0 
     64    while zoom <= 20.0: 
     65        zoomLevels.append(zoom) 
     66        zoom += 1.0 
     67 
     68    def __init__(self, parent=None): 
     69        QGraphicsView.__init__(self) 
     70        self.setAlignment(Qt.AlignLeft | Qt.AlignTop) 
     71        self._scene = GraphicsScene(self) 
     72        self.setScene(self._scene) 
     73        self._scene.setSceneRect(0,0,1000,1000) 
     74        self._scene.setItemIndexMethod(QGraphicsScene.NoIndex) 
    3975        self._canvas = None 
    4076        self._image = None 
    4177        self._dirty = True 
    42          
     78        self._zoom = 1.0 
     79        self._dx = 0 
     80        self._dy = 0 
     81        self._mouseDown = False 
     82         
     83    def scrollContentsBy(self, dx, dy): 
     84        self._dx -= dx 
     85        self._dy -= dy 
     86        QGraphicsView.scrollContentsBy(self, dx, dy) 
     87 
     88    def mousePressEvent(self, event): 
     89        if event.button() == Qt.LeftButton: 
     90            self._mouseDown = True 
     91 
     92    def mouseReleaseEvent(self, event): 
     93        if event.button() == Qt.LeftButton: 
     94            self._mouseDown = False 
     95 
     96    def mousePosition(self): 
     97        pos = self.mapFromGlobal(QCursor.pos()) 
     98        return QPoint(pos.x() + self._dx, pos.y() + self._dy) 
     99     
    43100    def _get_canvas(self): 
    44101        return self._canvas 
    45102    def _set_canvas(self, canvas): 
    46103        self._canvas = canvas 
    47         size = self.width(), self.height() 
    48         if size != self.canvas.size: 
    49             self.resize(*self.canvas.size) 
    50104        self.markDirty() 
    51105    canvas = property(_get_canvas, _set_canvas) 
    52          
     106 
     107    def _get_zoom(self): 
     108        return self._zoom 
     109    def _set_zoom(self, zoom): 
     110        self._zoom = zoom 
     111        self.document.zoomLevel.setText("%i%%" % (self._zoom * 100.0)) 
     112        self.document.zoomSlider.setValue(self._zoom * 100.0) 
     113        transform = QTransform() 
     114        transform.scale(self.zoom, self.zoom) 
     115        self.setTransform( transform) 
     116    zoom = property(_get_zoom, _set_zoom) 
     117 
     118    def findNearestZoomIndex(self, zoom): 
     119        """Returns the nearest zoom level, and whether we found a direct, exact 
     120        match or a fuzzy match.""" 
     121        try: # Search for a direct hit first. 
     122            idx = self.zoomLevels.index(zoom) 
     123            return idx, True 
     124        except ValueError: # Can't find the zoom level, try looking at the indexes. 
     125            idx = 0 
     126            try: 
     127                while self.zoomLevels[idx] < zoom: 
     128                    idx += 1 
     129            except KeyError: # End of the list 
     130                idx = len(self.zoomLevels) - 1 # Just return the last index. 
     131            return idx, False 
     132 
     133    def zoomIn_(self): 
     134        idx, direct = self.findNearestZoomIndex(self.zoom) 
     135        # Direct hits are perfect, but indirect hits require a bit of help. 
     136        # Because of the way indirect hits are calculated, they are already 
     137        # rounded up to the upper zoom level; this means we don't need to add 1. 
     138        if direct: 
     139            idx += 1 
     140        idx = max(min(idx, len(self.zoomLevels)-1), 0) 
     141        self.zoom = self.zoomLevels[idx] 
     142 
     143    def zoomOut_(self): 
     144        idx, direct = self.findNearestZoomIndex(self.zoom) 
     145        idx -= 1 
     146        idx = max(min(idx, len(self.zoomLevels)-1), 0) 
     147        self.zoom = self.zoomLevels[idx] 
     148 
     149    def zoomTo_(self, value): 
     150        self.zoom = value 
     151 
     152    def zoomToFit_(self): 
     153        w, h = self.canvas.size 
     154        viewport = self.viewport() 
     155        fw = viewport.width() 
     156        fh = viewport.height() 
     157        factor = min(fw / float(w), fh / float(h)) 
     158        self.zoom = factor         
     159 
     160    def dragZoom_(self, value): 
     161        self.zoom = value / 100.0 
     162 
    53163    def markDirty(self, redraw=True): 
    54164        self._dirty = True 
    55165        if redraw: 
    56             self.repaint() 
    57              
     166            self._updateImage() 
     167 
    58168    def _updateImage(self): 
    59         self._image = None 
    60169        if self.canvas is None: return 
    61         img = QPixmap(self.canvas.width, self.canvas.height) 
    62         p = QPainter(img) 
    63         p.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing) 
    64         p.setClipRect(QRectF(0, 0, self.canvas.width, self.canvas.height)) 
    65170        try: 
    66             self.canvas.draw(p) 
     171            self._scene.clear() 
     172            if self._scene.width() != self.canvas.width or self._scene.height() != self.canvas.height: 
     173                self._scene.setSceneRect(QRectF(0, 0, self.canvas.width, self.canvas.height)) 
     174            self.canvas._scene = self._scene 
     175            self.canvas.draw() 
     176            self._scene.update() 
    67177        except: 
    68178            # A lot of code just to display the error in the output view. 
     
    75185            outputView.setTextColor(QColor(255, 0, 0)) # TODO: Refactor color 
    76186            outputView.insertPlainText(data) 
    77         finally: 
    78             p.end() # TODO: This doesn't fix the QWidget warning if error happens during drawing. 
    79         self._image = img 
    80          
    81     def paintEvent(self, event): 
    82         p = QPainter(self) 
    83         p.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing) 
    84         if self.canvas is None: 
    85             p.fillRect(0, 0, self.width(), self.height(), QColor(Qt.white)) 
    86         else: 
    87             if self._dirty: 
    88                 self._updateImage() 
    89             p.drawPixmap(0, 0, self._image) 
    90187         
    91188class NodeBoxDocument(QMainWindow): 
     
    98195        self.namespace = {} 
    99196        self.canvas = graphics.Canvas() 
     197        textScaleFactor = QPixmap().logicalDpiX() / 72.0 
     198        self.canvas._setTextScaleFactor(textScaleFactor) 
    100199        self.context = graphics.Context(self.canvas, self.namespace) 
    101200        self.animationTimer = None 
     
    110209 
    111210    def createMenu(self): 
    112         #self.menuBar = QMenuBar() 
    113  
    114211        self.fileMenu = QMenu(self.tr("&File"), self) 
    115212        self.fileMenu.addAction(self.tr("&New"), self, SLOT("doNew()"), QKeySequence("Ctrl+N")) 
     
    134231        self.editMenu.addAction(self.tr("&Redo"), self.codeView, SLOT("redo()"), QKeySequence("Ctrl+Shift+Z")) 
    135232        self.editMenu.addSeparator() 
     233        self.editMenu.addAction(self.tr("&Switch Viewport"), self, SLOT("switchViewport()"), QKeySequence("Ctrl+Shift+P")) 
    136234        self.menuBar().addMenu(self.editMenu) 
     235 
     236        self.viewMenu = QMenu(self.tr("&View"), self) 
     237        self.viewMenu.addAction(self.tr("Zoom &In"), self, SLOT("zoomIn_()"), QKeySequence("Ctrl++")) 
     238        self.viewMenu.addAction(self.tr("Zoom &Out"), self, SLOT("zoomOut_()"), QKeySequence("Ctrl+-")) 
     239        self.zoomToMenu = QMenu(self.tr("Zoom to"), self) 
     240        self.zoomToMenu.addAction(self.tr("To &Fit"), self, SLOT("zoomToFit_()"), QKeySequence("Ctrl+0")) 
     241        self.zoomToMenu.addAction(self.tr("Actual &Size"), self, SLOT("zoomTo100_()"), QKeySequence("Ctrl+1")) 
     242        self.zoomToMenu.addAction(self.tr("200%"), self, SLOT("zoomTo200_()"), QKeySequence("Ctrl+2")) 
     243        self.zoomToMenu.addAction(self.tr("300%"), self, SLOT("zoomTo300_()"), QKeySequence("Ctrl+3")) 
     244        self.zoomToMenu.addAction(self.tr("400%"), self, SLOT("zoomTo400_()"), QKeySequence("Ctrl+4")) 
     245        self.zoomToMenu.addAction(self.tr("50%"), self, SLOT("zoomTo50_()"), QKeySequence("Ctrl+5")) 
     246        self.viewMenu.addMenu(self.zoomToMenu) 
     247        self.menuBar().addMenu(self.viewMenu) 
    137248 
    138249        self.pythonMenu = QMenu(self.tr("&Python"), self) 
    139250        self.runAction = self.pythonMenu.addAction(self.tr("&Run"), self, SLOT("doRun()"), QKeySequence("Ctrl+R")) 
    140         self.runAction = self.pythonMenu.addAction(self.tr("&Stop"), self, SLOT("doStop()"), QKeySequence("Ctrl+.")) 
     251        self.runAction = self.pythonMenu.addAction(self.tr("&Stop"), self, SLOT("doStop()"), QKeySequence("Ctrl+B")) 
    141252        self.menuBar().addMenu(self.pythonMenu) 
    142253 
     
    164275        codeFormat.setFont(codeFont) 
    165276         
    166         self.graphicsView = NodeBoxGraphicsView() 
     277        global app 
     278        self.graphicsView = NodeBoxGraphicsView(self) 
    167279        self.graphicsView.document = self 
    168         self.graphicsView.resize(1000, 1000) 
    169         self.graphicsScroll = QScrollArea() 
    170         self.graphicsScroll.setWidget(self.graphicsView) 
    171         self.graphicsScroll.setMinimumSize(300, 300) 
    172         self.codeView = CodeEdit() 
     280        self.graphicsView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 
     281        self.graphicsView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 
     282        if app._startgl: 
     283            self.graphicsView.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers | QGL.AlphaChannel))) 
     284            self.graphicsView.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) 
     285            self._oldViewport = "qwidget" 
     286        else: 
     287            self.graphicsView.setViewport(QWidget()) 
     288            self.graphicsView.setViewportUpdateMode(QGraphicsView.SmartViewportUpdate) 
     289            self._oldViewport = "qglwidget"             
     290        self.graphicsView.setOptimizationFlags(QGraphicsView.DontClipPainter | QGraphicsView.DontSavePainterState) 
     291         
     292        self.codeView = PyTextView() 
     293        self.codeView._document = self 
    173294        self.codeView.setFontFamily(codeFont.defaultFamily()) 
    174295        self.codeView.setCurrentFont(codeFont) 
     
    176297        self.codeView.setMinimumSize(300, 300) 
    177298        self.codeView.setAcceptRichText(False) 
     299        self.codeView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 
     300        self.codeView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 
    178301        PythonHighlighter(self.codeView.document()) 
    179302        self.outputView = QTextEdit() 
     
    184307        self.outputView.setAcceptRichText(False) 
    185308        self.outputView.setReadOnly(True) 
    186          
     309        self.outputView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 
     310 
    187311        self.code_errors = QSplitter(Qt.Vertical) 
    188312        self.code_errors.addWidget(self.codeView) 
    189313        self.code_errors.addWidget(self.outputView) 
    190314 
     315        self.zoomLevel = QLabel("100%") 
     316        ft = self.zoomLevel.font() 
     317        ft.setPointSizeF(10) 
     318        self.zoomLevel.setFont(ft) 
     319        self.zoomSlider = QSlider(Qt.Horizontal) 
     320        self.zoomSlider.setMinimumWidth(130) 
     321        self.zoomSlider.setMinimum(1) 
     322        self.zoomSlider.setMaximum(1000) 
     323        self.zoomSlider.setValue(100) 
     324        self.zoom_small = QPushButton() 
     325        self.zoom_small.setIcon(QIcon(os.path.join(app.dir_gui, "zoomsmall.png"))) 
     326        self.zoom_small.setStyleSheet("border: 0;") 
     327        self.zoom_big = QPushButton() 
     328        self.zoom_big.setIcon(QIcon(os.path.join(app.dir_gui, "zoombig.png"))) 
     329        self.zoom_big.setStyleSheet("border: 0;") 
     330        self.connect(self.zoom_small, SIGNAL("clicked()"), self, SLOT("zoomOut_()")) 
     331        self.connect(self.zoom_big, SIGNAL("clicked()"), self, SLOT("zoomIn_()")) 
     332        self.connect(self.zoomSlider, SIGNAL("valueChanged(int)"), self, SLOT("dragZoom_(int)")) 
     333 
     334        lh = QHBoxLayout() 
     335        lh.addWidget(self.zoomLevel) 
     336        lh.addWidget(self.zoom_small) 
     337        lh.addWidget(self.zoomSlider) 
     338        lh.addWidget(self.zoom_big) 
     339        lh.setContentsMargins(0,0,0,0) 
     340        lh.setSpacing(8) 
     341 
     342        self.zoom = QWidget(self) 
     343        self.zoom.setLayout(lh) 
     344 
     345        lv = QVBoxLayout() 
     346        lv.addWidget(self.graphicsView) 
     347        lv.addWidget(self.zoom, 0, Qt.AlignRight) 
     348        lv.setContentsMargins(0, 0, 0, 0) 
     349 
     350        self.graphics_zoom = QWidget(self) 
     351        self.graphics_zoom.setLayout(lv) 
     352 
    191353        self.view_edit = QSplitter() 
    192         self.view_edit.addWidget(self.graphicsScroll) 
     354        self.view_edit.addWidget(self.graphics_zoom) 
    193355        self.view_edit.addWidget(self.code_errors) 
    194356        self.view_edit.setObjectName("view_edit") 
    195         self.view_edit.setStyleSheet("QSplitter#view_edit { margin: 10px 10px 20px 10px; border: 0 }") 
     357        self.view_edit.setStyleSheet("#view_edit { margin: 10px 10px 20px 10px; border: 0 }") 
    196358        l = QGridLayout() 
    197359        l.setHorizontalSpacing(10) 
    198360        l.setVerticalSpacing(10) 
    199361        self.view_edit.setLayout(l) 
    200          
    201         #self.centralWidget = QWidget() 
    202         #self.centralWidget.addWidget(self.view_edit) 
    203         #self.centralWidget.setLayout(QGridLayout()) 
    204          
    205362        self.setCentralWidget(self.view_edit) 
    206363        self.setUnifiedTitleAndToolBarOnMac(True) 
     
    208365 
    209366        self.setWindowTitle(self.tr("Untitled")) 
     367        self.dashboardController = DashboardController(self) 
    210368        self.resize(800, 600) 
    211          
     369 
     370    @pyqtSignature("") 
     371    def switchViewport(self): 
     372        self.outputView.setTextColor(QColor(0,0,0)) 
     373        if self._oldViewport == "qwidget": 
     374            self.outputView.insertPlainText("switch to normal view\n") 
     375            self.graphicsView.setViewport(QWidget()) 
     376            self.graphicsView.setViewportUpdateMode(QGraphicsView.SmartViewportUpdate) 
     377            self._oldViewport = "qglwidget" 
     378        else: 
     379            self.outputView.insertPlainText("switch to OpenGL view\n") 
     380            self.graphicsView.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers | QGL.AlphaChannel))) 
     381            self.graphicsView.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) 
     382            self._oldViewport = "qwidget" 
     383 
     384    @pyqtSignature("") 
     385    def zoomIn_(self): 
     386        if self.fullScreen is not None: return         
     387        self.graphicsView.zoomIn_()         
     388         
     389    @pyqtSignature("") 
     390    def zoomOut_(self): 
     391        if self.fullScreen is not None: return 
     392        self.graphicsView.zoomOut_()         
     393 
     394    @pyqtSignature("int") 
     395    def dragZoom_(self, value): 
     396        if self.fullScreen is not None: return 
     397        self.graphicsView.dragZoom_(value)         
     398 
     399    @pyqtSignature("") 
     400    def zoomTo100_(self): 
     401        if self.fullScreen is not None: return 
     402        self.graphicsView.zoomTo_(1.0) 
     403 
     404    @pyqtSignature("") 
     405    def zoomTo200_(self): 
     406        if self.fullScreen is not None: return 
     407        self.graphicsView.zoomTo_(2.0) 
     408 
     409    @pyqtSignature("") 
     410    def zoomTo300_(self): 
     411        if self.fullScreen is not None: return 
     412        self.graphicsView.zoomTo_(3.0) 
     413 
     414    @pyqtSignature("") 
     415    def zoomTo400_(self): 
     416        if self.fullScreen is not None: return 
     417        self.graphicsView.zoomTo_(4.0) 
     418 
     419    @pyqtSignature("") 
     420    def zoomTo50_(self): 
     421        if self.fullScreen is not None: return 
     422        self.graphicsView.zoomTo_(0.5) 
     423 
     424    @pyqtSignature("") 
     425    def zoomToFit_(self): 
     426        if self.fullScreen is not None: return 
     427        self.graphicsView.zoomToFit_() 
     428 
    212429    @pyqtSignature("") 
    213430    def doNew(self): 
     
    279496 
    280497    @pyqtSignature("") 
     498    def doExportAsMovie(self): 
     499        pass 
     500 
     501    @pyqtSignature("") 
     502    def doPrint(self): 
     503        pass 
     504 
     505    @pyqtSignature("") 
    281506    def doRun(self): 
    282507        if self.fullScreen is not None: return 
     
    317542    fileName = property(_get_fileName, _set_fileName) 
    318543 
     544    def buildInterface_(self): 
     545        if not self.dashboardController: 
     546            self.dashboardController = DashboardController(self) 
     547        self.dashboardController.buildInterface_(self.vars) 
     548 
    319549    def _runScript(self, compile=True, newSeed=True): 
    320         if not self.cleanRun(self._execScript): 
     550        if not self.cleanRun(self._execScript, newSeed): 
    321551            pass 
    322552 
     
    337567            if self.namespace.has_key("setup"): 
    338568                self.fastRun(self.namespace["setup"]) 
    339             window = self.currentView.window() 
     569#            window = self.currentView.window() 
    340570            #window.makeFirstResponder_(self.currentView) 
    341571 
     
    344574            self.connect(self.animationTimer, SIGNAL("timeout()"), self, SLOT("doFrame()")) 
    345575            self.animationTimer.start(1000.0 / self.speed) 
    346  
     576             
    347577    def runScriptFast_(self):         
    348578        if self.animationTimer is None: 
     
    365595            # Build the interface 
    366596            self.vars = self.namespace["_ctx"]._vars 
    367             if len(self.vars) > 0: 
     597            if newSeed and len(self.vars) > 0: 
    368598                self.buildInterface_() 
    369599 
     
    406636 
    407637        # Set the mouse position 
    408         # TODO: Get correct mouse position 
    409         #window = self.currentView.window() 
    410         pt = 0, 0 # window.mouseLocationOutsideOfEventStream() 
    411         mx, my = 0, 0 # window.contentView().convertPoint_toView_(pt, self.currentView) 
    412         # Hack: mouse coordinates are flipped vertically in FullscreenView. 
    413         # This flips them back. 
    414         if isinstance(self.currentView, FullscreenView): 
    415             my = self.currentView.bounds()[1][1] - my 
     638        pos = self.currentView.mousePosition() 
     639        mx, my = pos.x(), pos.y() 
     640 
     641#        if isinstance(self.currentView, FullscreenView): 
     642#            my = self.currentView.bounds()[1][1] - my 
     643 
     644        if self.fullScreen is None: 
     645            mx /= self.currentView.zoom 
     646            my /= self.currentView.zoom             
    416647        self.namespace["MOUSEX"], self.namespace["MOUSEY"] = mx, my 
    417         #self.namespace["mousedown"] = QApplication.mouseButtons() & Qt.LeftButton 
     648        self.namespace["mousedown"] = self.currentView._mouseDown 
    418649        #self.namespace["keydown"] = self.currentView.keydown 
    419650        #self.namespace["key"] = self.currentView.key 
     
    563794class FullscreenView(QDialog): 
    564795    pass 
     796 
     797def overrideConfig(app): 
     798    settings = app.settings 
     799    Config["fontfamily"] = settings.value("fontfamily", 
     800            QVariant("Monaco")).toString() 
     801    Config["fontsize"] = settings.value("fontsize", 
     802            QVariant(11)).toInt()[0] 
     803    for name, color, bold, italic in ( 
     804            ("normal", "#000000", False, False), 
     805            ("keyword", "#0000FF", False, False), 
     806            ("builtin", "#000000", False, False), 
     807            ("constant", "#0000FF", False, False), 
     808            ("decorator", "#000000", False, False), 
     809            ("comment", "#808080", False, False), 
     810            ("string", "#FF00FF", False, False), 
     811            ("number", "#000000", False, False), 
     812            ("error", "#FF0000", False, False), 
     813            ("pyqt", "#000000", False, False)): 
     814        Config["%sfontcolor" % name] = settings.value( 
     815                "%sfontcolor" % name, QVariant(color)).toString() 
     816        Config["%sfontbold" % name] = settings.value( 
     817                "%sfontbold" % name, QVariant(bold)).toBool() 
     818        Config["%sfontitalic" % name] = settings.value( 
     819                "%sfontitalic" % name, QVariant(italic)).toBool() 
    565820     
    566821class NodeBoxApplication(QApplication): 
     
    572827        self.settings = QSettings() 
    573828        loadConfig() 
     829        overrideConfig(self) 
    574830        self.documents = [] 
    575831        if sys.platform == "win32": 
     
    591847        except OSError: pass 
    592848        except IOError: pass 
    593              
     849        import nodebox.gui 
     850        dir_gui = nodebox.gui.__file__ 
     851        self.dir_gui = os.path.split(os.path.realpath(dir_gui))[0] 
     852        self._startgl = '-gl' in args 
     853 
    594854    def newDocument(self): 
    595855        doc = NodeBoxDocument()