msgiowidget.py 8.44 KB
Newer Older
Ole Streicher's avatar
Ole Streicher committed
1 2 3 4
""" 'msgiowidget.py' -- this is a replacement for the msgiobuffer module.
   This contains the MsgIOWidget class, which is an optionally hidden
   scrolling canvas composed of a text widget and frame.  When "hidden",
   it turns into a single-line text widget.
5
   $Id$
Ole Streicher's avatar
Ole Streicher committed
6 7 8 9
"""
from __future__ import division # confidence high

# System level modules
10
import Tkinter as TKNTR # requires 2to3
11 12

# Our modules
13
from stsci.tools import tkrotext
Ole Streicher's avatar
Ole Streicher committed
14

15 16 17 18 19 20 21 22 23

def is_USING_X():
    """ This is specifically in a function and not at the top
    of this module so that it is not done until needed.  We do
    not want to casually import wutil anywhere. The import mechanism
    makes this speedy on the 2nd-Nth attempt anyway. """
    from . import wutil
    return wutil.WUTIL_USING_X

Ole Streicher's avatar
Ole Streicher committed
24

25
class MsgIOWidget(TKNTR.Frame):
Ole Streicher's avatar
Ole Streicher committed
26 27 28 29 30 31 32

    """MsgIOWidget class"""

    def __init__(self, parent, width=100, text=""):
        """Constructor"""

        # We are the main frame that holds everything we do
33
        TKNTR.Frame.__init__(self, parent)
Ole Streicher's avatar
Ole Streicher committed
34 35 36 37
        self._parent = parent

        # Create two sub-frames, one to hold the 1-liner msg I/O, and
        # the other one to hold the whole scrollable history.
38
        self._nowFrame = TKNTR.Frame(self, bd=2, relief=TKNTR.SUNKEN,
39
                                     takefocus=False)
40
        self._histFrame = TKNTR.Frame(self, bd=2, relief=TKNTR.SUNKEN,
41
                                      takefocus=False)
Ole Streicher's avatar
Ole Streicher committed
42 43 44 45

        # Put in the expand/collapse button (determine it's sizes)
        self._expBttnHasTxt = True
        btxt= '+'
46
        if is_USING_X():
Ole Streicher's avatar
Ole Streicher committed
47 48
            px = 2
            py = 0
49
        else: # Aqua
Ole Streicher's avatar
Ole Streicher committed
50 51
            px = 5
            py = 3
52
            if TKNTR.TkVersion > 8.4:
Ole Streicher's avatar
Ole Streicher committed
53 54 55
                px = py = 0
                btxt = ''
                self._expBttnHasTxt = False
56
        self._expBttn = TKNTR.Checkbutton(self._nowFrame, command=self._expand,
57 58 59
                                          padx=px, pady=py,
                                          text=btxt, indicatoron=0,
                                          state = TKNTR.DISABLED)
60
        self._expBttn.pack(side=TKNTR.LEFT, padx=3)#, ipadx=0)
Ole Streicher's avatar
Ole Streicher committed
61 62

        # Overlay a label on the frame
63
        self._msgLabelVar = TKNTR.StringVar()
Ole Streicher's avatar
Ole Streicher committed
64 65 66
        self._msgLabelVar.set(text)
        self._msgLabelMaxWidth = 65 # 70 works but causes plot redraws when
                                    # the history panel is opened/closed
67
        self._msgLabel = TKNTR.Label(self._nowFrame,
68 69 70 71 72 73
                                     textvariable=self._msgLabelVar,
                                     anchor=TKNTR.W,
                                     justify=TKNTR.LEFT,
                                     width=self._msgLabelMaxWidth,
                                     wraplength=width-100,
                                     takefocus=False)
74 75
        self._msgLabel.pack(side=TKNTR.LEFT,
                            fill=TKNTR.X,
Ole Streicher's avatar
Ole Streicher committed
76 77 78
                            expand=False)
        self._msgLabel.bind('<Double-Button-1>', self._lblDblClk)

79
        self._entry = TKNTR.Entry(self._nowFrame,
80 81 82 83 84
                                  state=TKNTR.DISABLED,
                                  width=1,
                                  takefocus=False,
                                  relief=TKNTR.FLAT,
                                  highlightthickness=0)
85
        self._entry.pack(side=TKNTR.LEFT, fill=TKNTR.X, expand=True)
Ole Streicher's avatar
Ole Streicher committed
86
        self._entry.bind('<Return>', self._enteredText)
87
        self._entryTyping = TKNTR.BooleanVar()
Ole Streicher's avatar
Ole Streicher committed
88 89 90
        self._entryTyping.set(False)

        # put in a spacer here for label height stability
91 92
        self._spacer = TKNTR.Label(self._nowFrame, text='', takefocus=False)
        self._spacer.pack(side=TKNTR.LEFT, expand=False, padx=5)
Ole Streicher's avatar
Ole Streicher committed
93

94
        self._nowFrame.pack(side=TKNTR.TOP, fill=TKNTR.X, expand=True)
Ole Streicher's avatar
Ole Streicher committed
95 96

        self._hasHistory = False
97 98
        self._histScrl = TKNTR.Scrollbar(self._histFrame)
        self._histScrl.pack(side=TKNTR.RIGHT, fill=TKNTR.Y)
Ole Streicher's avatar
Ole Streicher committed
99

