changeset 159:2fa58703f1b7

Primitivus: notification bar, first draft - popup queue is now managed - notifications can auto-hide when nothing to show - ctrl-n show next notification Primitivus: ctrl-s allow to temporarily hide a popup Primitivus: cards in card_game now answer to mouse click Primitivus: notification is shown when invalid card is played in card_game Primitivus: SelectableText has now methods get_text and set_text
author Goffi <goffi@goffi.org>
date Wed, 04 Aug 2010 17:57:51 +0800
parents 74aaf230a7c3
children f494cba56a9e
files frontends/primitivus/card_game.py frontends/primitivus/custom_widgets.py frontends/primitivus/primitivus frontends/quick_frontend/quick_app.py
diffstat 4 files changed, 141 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/primitivus/card_game.py	Wed Aug 04 17:53:20 2010 +0800
+++ b/frontends/primitivus/card_game.py	Wed Aug 04 17:57:51 2010 +0800
@@ -43,6 +43,14 @@
             self._emit('click')
         return key
 
+    def mouse_event(self, size, event, button, x, y, focus):
+        if urwid.is_mouse_press(event) and button == 1:
+            self.select(not self.__selected)
+            self._emit('click')
+            return True
+        
+        return False
+    
     def select(self, state=True):
         self.__selected = state
         attr,txt = self.card.getAttrText()
@@ -275,6 +283,7 @@
         @param invalid_cards: cards which are invalid"""
         QuickCardGame.invalidCards(self, phase, played_cards, invalid_cards)
         self.hand_wid.update(self.hand)
+        self.parent.host.notify(_('Cards played are invalid !'))
         self.parent.host.redraw()
     
     def cardsPlayed(self, player, cards):
--- a/frontends/primitivus/custom_widgets.py	Wed Aug 04 17:53:20 2010 +0800
+++ b/frontends/primitivus/custom_widgets.py	Wed Aug 04 17:57:51 2010 +0800
@@ -115,6 +115,14 @@
     def getValue(self):
         return self.text
 
+    def get_text(self):
+        """for compatibility with urwid.Text"""
+        return self.getValue()
+
+    def set_text(self, text):
+        self.text=unicode(text)
+        self._invalidate()
+
     def setAttribute(self, name, value):
         """Change attribut used for rendering widget
         @param name: one of
@@ -358,6 +366,74 @@
 
 ## MISC ##
 
+class NotificationBar(urwid.WidgetWrap):
+    """Bar used to show misc information to user"""
+    signals = ['change']
+
+    def __init__(self):
+        self.waitNotifs = urwid.Text('')
+        self.message = ClickableText('', default_attr='notifs')
+        urwid.connect_signal(self.message, 'click', lambda wid: self.showNext())
+        self.columns = urwid.Columns([('fixed',6,self.waitNotifs),self.message])
+        urwid.WidgetWrap.__init__(self, urwid.AttrMap(self.columns,'notifs'))
+        self.notifs = []
+    
+    def __modQueue(self):
+        """must be called each time the notifications queue is changed"""
+        self.waitNotifs.set_text("(%i)" % len(self.notifs) if self.notifs else '')
+        self._emit('change')
+
+    def addPopUp(self, pop_up_widget):
+        """Add a popup to the waiting queue"""
+        self.notifs.append(('popup',pop_up_widget))
+        self.__modQueue()
+
+    def addMessage(self, message):
+        "Add a message to the notificatio bar"
+        if not self.message.get_text():
+            self.message.set_text(message)
+            self._invalidate()
+            self._emit('change')
+        else:
+            self.notifs.append(('message',message))
+            self.__modQueue()
+
+    def showNext(self):
+        """Show next message if any, else delete current message"""
+        found = None
+        for notif in self.notifs:
+            if notif[0] == "message":
+                found = notif
+                break
+        if found:
+            self.notifs.remove(found)
+            self.message.set_text(found[1])
+            self.__modQueue()
+        else:
+            self.message.set_text('')
+            self._emit('change')
+
+    def getNextPopup(self):
+        """Return next pop-up and remove it from the queue
+        @return: pop-up or None if there is no more in the queue"""
+        ret = None
+        for notif in self.notifs:
+            if notif[0] == 'popup':
+                ret = notif[1]
+                break
+        if ret:
+            self.notifs.remove(notif)
+            self.__modQueue()
+        return ret
+
+    def isQueueEmpty(self):
+        return not bool(self.notifs)
+
+    def canHide(self):
+        """Return True if there is now important information to show"""
+        return self.isQueueEmpty() and not self.message.get_text()
+
+
 class MenuBox(urwid.WidgetWrap):
     """Show menu items of a category in a box"""
     signals = ['click']
--- a/frontends/primitivus/primitivus	Wed Aug 04 17:53:20 2010 +0800
+++ b/frontends/primitivus/primitivus	Wed Aug 04 17:57:51 2010 +0800
@@ -57,6 +57,7 @@
                  ('selected_menu', 'light gray,bold', 'dark green'),
                  ('menuitem', 'light gray,bold', 'dark red'),
                  ('menuitem_focus', 'light gray,bold', 'dark green'),
+                 ('notifs', 'black,bold', 'yellow'),
                  ('card_neutral', 'dark gray', 'white', 'standout,underline'),
                  ('card_neutral_selected', 'dark gray', 'dark green', 'standout,underline'),
                  ('card_special', 'brown', 'white', 'standout,underline'),
@@ -80,7 +81,7 @@
     
     def __init__(self):
         self.CM = QuickContactManagement() #FIXME: not the best place
