gui.py 15.3 KB
Newer Older
Daniel Brown's avatar
Daniel Brown committed
1
2
3
4
5
6
7
# -*- coding: utf-8 -*-
"""
Created on Tue Jan 29 11:35:48 2013

@author: Daniel
"""

8
from pykat.components import Component, space
9
10
from pykat.detectors import Detector

Daniel Brown's avatar
Daniel Brown committed
11
12
from PyQt4 import QtGui, QtCore
from PyQt4.Qt import *
13
from PyQt4.QtGui import QCursor, QGraphicsItem
Daniel Brown's avatar
Daniel Brown committed
14
15
from pykat.gui.graphics import *
import qt_gui
16
17
import functools

Daniel Brown's avatar
Daniel Brown committed
18
class pyKatGUI(QtGui.QMainWindow, qt_gui.Ui_MainWindow):
19
    def __init__(self, kat, parent=None):
Daniel Brown's avatar
Daniel Brown committed
20
        super(pyKatGUI, self).__init__(parent)
21
        
Daniel Brown's avatar
Daniel Brown committed
22
        self.setupUi(self)
23
        self.graphicsView = pyKatGraphicsView(self.centralwidget, kat)
24
        self.graphicsView.setObjectName("graphicsView")
25
        self.graphicsView.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
26
27
28
        self.graphicsView.viewport().setMouseTracking(True)
        self.setMouseTracking(True)
        
29
        self.gridLayout.addWidget(self.graphicsView, 0, 0, 1, 1)
Daniel Brown's avatar
Daniel Brown committed
30
31
        
        # create a new scene
32
33
        if kat.scene == None: 
            kat.scene = pyKatGraphicsScene()  
34
        
35
        self.__scene = kat.scene
Daniel Brown's avatar
Daniel Brown committed
36
37
38
        
        # add scene to the graphics view
        self.graphicsView.setScene(self.__scene)
39
        self.graphicsView.setRenderHint(QtGui.QPainter.Antialiasing)
Daniel Brown's avatar
Daniel Brown committed
40
                
41
42
43
        self.actionExport_to_SVG.triggered.connect(lambda: self.exportToSVG())
        self.actionClose.triggered.connect(lambda: self.close)

44
        self._kat = kat        
Daniel Brown's avatar
Daniel Brown committed
45
        
46
47
48
    @property
    def kat(self): return self._kat
    
Daniel Brown's avatar
Daniel Brown committed
49
50
51
52
53
54
55
56
57
    def main(self):
        self.show()
        
        self.addComponentsToScene()
        
    def scene(self):
        return self.__scene

    def addComponentsToScene(self):
58
59
        for c in self.kat.getComponents():
            self.addComponentToScene(c)
Daniel Brown's avatar
Daniel Brown committed
60
                
61
62
63
64
65
66
67
68
69
70
71
72
    def addComponentToScene(self,c,x=0,y=0):
        itm = c.getQGraphicsItem()
            
        if itm != None:
            itm.setPos(x,y)
            # uncomment this line to stop background bitmap caching of
            # svg rendering. Important to make sure when rendering to 
            # svg file that it is in a vector format. Gradients however
            # don't work...
            itm.refresh()
            itm.setCacheMode(QGraphicsItem.NoCache)
            self.__scene.addItem(itm)
Daniel Brown's avatar
Daniel Brown committed
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
    
    def _onComponentRemoved(self, comp, nodes):
        """
        When a component has been removed from the kat object this function should update
        all gui objects. 
            comp - object that is removed
            nodes - nodes that this comp was attached too, as that information may no longer be accessible
        """
        itm = comp.getQGraphicsItem()
            
        if itm != None:

            itm.refresh()
            self.__scene.removeItem(itm)
            
            for n in nodes:
                for cc in self._kat.nodes.getNodeComponents(n):
                    if cc != None:
                        ccitm = cc.getQGraphicsItem()
                        if ccitm != None:
                            ccitm.refresh()
            
95
            
96
97
98
99
    def exportToSVG(self):
        self.statusbar.showMessage("Saving to 'output.svg'...")
        
        svg = QSvgGenerator()
100
101
102
103
104
105
        filename = QtGui.QFileDialog.getSaveFileNameAndFilter(self,'Save SVG File',filter=".svg")
        
        if filename == None:
            return None
        
        svg.setFileName(filename)
