diff --git a/rigo/data/ui/gtk3/rigo.ui b/rigo/data/ui/gtk3/rigo.ui
index 34ba831cd..00cc12d1e 100644
--- a/rigo/data/ui/gtk3/rigo.ui
+++ b/rigo/data/ui/gtk3/rigo.ui
@@ -18,6 +18,20 @@
True
False
7
+
+
+
+ False
+ False
+ 0
+
+
-
-
-
- False
- False
1
diff --git a/rigo/rigo_app.py b/rigo/rigo_app.py
index d0a3943bc..51016ba26 100644
--- a/rigo/rigo_app.py
+++ b/rigo/rigo_app.py
@@ -26,7 +26,7 @@ from entropy.exceptions import RepositoryError
from entropy.client.interfaces import Client
from entropy.services.client import WebService
from entropy.misc import TimeScheduled, ParallelTask
-from entropy.i18n import _
+from entropy.i18n import _, ngettext
import entropy.tools
@@ -361,25 +361,117 @@ class WelcomeBox(Gtk.VBox):
image.show()
-class NotificationBox(Gtk.VBox):
+class NotificationBox(Gtk.HBox):
- def __init__(self, message):
- Gtk.VBox.__init__(self)
+ """
+ Generic notification widget to be used in the
+ Rigo notification area.
+ """
+
+ def __init__(self, message, message_type=None, tooltip=None):
+ Gtk.HBox.__init__(self)
self._message = message
+ self._buttons = []
+ self._type = message_type
+ if self._type is None:
+ self._type = Gtk.MessageType.INFO
+ self._tooltip = tooltip
+
+ def add_button(self, text, clicked_callback):
+ """
+ Add a Gtk.Button() to this container.
+ Return the newly created Gtk.Button().
+ """
+ button = Gtk.Button(text)
+ button.set_use_underline(True)
+ button.connect("clicked", clicked_callback)
+ self._buttons.append(button)
+ return button
def render(self):
+ """
+ Render the Notification box filling in the container.
+ """
+ bar = Gtk.InfoBar()
+ if self._tooltip is not None:
+ bar.set_tooltip_markup(self._tooltip)
+ bar.set_message_type(self._type)
+
+ content_area = bar.get_content_area()
+ hbox = Gtk.HBox()
label = Gtk.Label()
label.set_markup(self._message)
- self.pack_start(label, False, False, 0)
+ label.set_property("expand", True)
+ label.set_alignment(0.02, 0.50)
+ hbox.pack_start(label, True, True, 0)
label.show()
+ for button in self._buttons:
+ hbox.pack_start(button, False, False, 3)
+ button.show()
+
+ content_area.set_property("expand", False)
+ content_area.add(hbox)
+ content_area.show()
+ hbox.show()
+
+ bar.show()
+ bar.get_action_area().hide()
+ self.pack_start(bar, True, True, 0)
+
+
+class UpdatesNotificationBox(NotificationBox):
+
+ def __init__(self, entropy_client, pvc,
+ updates_len, security_updates_len):
+ self._entropy = entropy_client
+ self._pvc = pvc
+
+ msg = ngettext("There is %d update",
+ "There are %d updates",
+ updates_len)
+ msg = msg % (updates_len,)
+
+ if security_updates_len > 0:
+ sec_msg = ", " + ngettext("and %d security update",
+ "and %d security updates",
+ security_updates_len)
+ sec_msg = sec_msg % (security_updates_len,)
+ msg += sec_msg
+
+ msg += ". " + _("What to do?")
+
+ NotificationBox.__init__(self, msg,
+ tooltip=_("Updates available, how about installing them?"))
+ self.add_button(_("_Update System"), self._update)
+ self.add_button(_("_Ignore"), self._dismiss)
+
+ def _update(self, button):
+ """
+ Update button callback from the updates notification box.
+ """
+ # FIXME, lxnay complete
+ print("Update Button clicked", button)
+
+ def _dismiss(self, button):
+ """
+ Dismiss the notification.
+ """
+ self.destroy()
+
class NotificationController(object):
- def __init__(self, entropy_client, notification_box):
+ """
+ Notification area widget controller code.
+ """
+
+ def __init__(self, entropy_client, pvc, notification_box):
self._entropy = entropy_client
+ self._pvc = pvc
self._box = notification_box
self._updates = None
+ self._security_updates = None
def setup(self):
GLib.timeout_add(3000, self._calculate_updates)
@@ -394,17 +486,65 @@ class NotificationController(object):
updates, removal, fine, spm_fine = \
self._entropy.calculate_updates()
self._updates = updates
+ self._security_updates = self._entropy.calculate_security_updates()
GLib.idle_add(self._notify_updates_safe)
def _notify_updates_safe(self):
- # FIXME, use ngettext here
- msg = _("There are %d updates available, want to update now?")
- msg = msg % (len(self._updates),)
- box = NotificationBox(msg)
+ updates_len = len(self._updates)
+ if updates_len == 0:
+ # no updates, do not show anything
+ return
+
+ box = UpdatesNotificationBox(
+ self._entropy, self._pvc,
+ updates_len, len(self._security_updates))
+ self.append(box)
+
+ def append(self, box, timeout=None):
+ """
+ Append a notification to the Notification area.
+ """
box.render()
- self._box.pack_start(box, False, False, False)
+ self._box.pack_start(box, False, False, 0)
box.show()
self._box.show()
+ if timeout is not None:
+ GLib.timeout_add_seconds(timeout, self.remove, box)
+
+ def append_safe(self, box, timeout=None):
+ """
+ Thread-safe version of append().
+ """
+ def _append():
+ self.append(box, timeout=timeout)
+ GLib.idle_add(_append)
+
+ def remove(self, box):
+ """
+ Remove a NotificationBox from this notification
+ area, if there.
+ """
+ if box in self._box.get_children():
+ self._box.remove(box)
+
+ def remove_safe(self, box):
+ """
+ Thread-safe version of remove().
+ """
+ GLib.idle_add(self.remove, box)
+
+ def clear(self):
+ """
+ Clear all the notifications.
+ """
+ for child in self._box.get_children():
+ self._box.remove(child)
+
+ def clear_safe(self):
+ """
+ Thread-safe version of clear().
+ """
+ GLib.idle_add(self.clear)
class Rigo(Gtk.Application):
@@ -456,7 +596,7 @@ class Rigo(Gtk.Application):
self._entropy, icons, self._entropy_ws,
self, self._search_entry, self._view)
self._nc = NotificationController(
- self._entropy, self._notification)
+ self._entropy, self._pvc, self._notification)
BROWSER_VIEW_STATE = 1
STATIC_VIEW_STATE = 2
@@ -509,9 +649,15 @@ class Rigo(Gtk.Application):
GLib.idle_add(_do_change)
def _on_style_updated(self, widget, init_css_callback, *args):
+ """
+ Gtk Style callback, nothing to see here.
+ """
init_css_callback(widget, *args)
def _show_ok_dialog(self, parent, title, message):
+ """
+ Show ugly OK dialog window.
+ """
dlg = Gtk.MessageDialog(parent=parent,
type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.OK)
@@ -521,6 +667,9 @@ class Rigo(Gtk.Application):
dlg.destroy()
def _permissions_setup(self):
+ """
+ Check execution privileges and spawn the Rigo UI.
+ """
if not entropy.tools.is_user_in_entropy_group():
# otherwise the lock handling would potentially
# fail.