source: sasview/src/sas/sasgui/guiframe/gui_statusbar.py @ 34f23c8

ticket-1249
Last change on this file since 34f23c8 was 34f23c8, checked in by Paul Kienzle <pkienzle@…>, 5 years ago

py3/wx4 compatibility changes for gui. Refs #1249

  • Property mode set to 100644
File size: 15.0 KB
Line 
1"""
2Defines and draws the status bar that should appear along the bottom of the
3main SasView window.
4"""
5import wx
6import sys
7import logging
8import datetime
9from wx import StatusBar as wxStatusB
10from wx.lib import newevent
11import wx.richtext
12from sas.sasgui.guiframe.gui_style import GUIFRAME_ICON
13
14logger = logging.getLogger(__name__)
15
16# Number of fields on the status bar
17NB_FIELDS = 4
18#position of the status bar's fields
19ICON_POSITION = 0
20MSG_POSITION = 1
21GAUGE_POSITION = 2
22CONSOLE_POSITION = 3
23BUTTON_SIZE = 40
24STATUS_BAR_ICON_SIZE = 12
25CONSOLE_WIDTH = 500
26CONSOLE_HEIGHT = 300
27
28if sys.platform.count("win32") > 0:
29    FONT_VARIANT = 0
30else:
31    FONT_VARIANT = 1
32
33GREEN = wx.Colour(95, 190, 95)
34YELLOW = wx.Colour(247, 214, 49)
35RED = wx.Colour(234, 89, 78)
36
37class ConsolePanel(wx.Panel):
38    """
39    Interaction class for adding messages to the Console log.
40    """
41    def __init__(self, parent, *args, **kwargs):
42        wx.Panel.__init__(self, parent=parent, *args, **kwargs)
43        self.parent = parent
44        self.sizer = wx.BoxSizer(wx.VERTICAL)
45
46        self.msg_txt = wx.richtext.RichTextCtrl(self, size=(CONSOLE_WIDTH-40,
47                                                CONSOLE_HEIGHT-60),
48                                   style=wx.VSCROLL|wx.HSCROLL|wx.NO_BORDER)
49
50        self.msg_txt.SetEditable(False)
51        timestamp = datetime.datetime.now()
52        status = '{:%Y-%m-%d %H:%M:%S} : No message available'.format(timestamp)
53        self.msg_txt.SetValue(status)
54        self.sizer.Add(self.msg_txt, 1, wx.EXPAND|wx.ALL, 10)
55        self.SetSizer(self.sizer)
56
57    def set_message(self, status="", event=None):
58        """
59        Adds a message to the console log as well as the main sasview.log
60
61        :param status: A status message to be sent to the console log.
62        :param event: A wx event.
63        """
64        status = str(status)
65        if status.strip() == "":
66            return
67        # Add timestamp
68        timestamp = datetime.datetime.now()
69        status = '{:%Y-%m-%d %H:%M:%S} : '.format(timestamp) + status
70        color = (0, 0, 0) #black
71        icon_bmp = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_TOOLBAR)
72        if hasattr(event, "info"):
73            icon_type = event.info.lower()
74            if icon_type == "warning":
75                logger.warning(status)
76                color = (0, 0, 255) # blue
77                icon_bmp = wx.ArtProvider.GetBitmap(wx.ART_WARNING,
78                                                    wx.ART_TOOLBAR)
79            if icon_type == "error":
80                logger.error(status)
81                color = (255, 0, 0) # red
82                icon_bmp = wx.ArtProvider.GetBitmap(wx.ART_ERROR,
83                                                    wx.ART_TOOLBAR)
84            if icon_type == "info":
85                icon_bmp = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION,
86                                                    wx.ART_TOOLBAR)
87        self.msg_txt.Newline()
88        # wx 3 has WriteImage and WriteBitmap; not sure if there
89        # is any difference between them.  wx 4 has only WriteImage.
90        self.msg_txt.WriteImage(icon_bmp)
91        self.msg_txt.BeginTextColour(color)
92        self.msg_txt.WriteText("\t")
93        self.msg_txt.AppendText(status)
94        self.msg_txt.EndTextColour()
95
96
97class Console(wx.Frame):
98    """
99    The main class defining the Console window.
100    """
101    def __init__(self, parent=None, status="", *args, **kwds):
102        kwds["size"] = (CONSOLE_WIDTH, CONSOLE_HEIGHT)
103        kwds["title"] = "Console"
104        wx.Frame.__init__(self, parent=parent, *args, **kwds)
105        self.SetWindowVariant(FONT_VARIANT)
106        self.panel = ConsolePanel(self)
107        self.panel.set_message(status=status)
108        wx.EVT_CLOSE(self, self.Close)
109
110    def set_multiple_messages(self, messages=[]):
111        """
112        Method to send an arbitrary number of messages to the console log
113
114        :param messages: A list of strings to be sent to the console log.
115        """
116        if messages:
117            for status in messages:
118                self.panel.set_message(status=status)
119
120    def set_message(self, status, event=None):
121        """
122        Exposing the base ConsolePanel set_message
123
124        :param status: A status message to be sent to the console log.
125        :param event: A wx event.
126        """
127        self.panel.set_message(status=str(status), event=event)
128
129    def Close(self, event):
130        """
131        Calling close on the panel will hide the panel.
132
133        :param event: A wx event.
134        """
135        self.Hide()
136
137class StatusBar(wxStatusB):
138    """
139        Application status bar
140    """
141    def __init__(self, parent, id):
142        wxStatusB.__init__(self, parent, id)
143        self.parent = parent
144        self.parent.SetStatusBarPane(MSG_POSITION)
145
146        #Layout of status bar
147        width = STATUS_BAR_ICON_SIZE
148        height = STATUS_BAR_ICON_SIZE
149        self.SetFieldsCount(NB_FIELDS)
150        # Leave some space for the resize handle in the last field
151        console_btn_width = 80
152        self.SetStatusWidths([width+4, -2, -1, width+console_btn_width])
153        self.SetMinHeight(height + 10)
154
155        #display default message
156        self.msg_position = MSG_POSITION
157
158        # Create progress bar
159        gauge_width = 5 * width
160        self.gauge = wx.Gauge(self, size=(gauge_width, height),
161                              style=wx.GA_HORIZONTAL)
162        self.gauge.Hide()
163
164        # Create status bar icon reflecting the type of status
165        # for the last message
166        self.status_color = wx.StaticText(self, id=wx.NewId(), label="   ",
167                                          size=wx.Size(15, 15))
168        self.status_color.SetBackgroundColour(GREEN)
169        self.status_color.SetForegroundColour(GREEN)
170
171        # Create the button used to show the console dialog
172        self.console_button = wx.Button(self, wx.NewId(), "Console",
173                                        size=(console_btn_width, -1))
174        font = self.console_button.GetFont()
175        _, pixel_h = font.GetPixelSize()
176        font.SetPixelSize(wx.Size(0, int(pixel_h*0.9)))
177        self.console_button.SetFont(font)
178        self.console_button.SetToolTipString("History of status bar messages")
179        self.console_button.Bind(wx.EVT_BUTTON, self._onMonitor,
180                                 id=self.console_button.GetId())
181
182        self.reposition()
183        ## Current progress value of the bar
184        self.nb_start = 0
185        self.nb_progress = 0
186        self.nb_stop = 0
187        self.frame = None
188        self.list_msg = []
189        self.frame = Console(parent=self)
190        if hasattr(self.frame, "IsIconized"):
191            if not self.frame.IsIconized():
192                try:
193                    icon = self.parent.GetIcon()
194                    self.frame.SetIcon(icon)
195                except:
196                    try:
197                        FRAME_ICON = wx.Icon(GUIFRAME_ICON.FRAME_ICON_PATH,
198                                             wx.BITMAP_TYPE_ICO)
199                        self.frame.SetIcon(FRAME_ICON)
200                    except:
201                        pass
202        self.frame.set_multiple_messages(self.list_msg)
203        self.frame.Hide()
204        self.progress = 0
205        self.timer = wx.Timer(self, -1)
206        self.timer_stop = wx.Timer(self, -1)
207        self.thread = None
208        self.Bind(wx.EVT_TIMER, self._on_time, self.timer)
209        self.Bind(wx.EVT_TIMER, self._on_time_stop, self.timer_stop)
210        self.Bind(wx.EVT_SIZE, self.on_size)
211        self.Bind(wx.EVT_IDLE, self.on_idle)
212
213    def reposition(self):
214        """
215        Place the various fields in their proper position
216        """
217        rect = self.GetFieldRect(GAUGE_POSITION)
218        self.gauge.SetPosition((rect.x, rect.y))
219        rect = self.GetFieldRect(ICON_POSITION)
220        self.status_color.SetPosition((rect.x, rect.y))
221        rect = self.GetFieldRect(CONSOLE_POSITION)
222        self.console_button.SetPosition((rect.x, rect.y))
223        self.size_changed = False
224
225    def on_idle(self, event):
226        """
227        When the window is idle, check if the window has been resized
228        """
229        if self.size_changed:
230            self.reposition()
231
232    def on_size(self, evt):
233        """
234        If the window is resized, redraw the window.
235        """
236        self.reposition()
237        self.size_changed = True
238
239    def get_msg_position(self):
240        """
241        Get the last known message that was displayed on the console window.
242        """
243        return self.msg_position
244
245    def SetStatusText(self, text="", number=MSG_POSITION, event=None):
246        """
247        Set the text that will be displayed in the status bar.
248        """
249        wxStatusB.SetStatusText(self, text.split('\n', 1)[0], number)
250        self.list_msg.append(text)
251        self.status_color.SetBackgroundColour(GREEN)
252        self.status_color.SetForegroundColour(GREEN)
253
254        if self.frame is not None:
255            self.frame.set_message(status=text, event=event)
256
257    def PopStatusText(self, *args, **kwds):
258        """
259        Override status bar
260        """
261        wxStatusB.PopStatusText(self, field=MSG_POSITION)
262
263    def PushStatusText(self, *args, **kwds):
264        """
265        PushStatusText
266        """
267        text = "PushStatusText: What is this string?"
268        wxStatusB.PushStatusText(self, field=MSG_POSITION, string=text)
269
270    def enable_clear_gauge(self):
271        """
272        clear the progress bar
273        """
274        flag = True
275        # Why we do this?
276        #if (self.nb_start <= self.nb_stop) or \
277        #    (self.nb_progress <= self.nb_stop):
278        #    flag = True
279        return flag
280
281    def _on_time_stop(self, evt):
282        """
283        Clear the progress bar
284
285        :param evt: wx.EVT_TIMER
286        """
287        count = 0
288        while count <= 100:
289            count += 1
290        self.timer_stop.Stop()
291        self.clear_gauge(msg="")
292        self.nb_progress = 0
293        self.nb_start = 0
294        self.nb_stop = 0
295
296    def _on_time(self, evt):
297        """
298        Update the progress bar while the timer is running
299
300        :param evt: wx.EVT_TIMER
301        """
302        # Check stop flag that can be set from non main thread
303        if self.timer.IsRunning():
304            self.gauge.Pulse()
305
306    def clear_gauge(self, msg=""):
307        """
308        Hide the gauge
309        """
310        self.progress = 0
311        self.gauge.SetValue(0)
312        self.gauge.Hide()
313
314    def set_icon(self, event):
315        """
316        Display icons related to the type of message sent to the statusbar
317        when available. No icon is displayed if the message is empty
318        """
319        if hasattr(event, "status"):
320            status = str(event.status)
321            if status.strip() == "":
322                return
323        else:
324            return
325        if not hasattr(event, "info"):
326            return
327
328        # Get the size of the button images
329        height = STATUS_BAR_ICON_SIZE
330
331        msg = event.info.lower()
332        if msg == "warning":
333            self.status_color.SetBackgroundColour(YELLOW)
334            self.status_color.SetForegroundColour(YELLOW)
335        elif msg == "error":
336            self.status_color.SetBackgroundColour(RED)
337            self.status_color.SetForegroundColour(RED)
338        else:
339            self.status_color.SetBackgroundColour(GREEN)
340            self.status_color.SetForegroundColour(GREEN)
341
342    def set_dialog(self, event):
343        """
344        Display dialogbox
345        """
346        if not hasattr(event, "info"):
347            return
348        msg = event.info.lower()
349        if msg == "error":
350            e_msg = "Error(s) Occurred:\n"
351            e_msg += "\t" + event.status + "\n\n"
352            e_msg += "Further information might be available in "
353            e_msg += "the Console log (bottom right corner)."
354            wx.MessageBox(e_msg, style=wx.ICON_ERROR)
355
356    def set_message(self, event):
357        """
358        display received message on the statusbar
359        """
360        if hasattr(event, "status"):
361            self.SetStatusText(text=str(event.status), event=event)
362
363    def set_gauge(self, event):
364        """
365        change the state of the gauge according the state of the current job
366        """
367        if not hasattr(event, "type"):
368            return
369        type = event.type
370        self.gauge.Show(True)
371        if type.lower() == "start":
372            self.nb_start += 1
373            #self.timer.Stop()
374            self.progress += 5
375            self.gauge.SetValue(int(self.progress))
376            self.progress += 5
377            if self.progress < self.gauge.GetRange() - 20:
378                self.gauge.SetValue(int(self.progress))
379        if type.lower() == "progress":
380            self.nb_progress += 1
381            self.timer.Start(1)
382            self.gauge.Pulse()
383        if type.lower() == "update":
384            self.progress += 5
385            if self.progress < self.gauge.GetRange()- 20:
386                self.gauge.SetValue(int(self.progress))
387        if type.lower() == "stop":
388            self.nb_stop += 1
389            self.gauge.Show(True)
390            if self.enable_clear_gauge():
391                self.timer.Stop()
392                self.progress = 0
393                self.gauge.SetValue(100)
394                self.timer_stop.Start(5)
395
396    def set_status(self, event):
397        """
398        Update the status bar .
399
400        :param type: type of message send.
401            type  must be in ["start","progress","update","stop"]
402        :param msg: the message itself  as string
403        :param thread: if updatting using a thread status
404
405        """
406        self.set_message(event=event)
407        self.set_icon(event=event)
408        self.set_gauge(event=event)
409        # dialog on error
410        self.set_dialog(event=event)
411
412    def _onMonitor(self, event):
413        """
414        Pop up a frame with messages sent to the status bar
415        """
416        self.frame.Show(False)
417        self.frame.Show(True)
418
419
420class SPageStatusbar(wxStatusB):
421    def __init__(self, parent, timeout=None, *args, **kwds):
422        wxStatusB.__init__(self, parent, *args, **kwds)
423        self.SetFieldsCount(1)
424        self.timeout = timeout
425        width, height = parent.GetSize()
426        self.gauge = wx.Gauge(self, style=wx.GA_HORIZONTAL,
427                              size=(width, height/10))
428        rect = self.GetFieldRect(0)
429        self.gauge.SetPosition((rect.x , rect.y ))
430        if self.timeout is not None:
431            self.gauge.SetRange(int(self.timeout))
432        self.timer = wx.Timer(self, -1)
433        self.Bind(wx.EVT_TIMER, self._on_time, self.timer)
434        self.timer.Start(1)
435        self.pos = 0
436
437    def _on_time(self, evt):
438        """
439        Update the progress bar while the timer is running
440
441        :param evt: wx.EVT_TIMER
442
443        """
444        # Check stop flag that can be set from non main thread
445        if self.timeout is None and self.timer.IsRunning():
446            self.gauge.Pulse()
447
448
449if __name__ == "__main__":
450    app = wx.PySimpleApp()
451    frame = wx.Frame(None, wx.ID_ANY, 'test frame')
452    #statusBar = StatusBar(frame, wx.ID_ANY)
453    statusBar = SPageStatusbar(frame)
454    frame.SetStatusBar(statusBar)
455    frame.Show(True)
456    #event = MessageEvent()
457    #event.type = "progress"
458    #event.status  = "statusbar...."
459    #event.info = "error"
460    #statusBar.set_status(event=event)
461    app.MainLoop()
462
Note: See TracBrowser for help on using the repository browser.