106
107
108
109
110
111
112
113
114
        svg.setSize(QSize(self.__scene.width(), self.__scene.height()))
        svg.setViewBox(QRect(0,0,self.__scene.width(), self.__scene.height()))
        svg.setTitle("pyKat output of example.kat")
        
        pntr = QPainter(svg)
        self.__scene.render(pntr)
        pntr.end()
        
        self.statusbar.showMessage("Complete: Saved to 'output.svg'")
115
116
117
118
    
    def addMirror(self, x,y):
        name = self.kat.getNewComponentName('m')
        n = self.kat.getNewNodeNames('n',2)
119
        m = pykat.components.mirror(name,n[0],n[1],R=0.5,T=0.5)
Daniel Brown's avatar
Daniel Brown committed
120
121
        
        self.kat.add(m)
122
        self.addComponentToScene(m,x,y)
Daniel Brown's avatar
Daniel Brown committed
123
    
124
125
126
127
128
129
130
131
    def addBeamsplitter(self, x, y):
        name = self.kat.getNewComponentName('bs')
        n = self.kat.getNewNodeNames('n', 4)
        m = pykat.components.beamSplitter(name,n[0],n[1],n[2],n[3],R=0.5,T=0.5)
        
        self.kat.add(m)
        self.addComponentToScene(m,x,y)
        
Daniel Brown's avatar
Daniel Brown committed
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
    def addSpace(self, x,y):
        name = self.kat.getNewComponentName('s')
        n = self.kat.getNewNodeNames('n',2)
        s = pykat.components.space(name, n[0], n[1])

        self.kat.add(s)
        self.addComponentToScene(s,x,y) 
     
    def addLaser(self, x,y):
        name = self.kat.getNewComponentName('l')
        n = self.kat.getNewNodeNames('n',1)
        l = pykat.components.laser(name, n[0])

        self.kat.add(l)
        self.addComponentToScene(l,x,y)   
147
    
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
    def addModulator(self, x,y):
        name = self.kat.getNewComponentName('mod')
        n = self.kat.getNewNodeNames('n',2)
        l = pykat.components.modulator(name, n[0], n[1], 1e6, 0.1, 1)

        self.kat.add(l)
        self.addComponentToScene(l,x,y)
    
    def addLens(self, x,y):
        name = self.kat.getNewComponentName('lens')
        n = self.kat.getNewNodeNames('n',2)
        l = pykat.components.lens(name, n[0], n[1])

        self.kat.add(l)
        self.addComponentToScene(l,x,y)
        
    def addIsolator(self, x,y):
        name = self.kat.getNewComponentName('isol')
        n = self.kat.getNewNodeNames('n',3)
        l = pykat.components.isolator(name, n[0], n[1], node3=n[2])

        self.kat.add(l)
        self.addComponentToScene(l,x,y)
            
172
173
174
175
176
177
178
    def addPhotodiode(self, x, y):
        name = self.kat.getNewDetectorName('pd')
        n = self.kat.getNewNodeNames('n',1)
        l = pykat.detectors.photodiode(name, n[0], [])

        self.kat.add(l)
        self.addComponentToScene(l,x,y)   
179
180
181
182
    
    def deleteComponent(self, comp):
        comp.component.remove()
    
183
184
185
186
187
188
189
190
191
    def disconnect(self, node):
        comps = self.kat.nodes.getNodeComponents(node)
        
        spaces = [c for c in comps if isinstance(c, space)]
        
        if len(spaces) > 0:
            dis_comp = spaces[0]
        else:
            dis_comp = comps[0]
Daniel Brown's avatar
Daniel Brown committed
192
        
193
194
195
196
197
198
199
200
201
202
        new_node_name = self.kat.getNewNodeNames("n", 1)
        new_node = self.kat.nodes.createNode(new_node_name[0])
        
        self.kat.nodes.replaceNode(dis_comp, node, new_node)
        
        # refresh all the graphics that might be affected
        for c in node.components + new_node.components:
            if c != None:
                c.getQGraphicsItem().refresh()
    
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
class pyKatGraphicsScene(QGraphicsScene):
    def drawBackground(self, painter, rect):
        size = 10
        painter.setPen(QPen(QColor(200,200,255,255),0.5))
        
        start = round(rect.top(), size)
        
        if start > rect.top():
            start =- size
        
        y = start - size
        
        while y < rect.bottom():
            y += size
            painter.drawLine(rect.left(),y, rect.right(), y)
        
        start = round(rect.left(), size)
        
        if start > rect.left():
            start =- size
        
        y = start - size
        
        while y < rect.right():
            y += size
            painter.drawLine(y, rect.top(), y, rect.bottom())
                    
