Show
Ignore:
Timestamp:
09/04/08 15:29:15 (18 months ago)
Author:
stefan
Message:

initial changes

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • nodebox/branches/try-qt-painter/nodebox/gui/qt/__init__.py

    r286 r484  
    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 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 
     10from PyQt4.QtCore import Qt, SIGNAL, SLOT, pyqtSignature, QTimer, QRectF, QSize, QCoreApplication, QSettings, QVariant, QPoint 
    1111from PyQt4.QtSvg import QSvgGenerator 
    12  
    13 from nodebox.gui.qt.editor import PythonHighlighter, CodeEdit, loadConfig 
     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 
    1417 
    1518MAGICVAR = "__magic_var__" 
     
    3235        self.data.append((self.isErr, data)) 
    3336 
    34 class NodeBoxGraphicsView(QWidget): 
    35  
    36     def __init__(self, parent=None): 
    37         QWidget.__init__(self, parent) 
    38         self.setMinimumSize(300, 300) 
     37class 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): 
    3945        self._canvas = None 
    4046        self._image = None 
    4147        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 
    4363    def _get_canvas(self): 
    4464        return self._canvas 
    4565    def _set_canvas(self, canvas): 
    4666        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) 
    5072        self.markDirty() 
    5173    canvas = property(_get_canvas, _set_canvas) 
    5274         
     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 
    53129    def markDirty(self, redraw=True): 
    54130        self._dirty = True 
     
    59135        self._image = None 
    60136        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) 
    65144        try: 
     145            p.save() 
    66146            self.canvas.draw(p) 
    67147        except: 
     
    76156            outputView.insertPlainText(data) 
    77157        finally: 
     158            p.restore() 
    78159            p.end() # TODO: This doesn't fix the QWidget warning if error happens during drawing. 
    79         self._image = img 
    80160         
    81161    def paintEvent(self, event): 
    82         p = QPainter(self) 
    83         p.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing) 
    84162        if self.canvas is None: 
     163            p = QPainter(self) 
    85164            p.fillRect(0, 0, self.width(), self.height(), QColor(Qt.white)) 
    86165        else: 
    87166            if self._dirty: 
    88167                self._updateImage() 
    89             p.drawPixmap(0, 0, self._image) 
    90          
     168 
     169class QGLNodeBoxGraphicsView(QGLWidget, NodeBoxGraphicsView): 
     170    def __init__(self, parent=None): 
     171        QGLWidget.__init__(self, QGLFormat(QGL.SampleBuffers | QGL.AlphaChannel), parent) 
     172        NodeBoxGraphicsView.__init__(self) 
     173         
     174class QNodeBoxGraphicsView(QWidget, NodeBoxGraphicsView): 
     175    def __init__(self, parent=None): 
     176        QWidget.__init__(self, parent) 
     177        NodeBoxGraphicsView.__init__(self) 
     178 
    91179class NodeBoxDocument(QMainWindow): 
    92180    def __init__(self): 
     
    98186        self.namespace = {} 
    99187        self.canvas = graphics.Canvas() 
     188        textScaleFactor = QPixmap().logicalDpiX() / 72.0 
     189        self.canvas._setTextScaleFactor(textScaleFactor) 
    100190        self.context = graphics.Context(self.canvas, self.namespace) 
    101191        self.animationTimer = None 
     
    110200 
    111201    def createMenu(self): 
    112         #self.menuBar = QMenuBar() 
    113  
    114202        self.fileMenu = QMenu(self.tr("&File"), self) 
    115203        self.fileMenu.addAction(self.tr("&New"), self, SLOT("doNew()"), QKeySequence("Ctrl+N")) 
     
    136224        self.menuBar().addMenu(self.editMenu) 
    137225 
     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 
    138239        self.pythonMenu = QMenu(self.tr("&Python"), self) 
    139240        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")) 
    141242        self.menuBar().addMenu(self.pythonMenu) 
    142243 
     
    163264        codeFormat = QTextCharFormat() 
    164265        codeFormat.setFont(codeFont) 
    165          
    166         self.graphicsView = NodeBoxGraphicsView() 
     266 
     267        global app 
     268        self.graphicsView = app._NodeBoxGraphicsView(self) 
    167269        self.graphicsView.document = self 
    168270        self.graphicsView.resize(1000, 1000) 
    169271        self.graphicsScroll = QScrollArea() 
    170272        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 
    173277        self.codeView.setFontFamily(codeFont.defaultFamily()) 
    174278        self.codeView.setCurrentFont(codeFont) 
     
    176280        self.codeView.setMinimumSize(300, 300) 
    177281        self.codeView.setAcceptRichText(False) 
     282        self.codeView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 
     283        self.codeView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 
    178284        PythonHighlighter(self.codeView.document()) 
    179285        self.outputView = QTextEdit() 
     
    184290        self.outputView.setAcceptRichText(False) 
    185291        self.outputView.setReadOnly(True) 
     292        self.outputView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 
    186293         
    187294        self.code_errors = QSplitter(Qt.Vertical) 
     
    189296        self.code_errors.addWidget(self.outputView) 
    190297 
     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 
    191336        self.view_edit = QSplitter() 
    192         self.view_edit.addWidget(self.graphicsScroll) 
     337        self.view_edit.addWidget(self.graphics_zoom) 
    193338        self.view_edit.addWidget(self.code_errors) 
    194339        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 }") 
    196341        l = QGridLayout() 
    197342        l.setHorizontalSpacing(10) 
    198343        l.setVerticalSpacing(10) 
    199344        self.view_edit.setLayout(l) 
    200          
    201         #self.centralWidget = QWidget() 
    202         #self.centralWidget.addWidget(self.view_edit) 
    203         #self.centralWidget.setLayout(QGridLayout()) 
    204          
    205345        self.setCentralWidget(self.view_edit) 
    206346        self.setUnifiedTitleAndToolBarOnMac(True) 
     
    208348 
    209349        self.setWindowTitle(self.tr("Untitled")) 
     350        self.dashboardController = DashboardController(self) 
    210351        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 
    212398    @pyqtSignature("") 
    213399    def doNew(self): 
     
    279465 
    280466    @pyqtSignature("") 
     467    def doExportAsMovie(self): 
     468        pass 
     469 
     470    @pyqtSignature("") 
     471    def doPrint(self): 
     472        pass 
     473 
     474    @pyqtSignature("") 
    281475    def doRun(self): 
    282476        if self.fullScreen is not None: return 
     
    317511    fileName = property(_get_fileName, _set_fileName) 
    318512 
     513    def buildInterface_(self): 
     514        if not self.dashboardController: 
     515            self.dashboardController = DashboardController(self) 
     516        self.dashboardController.buildInterface_(self.vars) 
     517 
    319518    def _runScript(self, compile=True, newSeed=True): 
    320         if not self.cleanRun(self._execScript): 
     519        if not self.cleanRun(self._execScript, newSeed): 
    321520            pass 
    322521 
     
    337536            if self.namespace.has_key("setup"): 
    338537                self.fastRun(self.namespace["setup"]) 
    339             window = self.currentView.window() 
     538#            window = self.currentView.window() 
    340539            #window.makeFirstResponder_(self.currentView) 
    341540 
     
    344543            self.connect(self.animationTimer, SIGNAL("timeout()"), self, SLOT("doFrame()")) 
    345544            self.animationTimer.start(1000.0 / self.speed) 
    346  
     545             
    347546    def runScriptFast_(self):         
    348547        if self.animationTimer is None: 
     
    365564            # Build the interface 
    366565            self.vars = self.namespace["_ctx"]._vars 
    367             if len(self.vars) > 0: 
     566            if newSeed and len(self.vars) > 0: 
    368567                self.buildInterface_() 
    369568 
     
    406605 
    407606        # 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             
    416616        self.namespace["MOUSEX"], self.namespace["MOUSEY"] = mx, my 
    417         #self.namespace["mousedown"] = QApplication.mouseButtons() & Qt.LeftButton 
     617        self.namespace["mousedown"] = self.currentView._mouseDown 
    418618        #self.namespace["keydown"] = self.currentView.keydown 
    419619        #self.namespace["key"] = self.currentView.key 
     
    563763class FullscreenView(QDialog): 
    564764    pass 
     765 
     766def 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() 
    565789     
    566790class NodeBoxApplication(QApplication): 
     
    572796        self.settings = QSettings() 
    573797        loadConfig() 
     798        overrideConfig(self) 
    574799        self.documents = [] 
    575800        if sys.platform == "win32": 
     
    591816        except OSError: pass 
    592817        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 
    593825             
    594826    def newDocument(self):