Changeset 484
- Timestamp:
- 09/04/08 15:29:15 (19 months ago)
- Location:
- nodebox/branches/try-qt-painter/nodebox
- Files:
-
- 2 modified
-
graphics/qt.py (modified) (36 diffs)
-
gui/qt/__init__.py (modified) (21 diffs)
Legend:
- Unmodified
- Added
- Removed
-
nodebox/branches/try-qt-painter/nodebox/graphics/qt.py
r286 r484 3 3 from random import choice, shuffle 4 4 5 from PyQt4.QtGui import QPainterPath, QColor, QTransform, QBrush, QPen, QImage, QPrinter, QPainter, QFontMetrics, QFont 5 from PyQt4.QtGui import QPainterPath, QColor, QTransform, QBrush, QPen, QImage, QPrinter, QPainter, QFontMetrics, QFontMetricsF, QFont, QTextLayout, QTextOption 6 6 from PyQt4.QtCore import Qt, QSize, QPointF, QRectF 7 7 from PyQt4.QtSvg import QSvgGenerator … … 36 36 CORNER = "corner" 37 37 38 MOVETO = 039 LINETO = 140 CURVETO = 241 CLOSE = 338 MOVETO = QPainterPath.MoveToElement 39 LINETO = QPainterPath.LineToElement 40 CURVETO = QPainterPath.CurveToElement 41 CLOSE = "close" 42 42 43 43 LEFT = 0 … … 212 212 ColorMixin.__init__(self, **kwargs) 213 213 self._segment_cache = None 214 self._qpath_segment_cache = None 214 215 if path is None: 215 216 self._qpath = QPainterPath() … … 237 238 def moveto(self, x, y): 238 239 self._segment_cache = None 240 self._qpath_segment_cache = None 239 241 self._qpath.moveTo(x, y) 240 242 241 243 def lineto(self, x, y): 242 244 self._segment_cache = None 245 self._qpath_segment_cache = None 243 246 self._qpath.lineTo(x, y) 244 247 245 248 def curveto(self, x1, y1, x2, y2, x3, y3): 246 249 self._segment_cache = None 250 self._qpath_segment_cache = None 247 251 self._qpath.cubicTo(x1, y1, x2, y2, x3, y3) 248 252 249 253 def closepath(self): 250 254 self._segment_cache = None 255 self._qpath_segment_cache = None 251 256 self._qpath.closeSubpath() # XXX: Is this correct? 252 257 … … 272 277 def rect(self, x, y, width, height): 273 278 self._segment_cache = None 279 self._qpath_segment_cache = None 274 280 self._qpath.addRect(x, y, width, height) 275 281 276 282 def oval(self, x, y, width, height): 277 283 self._segment_cache = None 284 self._qpath_segment_cache = None 278 285 self._qpath.addEllipse(x, y, width, height) 279 286 280 287 def line(self, x1, y1, x2, y2): 281 288 self._segment_cache = None 289 self._qpath_segment_cache = None 282 290 self._qpath.moveTo(x1, y1) 283 291 self._qpath.lineTo(x2, y2) … … 286 294 287 295 def __getitem__(self, index): 288 el = self._qpath.elementAt(index) 289 return PathElement(el) 296 if self._qpath_segment_cache == None: 297 self._set_qpath_segment_cache() 298 cmd, el = self._qpath_segment_cache[index] 299 return PathElement(cmd, el) 290 300 291 301 def __iter__(self): … … 294 304 295 305 def __len__(self): 296 return self._qpath.elementCount() 306 if self._qpath_segment_cache == None: 307 self._set_qpath_segment_cache() 308 return len(self._qpath_segment_cache) 309 310 def _set_qpath_segment_cache(self): 311 self._qpath_segment_cache = [] 312 length = self._qpath.elementCount() 313 temp = [] 314 for i in range(length): 315 el = self._qpath.elementAt(i) 316 if el.type in [LINETO, MOVETO, CURVETO, CLOSE]: 317 temp.append(i) 318 for i in temp: 319 el = self._qpath.elementAt(i) 320 if el.type in [LINETO, MOVETO, CLOSE]: 321 self._qpath_segment_cache.append((el.type, ((el.x, el.y),))) 322 elif el.type == CURVETO: 323 ctrl1 = self._qpath.elementAt(i+1) 324 ctrl2 = self._qpath.elementAt(i+2) 325 self._qpath_segment_cache.append((el.type, ((el.x, el.y), (ctrl1.x, ctrl1.y), (ctrl2.x, ctrl2.y)))) 297 326 298 327 def extend(self, pathElements): 299 328 self._segment_cache = None 329 self._qpath_segment_cache = None 300 330 for el in pathElements: 301 331 if isinstance(el, (list, tuple)): … … 313 343 def append(self, el): 314 344 self._segment_cache = None 345 self._qpath_segment_cache = None 315 346 if el.cmd == MOVETO: 316 347 self.moveto(el.x, el.y) … … 355 386 painter.restore() 356 387 388 def fit(self, x=None, y=None, width=None, height=None, stretch=False): 389 390 """Fits this path to the specified bounds. 391 392 All parameters are optional; if no parameters are specified, nothing will happen. 393 Specifying a parameter will constrain its value: 394 395 - x: The path will be positioned at the specified x value 396 - y: The path will be positioned at the specified y value 397 - width: The path will be of the specified width 398 - height: The path will be of the specified height 399 - stretch: If both width and height are defined, either stretch the path or 400 keep the aspect ratio. 401 """ 402 403 (px, py), (pw, ph) = self.bounds 404 t = Transform() 405 if x is not None and y is None: 406 t.translate(x, py) 407 elif x is None and y is not None: 408 t.translate(px, y) 409 elif x is not None and y is not None: 410 t.translate(x, y) 411 else: 412 t.translate(px, py) 413 if width is not None and height is None: 414 t.scale(width / pw) 415 elif width is None and height is not None: 416 t.scale(height / ph) 417 elif width is not None and height is not None: 418 if stretch: 419 t.scale(width /pw, height / ph) 420 else: 421 t.scale(min(width /pw, height / ph)) 422 t.translate(-px, -py) 423 self._qpath = t.transformBezierPath(self)._qpath 424 357 425 ### Mathematics ### 358 426 … … 394 462 def addpoint(self, t): 395 463 import bezier 396 self._ nsBezierPath = bezier.insert_point(self, t)._nsBezierPath464 self._qPath = bezier.insert_point(self, t)._qPath 397 465 self._segment_cache = None 466 self._qpath_segment_cache = None 467 468 ### Clipping operations ### 469 470 def intersects(self, other): 471 return self._qpath.intersects(other._qpath) 472 473 def union(self, other, flatness=0.6): 474 return BezierPath(self._ctx, self._qpath.united(other._qpath)) 475 476 def intersect(self, other, flatness=0.6): 477 return BezierPath(self._ctx, self._qpath.intersected(other._qpath)) 478 479 def difference(self, other, flatness=0.6): 480 return BezierPath(self._ctx, self._qpath.subtracted(other._qpath)) 481 482 def xor(self, other, flatness=0.6): 483 union = self._qpath.united(other._qpath) 484 intersection = self._qpath.intersected(other._qpath) 485 return BezierPath(self._ctx, union.subtracted(intersection)) 398 486 399 487 class PathElement(object): … … 456 544 self._grobs.append(grob) 457 545 458 def _draw(self): 459 _save() 460 cp = self.path.transform.transformBezierPath(self.path) 461 cp._nsBezierPath.addClip() 546 def _draw(self, painter): 547 painter.save() 548 transform = self.path.transform 549 cp = transform.transformBezierPath(self.path) 550 painter.setClipPath(cp._qpath) 462 551 for grob in self._grobs: 463 grob._draw( )464 _restore()552 grob._draw(painter) 553 painter.restore() 465 554 466 555 class Rect(BezierPath): … … 468 557 def __init__(self, ctx, x, y, width, height, **kwargs): 469 558 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) 559 p=QPainterPath() 560 p.addRect(x, y, width, height) 561 super(Rect, self).__init__(ctx, p, **kwargs) 472 562 473 563 def copy(self): … … 478 568 def __init__(self, ctx, x, y, width, height, **kwargs): 479 569 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) 570 p=QPainterPath() 571 p.addEllipse(x, y, width, height) 572 super(Oval, self).__init__(ctx, p, **kwargs) 482 573 483 574 def copy(self): … … 519 610 args = self._normalizeList(args) 520 611 h, s, b = args 612 if h == 1.0: 613 h = .99998 521 614 clr = QColor.fromHsvF(h, s, b, 1) 522 615 elif params == 4 and self._ctx._colormode == RGB: # RGB and alpha … … 527 620 args = self._normalizeList(args) 528 621 h, s, b, a = args 622 if h == 1.0: 623 h = .99998 529 624 clr = QColor.fromHsvF(h, s, b, a) 530 625 elif params == 4 and self._ctx._colormode == CMYK: # CMYK, no alpha … … 557 652 qColor = property(_get_qColor) 558 653 559 560 654 def copy(self): 561 655 new = self.__class__(self._ctx) 562 656 new._rgb = QColor(self._rgb) 563 new._updateCmyk()657 # new._updateCmyk() 564 658 return new 565 659 … … 573 667 574 668 def _get_hue(self): 575 return self._rgb.hueF() 669 hue = self._rgb.hueF() 670 if round(hue, 4) == 1.0: 671 return 1.0 672 if hue < 0.0: 673 return 0.0 674 return hue 576 675 def _set_hue(self, val): 577 676 val = self._normalize(val) 578 c = self._rgb 579 h, s, b, a = c.hueF(), c.saturationF(), c.valueF(), c.alpha() 677 if val == 1.0: 678 val = .99998 679 h, s, b, a = self.hsba 580 680 self._rgb.setHsvF(val, s, b, a) 581 681 self._updateCmyk() 582 682 h = hue = property(_get_hue, _set_hue, doc="the hue of the color") 683 684 def _get_saturation(self): 685 return self._rgb.saturationF() 686 def _set_saturation(self, val): 687 val = self._normalize(val) 688 h, s, b, a = self.hsba 689 self._rgb.setHsvF(h, val, b, a) 690 self._updateCmyk() 691 s = saturation = property(_get_saturation, _set_saturation, doc="the saturation of the color") 692 693 def _get_brightness(self): 694 return self._rgb.valueF() 695 def _set_brightness(self, val): 696 val = self._normalize(val) 697 h, s, b, a = self.hsba 698 self._rgb.setHsvF(h, s, val, a) 699 self._updateCmyk() 700 v = brightness = property(_get_brightness, _set_brightness, doc="the saturation of the color") 701 702 def _get_hsba(self): 703 c = self._rgb 704 return c.hueF(), c.saturationF(), c.valueF(), c.alphaF() 705 def _set_hsba(self, values): 706 values = self._normalizeList(values) 707 h, s, b, a = values 708 self._rgb.setHsvF(h, s, b, a) 709 hsba = property(_get_hsba, _set_hsba, doc="the hue, saturation, brightness and alpha of the color") 710 711 def _get_red(self): 712 return self._rgb.redF() 713 def _set_red(self, val): 714 val = self._normalize(val) 715 r, g, b, a = self.rgba 716 self._rgb.setRgbF(val, g, b, a) 717 self._updateCmyk() 718 r = red = property(_get_red, _set_red, doc="the red component of the color") 719 720 def _get_green(self): 721 return self._rgb.greenF() 722 def _set_green(self, val): 723 val = self._normalize(val) 724 r, g, b, a = self.rgba 725 self._rgb.setRgbF(r, val, b, a) 726 self._updateCmyk() 727 g = green = property(_get_green, _set_green, doc="the green component of the color") 728 729 def _get_blue(self): 730 return self._rgb.blueF() 731 def _set_blue(self, val): 732 val = self._normalize(val) 733 r, g, b, a = self.rgba 734 self._rgb.setRgbF(r, g, val, a) 735 self._updateCmyk() 736 b = blue = property(_get_blue, _set_blue, doc="the blue component of the color") 737 738 def _get_alpha(self): 739 return self._rgb.alphaF() 740 def _set_alpha(self, val): 741 val = self._normalize(val) 742 r, g, b, a = self.rgba 743 self._rgb.setRgbF(r, g, b, val) 744 self._updateCmyk() 745 a = alpha = property(_get_alpha, _set_alpha, doc="the alpha component of the color") 746 747 def _get_rgba(self): 748 c = self._rgb 749 return c.redF(), c.greenF(), c.blueF(), c.alphaF() 750 def _set_rgba(self, values): 751 values = self._normalizeList(values) 752 r, g, b, a = values 753 self._rgb.setRgbF(r, g, b, a) 754 rgba = property(_get_rgba, _set_rgba, doc="the red, green, blue and alpha values of the color") 755 756 def _get_cyan(self): 757 return self._rgb.cyanF() 758 def _set_cyan(self, val): 759 val = self._normalize(val) 760 c, m, y, k, a = self.cmyka 761 self._rgb.setCmykF(val, m, y, k, a) 762 self._updateRgb() 763 c = cyan = property(_get_cyan, _set_cyan, doc="the cyan component of the color") 764 765 def _get_magenta(self): 766 return self._rgb.magentaF() 767 def _set_magenta(self, val): 768 val = self._normalize(val) 769 c, m, y, k, a = self.cmyka 770 self._rgb.setCmykF(c, val, y, k, a) 771 self._updateRgb() 772 m = magenta = property(_get_magenta, _set_magenta, doc="the magenta component of the color") 773 774 def _get_yellow(self): 775 return self._rgb.yellowF() 776 def _set_yellow(self, val): 777 val = self._normalize(val) 778 c, m, y, k, a = self.cmyka 779 self._rgb.setCmykF(c, m, val, k, a) 780 self._updateRgb() 781 y = yellow = property(_get_yellow, _set_yellow, doc="the yellow component of the color") 782 783 def _get_black(self): 784 return self._rgb.blackF() 785 def _set_black(self, val): 786 val = self._normalize(val) 787 c, m, y, k, a = self.cmyka 788 self._rgb.setCmykF(c, m, y, val, a) 789 self._updateRgb() 790 k = black = property(_get_black, _set_black, doc="the black component of the color") 791 792 def _get_cmyka(self): 793 c = self._rgb 794 return c.cyanF(), c.magentaF(), c.yellowF(), c.blackF(), c.alphaF() 795 cmyka = property(_get_cmyka, doc="a tuple containing the CMYKA values for this color") 583 796 584 797 def _normalize(self, v): … … 604 817 elif isinstance(transform, (list, tuple)): 605 818 matrix = tuple(transform) 606 transform = QTransform() 607 transform.setMatrix(*matrix) 819 transform = QTransform(*matrix) 608 820 elif isinstance(transform, QTransform): 609 821 pass … … 622 834 def concat(self, painter): 623 835 trans = painter.transform() 624 painter.setTransform( trans * self._qtransform)836 painter.setTransform(self._qtransform * trans) 625 837 626 838 def copy(self): … … 637 849 def _get_matrix(self): 638 850 q = self._qtransform 639 return (q.m11(), q.m12(), q.m 13(), q.m21(), q.m22(), q.m23(), q.m31(), q.m32(), q.m33())851 return (q.m11(), q.m12(), q.m21(), q.m22(), q.m31(), q.m32()) 640 852 def _set_matrix(self, value): 641 self._qtransform .setMatrix(value)853 self._qtransform = QTransform(*value) 642 854 matrix = property(_get_matrix, _set_matrix) 643 855 … … 657 869 658 870 def skew(self, x=0, y=0): 659 self._qtransform.shear(x, y) 871 import math 872 x = math.pi * x / 180. 873 y = math.pi * y / 180. 874 t = Transform() 875 t.matrix = 1, math.tan(y), -math.tan(x), 1, 0, 0 876 self.prepend(t) 660 877 661 878 def invert(self): … … 711 928 TransformMixin.__init__(self) 712 929 if data is not None: 713 self._ nsImage = QImage(data)930 self._qimage = QImage(data) 714 931 if self._qimage is None: 715 932 raise NodeBoxError, "can't read image %r" % path … … 753 970 754 971 def getSize(self): 755 return self._ nsImage.size()972 return self._qimage.width(), self._qimage.height() 756 973 757 974 size = property(getSize) … … 760 977 """Draw an image on the given coordinates.""" 761 978 762 srcW, srcH = self._qimage.width(), self._qimage.height()979 srcW, srcH = float(self._qimage.width()), float(self._qimage.height()) 763 980 srcRect = ((0, 0), (srcW, srcH)) 764 981 … … 777 994 # This is the hardest case: center-mode transformations with given width or height. 778 995 # Order is very important in this code. 779 780 996 # Set the position first, before any of the scaling or transformations are done. 781 997 # Context transformations might change the translation, and we don't want that. … … 783 999 t.translate(self.x, self.y) 784 1000 t.concat(painter) 785 1001 786 1002 # 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 .1003 # to set the new center of the image according to the scaling factors 788 1004 srcW = srcW * factor 789 1005 srcH = srcH * factor 790 791 # Move image to newly calculated center. 1006 1007 # Move image to newly calculated center. 792 1008 dX = srcW / 2 793 1009 dY = srcH / 2 … … 799 1015 self._transform.concat(painter) 800 1016 801 # Move back to the previous position. 1017 # Move back to the previous position. 802 1018 t = Transform() 803 1019 t.translate(-dX, -dY) 804 1020 t.concat(painter) 805 1021 806 # Finally, scale the image according to the factors. 1022 # Finally, scale the image according to the factors. 807 1023 t = Transform() 808 1024 t.scale(factor) 809 t.concat(painter) 1025 t.concat(painter) 810 1026 else: 811 1027 # Do current transformation 812 #self._transform.concat()1028 self._transform.concat(painter) 813 1029 # Scale according to width or height factor 814 1030 t = Transform() … … 819 1035 # A debugImage draws a black rectangle instead of an image. 820 1036 if self.debugImage: 821 painter.setBrush(QBrush(Qt.SolidPattern)) 822 painter.fillRect(QRectF(0, 0, srcW / factor, srcH / factor)) 1037 painter.setBrush(QBrush(Qt.black)) 1038 painter.setPen(QPen(Qt.NoPen)) 1039 painter.drawRect(QRectF(0, 0, srcW / factor, srcH / factor)) 823 1040 else: 824 # TODO: Stuff with composition modes to allow for alpha transparency825 painter.drawImage( QPointF(0, 0), self._qimage, QRectF(0, 0, srcW, srcH))1041 painter.setOpacity(self.alpha) 1042 painter.drawImage(0, 0, self._qimage) 826 1043 painter.restore() 827 1044 # No width or height given … … 842 1059 # A debugImage draws a black rectangle instead of an image. 843 1060 if self.debugImage: 844 painter.setBrush(QBrush(Qt.SolidPattern)) 845 painter.fillRect(QRectF(0, 0, srcW, srcH)) 1061 painter.setBrush(QBrush(Qt.black)) 1062 painter.setPen(QPen(Qt.NoPen)) 1063 painter.drawRect(QRectF(x, y, srcW, srcH)) 846 1064 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)) 1065 t = Transform() 1066 t.translate(x, y) 1067 t.concat(painter) 1068 painter.setOpacity(self.alpha) 1069 painter.drawImage(0, 0, self._qimage) 849 1070 painter.restore() 850 1071 … … 855 1076 856 1077 __dummy_color = QColor() 857 __alignMap = { LEFT: Qt.AlignLeft, RIGHT: Qt.AlignRight, CENTER: Qt.Align Center, JUSTIFY: Qt.AlignJustify }1078 __alignMap = { LEFT: Qt.AlignLeft, RIGHT: Qt.AlignRight, CENTER: Qt.AlignHCenter, JUSTIFY: Qt.AlignJustify } 858 1079 859 1080 def __init__(self, ctx, text, x=0, y=0, width=None, height=None, **kwargs): … … 885 1106 886 1107 def _get_font(self): 887 return QFont(self._fontname, self._fontsize) 1108 f = QFont(self._fontname) 1109 f.setPointSizeF(self._fontsize / textScaleFactor) 1110 return f 888 1111 _qfont = property(_get_font) 889 1112 890 1113 def _draw(self, painter): 891 if self.width is None:892 w = 100000893 else:894 w = self.width895 if self.height is None:896 h = 100000897 else:898 h = self.height899 fm = painter.fontMetrics()900 flags = self.__alignMap[self._align]901 r = fm.boundingRect(self.x, self.y, w, h, flags, self.text)902 903 1114 if self._fillcolor is None: return 904 x,y = r.x(), r.y() 1115 flags = self.__alignMap[self._align] | Qt.TextWordWrap 1116 fm = QFontMetricsF(self._qfont) 1117 w = self.width or 100000 1118 h = self.height or 100000 1119 preferredWidth, preferredHeight = w, h 1120 r = fm.boundingRect(QRectF(self.x, self.y, w, h), flags, self.text) 1121 x, y = r.x(), r.y() 905 1122 w, h = r.width(), r.height() 906 preferredWidth, preferredHeight = r.width(), r.height()907 1123 if self.width is not None: 908 1124 if self._align == RIGHT: … … 911 1127 x += preferredWidth/2 - w/2 912 1128 1129 textLayout = QTextLayout() 1130 textLayout.setFont(self._qfont) 1131 textLayout.setText(self.text) 1132 opt = QTextOption() 1133 opt.setAlignment(self.__alignMap[self._align]) 1134 opt.setWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere) 1135 textLayout.setTextOption(opt) 1136 textLayout.beginLayout() 1137 lineSpacing = (fm.ascent() + fm.descent() + fm.leading()) * self._lineheight 1138 1139 advance = 0 1140 while True: 1141 line = textLayout.createLine() 1142 if not line.isValid(): 1143 break 1144 line.setLineWidth(w) 1145 line.setPosition(QPointF(0, advance)) 1146 advance += lineSpacing 1147 textLayout.endLayout() 1148 913 1149 painter.save() 914 p ainter.setFont(self._qfont)915 self._fillcolor._set(painter)916 #painter.setBrush(QBrush(self._fillcolor))1150 p = QPen(QBrush(self._fillcolor._rgb), 0) 1151 painter.setPen(p) 1152 917 1153 # Center-mode transforms: translate to image center 918 1154 if self._transformmode == CENTER: 1155 lc = textLayout.lineCount()*lineSpacing 919 1156 deltaX = w / 2 920 1157 deltaY = h / 2 921 1158 t = Transform() 922 #t.translate(x+deltaX, y+fm.ascent()+deltaY)1159 t.translate(deltaX,lc/2) 923 1160 t.concat(painter) 924 1161 self._transform.concat(painter) 925 painter.drawText(x, y, w, h, flags, self.text) 926 #layoutManager.drawGlyphsForGlyphRange_atPoint_(glyphRange, (-deltaX-dx,-deltaY-dy)) 1162 t = Transform() 1163 t.translate(-deltaX,-lc/2) 1164 t.concat(painter) 927 1165 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()))1166 self._transform.concat(painter) 1167 1168 textLayout.draw(painter, QPointF(x, y)) 931 1169 painter.restore() 932 1170 return (w, h) … … 934 1172 def _get_metrics(self): 935 1173 # 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) 1174 flags = self.__alignMap[self._align] | Qt.TextWordWrap 1175 fm = QFontMetricsF(self._qfont) 1176 w = self.width or 100000 1177 h = self.height or 100000 1178 r = fm.boundingRect(QRectF(self.x, self.y, w, h), flags, self.text) 949 1179 return r.width(), r.height() 950 1180 metrics = property(_get_metrics) … … 1059 1289 raise NodeBoxError, "pop: too many canvas pops!" 1060 1290 1291 def _setTextScaleFactor(self, factor): 1292 global textScaleFactor 1293 textScaleFactor = factor 1294 1061 1295 def draw(self, painter): 1062 1296 if self.background is not None: 1297 painter.save() 1063 1298 painter.fillRect(0,0, self.width, self.height, self.background._rgb) 1299 painter.restore() 1064 1300 for grob in self._grobs: 1065 1301 grob._draw(painter) … … 1073 1309 svgGen.setFileName(fname) 1074 1310 svgGen.setSize(QSize(self.width, self.height)) 1075 painter = QPainter(svgGen) 1311 painter = QPainter() 1312 painter.begin(svgGen) 1076 1313 self.draw(painter) 1077 1314 painter.end() … … 1081 1318 printer.setOutputFileName(fname) 1082 1319 printer.setFullPage(True) 1083 #printer.setPageSize(QPrinter.Custom) 1084 painter = QPainter(printer) 1320 printer.setPaperSize(QSizeF(self.width, self.height), QPrinter.Point) 1321 painter = QPainter() 1322 painter.begin(printer) 1085 1323 self.draw(painter) 1086 1324 painter.end() 1087 1325 elif format in ("png", "tiff", "jpg", "jpeg"): 1088 1326 img = QImage(self.width, self.height, QImage.Format_ARGB32) 1089 painter = QPainter(img) 1090 painter.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing) 1327 painter = QPainter() 1328 painter.begin(img) 1329 painter.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform) 1330 if format in ("jpg", "jpeg"): 1331 painter.fillRect(0, 0, self.width, self.height, Qt.white) 1091 1332 self.draw(painter) 1092 1333 painter.end() -
nodebox/branches/try-qt-painter/nodebox/gui/qt/__init__.py
r286 r484 7 7 import random 8 8 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 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, QVBoxLayout, QHBoxLayout, QLabel, QSlider, QPushButton, QIcon, QTransform, QCursor 10 from PyQt4.QtCore import Qt, SIGNAL, SLOT, pyqtSignature, QTimer, QRectF, QSize, QCoreApplication, QSettings, QVariant, QPoint 11 11 from PyQt4.QtSvg import QSvgGenerator 12 13 from nodebox.gui.qt.editor import PythonHighlighter, CodeEdit, loadConfig 12 from PyQt4.QtOpenGL import QGLWidget, QGLFormat, QGL 13 14 from nodebox.gui.qt.ValueLadder import MAGICVAR 15 from nodebox.gui.qt.pytextview import PythonHighlighter, PyTextView, loadConfig, Config 16 from nodebox.gui.qt.dashboard import DashboardController 14 17 15 18 MAGICVAR = "__magic_var__" … … 32 35 self.data.append((self.isErr, data)) 33 36 34 class NodeBoxGraphicsView(QWidget): 35 36 def __init__(self, parent=None): 37 QWidget.__init__(self, parent) 38 self.setMinimumSize(300, 300) 37 class NodeBoxGraphicsView(): 38 zoomLevels = [0.1, 0.25, 0.5, 0.75] 39 zoom = 1.0 40 while zoom <= 20.0: 41 zoomLevels.append(zoom) 42 zoom += 1.0 43 44 def __init__(self): 39 45 self._canvas = None 40 46 self._image = None 41 47 self._dirty = True 42 48 self._zoom = 1.0 49 self._mouseDown = False 50 51 def mousePressEvent(self, event): 52 if event.button() == Qt.LeftButton: 53 self._mouseDown = True 54 55 def mouseReleaseEvent(self, event): 56 if event.button() == Qt.LeftButton: 57 self._mouseDown = False 58 59 def mousePosition(self): 60 pos = self.mapFromGlobal(QCursor.pos()) 61 return QPoint(pos.x(), pos.y()) 62 43 63 def _get_canvas(self): 44 64 return self._canvas 45 65 def _set_canvas(self, canvas): 46 66 self._canvas = canvas 47 size = self.width(), self.height() 48 if size != self.canvas.size: 49 self.resize(*self.canvas.size) 67 if canvas is not None: 68 size = int(self.width()/float(self._zoom)), int(self.height()/float(self._zoom)) 69 if size != self.canvas.size: 70 width, height = self.canvas.size 71 self.resize(width*self._zoom, height*self._zoom) 50 72 self.markDirty() 51 73 canvas = property(_get_canvas, _set_canvas) 52 74 75 def _get_zoom(self): 76 return self._zoom 77 def _set_zoom(self, zoom): 78 self._zoom = zoom 79 self.document.zoomLevel.setText("%i%%" % (self._zoom * 100.0)) 80 self.document.zoomSlider.setValue(self._zoom * 100.0) 81 self.canvas = self.canvas 82 zoom = property(_get_zoom, _set_zoom) 83 84 def findNearestZoomIndex(self, zoom): 85 """Returns the nearest zoom level, and whether we found a direct, exact 86 match or a fuzzy match.""" 87 try: # Search for a direct hit first. 88 idx = self.zoomLevels.index(zoom) 89 return idx, True 90 except ValueError: # Can't find the zoom level, try looking at the indexes. 91 idx = 0 92 try: 93 while self.zoomLevels[idx] < zoom: 94 idx += 1 95 except KeyError: # End of the list 96 idx = len(self.zoomLevels) - 1 # Just return the last index. 97 return idx, False 98 99 def zoomIn_(self): 100 idx, direct = self.findNearestZoomIndex(self.zoom) 101 # Direct hits are perfect, but indirect hits require a bit of help. 102 # Because of the way indirect hits are calculated, they are already 103 # rounded up to the upper zoom level; this means we don't need to add 1. 104 if direct: 105 idx += 1 106 idx = max(min(idx, len(self.zoomLevels)-1), 0) 107 self.zoom = self.zoomLevels[idx] 108 109 def zoomOut_(self): 110 idx, direct = self.findNearestZoomIndex(self.zoom) 111 idx -= 1 112 idx = max(min(idx, len(self.zoomLevels)-1), 0) 113 self.zoom = self.zoomLevels[idx] 114 115 def zoomTo_(self, value): 116 self.zoom = value 117 118 def zoomToFit_(self, scroll): 119 w, h = self.canvas.size 120 viewport = scroll.viewport() 121 fw = viewport.width() 122 fh = viewport.height() 123 factor = min(fw / float(w), fh / float(h)) 124 self.zoom = factor 125 126 def dragZoom_(self, value): 127 self.zoom = value / 100.0 128 53 129 def markDirty(self, redraw=True): 54 130 self._dirty = True … … 59 135 self._image = None 60 136 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)) 137 p = QPainter() 138 p.begin(self) 139 p.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing) 140 p.setClipRect(QRectF(0, 0, self.canvas.width * self.zoom, self.canvas.height * self.zoom)) 141 142 if self.zoom != 1.0: 143 p.scale(self.zoom, self.zoom) 65 144 try: 145 p.save() 66 146 self.canvas.draw(p) 67 147 except: … … 76 156 outputView.insertPlainText(data) 77 157 finally: 158 p.restore() 78 159 p.end() # TODO: This doesn't fix the QWidget warning if error happens during drawing. 79 self._image = img80 160 81 161 def paintEvent(self, event): 82 p = QPainter(self)83 p.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing)84 162 if self.canvas is None: 163 p = QPainter(self) 85 164 p.fillRect(0, 0, self.width(), self.height(), QColor(Qt.white)) 86 165 else: 87 166 if self._dirty: 88 167 self._updateImage() 89 p.drawPixmap(0, 0, self._image) 90 168 169 class QGLNodeBoxGraphicsView(QGLWidget, NodeBoxGraphicsView): 170 def __init__(self, parent=None): 171 QGLWidget.__init__(self, QGLFormat(QGL.SampleBuffers | QGL.AlphaChannel), parent) 172 NodeBoxGraphicsView.__init__(self) 173 174 class QNodeBoxGraphicsView(QWidget, NodeBoxGraphicsView): 175 def __init__(self, parent=None): 176 QWidget.__init__(self, parent) 177 NodeBoxGraphicsView.__init__(self) 178 91 179 class NodeBoxDocument(QMainWindow): 92 180 def __init__(self): … … 98 186 self.namespace = {} 99 187 self.canvas = graphics.Canvas() 188 textScaleFactor = QPixmap().logicalDpiX() / 72.0 189 self.canvas._setTextScaleFactor(textScaleFactor) 100 190 self.context = graphics.Context(self.canvas, self.namespace) 101 191 self.animationTimer = None … … 110 200 111 201 def createMenu(self): 112 #self.menuBar = QMenuBar()113 114 202 self.fileMenu = QMenu(self.tr("&File"), self) 115 203 self.fileMenu.addAction(self.tr("&New"), self, SLOT("doNew()"), QKeySequence("Ctrl+N")) … … 136 224 self.menuBar().addMenu(self.editMenu) 137 225 226 self.viewMenu = QMenu(self.tr("&View"), self) 227 self.viewMenu.addAction(self.tr("Zoom &In"), self, SLOT("zoomIn_()"), QKeySequence("Ctrl++")) 228 self.viewMenu.addAction(self.tr("Zoom &Out"), self, SLOT("zoomOut_()"), QKeySequence("Ctrl+-")) 229 self.zoomToMenu = QMenu(self.tr("Zoom to"), self) 230 self.zoomToMenu.addAction(self.tr("To &Fit"), self, SLOT("zoomToFit_()"), QKeySequence("Ctrl+0")) 231 self.zoomToMenu.addAction(self.tr("Actual &Size"), self, SLOT("zoomTo100_()"), QKeySequence("Ctrl+1")) 232 self.zoomToMenu.addAction(self.tr("200%"), self, SLOT("zoomTo200_()"), QKeySequence("Ctrl+2")) 233 self.zoomToMenu.addAction(self.tr("300%"), self, SLOT("zoomTo300_()"), QKeySequence("Ctrl+3")) 234 self.zoomToMenu.addAction(self.tr("400%"), self, SLOT("zoomTo400_()"), QKeySequence("Ctrl+4")) 235 self.zoomToMenu.addAction(self.tr("50%"), self, SLOT("zoomTo50_()"), QKeySequence("Ctrl+5")) 236 self.viewMenu.addMenu(self.zoomToMenu) 237 self.menuBar().addMenu(self.viewMenu) 238 138 239 self.pythonMenu = QMenu(self.tr("&Python"), self) 139 240 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+ ."))241 self.runAction = self.pythonMenu.addAction(self.tr("&Stop"), self, SLOT("doStop()"), QKeySequence("Ctrl+B")) 141 242 self.menuBar().addMenu(self.pythonMenu) 142 243 … … 163 264 codeFormat = QTextCharFormat() 164 265 codeFormat.setFont(codeFont) 165 166 self.graphicsView = NodeBoxGraphicsView() 266 267 global app 268 self.graphicsView = app._NodeBoxGraphicsView(self) 167 269 self.graphicsView.document = self 168 270 self.graphicsView.resize(1000, 1000) 169 271 self.graphicsScroll = QScrollArea() 170 272 self.graphicsScroll.setWidget(self.graphicsView) 171 self.graphicsScroll.setMinimumSize(300, 300) 172 self.codeView = CodeEdit() 273 self.graphicsScroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 274 self.graphicsScroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 275 self.codeView = PyTextView() 276 self.codeView._document = self 173 277 self.codeView.setFontFamily(codeFont.defaultFamily()) 174 278 self.codeView.setCurrentFont(codeFont) … … 176 280 self.codeView.setMinimumSize(300, 300) 177 281 self.codeView.setAcceptRichText(False) 282 self.codeView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 283 self.codeView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 178 284 PythonHighlighter(self.codeView.document()) 179 285 self.outputView = QTextEdit() … … 184 290 self.outputView.setAcceptRichText(False) 185 291 self.outputView.setReadOnly(True) 292 self.outputView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 186 293 187 294 self.code_errors = QSplitter(Qt.Vertical) … … 189 296 self.code_errors.addWidget(self.outputView) 190 297 298 self.zoomLevel = QLabel("100%") 299 ft = self.zoomLevel.font() 300 ft.setPointSizeF(10) 301 self.zoomLevel.setFont(ft) 302 self.zoomSlider = QSlider(Qt.Horizontal) 303 self.zoomSlider.setMinimumWidth(130) 304 self.zoomSlider.setMinimum(1) 305 self.zoomSlider.setMaximum(1000) 306 self.zoomSlider.setValue(100) 307 self.zoom_small = QPushButton() 308 self.zoom_small.setIcon(QIcon(os.path.join(app.dir_gui, "zoomsmall.png"))) 309 self.zoom_small.setStyleSheet("border: 0;") 310 self.zoom_big = QPushButton() 311 self.zoom_big.setIcon(QIcon(os.path.join(app.dir_gui, "zoombig.png"))) 312 self.zoom_big.setStyleSheet("border: 0;") 313 self.connect(self.zoom_small, SIGNAL("clicked()"), self, SLOT("zoomOut_()")) 314 self.connect(self.zoom_big, SIGNAL("clicked()"), self, SLOT("zoomIn_()")) 315 self.connect(self.zoomSlider, SIGNAL("valueChanged(int)"), self, SLOT("dragZoom_(int)")) 316 317 lh = QHBoxLayout() 318 lh.addWidget(self.zoomLevel) 319 lh.addWidget(self.zoom_small) 320 lh.addWidget(self.zoomSlider) 321 lh.addWidget(self.zoom_big) 322 lh.setContentsMargins(0,0,0,0) 323 lh.setSpacing(8) 324 325 self.zoom = QWidget(self) 326 self.zoom.setLayout(lh) 327 328 lv = QVBoxLayout() 329 lv.addWidget(self.graphicsScroll) 330 lv.addWidget(self.zoom, 0, Qt.AlignRight) 331 lv.setContentsMargins(0, 0, 0, 0) 332 333 self.graphics_zoom = QWidget(self) 334 self.graphics_zoom.setLayout(lv) 335 191 336 self.view_edit = QSplitter() 192 self.view_edit.addWidget(self.graphics Scroll)337 self.view_edit.addWidget(self.graphics_zoom) 193 338 self.view_edit.addWidget(self.code_errors) 194 339 self.view_edit.setObjectName("view_edit") 195 self.view_edit.setStyleSheet(" QSplitter#view_edit { margin: 10px 10px 20px 10px; border: 0 }")340 self.view_edit.setStyleSheet("#view_edit { margin: 10px 10px 20px 10px; border: 0 }") 196 341 l = QGridLayout() 197 342 l.setHorizontalSpacing(10) 198 343 l.setVerticalSpacing(10) 199 344 self.view_edit.setLayout(l) 200 201 #self.centralWidget = QWidget()202 #self.centralWidget.addWidget(self.view_edit)203 #self.centralWidget.setLayout(QGridLayout())204 205 345 self.setCentralWidget(self.view_edit) 206 346 self.setUnifiedTitleAndToolBarOnMac(True) … … 208 348 209 349 self.setWindowTitle(self.tr("Untitled")) 350 self.dashboardController = DashboardController(self) 210 351 self.resize(800, 600) 211 352 353 @pyqtSignature("") 354 def zoomIn_(self): 355 if self.fullScreen is not None: return 356 self.graphicsView.zoomIn_() 357 358 @pyqtSignature("") 359 def zoomOut_(self): 360 if self.fullScreen is not None: return 361 self.graphicsView.zoomOut_() 362 363 @pyqtSignature("int") 364 def dragZoom_(self, value): 365 if self.fullScreen is not None: return 366 self.graphicsView.dragZoom_(value) 367 368 @pyqtSignature("") 369 def zoomTo100_(self): 370 if self.fullScreen is not None: return 371 self.graphicsView.zoomTo_(1.0) 372 373 @pyqtSignature("") 374 def zoomTo200_(self): 375 if self.fullScreen is not None: return 376 self.graphicsView.zoomTo_(2.0) 377 378 @pyqtSignature("") 379 def zoomTo300_(self): 380 if self.fullScreen is not None: return 381 self.graphicsView.zoomTo_(3.0) 382 383 @pyqtSignature("") 384 def zoomTo400_(self): 385 if self.fullScreen is not None: return 386 self.graphicsView.zoomTo_(4.0) 387 388 @pyqtSignature("") 389 def zoomTo50_(self): 390 if self.fullScreen is not None: return 391 self.graphicsView.zoomTo_(0.5) 392 393 @pyqtSignature("") 394 def zoomToFit_(self): 395 if self.fullScreen is not None: return 396 self.graphicsView.zoomToFit_(self.graphicsScroll) 397 212 398 @pyqtSignature("") 213 399 def doNew(self): … … 279 465 280 466 @pyqtSignature("") 467 def doExportAsMovie(self): 468 pass 469 470 @pyqtSignature("") 471 def doPrint(self): 472 pass 473 474 @pyqtSignature("") 281 475 def doRun(self): 282 476 if self.fullScreen is not None: return … … 317 511 fileName = property(_get_fileName, _set_fileName) 318 512 513 def buildInterface_(self): 514 if not self.dashboardController: 515 self.dashboardController = DashboardController(self) 516 self.dashboardController.buildInterface_(self.vars) 517 319 518 def _runScript(self, compile=True, newSeed=True): 320 if not self.cleanRun(self._execScript ):519 if not self.cleanRun(self._execScript, newSeed): 321 520 pass 322 521 … … 337 536 if self.namespace.has_key("setup"): 338 537 self.fastRun(self.namespace["setup"]) 339 window = self.currentView.window()538 # window = self.currentView.window() 340 539 #window.makeFirstResponder_(self.currentView) 341 540 … … 344 543 self.connect(self.animationTimer, SIGNAL("timeout()"), self, SLOT("doFrame()")) 345 544 self.animationTimer.start(1000.0 / self.speed) 346 545 347 546 def runScriptFast_(self): 348 547 if self.animationTimer is None: … … 365 564 # Build the interface 366 565 self.vars = self.namespace["_ctx"]._vars 367 if len(self.vars) > 0:566 if newSeed and len(self.vars) > 0: 368 567 self.buildInterface_() 369 568 … … 406 605 407 606 # 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 607 pos = self.currentView.mousePosition() 608 mx, my = pos.x(), pos.y() 609 610 # if isinstance(self.currentView, FullscreenView): 611 # my = self.currentView.bounds()[1][1] - my 612 613 if self.fullScreen is None: 614 mx /= self.currentView.zoom 615 my /= self.currentView.zoom 416 616 self.namespace["MOUSEX"], self.namespace["MOUSEY"] = mx, my 417 #self.namespace["mousedown"] = QApplication.mouseButtons() & Qt.LeftButton617 self.namespace["mousedown"] = self.currentView._mouseDown 418 618 #self.namespace["keydown"] = self.currentView.keydown 419 619 #self.namespace["key"] = self.currentView.key … … 563 763 class FullscreenView(QDialog): 564 764 pass 765 766 def overrideConfig(app): 767 settings = app.settings 768 Config["fontfamily"] = settings.value("fontfamily", 769 QVariant("Monaco")).toString() 770 Config["fontsize"] = settings.value("fontsize", 771 QVariant(11)).toInt()[0] 772 for name, color, bold, italic in ( 773 ("normal", "#000000", False, False), 774 ("keyword", "#0000FF", False, False), 775 ("builtin", "#000000", False, False), 776 ("constant", "#0000FF", False, False), 777 ("decorator", "#000000", False, False), 778 ("comment", "#808080", False, False), 779 ("string", "#FF00FF", False, False), 780 ("number", "#000000", False, False), 781 ("error", "#FF0000", False, False), 782 ("pyqt", "#000000", False, False)): 783 Config["%sfontcolor" % name] = settings.value( 784 "%sfontcolor" % name, QVariant(color)).toString() 785 Config["%sfontbold" % name] = settings.value( 786 "%sfontbold" % name, QVariant(bold)).toBool() 787 Config["%sfontitalic" % name] = settings.value( 788 "%sfontitalic" % name, QVariant(italic)).toBool() 565 789 566 790 class NodeBoxApplication(QApplication): … … 572 796 self.settings = QSettings() 573 797 loadConfig() 798 overrideConfig(self) 574 799 self.documents = [] 575 800 if sys.platform == "win32": … … 591 816 except OSError: pass 592 817 except IOError: pass 818 import nodebox.gui 819 dir_gui = nodebox.gui.__file__ 820 self.dir_gui = os.path.split(os.path.realpath(dir_gui))[0] 821 if '-gl' in args: 822 self._NodeBoxGraphicsView = QGLNodeBoxGraphicsView 823 else: 824 self._NodeBoxGraphicsView = QNodeBoxGraphicsView 593 825 594 826 def newDocument(self):