Daniel Brown's avatar
Daniel Brown committed
230
class pyKatGraphicsView(QGraphicsView):
231
232
233
    def __init__(self, val, kat):
        QGraphicsView.__init__(self, val)
        self._kat = kat
Daniel Brown's avatar
Daniel Brown committed
234
235
        self.__selected_item = None
        self.__prev_pt = None
236
237
238
239
        self.setMouseTracking(True)
        self.__itemHover = None
        self.__marked = None
        
Daniel Brown's avatar
Daniel Brown committed
240
241
    def contextMenuEvent(self, ev):  
        pt = self.mapToScene(ev.pos())
242
              
243
244
        gui = self.parentWidget().parent() # get the main gui window
        
Daniel Brown's avatar
Daniel Brown committed
245
246
        menu = QMenu(self)
        addmenu = menu.addMenu("Add...")
247
        
Daniel Brown's avatar
Daniel Brown committed
248
249
250
        action = addmenu.addAction("Space")
        action.triggered.connect(functools.partial(gui.addSpace, pt.x(), pt.y()))
        
251
        action = addmenu.addAction("Mirror")
Daniel Brown's avatar
Daniel Brown committed
252
253
        action.triggered.connect(functools.partial(gui.addMirror, pt.x(), pt.y()))
        
254
255
256
        action = addmenu.addAction("Beamsplitter")
        action.triggered.connect(functools.partial(gui.addBeamsplitter, pt.x(), pt.y()))
        
Daniel Brown's avatar
Daniel Brown committed
257
258
259
        action = addmenu.addAction("Laser")
        action.triggered.connect(functools.partial(gui.addLaser, pt.x(), pt.y()))
        
260
261
262
263
264
265
266
267
268
        action = addmenu.addAction("Lens")
        action.triggered.connect(functools.partial(gui.addLens, pt.x(), pt.y()))
        
        action = addmenu.addAction("Isolator")
        action.triggered.connect(functools.partial(gui.addIsolator, pt.x(), pt.y()))
        
        action = addmenu.addAction("Modulator")
        action.triggered.connect(functools.partial(gui.addModulator, pt.x(), pt.y()))
        
269
270
        action = addmenu.addAction("Photodiode")
        action.triggered.connect(functools.partial(gui.addPhotodiode, pt.x(), pt.y()))
Daniel Brown's avatar
Daniel Brown committed
271
        
272
        item = self.scene().itemAt(pt.x(),pt.y())
Daniel Brown's avatar
Daniel Brown committed
273
        
274
275
276
277
        print pt.x(),pt.y(),item
        
        if item is not None :
            if isinstance(item, ComponentQGraphicsItem):           
Daniel Brown's avatar
Daniel Brown committed
278
279
                menu.addSeparator()
                menu.addAction("Edit")
280
281
                action = menu.addAction("Delete")
                action.triggered.connect(functools.partial(gui.deleteComponent, item))
Daniel Brown's avatar
Daniel Brown committed
282
283
            if isinstance(item,NodeQGraphicItem):
                menu.addSeparator()
284
                comps = self._kat.nodes.getNodeComponents(item.node)
285
                
286
287
288
                if(comps.count(None) == 0):
                    action = menu.addAction("Disconnect")
                    action.triggered.connect(functools.partial(gui.disconnect, item.node))
Daniel Brown's avatar
Daniel Brown committed
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308

        menu.popup(ev.globalPos());        
        
    def mousePressEvent(self, ev):
        
        if ev.button()==Qt.LeftButton:
            
            pt = self.mapToScene(ev.pos())
            
            item = self.scene().itemAt(pt)
            
            if isinstance(item, ComponentQGraphicsItem):
                if item == None:
                    self.__selected_item = None
                    self.__prev_pt = None
                else:
                    item.setFocus(Qt.MouseFocusReason)
        
                    self.__selected_item = item
                    self.__prev_pt = pt
309
            elif isinstance(item, NodeQGraphicItem):
310
311
312
313
314
                
                 
                if isinstance(item.parentItem(),SpaceQGraphicsItem):        
                    self.__selected_item = item
                    self.__prev_pt = pt
