source: trunk/python/customgui_qt4agg.py @ 2168

Last change on this file since 2168 was 2168, checked in by Kana Sugimoto, 13 years ago

New Development: Yes

JIRA Issue: No

Ready for Test: Yes

Interface Changes: No

What Interface Changed:

Test Programs: Interactive test with PyQt4 backend

Put in Release Notes: Yes

Module(s): asapplotter, asaplotbase, and sdplot

Description: Enabled additional toolbar, casabar, in asapplotter for Qt4Agg bacend


File size: 18.4 KB
Line 
1import os
2import matplotlib, numpy
3from asap.logging import asaplog, asaplog_post_dec
4from matplotlib.patches import Rectangle
5from asap.parameters import rcParams
6from asap._asap import stmath
7from asap.customgui_base import *
8
9import PyQt4 as qt
10
11######################################
12##    Add CASA custom toolbar       ##
13######################################
14class CustomToolbarQT4Agg(CustomToolbarCommon,  qt.QtGui.QToolBar):
15    def __init__(self,parent):
16        from asap.asapplotter import asapplotter
17        if not isinstance(parent,asapplotter):
18            return False
19        if not parent._plotter:
20            return False
21        self._p = parent._plotter
22        self.figmgr = self._p.figmgr
23        self.canvas = self.figmgr.canvas
24        self.mode = ''
25        self.button = True
26        self.pagecount = None
27        CustomToolbarCommon.__init__(self,parent)
28        self.notewin = NotationWindowQT4Agg(master=self.canvas)
29        self._add_custom_toolbar()
30
31    def _add_custom_toolbar(self):
32        qt.QtGui.QToolBar.__init__(self,parent=self.figmgr.window)
33        self.figmgr.window.addToolBar(qt.QtCore.Qt.BottomToolBarArea,self)
34        self.bNote = self._NewButton(master=self,
35                                     text='notation',
36                                     command=self.modify_note,
37                                     balloon="Add note")
38        self.bNote.setCheckable(True)
39
40        self.bStat = self._NewButton(master=self,
41                                     text='statistics',
42                                     command=self.stat_cal,
43                                     balloon="Calculate statistics")
44        self.bStat.setCheckable(True)
45
46        # page change oparations
47        frPage = qt.QtGui.QWidget(parent=self,flags=qt.QtCore.Qt.Tool)
48        loPage = qt.QtGui.QHBoxLayout(self)
49        loPage.addStretch(1)
50        self.lPagetitle = qt.QtGui.QLabel('Page:',parent=frPage)
51        self.lPagetitle.setMargin(5)
52        loPage.addWidget(self.lPagetitle)
53        self.pagecount = qt.QtGui.QLabel(parent=frPage)
54        self.pagecount.setStyleSheet("background-color: white")
55        self.pagecount.setMargin(3)
56        self.pagecount.setText('   1')
57        loPage.addWidget(self.pagecount)
58       
59        self.bNext = self._NewButton(master=frPage,
60                                     text=' + ',
61                                     command=self.next_page,
62                                     addit=False)
63        loPage.addWidget(self.bNext)
64        self.bPrev = self._NewButton(master=frPage,
65                                     text=' - ',
66                                     command=self.prev_page,addit=False)
67        loPage.addWidget(self.bPrev)
68        frPage.setLayout(loPage)
69        self.addWidget(frPage)
70
71        self.bQuit = self._NewButton(master=self,
72                                     text='Quit',
73                                     command=self.quit,
74                                     balloon="Close window")
75
76        self.pagecount.setText(' '*4)
77
78        self.disable_button()
79        return
80
81    def _NewButton(self, master, text, command, balloon=None,addit=True):
82        b = qt.QtGui.QPushButton(text,parent=master)
83        if balloon: b.setToolTip(balloon)
84        if addit: master.addWidget(b)
85        master.connect(b,qt.QtCore.SIGNAL('clicked()'),command)
86        return b
87
88    def show_pagenum(self,pagenum,formatstr):
89        self.pagecount.setText(formatstr % (pagenum))
90
91    def spec_show(self):
92        if not self.figmgr.toolbar.mode == '' or not self.button: return
93        self.figmgr.toolbar.set_message('spec value: drag on a spec')
94        if self.mode == 'spec': return
95        self.mode = 'spec'
96        self.notewin.close_widgets()
97        self.__disconnect_event()
98        self._p.register('button_press',self._select_spectrum)
99
100    def stat_cal(self):
101        if not self.figmgr.toolbar.mode == '' or not self.button:
102            # Get back button status BEFORE clicked
103            self.bStat.setChecked(not self.bStat.isChecked())
104            return
105        if self.mode == 'stat':
106            # go back to spec mode
107            self.bStat.setChecked(False)
108            self.bStat.setToolTip("Calculate statistics")
109            self.spec_show()
110            return
111        self.figmgr.toolbar.set_message('statistics: select a region')
112        self.bStat.setChecked(True)
113        self.bStat.setToolTip("Back to spec value mode")
114        self.bNote.setChecked(False)
115        self.mode = 'stat'
116        self.notewin.close_widgets()
117        self.__disconnect_event()
118        self._p.register('button_press',self._single_mask)
119
120    def modify_note(self):
121        if not self.figmgr.toolbar.mode == '':
122            # Get back button status BEFORE clicked
123            self.bNote.setChecked(not self.bNote.isChecked())
124            return
125        self.figmgr.toolbar.set_message('text: select a position/text')
126        if self.mode == 'note':
127            self.bNote.setChecked(False)
128            self.bNote.setToolTip("Add note")
129            self.mode = 'none'
130            self.spec_show()
131            return
132        self.bStat.setChecked(False)
133        self.bNote.setChecked(True)
134        self.bNote.setToolTip("Back to spec value mode")
135        self.mode = 'note'
136        self.__disconnect_event()
137        self._p.register('button_press',self._mod_note)
138
139    def quit(self):
140        self.__disconnect_event()
141        self.disable_button()
142        self._p.quit()
143
144    def enable_button(self):
145        if self.button: return
146        self.bStat.setEnabled(True)
147        self.button = True
148        self.spec_show()
149
150    def disable_button(self):
151        if not self.button: return
152        self.bStat.setChecked(False)
153        self.bStat.setDisabled(True)
154        self.button = False
155        self.mode = ''
156        self.__disconnect_event()
157
158    def enable_next(self):
159        self.bNext.setEnabled(True)
160
161    def disable_next(self):
162        self.bNext.setDisabled(True)
163
164    def enable_prev(self):
165        self.bPrev.setEnabled(True)
166
167    def disable_prev(self):
168        self.bPrev.setDisabled(True)
169
170    # pause buttons for slow operations
171    def _pause_buttons(self,operation="end",msg=""):
172        buttons = ["bStat","bNote","bQuit"]
173        if operation == "start":
174            enable = False
175        else:
176            enable = True
177        for btn in buttons:
178            getattr(self,btn).setEnabled(enable)
179        self.figmgr.toolbar.set_message(msg)
180
181    def delete_bar(self):
182        self.__disconnect_event()
183        self.destroy()
184
185    def __disconnect_event(self):
186        self._p.register('button_press',None)
187        self._p.register('button_release',None)
188
189
190
191
192######################################
193##    Notation box window           ##
194######################################
195class NotationWindowQT4Agg(NotationWindowCommon):
196    """
197    Backend based class to create widgets to add, modify, or delete
198    note on the plot.
199
200    Note:
201    Press LEFT-mouse button on the plot to ADD a note on the canvas.
202    A notation window will be loaded for specifying note string and
203    anchor. The note will be anchored on a position in whether figure-
204    (0-1 relative in a figure), panel- (0-1 relative in a plot axes),
205    or data-coordinate (data value in a plot axes).
206    Press RIGHT-mouse button on a note to MODIFY/DELETE it. A cascade
207    menu will be displayed and you can select an operation.
208    """
209    def __init__(self,master=None):
210        self.parent = master
211        NotationWindowCommon.__init__(self,master=master)
212        self.anchval = None
213        self.textwin = self._create_textwindow(master=None)
214        self.menu = self._create_modmenu(master=self.parent)
215
216    ### Notation window widget
217    def _create_textwindow(self,master=None):
218        """Create notation window widget and iconfy it"""
219        #twin = qt.QtGui.QWidget(parent=master, flags=qt.QtCore.Qt.Popup)
220        twin = qt.QtGui.QWidget(parent=master, flags=qt.QtCore.Qt.Dialog)
221        twin.setWindowTitle("Notation")
222        self.textbox = self._NotationBox(parent=twin)
223        radiobox = self._AnchorRadio(parent=twin)
224        self.actionbs = self._ActionButtons(parent=twin)
225        vbox = qt.QtGui.QVBoxLayout(twin)
226        vbox.addWidget(self.textbox)
227        vbox.addWidget(radiobox)
228        vbox.addLayout(self.actionbs)
229        twin.setLayout(vbox)
230        #twin.setCentralWidget(self.textbox)
231        twin.hide()
232        return twin
233
234    def _NotationBox(self,parent=None):
235        textbox = qt.QtGui.QPlainTextEdit(parent=parent)
236        textbox.setStyleSheet("background-color: white")
237        fmetric = qt.QtGui.QFontMetrics(textbox.currentCharFormat().font())
238        textbox.resize(fmetric.width("A")*20+fmetric.leading()*2,
239                       fmetric.height()*2+fmetric.ascent()+fmetric.descent())
240        del fmetric
241        textbox.setMinimumSize(textbox.size())
242        textbox.setUndoRedoEnabled(True)
243        textbox.setMidLineWidth(3)
244        textbox.setFrameShadow(qt.QtGui.QFrame.Sunken)
245        textbox.setCursor(qt.QtCore.Qt.IBeamCursor)
246        textbox.setFocus()
247        return textbox
248
249    def _AnchorRadio(self,parent=None):
250        # Returns a QGoupBox object which includes radio butons to
251        # select an anchor
252        anchbox = qt.QtGui.QGroupBox("anchor",parent=parent)
253        self.radio = qt.QtGui.QButtonGroup(parent=anchbox)
254        self.rFig = self._NewRadioButton(anchbox,"figure",\
255                                         bgr=self.radio,value=0,\
256                                         balloon="a fixed position in figure")
257        self.rAxis = self._NewRadioButton(anchbox,"panel",\
258                                          bgr=self.radio,value=1,\
259                                          balloon="a fixed realtive position in subplot")
260        self.rData = self._NewRadioButton(anchbox,"data",\
261                                          bgr=self.radio,value=2,\
262                                          balloon="a fixed data position in subplot")
263        hbox = qt.QtGui.QHBoxLayout(anchbox)
264        hbox.addWidget(self.rFig)
265        hbox.addWidget(self.rAxis)
266        hbox.addWidget(self.rData)
267        anchbox.setLayout(hbox)
268        # set initial selection "figure"
269        self.rFig.setChecked(True)
270        self.radio.setExclusive(True)
271        self.anchval = self.radio.checkedId()
272        return anchbox
273
274    def _NewRadioButton(self,parent,text,balloon=None,bgr=None,value=None):
275        rb= qt.QtGui.QRadioButton(text,parent=parent)
276        if bgr:
277            if value is not None:
278                bgr.addButton(rb,value)
279            else:
280                bgr.addButton(rb)
281        if balloon: rb.setToolTip(balloon)
282        return rb
283
284    def _enable_radio(self):
285        """Enable 'panel' and 'data' radio button"""
286        self.rAxis.setEnabled(True)
287        self.rData.setEnabled(True)
288        # select Figure as the default value
289        self.rFig.setChecked(True)
290        self.anchval = self.radio.checkedId()
291
292    def _reset_radio(self):
293        """Disable 'panel' and 'data' radio button"""
294        self.rAxis.setDisabled(True)
295        self.rData.setDisabled(True)
296        self.rFig.setEnabled(True)
297        # select Figure as the default value
298        self.rFig.setChecked(True)
299        self.anchval = self.radio.checkedId()
300
301    def _select_radio(self,selection):
302        """Select a specified radio button"""
303        if not selection in self.anchors:
304            return
305        if selection == "data":
306            self.rData.setChecked(True)
307        elif selection == "axes":
308            self.rAxis.setChecked(True)
309        else:
310            self.rFig.setChecked(True)
311        self.anchval = self.radio.checkedId()
312
313    def _get_anchval(self):
314        """Returns a integer of a selected radio button"""
315        self.anchval = self.radio.checkedId()
316        return self.anchval
317
318    def _get_note(self):
319        """Returns a note string specified in the text box"""
320        return str(self.textbox.toPlainText())
321
322    def _clear_textbox(self):
323        """Clear the text box"""
324        self.textbox.clear()
325
326    def _set_note(self,note=None):
327        """Set a note string to the text box"""
328        self._clear_textbox()
329        if len(note) >0:
330            self.textbox.setPlainText(note)
331
332    def _ActionButtons(self,parent=None):
333        # Returns a layout object which includes "cancel" and "print" buttons
334        actbuts = qt.QtGui.QHBoxLayout()
335        bCancel = self._NewButton(parent,"cancel",self._cancel_text,\
336                                  addit=False,\
337                                  balloon="cancel printing/modifying")
338        bPrint = self._NewButton(parent,"print", self._print_text,\
339                                 addit=False,\
340                                 balloon="print text on plot")
341        actbuts.addWidget(bCancel)
342        actbuts.addWidget(bPrint)
343        return actbuts
344
345    def _NewButton(self, parent, text, command, balloon=None, addit=True):
346        b = qt.QtGui.QPushButton(text,parent=parent)
347        if balloon: b.setToolTip(balloon)
348        if addit: parent.addWidget(b)
349        parent.connect(b,qt.QtCore.SIGNAL('clicked()'),command)
350        return b
351
352    def _cancel_text(self):
353        """
354        Cancel adding/modifying a note and close notaion window.
355        called when 'cancel' is selected.
356        """
357        self.close_textwindow()
358
359    def _print_text(self):
360        """
361        Add/Modify a note. Called when 'print' is selected on the
362        notation window.
363        """
364        self.print_text()
365        self.close_textwindow()
366
367    def load_textwindow(self,event):
368        """
369        Load text window at a event position to add a note on a plot.
370        Parameter:
371            event:   an even object to specify the position to load
372                     text window.
373        """
374        self.close_modmenu()
375        if event.canvas != self.parent:
376            raise RuntimeError, "Got invalid event!"
377
378        self.event = event
379        is_ax = (event.inaxes != None)
380        (xpix, ypix) = self._disppix2screen(event.x, event.y)
381        offset = 5
382        self.show_textwindow(xpix+offset,ypix+offset,enableaxes=is_ax)
383
384    def show_textwindow(self,xpix,ypix,basetext=None,enableaxes=False):
385        """
386        Load text window at a position of screen to add a note on a plot.
387        Parameters:
388            xpix, ypix:   a pixel position from Upper-left corner
389                          of the screen.
390            basetext:     None (default) or any string.
391                          A string to be printed on text box when loaded.
392            enableaxes:   False (default) or True.
393                          If True, 'panel' & 'data' radio button is enabled.
394        """
395        if not self.textwin: return
396        self._reset_radio()
397        if enableaxes:
398            self._enable_radio()
399        self.textwin.activateWindow()
400        h = self.textwin.minimumHeight()
401        w = self.textwin.minimumWidth()
402        self.textwin.resize(w,h)
403        self.textwin.move(xpix,ypix)
404        self.textbox.setFocus()
405        self.textwin.raise_()
406        self.textwin.show()
407        if w*h <= 1: # Initial load
408            self.textwin.setMinimumSize(self.textwin.size())
409
410    def close_textwindow(self):
411        """Close text window."""
412        self.seltext = {}
413        self._reset_radio()
414        self._clear_textbox()
415        self.textwin.hide()
416
417
418    ### Modify/Delete menu widget
419    def _create_modmenu(self,master=None):
420        """Create modify/delete menu widget"""
421        if master:
422            self.parent = master
423        if not self.parent:
424            return False
425        menu = qt.QtGui.QMenu(parent=self.parent)
426        menu.setTearOffEnabled(False)
427        menu.addAction("Modify",self._modify_note)
428        menu.addAction("Delete",self._delnote_dialog)
429        return menu
430
431    def load_modmenu(self,event):
432        """
433        Load cascade menu at a event position to modify or delete
434        selected text.
435        Parameter:
436            event:  an even object to specify the position to load
437                    text window.
438        """
439        self.close_textwindow()
440        self.seltext = self._get_selected_text(event)
441        if len(self.seltext) == 3:
442            canvas = event.canvas
443            corig = canvas.mapToGlobal(qt.QtCore.QPoint(0,0))
444            xpixs = corig.x() + int(event.x)
445            ypixs = corig.y() + canvas.height() - int(event.y)
446            self.menu.activateWindow()
447            self.menu.move(xpixs,ypixs)
448            self.menu.show()
449
450    def close_modmenu(self):
451        """Close cascade menu."""
452        self.seltext = {}
453        self.menu.hide()
454
455    ### load text window for modification
456    def _modify_note(self):
457        """helper function to load text window to modify selected note"""
458        textobj = self.seltext['textobj']
459        (xtx, ytx) = textobj._get_xy_display()
460        is_ax = (self.seltext['anchor'] != 'figure')
461        if not is_ax:
462            # previous anchor is figure
463            pos = textobj.get_position()
464            is_ax = (self._get_axes_from_pos(pos,self.canvas) != None)
465
466        (xpix, ypix) = self._disppix2screen(xtx,ytx)
467        offset = int(textobj.get_size())*2
468        self.show_textwindow(xpix,ypix+offset,basetext=textobj.get_text(),\
469                             enableaxes=is_ax)
470        self._select_radio(self.seltext['anchor'])
471        self._set_note(textobj.get_text())
472
473    ### close all widgets
474    def close_widgets(self):
475        """Close note window and menu"""
476        self.close_textwindow()
477        self.close_modmenu()
478
479    ### dialog to confirm deleting note
480    def _delnote_dialog(self):
481        """Load dialog to confirm deletion of the text"""
482        remind = "Delete text?\n '"+self.seltext['textobj'].get_text()+"'"
483        from PyQt4.QtGui import QMessageBox as mbox
484        answer = mbox.question(self.parent,"Delete?",remind,
485                               buttons = mbox.Ok | mbox.Cancel,
486                               defaultButton=mbox.Cancel)
487        if answer == mbox.Ok:
488            self.delete_note()
489        else:
490            self.cancel_delete()
491
492    ### helper functions
493    def _disppix2screen(self,xpixd,ypixd):
494        """
495        helper function to calculate a pixel position form Upper-left
496        corner of the SCREEN from a pixel position (xpixd, ypixd)
497        from Lower-left of the CANVAS (which, e.g., event.x/y returns)
498
499        Returns:
500            (x, y):  pixel position from Upper-left corner of the SCREEN.
501        """
502        corig = self.parent.mapToGlobal(qt.QtCore.QPoint(0,0))
503        xpixs = corig.x() + xpixd
504        ypixs = corig.y() + self.parent.height() - ypixd
505        return (int(xpixs), int(ypixs))
506       
507
508
509
510
511
512###########################################
513##    Add CASA custom Flag toolbar       ##
514###########################################
Note: See TracBrowser for help on using the repository browser.