msgiobuffer.py 8.31 KB
Newer Older
Ole Streicher's avatar
Ole Streicher committed
1 2 3 4 5 6
"""module 'msgiobuffer.py' -- module containing the MsgIOBuffer class.  This
   class creates a scrolling canvas composed of a message box and an I/O
   frame.  The message box contains the history of I/O messages; the I/O
   frame contains the latest I/O from the interactive program.

M.D. De La Pena, 2000 June 28
7
$Id$
Ole Streicher's avatar
Ole Streicher committed
8 9 10 11
"""
from __future__ import division # confidence high

# System level modules
12
from Tkinter import * # requires 2to3
Ole Streicher's avatar
Ole Streicher committed
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 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 172 173 174 175 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 201 202 203 204 205 206 207 208 209 210 211 212 213
import string

class MsgIOBuffer(Frame):

    """MsgIOBuffer class"""

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

        """Constructor for the MsgIOBuffer class"""

        Frame.__init__(self)

        # Initialize class attributes
        self.messageText  = ""
        self.currentText  = text
        self.minHgt       = 25 # try 65 with Tk8.5 on OSX
        self.viewHeight   = viewHeight
        self.entrySetting = StringVar()
        self.entrySetting.set("")
        self.entryValue   = self.entrySetting.get()

        self.waitFlag     = BooleanVar()
        self.waitFlag.set(TRUE)

        # Set up the frame to hold the message and the I/O
        self.parent = parent
        self.msgIO  = Frame(self.parent, bd = 2, relief = FLAT,
                            takefocus = FALSE)

        # Overlay a canvas on the frame
        self.msgIO.canvas = Canvas(self.msgIO,
                                   takefocus = FALSE,
                                   highlightthickness = 0)

        # Attach a vertical scrollbar to the canvas
        self.msgIO.vscroll = Scrollbar(self.msgIO,
                                       orient       = VERTICAL,
                                       width        = 11,
                                       relief       = SUNKEN,
                                       activerelief = RAISED,
                                       takefocus    = FALSE)
        self.msgIO.canvas['yscrollcommand']  = self.msgIO.vscroll.set
        self.msgIO.vscroll['command']        = self.msgIO.canvas.yview
        self.msgIO.vscroll.pack(side = RIGHT, fill = Y)

        # Pack the canvas
        self.msgIO.canvas.pack(side = LEFT, fill = X, expand = TRUE, padx = 4)

        ### Do not pack the frame here.  Do it in the application. ###
        #self.msgIO.pack(side = TOP, fill = X, expand = TRUE)

        # Define a frame that will sit on the canvas
        # This frame will hold a message box and a small I/O frame
        self.msgIO.canvas.f = Frame(self.msgIO.canvas)
        self.msgIO.canvas.f.pack(fill = X, expand = TRUE)

        # Generate the window for the canvas
        self.msgIO.canvas.create_window(0, 0,
                                        anchor = NW,
                                        window = self.msgIO.canvas.f)

        # Define a frame for I/O to be placed on the canvas
        self.msgIO.canvas.f.iomb = Frame(self.msgIO.canvas.f,
                                         relief = FLAT,
                                         bd     = 0)

        # Frame may contain message and a label, or message, label and entry.
        self.msgIO.canvas.f.iomb.label = Label(self.msgIO.canvas.f.iomb,
                                               text = self.currentText,
                                               bd   = 5,
                                               takefocus = FALSE)

        self.msgIO.canvas.f.iomb.entry = Entry(self.msgIO.canvas.f.iomb,
                                               highlightthickness = 0,
                                               bg           = "#d9d9d9",
                                               relief       = FLAT,
                                               textvariable = self.entrySetting,
                                               state        = DISABLED,
                                               insertwidth  = 2,
                                               takefocus    = FALSE)

        # Bind the carriage return to the entry
        self.msgIO.canvas.f.iomb.entry.bind('<Return>', self.__getEntryValue)

        # Define a message box to be placed in the frame
        self.msgIO.canvas.f.iomb.msg = Message(self.msgIO.canvas.f.iomb,
                                               bd     = 0,
                                               relief = FLAT,
                                               text   = self.messageText,
                                               anchor = SW,
                                               width  = width,
                                               takefocus = FALSE)

        # Pack the widgets in the frame
        self.msgIO.canvas.f.iomb.msg.pack(side = TOP,
                                          fill = X,
                                          expand = TRUE)
        self.msgIO.canvas.f.iomb.label.pack(side = LEFT)
        self.msgIO.canvas.f.iomb.entry.pack(side = LEFT,
                                            fill = X,
                                            expand = TRUE)
        self.msgIO.canvas.f.iomb.pack(side = TOP,
                                      fill = X,
                                      expand = TRUE)

        # The full scrolling region is the width of the parent and
        # the height of the label/entry (25) and the message box (18)
        # combined.  Hardcoded to avoid too much updating which causes
        # redraws in PyRAF.
        scrollHgt = 43
        self.msgIO.canvas.itemconfigure(1, height = scrollHgt)
        self.msgIO.canvas.configure(scrollregion = (0, 0, 0, scrollHgt))

        # The displayed portion of the window on the canvas is primarily
        # the label/entry region.
        if (self.viewHeight == None or self.viewHeight < self.minHgt):
            self.msgIO.canvas.configure(height = self.minHgt)
        else:
            self.msgIO.canvas.configure(height = self.viewHeight)

        # View is to show the information just moved into the message area
        self.msgIO.canvas.yview_moveto(1)


    def updateIO(self, text = ""):

        """Method to update the I/O portion of the scrolling canvas"""

        # Move the current contents of the I/O frame to the message box
        self.__updateMsg(self.currentText)

        # Update the class variable with the latest text
        self.currentText = text

        # Now reconfigure the I/O frame
        self.msgIO.canvas.f.iomb.label.configure(text = text)


    def readline(self):

        """Method to set focus to the Entry widget and XXX"""

        self.__enableEntry()
        self.msgIO.canvas.f.iomb.entry.wait_variable(self.waitFlag)
        self.waitFlag.set(TRUE)

        # Important to have the "\n" on the returned value
        return (self.entryValue + "\n")


    def __getEntryValue(self, event = None):

        """Private method to obtain any value entered in the Entry"""

        self.entryValue = self.entrySetting.get()
        self.msgIO.canvas.f.iomb.entry.delete(0, END)

        # Combine any label value and the entry value in order
        # to update the current text
        self.currentText = self.currentText + " " + self.entryValue

        # Disable the entry
        self.msgIO.canvas.f.iomb.entry.configure(state = DISABLED)
        self.waitFlag.set(FALSE)
        if self.lastFocus:
            self.lastFocus.focus_set()


    def __enableEntry(self):

        """Private method to put the Entry into a normal state for input."""

        # Input is requested, so enable the entry box
        f = self.focus_displayof()
        if f:
            self.lastFocus = f.focus_lastfor()
        else:
            self.lastFocus = None
        self.msgIO.canvas.f.iomb.entry.configure(state = NORMAL)
        self.msgIO.canvas.f.iomb.entry.focus_set()


    def __updateMsg(self, text = ""):

        """Private method to update the message box of the scrolling canvas."""

        # Ensure there is a new line
        text = "\n" + text

        # Append the new text to the previous message text
        self.messageText = self.messageText + text
        self.msgIO.canvas.f.iomb.msg.configure(text = self.messageText)
        self.msgIO.canvas.f.update_idletasks()

        # Reconfigure the canvas size/scrollregion based upon the message box
        mbHgt = self.msgIO.canvas.f.iomb.msg.winfo_height()
        scrollHgt = mbHgt + self.minHgt
        self.msgIO.canvas.itemconfigure(1, height = scrollHgt)
        self.msgIO.canvas.configure(scrollregion = (0, 0, 0, scrollHgt))
        self.msgIO.canvas.yview_moveto(1)