315
                    
316
317
318
319
320
321
322
                elif isinstance(item.parentItem(),ComponentQGraphicsItem):
                    self.__selected_item = item.parentItem()
                    self.__prev_pt = pt
            
            if self.__selected_item is not None:
                self.setCursor(QCursor(Qt.ClosedHandCursor))
                        
Daniel Brown's avatar
Daniel Brown committed
323
    def mouseReleaseEvent(self, ev):
324
325
        # if we have dragged a node and marked another to connect it too
        if self.__selected_item is not None and isinstance(self.__selected_item, NodeQGraphicItem) and self.__marked is not None:
Daniel Brown's avatar
Daniel Brown committed
326
            
327
328
329
330
331
332
333
334
335
336
337
338
339
            # node attached to space which needs to be removed
            node_s = self.__selected_item.node
            
            # get the selected node which must be attached to a space, which is the nodes parent
            # and then get the space component from it
            qspace = self.__selected_item.parentItem()
            space = qspace.space
            
            # marked node, then get the parent object and component
            node_c = self.__marked.node
            qcomp = self.__marked.parentItem()
            
            # connect space of node dragged to the component node
Daniel Brown's avatar
Daniel Brown committed
340
341
            # the space node that has been dragged gets deleted and we
            # replace it with the components
342
            self._kat.nodes.replaceNode(space, node_s, node_c)
343
344
345
346
            
            # then refresh the graphical items
            qspace.refresh()
            qcomp.refresh()
347
348

            self.setCursor(QCursor(Qt.ArrowCursor))
Daniel Brown's avatar
Daniel Brown committed
349
                        
350
351
352
353
354
        if self.__marked is not None:
            self.__marked.marked = False
            self.__marked.refresh()
            self.__marked = None
            
Daniel Brown's avatar
Daniel Brown committed
355
        self.__selected_item = None
356
        
Daniel Brown's avatar
Daniel Brown committed
357
358
359
360
    def mouseMoveEvent(self, ev):
        if self.__selected_item != None:
            
            item = self.__selected_item
361
            #pt_ = self.__prev_pt
Daniel Brown's avatar
Daniel Brown committed
362
363
            pt = self.mapToScene(ev.pos())
            
364
365
366
367
            # smooth moving of item depending on where you click
            #item.moveBy(pt.x()-pt_.x(), pt.y()-pt_.y())
            # then snap to some integer value
            snap = 10.0
368
            
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
            # if we are moving a node, it must be attached to a space
            # component otherwise we shouldn't be here
            if isinstance(item, NodeQGraphicItem) and isinstance(item.parentItem(), SpaceQGraphicsItem):
                space = item.parentItem()
                
                item.setPos(pt.x()-space.x(),pt.y()-space.y())
                space.refresh()
                
                # now check to see if any other connectable nodes are within reach
                # and if so hightlight them
                select_size = 20
                rect = QRectF(pt.x()-select_size/2,pt.y()-select_size/2,select_size,select_size)
                itms = item.scene().items(rect)
                
                # remove the node we are dragging
                if item in itms:
                    itms.remove(item)
                
                if self.__marked is not None:
                    self.__marked.marked = False
                    self.__marked.refresh()
                    self.__marked = None
                    
                if len(itms) > 0:
                    for i in itms:
                        if isinstance(i,NodeQGraphicItem) and i != item:
                            i.marked = True
                            i.refresh()
                            self.__marked = i
                            break
            else:
                item.setPos(int(round(pt.x()/snap)*snap),int(round(pt.y()/snap)*snap))
                
402
403
            self.__prev_pt = pt
            
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
            return
        
        else:
            item = self.itemAt(ev.pos())
            
            if isinstance(item, (NodeQGraphicItem, ComponentQGraphicsItem)) or item is None:
                #if item is not None or self.__itemHover is not None:
                    
                if isinstance(item, ComponentQGraphicsItem):
                    self.__itemHover = item
                elif isinstance(item,NodeQGraphicItem) and (not item.node.isConnected()):
                    self.__itemHover = item
                else:
                    self.__itemHover = None
            
419
            if self.__itemHover is not None:
420
421
422
423
                self.setCursor(QCursor(Qt.OpenHandCursor))
            else:
                self.setCursor(QCursor(Qt.ArrowCursor))
                        
424
425