-        QuickApp.__init__(self)  #XXX: yes it's an unusual place for the constructor of a parent class, but the init order is important
+        QuickApp.__init__(self) 
         
         ## main loop setup ##
         self.main_widget = ProfileManager(self)
@@ -88,6 +89,9 @@
 
         ##misc setup##
         self.chat_wins=ChatList(self)
+        self.notBar = custom_widgets.NotificationBar()
+        urwid.connect_signal(self.notBar,'change',self.onNotification)
+        self.__saved_overlay = None
     
     def debug(self):
         """convenient method to reset screen and launch pdb"""
@@ -111,6 +115,8 @@
         self.loop.run()
 
     def inputFilter(self, input, raw):
+        if self.__saved_overlay and input != ['ctrl s']:
+            return
         for i in input:
             if isinstance(i,tuple):
                 if i[0] == 'mouse press':
@@ -129,6 +135,19 @@
                     self.main_widget.header = None
             except AttributeError:
                 pass
+        elif input == 'ctrl n':
+            """User wants to see next notification"""
+            self.notBar.showNext()
+        elif input == 'ctrl s':
+            """User wants to (un)hide overlay window"""
+            if isinstance(self.loop.widget,urwid.Overlay):
+                self.__saved_overlay = self.loop.widget
+                self.loop.widget = self.main_widget
+            else:
+                if self.__saved_overlay:
+                    self.loop.widget = self.__saved_overlay
+                    self.__saved_overlay = None
+
         elif input == 'ctrl d' and 'D' in self.bridge.getVersion(): #Debug only for dev versions
             self.debug()
         elif input == 'f2': #user wants to (un)hide the contact_list
@@ -190,10 +209,10 @@
         self.contactList = ContactList(self, self.CM, on_click = self.contactSelected, on_change=lambda w: self.redraw())
         #self.center_part = urwid.Columns([('weight',2,self.contactList),('weight',8,Chat('',self))])
         self.center_part = urwid.Columns([('weight',2,self.contactList), ('weight',8,urwid.Filler(urwid.Text('')))])
-        editBar = custom_widgets.AdvancedEdit('> ')
-        urwid.connect_signal(editBar,'click',self.onTextEntered)
+        self.editBar = custom_widgets.AdvancedEdit('> ')
+        urwid.connect_signal(self.editBar,'click',self.onTextEntered)
         self.menu_roller = self.__buildMenuRoller()
-        self.main_widget = custom_widgets.FocusFrame(self.center_part, header=self.menu_roller, footer=editBar, focus_part='footer')
+        self.main_widget = custom_widgets.FocusFrame(self.center_part, header=self.menu_roller, footer=self.editBar, focus_part='footer')
         return self.main_widget
 
     def plug_profile(self, profile_key='@DEFAULT@'):
@@ -201,11 +220,23 @@
         QuickApp.plug_profile(self, profile_key)
     
     def removePopUp(self, widget=None):
+        "Remove current pop-up, and if there is other in queue, show it"
         self.loop.widget = self.main_widget
+        next_popup = self.notBar.getNextPopup()
+        if next_popup:
+            #we still have popup to show, we display it
+            self.showPopUp(next_popup)
 
     def showPopUp(self, pop_up_widget):
-        display_widget = urwid.Overlay(pop_up_widget, self.main_widget, 'center', ('relative', 40), 'middle', ('relative', 40))
-        self.loop.widget = display_widget
+        "Show a pop-up window if possible, else put it in queue"
+        if not isinstance(self.loop.widget,urwid.Overlay):
+            display_widget = urwid.Overlay(pop_up_widget, self.main_widget, 'center', ('relative', 40), 'middle', ('relative', 40))
+            self.loop.widget = display_widget
+        else:
+            self.notBar.addPopUp(pop_up_widget)
+
+    def notify(self, message):
+        self.notBar.addMessage(message)
 
     def contactSelected(self, contact_list):
         contact = contact_list.get_contact()
@@ -233,6 +264,24 @@
         if JID(self.contactList.selected).short != sender.short:
             self.contactList.putAlert(sender)
 
+    def onNotification(self, notBar):
+        """Called when a new notification has been received"""
+        if not isinstance(self.main_widget, custom_widgets.FocusFrame):
+            #if we are not in the main configuration, we ignore the notifications bar
+            return
+        if isinstance(self.main_widget.footer,custom_widgets.AdvancedEdit):
+            if not self.notBar.canHide():
+                #the notification bar is not visible and has usefull informations, we show it
+                pile = urwid.Pile([self.notBar, self.editBar])
+                self.main_widget.footer = pile
+        else:
+            if not isinstance(self.main_widget.footer, urwid.Pile):
+                error(_("INTERNAL ERROR: Unexpected class for main widget's footer"))
+                assert(False)
+            if self.notBar.canHide():
+                #No notification left, we can hide the bar
+                self.main_widget.footer = self.editBar
+
     def actionResult(self, type, id, data):
         if not id in self.current_action_ids:
             debug (_('unknown id, ignoring'))
--- a/frontends/quick_frontend/quick_app.py	Wed Aug 04 17:53:20 2010 +0800
+++ b/frontends/quick_frontend/quick_app.py	Wed Aug 04 17:57:51 2010 +0800
@@ -72,7 +72,7 @@
         return profile in self.profiles.keys()
 
     def postInit(self):
-        """Must be called after __init__, do all automatic task (auto plug profile)"""
+        """Must be called after initialization is done, do all automatic task (auto plug profile)"""
         if self.options.profile:
             if not self.bridge.getProfileName(self.options.profile): 
                 error(_("Trying to plug an unknown profile (%s)" % self.options.profile))