source: trunk/python/notationwindow.py @ 1884

Last change on this file since 1884 was 1884, checked in by Kana Sugimoto, 14 years ago

New Development: Yes

JIRA Issue: Yes (CAS-1801)

Ready for Test: Yes

Interface Changes: Yes

What Interface Changed: A new button 'note' added to casatoolbar

Test Programs: Need GUI testing

Put in Release Notes: Yes

Module(s): asap.plotter

Description:

A new button, 'note', added to casatoolbar.
You can graphically add an arbitrary text string on the plotter
when 'note' button is active.


File size: 15.4 KB
Line 
1import os
2import matplotlib
3from asap.logging import asaplog, asaplog_post_dec
4
5######################################
6##    Notation box window           ##
7######################################
8class NotationWindowCommon:
9    def __init__(self,master=None):
10        #self.parent = master
11        self.canvas = master
12        self.event = None
13        self.note = None
14        self.ancval = None
15        self.anchors = ["figure","axes","data"]
16        self.seltext = {}
17        self.numnote = 0
18
19    @asaplog_post_dec
20    def print_text(self):
21        anchor = self.anchors[self.ancval.get()]
22        notestr = self._get_note().rstrip("\n")
23        if len(notestr.strip()) == 0:
24            self._clear_textbox()
25            #print "Empty string!"
26            return
27
28        myaxes = None
29        calcpos = True
30        xpos=None
31        ypos=None
32        if self.seltext:
33            # You are modifying a text
34            mycanvas = self.canvas
35            oldanch = self.seltext['anchor']
36            if oldanch != 'figure':
37                myaxes = self.seltext['parent']
38            calcpos = (anchor != oldanch)
39            if not calcpos:
40                # printing text in the same coord.
41                # you don't have to recalc position
42                (xpos, ypos) = self.seltext['textobj'].get_position()
43                transform = self.seltext['textobj'].get_transform()
44                parent = self.seltext['parent']
45            elif anchor == "figure":
46                # converting from "axes"/"data" -> "figure"
47                (x, y) = self.seltext['textobj']._get_xy_display()
48            elif oldanch == "data":
49                # converting from "data" -> "axes".
50                # need xdata & ydata in the axes
51                (x, y) = self.seltext['textobj'].get_position()
52            else:
53                # converting "figure"/"axes" -> "data"
54                # need to calculate xdata & ydata in the axes
55                pixpos = self.seltext['textobj']._get_xy_display()
56                (w,h) = mycanvas.get_width_height()
57                relpos = (pixpos[0]/float(w), pixpos[1]/float(h))
58                if not myaxes:
59                    myaxes = self._get_axes_from_pos(relpos,mycanvas)
60                    if not myaxes:
61                        raise RuntimeError, "Axes resolution failed!"
62                (x, y) = self._convert_pix2dat(relpos,myaxes)
63            self._remove_seltext()
64        elif self.event:
65            mycanvas = self.event.canvas
66            myaxes = self.event.inaxes
67            if myaxes and (anchor != "figure"):
68                x = self.event.xdata
69                y = self.event.ydata
70            else:
71                x = self.event.x
72                y = self.event.y
73        else:
74            raise RuntimeError, "No valid position to print data"
75            return
76
77        # now you know
78        picker = True
79        # alignment of the text: ha (horizontal), va (vertical)
80        ha = 'left'
81        va = 'center'
82        if not calcpos:
83            # you aready know parent, tansform, xpos and ypos
84            pass
85        elif anchor == "figure":
86            # text instance will be appended to mycanvas.figure.texts
87            parent = mycanvas.figure
88            transform = parent.transFigure
89            (w,h) = mycanvas.get_width_height()
90            xpos = x/float(w)
91            ypos = y/float(h)           
92        elif myaxes:
93            ## text instance will be appended to myaxes.texts
94            parent = myaxes
95            if anchor == "axes":
96                transform = myaxes.transAxes
97                lims = myaxes.get_xlim()
98                xpos = (x-lims[0])/(lims[1]-lims[0])
99                lims = myaxes.get_ylim()
100                ypos = (y-lims[0])/(lims[1]-lims[0])
101            else:
102                # anchored on "data"
103                transform = myaxes.transData
104                xpos = x
105                ypos = y
106        parent.text(xpos,ypos,notestr,transform=transform,
107                    ha=ha,va=va,picker=picker)
108        mycanvas.draw()
109
110        self.numnote += 1
111
112        self._clear_textbox()
113        msg = "A note added to figure: str = '"+notestr+"'"
114        msg += "   at ["+str(xpos)+", "+str(ypos)+"] in "+anchor+"-coordinate"
115        msg += "\nTotal number of notes are "+str(self.numnote)
116        asaplog.push( msg )
117
118    def _get_axes_from_pos(self,pos,canvas):
119        if len(pos) != 2:
120            raise ValueError, "pixel position should have 2 elements"
121        for axes in canvas.figure.axes:
122            ##check if pos is in the axes
123            #if axes.contains_point(pos): ### seems not working
124            #    return axes
125            axbox = axes.get_position().get_points()
126            if (axbox[0][0] <= pos[0] <= axbox[1][0]) and \
127               (axbox[0][1] <= pos[1] <= axbox[1][1]):
128                return axes
129        return None
130       
131    def _convert_pix2dat(self,pos,axes):
132        # convert a relative position from lower-left of the canvas
133        # to a data in axes
134        if len(pos) != 2:
135            raise ValueError, "pixel position should have 2 elements"
136        # left-/bottom-pixel, and pixel width & height of the axes
137        bbox = axes.get_position()
138        lbpos = bbox.get_points()[0]
139        wax = bbox.width
140        hax = bbox.height
141        # check pos value
142        if (pos[0] < lbpos[0]) or (pos[1] < lbpos[1]) \
143               or (pos[0] > (lbpos[0]+wax)) or (pos[1] > (lbpos[1]+hax)):
144            raise ValueError, "The position is out of the axes"
145        xlims = axes.get_xlim()
146        ylims = axes.get_ylim()
147        wdat = xlims[1] - xlims[0]
148        hdat = ylims[1] - ylims[0]
149        xdat = xlims[0] + wdat*(pos[0] - lbpos[0])/wax
150        ydat = ylims[0] + hdat*(pos[1] - lbpos[1])/hax
151        return (xdat, ydat)
152
153    @asaplog_post_dec
154    def _selected_text(self,event):
155        (w,h) = event.canvas.get_width_height()
156        dist2 = w*w+h*h
157        selected = {}
158        for textobj in self.canvas.figure.texts:
159            if textobj.contains(event)[0]:
160                d2 = self._get_text_dist2(event,textobj)
161                if dist2 >= d2:
162                    dist2=d2
163                    selected={'anchor': 'figure', \
164                             'parent': event.canvas.figure, 'textobj': textobj}
165                    msg = "Fig loop: a text, '"+textobj.get_text()+"', at "
166                    msg += str(textobj.get_position())+" detected"
167                    asaplog.push(msg)
168        for ax in self.canvas.figure.axes:
169            for textobj in ax.texts:
170                if textobj.contains(event)[0]:
171                    d2 = self._get_text_dist2(event,textobj)
172                    if dist2 >= d2:
173                        anchor='axes'
174                        if ax.transData == textobj.get_transform():
175                            anchor='data'                   
176                        selected={'anchor': anchor, 'parent': ax, 'textobj': textobj}
177                        msg = "Ax loop: a text, '"+textobj.get_text()+"', at "
178                        msg += str(textobj.get_position())+" detected"
179                        asaplog.push(msg)
180
181        return selected
182
183    def _get_text_dist2(self,event,textobj):
184        (x,y) = textobj._get_xy_display()
185        return (x-event.x)**2+(y-event.y)**2
186
187    def delete_note(self):
188        #print "You selected 'OK'"
189        self._remove_seltext()
190        self.canvas.draw()
191
192    @asaplog_post_dec
193    def _remove_seltext(self):
194        if len(self.seltext) < 3:
195            raise ValueError, "Don't under stand selected text obj."
196            return
197        try:
198            self.seltext['textobj'].remove()
199        except NotImplementedError:
200                self.seltext['parent'].texts.pop(self.seltext['parent'].texts.index(self.seltext['textobj']))
201        self.numnote -= 1
202
203        textobj = self.seltext['textobj']
204        msg = "A note deleted from figure: str = '"+textobj.get_text()+"'"
205        msg += "   at "+str(textobj.get_position())\
206               +" in "+self.seltext['anchor']+"-coordinate"
207        msg += "\nTotal number of notes are "+str(self.numnote)
208        asaplog.push( msg )
209
210        self.seltext = {}
211
212    def cancel_delete(self):
213        #print "You selected 'CANCEL'"
214        self.seltext = {}
215
216
217#####################################
218##    Backend dependent Classes    ##
219#####################################
220### TkAgg
221if matplotlib.get_backend() == 'TkAgg':
222    import Tkinter as Tk
223    import tkMessageBox
224
225class NotationWindowTkAgg(NotationWindowCommon):
226    def __init__(self,master=None):
227        self.parent = master._tkcanvas
228        NotationWindowCommon.__init__(self,master=master)
229        self.textwin=self._create_textwindow(master=None)
230        self.menu=self._create_modmenu(master=self.parent)
231       
232    def _create_textwindow(self,master=None):
233        twin = Tk.Toplevel(padx=3,pady=3)
234        twin.title("Annotation")
235        twin.resizable(width=True,height=True)
236        self.textbox = self._NotationBox(parent=twin)
237        self.radio = self._AnchorRadio(parent=twin)
238        self.actionbs = self._ActionButtons(parent=twin)
239       
240        self.textbox.pack(side=Tk.TOP,fill=Tk.BOTH,expand=True)
241        self.actionbs.pack(side=Tk.BOTTOM)
242        self.radio.pack(side=Tk.BOTTOM)
243        #twin.deiconify()
244        #twin.minsize(width=twin.winfo_width(),height=twin.winfo_height())
245        twin.withdraw()
246        return twin
247
248    def _NotationBox(self,parent=None):
249        textbox = Tk.Text(master=parent,background='white',
250                          height=2,width=20,cursor="xterm",
251                          padx=2,pady=2,undo=True,maxundo=10)
252        return textbox
253
254    def _AnchorRadio(self,parent=None):
255        radio = Tk.LabelFrame(master=parent,text="anchor",
256                            labelanchor="nw",padx=5,pady=3)
257        self.ancval = Tk.IntVar(master=radio,value=0)
258        self.rFig = self._NewRadioButton(radio,"figure",state=Tk.NORMAL,
259                                         variable=self.ancval,value=0,
260                                         side=Tk.LEFT)
261        self.rAxis = self._NewRadioButton(radio,"panel",state=Tk.DISABLED,
262                                          variable=self.ancval,value=1,
263                                          side=Tk.LEFT)
264        self.rData = self._NewRadioButton(radio,"data",state=Tk.DISABLED,
265                                          variable=self.ancval,value=2,
266                                          side=Tk.LEFT)
267        # set initial selection "figure"
268        self.ancval.set(0)
269        return radio
270
271    def _NewRadioButton(self,parent,text,state=Tk.NORMAL,variable=None,value=None,side=Tk.LEFT):
272        rb = Tk.Radiobutton(master=parent,text=text,state=state,
273                          variable=variable,value=value)
274        rb.pack(side=side)
275        return rb
276
277    def _enable_radio(self):
278        self.rAxis.config(state=Tk.NORMAL)
279        self.rData.config(state=Tk.NORMAL)
280        #self.rFig.config(state=Tk.NORMAL)
281        self.rFig.select()
282
283    def _reset_radio(self):
284        self.rAxis.config(state=Tk.DISABLED)
285        self.rData.config(state=Tk.DISABLED)
286        self.rFig.config(state=Tk.NORMAL)
287        self.rFig.select()
288
289    def _select_radio(self,selection):
290        if not selection in self.anchors:
291            return
292        if selection == "data":
293            self.rData.select()
294        elif selection == "axes":
295            self.rAxis.select()
296        else:
297            self.rFig.select()
298
299    def _get_note(self):
300        return self.textbox.get("1.0",Tk.END)
301
302    def _clear_textbox(self):
303        self.textbox.delete("1.0",Tk.END)
304
305    def _set_note(self,note=None):
306        self._clear_textbox()
307        if len(note) >0:
308            self.textbox.insert("1.0",note)
309
310    def _ActionButtons(self,parent=None):
311        actbuts = Tk.Frame(master=parent)
312        bCancel = self._NewButton(actbuts,"cancel",self._cancel_text,side=Tk.LEFT)
313        bPrint = self._NewButton(actbuts,"print", self._print_text,side=Tk.LEFT)
314        return actbuts
315
316    def _NewButton(self, parent, text, command, side=Tk.LEFT):
317        if(os.uname()[0] == 'Darwin'):
318            b = Tk.Button(master=parent, text=text, command=command)
319        else:
320            b = Tk.Button(master=parent, text=text, padx=2, pady=2, command=command)
321        b.pack(side=side)
322        return b
323
324    def _cancel_text(self):
325        self._finish_textwindow()
326
327    def _print_text(self):
328        self.print_text()
329        self._finish_textwindow()
330
331    def load_textwindow(self,event):
332        if event.canvas._tkcanvas != self.parent:
333            raise RuntimeError, "Got invalid event!"
334       
335        self.event = event
336        is_ax = (event.inaxes != None)
337        (xpix, ypix) = self._disppix2screen(event.x, event.y)
338        offset=5
339        self.show_textwindow(xpix+offset,ypix+offset,enableaxes=is_ax)
340       
341    def show_textwindow(self,xpix,ypix,basetext=None,enableaxes=False):
342        if not self.textwin: return
343        self._reset_radio()
344        if enableaxes:
345            self._enable_radio()
346        self.textwin.deiconify()
347        (w,h) = self.textwin.minsize()
348        if w*h <= 1:
349            self.textwin.minsize(width=self.textwin.winfo_width(),
350                                 height=self.textwin.winfo_height())
351            (w,h) = self.textwin.minsize()
352        self.textwin.geometry("%sx%s+%s+%s"%(w,h,xpix,ypix))
353
354    def _finish_textwindow(self):
355        self._reset_radio()
356        self._clear_textbox()
357        self.textwin.withdraw()
358
359    def _create_modmenu(self,master=None):
360        if master:
361            self.parent=master
362        if not self.parent:
363            return False
364        menu = Tk.Menu(master=self.parent,tearoff=False)
365        menu.add_command(label="Modify",command=self.modify)
366        menu.add_command(label="Delete",command=self.delnote_dialog)
367        return menu
368
369    def show_modmenu(self,event):
370        self.seltext = self._selected_text(event)
371        if len(self.seltext) == 3:
372            tkcanvas=event.canvas._tkcanvas
373            xpos = tkcanvas.winfo_rootx() + int(event.x)
374            ypos = tkcanvas.winfo_rooty() \
375                   + tkcanvas.winfo_height() - int(event.y)
376            self.menu.post(xpos,ypos)
377
378    def modify(self):
379        #print "Modify selected!!"
380        textobj = self.seltext['textobj']
381        (xtx, ytx) = textobj._get_xy_display()
382        is_ax = (self.seltext['anchor'] != 'figure')
383        if not is_ax:
384            # previous anchor is figure
385            pos = textobj.get_position()
386            is_ax = (self._get_axes_from_pos(pos,self.canvas) != None)
387
388        (xpix, ypix) = self._disppix2screen(xtx,ytx)
389        offset = int(textobj.get_size())*2
390        self.show_textwindow(xpix,ypix+offset,basetext=textobj.get_text(),\
391                             enableaxes=is_ax)
392        self._select_radio(self.seltext['anchor'])
393        self._set_note(textobj.get_text())
394
395    def delnote_dialog(self):
396        remind = "Delete text?\n '"+self.seltext['textobj'].get_text()+"'"
397        answer = tkMessageBox.askokcancel(parent=self.parent,title="Delete?",
398                                          message=remind,
399                                          default=tkMessageBox.CANCEL)
400        if answer:
401            self.delete_note()
402        else:
403            self.cancel_delete()
404
405
406    def _disppix2screen(self,xpixd,ypixd):
407        # calculate a pixel position form Upper-left of the SCREEN
408        # from a pixel from Lower-left of the CANVAS (e.g., event.x/y)
409        xpixs = self.parent.winfo_rootx() + xpixd
410        ypixs = self.parent.winfo_rooty() + self.parent.winfo_height() \
411               - ypixd
412        return (int(xpixs), int(ypixs))
413       
Note: See TracBrowser for help on using the repository browser.