100
        self._histText = tkrotext.ROText(self._histFrame, wrap=TKNTR.WORD,
101 102 103
                                         takefocus=False,
                                         height=10,
                                         yscrollcommand=self._histScrl.set)
104
# (use if just TKNTR.Text) state=TKNTR.DISABLED, takefocus=False,
Ole Streicher's avatar
Ole Streicher committed
105
#                        exportselection=True is the default
106
        self._histText.pack(side=TKNTR.TOP, fill=TKNTR.X, expand=True)
Ole Streicher's avatar
Ole Streicher committed
107 108 109
        self._histScrl.config(command=self._histText.yview)

        # don't pack this one now - start out with it hidden
110
#       self._histFrame.pack(side=TKNTR.TOP, fill=TKNTR.X)
Ole Streicher's avatar
Ole Streicher committed
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130

        ### Do not pack the main frame here.  Let the application do it. ###

        # At very end of ctor, add the init text to our history
        self._appendToHistory(text)

    def _lblDblClk(self, event=None):
        if self._hasHistory:
            # change the button appearance
            self._expBttn.toggle() # or .select() / .deselect()
            # and then act as if it was clicked
            self._expand()

    def _expand(self):
        ism = self._histFrame.winfo_ismapped()
        if ism: # need to collapse
            self._histFrame.pack_forget()
            if self._expBttnHasTxt:
                self._expBttn.configure(text='+')
        else:   # need to expand
131
            self._histFrame.pack(side=TKNTR.TOP, fill=TKNTR.BOTH, expand=True) #.X)
Ole Streicher's avatar
Ole Streicher committed
132 133 134
            if self._expBttnHasTxt:
                self._expBttn.configure(text='-')
            if self._hasHistory:
135
                self._histText.see(TKNTR.END)
Ole Streicher's avatar
Ole Streicher committed
136 137 138 139 140 141 142 143 144

    def updateIO(self, text=""):
        """ Update the text portion of the scrolling canvas """
        # Update the class variable with the latest text, and append the
        # new text to the end of the history.
        self._appendToHistory(text)
        self._msgLabelVar.set(text)
        # this is a little debugging "easter egg"
        if text.find('long debug line') >=0:
145
           self.updateIO('and now we are going to talk and talk for a while'+
Ole Streicher's avatar
Ole Streicher committed
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
                         ' about nothing at all because we want a lot of text')
        self._nowFrame.update_idletasks()

    def readline(self):
        """ Set focus to the entry widget and return it's contents """
        # Find what had focus up to this point
        lastFoc = self.focus_get()

        # Collapse the label as much as possible, it is too big on Linux & OSX
        lblTxt = self._msgLabelVar.get()
        lblTxtLen = len(lblTxt.strip())
        lblTxtLen -= 3
        self._msgLabel.configure(width=min(self._msgLabelMaxWidth, lblTxtLen))

        # Enable the entry widget
161
        self._entry.configure(state=TKNTR.NORMAL, relief=TKNTR.SUNKEN, width=15,
Ole Streicher's avatar
Ole Streicher committed
162 163 164 165 166 167 168 169 170 171 172
                              takefocus=True, highlightthickness=2)
        self._entry.focus_set()
        self._entryTyping.set(True)

        # Wait until they are done entering their answer
        self._entry.wait_variable(self._entryTyping)

        # By now they have hit enter
        ans = self._entry.get().strip()

        # Clear and disable the entry widget
173 174 175
        self._entry.delete(0, TKNTR.END)
        self._entry.configure(state=TKNTR.DISABLED, takefocus=False, width=1,
                              relief=TKNTR.FLAT, highlightthickness=0)
Ole Streicher's avatar
Ole Streicher committed
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
        self._entryTyping.set(False)

        # Expand the label back to normal width
        self._msgLabel.configure(width=self._msgLabelMaxWidth)

        # list the answer
        self.updateIO(ans)

        # return focus
        if lastFoc:
            lastFoc.focus_set()

        # return the answer - important to have the "\n" on it
        return ans+"\n"

    def _enteredText(self, event=None):
        self._entryTyping.set(False) # end the waiting
        self._expBttn.focus_set()

    def _appendToHistory(self, txt):
        # sanity check - need no blank lines in the history
        if len(txt.strip()) < 1:
            return

        # enable widget temporarily so we can add text
201
#       self._histText.config(state=TKNTR.NORMAL)
Ole Streicher's avatar
Ole Streicher committed
202 203 204 205
#       self._histText.delete(1.0, END)

        # add the new text
        if self._hasHistory:
206
            self._histText.insert(TKNTR.END, '\n'+txt.strip(), force=True)
Ole Streicher's avatar
Ole Streicher committed
207
        else:
208
            self._histText.insert(TKNTR.END, txt.strip(), force=True)
Ole Streicher's avatar
Ole Streicher committed
209 210 211
            self._hasHistory = True

        # disable it again
212
#       self._histText.config(state=TKNTR.DISABLED)
Ole Streicher's avatar
Ole Streicher committed
213 214 215

        # show it
        if self._histFrame.winfo_ismapped():
216
            self._histText.see(TKNTR.END)
Ole Streicher's avatar
Ole Streicher committed
217 218 219
#       self._histFrame.update_idletasks()

        # finally, make sure expand/collapse button is enabled now
220
        self._expBttn.configure(state = TKNTR.NORMAL)