source: sasview/src/sas/sasgui/guiframe/gui_statusbar.py @ 9a26428

Last change on this file since 9a26428 was 3a22ce7, checked in by Piotr Rozyczko <rozyczko@…>, 8 years ago

Add timestamp to console messages. Fixes #637

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