Ejemplo n.º 1
0
class Scene(quickstart.scenes.BaseScene):
    """ Desktop preferences. """

    events = {
        "changed": ["selected_channel"],
        "toggled": [
            "enable_proposed_updates", "enable_development_updates",
            "show_details_button"
        ],
        "clicked": ["refresh_button", "download_button", "install_button"],
        "realize": ["install_scene"],
        "size-allocate": ["details_textview"],
    }

    # Used to define the currently-enabled semplice-base channel.
    base_channel_enabled = None

    # Current variant
    current_variant = "current"

    building = False

    package_transactions = {}

    def on_scene_asked_to_close(self):
        """
		Do some cleanup
		"""

        # If we are in the install scene, return to the main one,
        # but only if the installation has been completed
        if self.objects.main.get_visible_child(
        ) == self.objects.install_scene and not self.handler.props.installing:
            self.objects.main.set_visible_child(self.objects.summary)

            # Clear list
            self.update_list.clear()

            # Check for updates
            self.handler.check(force=True)

            return False

        # This is relatively safe
        self.bus_cancellable.cancel()

        return True

    def prepare_scene(self):
        """
		Fired when doing the scene setup.
		"""

        self.scene_container = self.objects.main

        # Connect to the handler
        self.handler = UpdateHandler()

        # Convenience variable that houses the "installing" state.
        # Used when checking whether to continue following the apt log,
        # without flooding the DBus bus.
        self._installing = False

        # Determines if we are following the log or not
        self.following_log = False

        # APT_LOG follower (FIXME: should move it elsewhere)
        self.follower = None

        # Determines if the log has been fully written to the buffer
        self.log_written = False

        # Set appropriate font size and weight for the "Distribution upgrades" label
        context = self.objects.distribution_upgrade_label.create_pango_context(
        )
        desc = context.get_font_description()
        desc.set_weight(Pango.Weight.LIGHT)  # Weight
        desc.set_size(Pango.SCALE * 13)  # Size
        self.objects.distribution_upgrade_label.override_font(desc)

        # Do the same for the "Application updates" label
        self.objects.application_updates_label.override_font(desc)

        # ...and for the "The software is up-to-date" one
        self.objects.application_updates_status.override_font(desc)

        # ..and for the error_description
        desc.set_size(Pango.SCALE * 10)
        self.objects.error_description.override_font(desc)

        # ...and for the "Semplice is installing the updates" one
        desc.set_size(Pango.SCALE * 20)
        self.objects.install_label.override_font(desc)

        # Create unlockbar
        self.unlockbar = UnlockBar("org.semplicelinux.channels.manage")
        self.objects.summary.pack_start(self.unlockbar, False, False, 0)

        # Set-up channel combobox
        renderer = Gtk.CellRendererText()
        self.objects.selected_channel.pack_start(renderer, True)
        self.objects.selected_channel.add_attribute(renderer, "text", 1)
        self.objects.selected_channel.add_attribute(renderer, "sensitive", 2)

        # Create update list
        self.update_list = UpdateList()
        self.objects.application_updates_scroll.add(self.update_list)
        #self.objects.application_updates_content.pack_start(self.update_list, True, True, 5)
        #self.update_list.prepend(Gtk.Label("HAAA"))
        #print(len(self.update_list), self.update_list.props.empty, self.update_list.props.selection_mode)
        self.update_list.show_all()

        # Open AppStream database
        # FIXME pending AppStream API update. See #4
        #Database.open()

        #for item in ["glade", "pokerth", "banshee", "gnome-software"]:
        #	self.update_list.add_item(-1, item, "XX", "upgrade", True)

        # FIXME
        self.objects.distribution_upgrade.hide()

        # Connect to status-toggled
        self.update_list.connect("status-toggled", self.on_status_toggled)

        # Connect to lock failed:
        self.handler.connect("lock-failed", self.on_lock_failed)

        # Connect to generic failure:
        self.handler.connect("generic-failure", self.on_generic_failure)

        # Connect to update found:
        self.handler.connect("update-found", self.on_update_found)

        # Connect to package-status-changed
        self.handler.connect("package-status-changed",
                             self.on_package_status_changed)

        # Connect to package-fetch signals
        self.handler.connect("package-fetch-started",
                             self.on_package_fetch_started)
        self.handler.connect("package-fetch-failed",
                             self.on_package_fetch_failed)
        self.handler.connect("package-fetch-finished",
                             self.on_package_fetch_finished)

        # React when checking changes
        self.handler.connect("notify::checking", self.on_checking_changed)

        # React when refreshing
        self.handler.connect("notify::refreshing", self.on_refreshing_changed)

        # React when downloading
        self.handler.connect("notify::downloading",
                             self.on_downloading_changed)

        # React when installing
        self.handler.connect("notify::installing", self.on_installing_changed)

        # React when we know the total size to download
        self.handler.connect("notify::update-required-download",
                             self.on_update_required_download_changed)

        # Ensure that the updates_frame is not sensitive when the settings
        # are locked...
        self.unlockbar.bind_property("lock", self.objects.settings_frame,
                                     "sensitive",
                                     GObject.BindingFlags.INVERT_BOOLEAN)

        # Show the up-to-date container if the update list is empty
        self.update_list.bind_property(
            "empty", self.objects.application_updates_status, "visible",
            GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property(
            "status-scene", self.objects.application_updates_status,
            "visible_child_name",
            GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE)
        #self.handler.bind_property(
        #	"cache-operation",
        #	self.objects.application_updates_checking_container,
        #	"visible",
        #	GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
        #)
        #self.objects.application_updates_checking_container.bind_property(
        #	"visible",
        #	self.objects.software_uptodate_container,
        #	"visible",
        #	GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
        #)
        self.update_list.bind_property(
            "empty", self.objects.application_updates_scroll, "visible",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)
        self.update_list.bind_property(
            "empty", self.objects.application_updates_actions, "visible",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)

        # Show download indicators when the cache is refreshing
        self.handler.bind_property("cache-operation", self.objects.spinner,
                                   "active", GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property("download-operation",
                                   self.objects.download_rate, "visible",
                                   GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property("download-operation",
                                   self.objects.download_eta, "visible",
                                   GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property("download-rate", self.objects.download_rate,
                                   "label", GObject.BindingFlags.DEFAULT)
        self.handler.bind_property("download-eta", self.objects.download_eta,
                                   "label", GObject.BindingFlags.DEFAULT)
        self.handler.bind_property("download-current-item",
                                   self.objects.download_rate, "tooltip_text",
                                   GObject.BindingFlags.DEFAULT)
        self.handler.bind_property(
            "cache-operation", self.objects.refresh_button, "sensitive",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property(
            "cache-operation", self.update_list, "sensitive",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property(
            "cache-operation", self.objects.application_updates_actions,
            "sensitive", GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)

        # Remove sensitiveness on checkbuttons when downloading
        self.handler.bind_property(
            "downloading", self.update_list.package_checkbox, "sensitive",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)

        # Update labels in the action buttons accordingly to the current mode
        self.handler.bind_property("download-operation-label",
                                   self.objects.download_button, "label",
                                   GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property("install-operation-label",
                                   self.objects.install_button, "label",
                                   GObject.BindingFlags.SYNC_CREATE)

        # Update sensitiveness of the install_button when downloading
        self.handler.bind_property(
            "downloading", self.objects.install_button, "sensitive",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)

        # Update the installation_scene label
        self.handler.bind_property("install-scene-label",
                                   self.objects.install_label, "label",
                                   GObject.BindingFlags.SYNC_CREATE)

        # Show the log textview when "Show details" has been pressed
        self.objects.show_details_button.bind_property(
            "active", self.objects.details_scrolled, "visible",
            GObject.BindingFlags.SYNC_CREATE)

        # Switch into the installation mode if channels is already installing
        if self.handler.props.installing:
            self.on_installing_changed()
        else:
            # Check for updates
            if not self.handler.props.refreshing:
                self.handler.check()

            # Downloading? Enable the mode
            if self.handler.props.downloading:
                #self.on_downloading_changed()
                self.update_list.enable_downloading_mode()

    def on_install_scene_realize(self, widget):
        """
		Fired when the install_progress is going to be realized.
		"""

        # Create a circular progress bar, and add it to the stack
        self.install_progress = CircularProgressBar()
        self.objects.progress_stack.add_named(self.install_progress,
                                              "progress")

        self.handler.bind_property("install-progress", self.install_progress,
                                   "fraction",
                                   GObject.BindingFlags.SYNC_CREATE)

        # Resize the CircularProgressBar so that it has the same requested
        # width and height of the details_scrolled
        width, height = self.objects.details_scrolled.get_size_request()
        self.install_progress.set_size_request(width, height)

        # Ensure that the CircularProgressBar stays visible when the
        # details textview is not
        self.objects.details_scrolled.bind_property(
            "visible", self.install_progress, "visible",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)

        #self.install_progress.show()

        #self.objects.show_details_button.set_active(True)

    def on_show_details_button_toggled(self, button):
        """
		Fired when the show_details_button has been toggled.
		"""

        if button.props.active and not self.following_log and not self.log_written:
            self.follow_log()

    @quickstart.threads.on_idle
    def append_text(self, line):
        """
		Appends the text to the details_buffer.
		"""

        self.objects.details_buffer.insert(
            self.objects.details_buffer.get_end_iter(), line)

    @quickstart.threads.thread
    def follow_log(self):
        """
		Follows the APT logfile
		"""

        self.following_log = True

        # Wait until it has been properly created
        while not os.path.exists(APT_LOGFILE) and self._installing:
            time.sleep(1)

        if self._installing:
            self.follower = Follower(APT_LOGFILE)
            for line in self.follower:
                Gdk.threads_enter()
                self.append_text(line)
                Gdk.threads_leave()
        else:
            # Not installed anymore, the upgrade process completed
            # So directly dump the entire file:
            with open(APT_LOGFILE) as f:
                for line in f:
                    Gdk.threads_enter()
                    self.append_text(line)
                    Gdk.threads_leave()
            self.log_written = True

        self.following_log = False

    def on_details_textview_size_allocate(self, textview, allocation):
        """
		Fired when the new text has been properly allocated.
		"""

        adj = self.objects.details_scrolled.get_vadjustment()
        adj.set_value(adj.props.upper - adj.props.page_size)

    def on_installing_changed(self, handler=None, value=None):
        """
		Fired when the service began the package installation.
		"""

        # FIXME: Remove that once proper DBus proprieties caching
        # has been implemented
        self._installing = self.handler.props.installing

        # FIXME: Should move that elsewhere
        if not self._installing and self.following_log and self.follower:
            # Stop the follower
            print("Stopping follower")
            self.follower.stop()
            self.log_written = True
            self.follower = None

        if self._installing:
            # New installation, ensure that we are in a somewhat clean state
            self.log_written = False

            # Hide the details scrolled for now
            self.objects.details_scrolled.hide()

            # Finally, show the scene!
            self.scene_container.set_visible_child(self.objects.install_scene)

    def on_generic_failure(self, handler, error, description):
        """
		Fired when the APT Lock failed.
		"""

        dialog = Gtk.MessageDialog(
            self.objects.main.get_toplevel(),
            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
            Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, error)
        dialog.set_title(_("Error"))
        dialog.format_secondary_text(description)

        dialog.run()
        dialog.destroy()

    def on_lock_failed(self, handler):
        """
		Fired when the APT Lock failed.
		"""

        return self.on_generic_failure(
            handler, _("Unable to lock the APT database"),
            _("Please close the active package managers."))

    @quickstart.threads.on_idle
    def on_update_found(self, handler, id, name, version, reason, status,
                        size):
        """
		Fired when an update has been found.
		"""

        self.update_list.add_item(id, name, version, reason, status, size)

    @quickstart.threads.on_idle
    def on_package_status_changed(self, handler, id, reason):
        """
		Fired when a package status has been changed.
		"""

        print("STATUS!")
        self.update_list.update_status(id, reason)

    def on_package_fetch_started(self, handler, transaction_id, description,
                                 shortdesc):
        """
		Fired when a package is being fetched.
		"""

        if not shortdesc in self.update_list.names_with_id:
            # wat?
            return

        # Get package id from the UpdateList
        id = self.update_list.names_with_id[shortdesc]

        # Associate transaction with the id
        self.package_transactions[transaction_id] = id

        # Finally update the status on the list
        self.update_list.set_downloading(id, True)

    def on_package_fetch_failed(self, handler, transaction_id):
        """
		Fired when a package failed to download.
		"""

        # FIXME: should notify the user!

        if not transaction_id in self.package_transactions:
            return

        self.update_list.set_downloading(
            self.package_transactions[transaction_id], False)

    def on_package_fetch_finished(self, handler, transaction_id):
        """
		Fired when a package has been downloaded.
		"""

        if not transaction_id in self.package_transactions:
            return

        self.update_list.set_downloading(
            self.package_transactions[transaction_id], False)

    def on_downloading_changed(self, handler, value):
        """
		Fired when handler's downloading property changed.
		"""

        if handler.props.downloading:
            self.update_list.enable_downloading_mode()
        else:
            self.update_list.disable_downloading_mode()
            self.package_transactions = {}

    def on_status_toggled(self, updatelist, id, reason):
        """
		Fired when a package status changed locally.
		"""

        print(id, reason)

        self.handler.change_status(id, reason)

    def on_download_button_clicked(self, button):
        """
		Fired when the download button has been clicked.
		"""

        if not self.handler.props.downloading:
            # Start fetching
            self.handler.fetch()
        else:
            # Stop fetching
            self.handler.fetch_stop()

    def on_install_button_clicked(self, button):
        """
		Fired when the install button has been clicked.
		"""

        if not self.handler.props.downloading:
            # Start fetching
            self.handler.fetch(trigger_installation=True)

    def on_refresh_button_clicked(self, button):
        """
		Fired when the refresh button has been clicked.
		"""

        # Clear list
        self.update_list.clear()

        self.handler.refresh()

    def on_checking_changed(self, handler, value):
        """
		Fired when handler's checking property changed.
		"""

        print("Checking changed!")

        if not self.handler.props.checking:
            # Expand the update_list
            self.update_list.expand_all()

    def on_refreshing_changed(self, handler, value):
        """
		Fired when handler's refreshing property changed.
		"""

        if not self.handler.props.refreshing:
            # Check for updates
            self.handler.check()

    def on_update_required_download_changed(self, handler, value):
        """
		Fired when handler's update-required-download property changed.
		"""

        self.objects.download_size.show()
        self.objects.download_size.set_text(
            _("Total download size: %s") %
            self.handler.props.update_required_download)

    def on_selected_channel_changed(self, combobox):
        """
		Fired when the selected channel has been changed.
		"""

        if self.building:
            return

        new_channel = self.objects.semplice_base_channels.get_value(
            combobox.get_active_iter(), 0)

        self.Channels.Enable("(s)", new_channel)

        # Store the new choice
        self.base_channel_enabled = new_channel

        # Check for features
        self.check_for_features()

    def on_enable_proposed_updates_toggled(self, checkbutton):
        """
		Fired when the Enable proposed updates checkbutton has been toggled.
		"""

        if self.building:
            return

        (self.Channels.EnableComponent if checkbutton.get_active() else
         self.Channels.DisableComponent)("(ss)", self.base_channel_enabled,
                                         PROPOSED_COMPONENT)

    def on_enable_development_updates_toggled(self, checkbutton):
        """
		Fired when the Enable development updates checkbutton has been toggled.
		"""

        if self.building:
            return

        (self.Channels.Enable if checkbutton.get_active() else
         self.Channels.Disable)("(s)", DEVELOPMENT_CHANNEL)

    @quickstart.threads.on_idle
    def check_for_features(self):
        """
		"Enable proposed updates" and "Enable development updates" are
		available only on semplice-current.
		
		This method ensures that those checkbuttons are not sensitive if
		the selected base channel is not semplice-current and loads their
		settings if it is.
		"""

        self.building = True

        if self.base_channel_enabled == CURRENT_CHANNEL:
            self.objects.enable_development_updates.set_sensitive(True)
            if self.Channels.GetEnabled("(s)", DEVELOPMENT_CHANNEL):
                self.objects.enable_development_updates.set_active(True)
            else:
                self.objects.enable_development_updates.set_active(False)
        else:
            self.objects.enable_development_updates.set_sensitive(False)
            self.objects.enable_development_updates.set_active(False)

        # Check if the "proposed" channel is available
        if self.Channels.HasComponent("(ss)", self.base_channel_enabled,
                                      PROPOSED_COMPONENT):
            self.objects.enable_proposed_updates.set_sensitive(True)

            if self.Channels.GetComponentEnabled("(ss)",
                                                 self.base_channel_enabled,
                                                 PROPOSED_COMPONENT):
                self.objects.enable_proposed_updates.set_active(True)
            else:
                self.objects.enable_proposed_updates.set_active(False)
        else:
            self.objects.enable_proposed_updates.set_sensitive(False)
            self.objects.enable_proposed_updates.set_active(False)

        self.building = False

    @quickstart.threads.on_idle
    def load(self):
        """
		Reloads the semplice_base_channels ListStore.
		"""

        self.building = True

        self.objects.semplice_base_channels.clear()

        for channel in self.Providers.WhatProvides("(s)", BASE_PROVIDER):
            details = self.Channels.GetDetails("(sas)", channel, ["name"])
            itr = self.objects.semplice_base_channels.append([
                channel, details["name"] if "name" in details else channel,
                False if self.current_variant == "current"
                and channel != CURRENT_CHANNEL else True
            ])

            if self.Channels.GetEnabled("(s)", channel):
                self.objects.selected_channel.set_active_iter(itr)
                self.base_channel_enabled = channel

        self.check_for_features()

        # self.building will be restored by check_for_features()

    def on_scene_called(self):
        """
		Fired when the scene has been called.
		"""

        # Load current variant
        if os.path.exists(VARIANT_FILE):
            with open(VARIANT_FILE, "r") as f:
                self.current_variant = f.read().strip()

        # Enter in the bus
        self.bus_cancellable = Gio.Cancellable()
        self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
        self.Channels = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME,
            "/org/semplicelinux/channels/channels",
            "org.semplicelinux.channels.channels", self.bus_cancellable)
        self.Providers = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME,
            "/org/semplicelinux/channels/providers",
            "org.semplicelinux.channels.providers", self.bus_cancellable)

        # We are locked
        self.unlockbar.emit("locked")

        self.load()
Ejemplo n.º 2
0
class Scene(quickstart.scenes.BaseScene):
	""" Desktop preferences. """
	
	events = {
		"toggled": ("show_all", "savespace_enable"),
		"cursor-changed": ("locale_view",),
	}
	
	@quickstart.threads.thread
	def set_locale(self, locale, sel, itr):
		"""
		Sets the given locale.
		"""
		
		try:
			self.Locale.set(locale)
			self.default = itr
			
			# Create stamp
			self.Locale.create_stamp([".alan2-locale-changed"])
			
			GObject.idle_add(self.RebootDialog.show)
		except:
			sel.select_iter(self.default)

		GObject.idle_add(self.objects.region_spinner.hide)
		GObject.idle_add(self.scene_container.set_sensitive, True)
	
	@quickstart.threads.thread
	def savespace_purge(self, locale):
		"""
		Purges foreign locales.
		"""
		
		self.Locale.savespace_purge(locale)
		
		GObject.idle_add(self.objects.other_spinner.hide)
		GObject.idle_add(self.scene_container.set_sensitive, True)
	
	def on_locale_view_cursor_changed(self, locale_view):
		"""
		Fired when the user changes the locale.
		"""
				
		# selection
		sel = self.objects.locale_view.get_selection()
		if not sel: return
		
		# iter
		model, itr = sel.get_selected()
		if not itr: return
		
		selected = self.objects.locales.get_value(itr, 0)
		if selected == self.Locale.default: return
		
		if self.objects.savespace_enable.get_active():
			# Display warning
			if self.objects.savespace_warning.run() == Gtk.ResponseType.NO:
				self.objects.savespace_warning.hide()
				
				sel.select_iter(self.default)
				return
			
			self.objects.savespace_enable.set_active(False)
			self.objects.savespace_warning.hide()
		
		GObject.idle_add(self.objects.region_spinner.show)
		GObject.idle_add(self.scene_container.set_sensitive, False)
		
		self.set_locale(selected, sel, itr)
	
	def on_show_all_toggled(self, checkbutton):
		"""
		Fired when the 'Show all locales' checkbutton has been clicked.
		"""
		
		GObject.idle_add(self.build_locale_list, self.objects.show_all.get_active())
	
	def on_savespace_enable_toggled(self, checkbutton):
		"""
		Fired when the 'Enable savespace' checkbutton has been clicked.
		"""
		
		locale = self.objects.locales.get_value(self.default, 0)
		
		if checkbutton.get_active():
			self.Locale.savespace_enable(locale)
			
			# Purge window
			if self.objects.savespace_window.run() == Gtk.ResponseType.YES:
				# Purge!!
				self.savespace_purge(locale)
				
				GObject.idle_add(self.objects.other_spinner.show)
				GObject.idle_add(self.scene_container.set_sensitive, False)
			self.objects.savespace_window.hide()
		else:
			self.Locale.savespace_disable()
	
	def build_locale_list(self, all=False):
		"""
		Populates the listbox with locales.
		"""
		
		self.objects.locales.clear()
				
		for locale, human in self.Locale.human_form(all=all).items():
			if all:
				codepage = self.Locale.codepages[locale]
			else:
				codepage = ""
			itr = self.objects.locales.append((locale, human, codepage))
			
			# Save iter if this is the default...
			if locale == self.Locale.default:
				self.default = itr
		
		if self.default:
			sel = self.objects.locale_view.get_selection()
			sel.select_iter(self.default)
						
			GObject.idle_add(self.objects.locale_view.scroll_to_cell, sel.get_selected_rows()[1][0])
	
	def on_locked(self, unlockbar):
		"""
		Fired when the scene has been locked.
		"""
		
		GObject.idle_add(self.objects.content.set_sensitive, False)
	
	def on_unlocked(self, unlockbar):
		"""
		Fired when the scene has been unlocked.
		"""
		
		GObject.idle_add(self.objects.content.set_sensitive, True)
	
	def prepare_scene(self):
		""" Called when doing the scene setup. """
		
		self.scene_container = self.objects.main
		
		# Check for savespace...
		if os.path.exists("/etc/dpkg/dpkg.cfg.d/keeptalking"):
			self.objects.savespace_enable.set_active(True)
		
		# Create unlockbar
		self.unlockbar = UnlockBar("org.semplicelinux.keeptalking2.change-locale")
		self.unlockbar.connect("locked", self.on_locked)
		self.unlockbar.connect("unlocked", self.on_unlocked)
		self.objects.main.pack_start(self.unlockbar, False, False, 0)		

		self.Locale = Locale()
		
		self.default = None

		# Make the locale_view treeview working...
		locale_renderer = Gtk.CellRendererText()
		self.locale_column = Gtk.TreeViewColumn("Locale", locale_renderer, text=1)
		self.objects.locales.set_sort_column_id(1, Gtk.SortType.ASCENDING)
		self.objects.locale_view.append_column(self.locale_column)
		
		type_renderer = Gtk.CellRendererText()
		self.type_column = Gtk.TreeViewColumn("Type", type_renderer, text=2)
		self.objects.locale_view.append_column(self.type_column)

		# Populate the locale list
		GObject.idle_add(self.build_locale_list)
	
	def on_scene_called(self):
		"""
		Show the scene!
		"""
		
		# We are locked
		self.unlockbar.emit("locked")
		
		self.cancellable = Gio.Cancellable()
		self.RebootDialog = RebootDialog(self.cancellable)
		self.RebootDialog.bind_property(
			"visible",
			self.scene_container,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN
		)

	def on_scene_asked_to_close(self):
		"""
		Do some cleanup before returning home
		"""
		
		self.unlockbar.cancel_authorization()
		self.cancellable.cancel()
		
		return True
Ejemplo n.º 3
0
class Scene(quickstart.scenes.BaseScene):
    """ Desktop preferences. """

    events = {
        "value-changed": ["brightness_scale"],
    }

    building = False

    client = Up.Client.new()
    with_battery = False

    @quickstart.threads.thread
    def set_value(self, obj, value):
        """
		Sets the value
		"""

        obj('(sb)', value, True)

    def on_brightness_scale_value_changed(self, scale):
        """
		Fired when the brightness level has been changed.
		"""

        if not self.building:
            self.VeraPowerManager.SetBrightness('(i)',
                                                int(scale.get_value()) + 1)

    def on_brightness_level_changed_external(self, params=None):
        """
		Fired when the brightness level has been changed from the outside.
		"""

        if not params: params = (self.VeraPowerManager.GetBrightness(), )

        self.building = True
        self.objects.brightness_level.set_value(float(params[0]))
        self.building = False

    def on_combobox_changed(self, combobox):
        """
		Fired when a combobox has been changed.
		"""

        if self.building: return

        if combobox == self.objects.lid_switch_action:
            # Lid switch
            obj = self.VeraPowerManager.SetHandleLidSwitch
        elif combobox == self.objects.power_button_action:
            # Power button
            obj = self.VeraPowerManager.SetHandlePowerKey

        self.set_value(obj, ACTIONS[combobox.get_active()])

    def prepare_scene(self):
        """ Called when doing the scene setup. """

        self.scene_container = self.objects.main

        # g-signals
        self.signal_handlers = {
            "BrightnessChanged": self.on_brightness_level_changed_external,
        }

        # Create unlockbar
        self.unlockbar = UnlockBar(
            "org.semplicelinux.vera.powermanager.modify-logind")
        self.objects.main.pack_start(self.unlockbar, False, False, 0)

        # Search for batteries
        for device in self.client.get_devices():
            if device.props.is_present and device.props.power_supply and device.props.kind == Up.DeviceKind.BATTERY:
                # Found a power supply, and we'll show this in the UI.
                self.with_battery = device

                # We show only one battery, otherwise the UI will be a mess
                break

        # If there is a battery, bind properties to get live updates on the status
        if self.with_battery:
            # Show the battery frame
            self.objects.battery_frame.show()

            # Name
            self.objects.battery_name.set_text("%s %s" %
                                               (self.with_battery.props.vendor,
                                                self.with_battery.props.model))

            # Percentage on charge_bar
            self.with_battery.bind_property(
                "percentage", self.objects.charge_bar, "value",
                GObject.BindingFlags.DEFAULT
                | GObject.BindingFlags.SYNC_CREATE)

            # Percentage on label
            # Currently python-gi doesn't support bind_property_full(), which is
            # a shame because it's fantastic.
            # So we are using a connection here.
            percentage_callback = lambda x, y: self.objects.battery_percentage.set_text(
                "%s%%" % int(self.with_battery.props.percentage))
            self.with_battery.connect("notify::percentage",
                                      percentage_callback)
            percentage_callback(None, None)

            # Status on label
            # Unforunately, same as above.
            status_callback = lambda x, y: self.objects.battery_status.set_text(
                "%s, " % BATTERY_STATE[self.with_battery.props.state])
            self.with_battery.connect("notify::state", status_callback)
            status_callback(None, None)

        # Check for lid switch
        if os.path.exists("/proc/acpi/button/lid"):
            # That's a pretty dirty check
            self.objects.lid_switch_container.show()

        # Create cells for the comboboxes
        for combo in (self.objects.power_button_action,
                      self.objects.lid_switch_action):
            cellrenderer = Gtk.CellRendererText()
            combo.pack_start(cellrenderer, True)
            combo.add_attribute(cellrenderer, "text", 1)

            combo.connect("changed", self.on_combobox_changed)

        # Disable "Buttons" frame when locked
        self.unlockbar.bind_property(
            "lock", self.objects.buttons_frame, "sensitive",
            GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
            | GObject.BindingFlags.INVERT_BOOLEAN)

    def on_scene_called(self):
        """
		Fired when the scene has been called.
		"""

        # Locked
        self.unlockbar.emit("locked")

        # Enter in the bus
        self.bus_cancellable = Gio.Cancellable()
        self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
        self.VeraPowerManager = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME,
            "/org/semplicelinux/vera/powermanager", BUS_NAME,
            self.bus_cancellable)
        # connect signals
        self.VeraPowerManager.connect(
            "g-signal",
            lambda proxy, sender, signal, params: self.signal_handlers[signal]
            (params) if signal in self.signal_handlers else None)

        # Check for backlight support
        if self.VeraPowerManager.IsBacklightSupported():
            self.objects.display_frame.show()
            self.on_brightness_level_changed_external()
        else:
            self.objects.display_frame.hide()

        # Update comboboxes
        self.building = True
        self.objects.power_button_action.set_active(
            ACTIONS.index(self.VeraPowerManager.GetHandlePowerKey()))
        self.objects.lid_switch_action.set_active(
            ACTIONS.index(self.VeraPowerManager.GetHandleLidSwitch()))
        self.building = False

    def on_scene_asked_to_close(self):
        """
		Fired when the scene has been asked to close.
		"""

        # Close dbus connection
        self.bus_cancellable.cancel()

        return True
Ejemplo n.º 4
0
class Scene(quickstart.scenes.BaseScene):
    """ Desktop preferences. """

    events = {
        "toggled": ("show_all", "savespace_enable"),
        "cursor-changed": ("locale_view", ),
    }

    @quickstart.threads.thread
    def set_locale(self, locale, sel, itr):
        """
		Sets the given locale.
		"""

        try:
            self.Locale.set(locale)
            self.default = itr

            # Create stamp
            self.Locale.create_stamp([".alan2-locale-changed"])

            GObject.idle_add(self.RebootDialog.show)
        except:
            sel.select_iter(self.default)

        GObject.idle_add(self.objects.region_spinner.hide)
        GObject.idle_add(self.scene_container.set_sensitive, True)

    @quickstart.threads.thread
    def savespace_purge(self, locale):
        """
		Purges foreign locales.
		"""

        self.Locale.savespace_purge(locale)

        GObject.idle_add(self.objects.other_spinner.hide)
        GObject.idle_add(self.scene_container.set_sensitive, True)

    def on_locale_view_cursor_changed(self, locale_view):
        """
		Fired when the user changes the locale.
		"""

        # selection
        sel = self.objects.locale_view.get_selection()
        if not sel: return

        # iter
        model, itr = sel.get_selected()
        if not itr: return

        selected = self.objects.locales.get_value(itr, 0)
        if selected == self.Locale.default: return

        if self.objects.savespace_enable.get_active():
            # Display warning
            if self.objects.savespace_warning.run() == Gtk.ResponseType.NO:
                self.objects.savespace_warning.hide()

                sel.select_iter(self.default)
                return

            self.objects.savespace_enable.set_active(False)
            self.objects.savespace_warning.hide()

        GObject.idle_add(self.objects.region_spinner.show)
        GObject.idle_add(self.scene_container.set_sensitive, False)

        self.set_locale(selected, sel, itr)

    def on_show_all_toggled(self, checkbutton):
        """
		Fired when the 'Show all locales' checkbutton has been clicked.
		"""

        GObject.idle_add(self.build_locale_list,
                         self.objects.show_all.get_active())

    def on_savespace_enable_toggled(self, checkbutton):
        """
		Fired when the 'Enable savespace' checkbutton has been clicked.
		"""

        locale = self.objects.locales.get_value(self.default, 0)

        if checkbutton.get_active():
            self.Locale.savespace_enable(locale)

            # Purge window
            if self.objects.savespace_window.run() == Gtk.ResponseType.YES:
                # Purge!!
                self.savespace_purge(locale)

                GObject.idle_add(self.objects.other_spinner.show)
                GObject.idle_add(self.scene_container.set_sensitive, False)
            self.objects.savespace_window.hide()
        else:
            self.Locale.savespace_disable()

    def build_locale_list(self, all=False):
        """
		Populates the listbox with locales.
		"""

        self.objects.locales.clear()

        for locale, human in self.Locale.human_form(all=all).items():
            if all:
                codepage = self.Locale.codepages[locale]
            else:
                codepage = ""
            itr = self.objects.locales.append((locale, human, codepage))

            # Save iter if this is the default...
            if locale == self.Locale.default:
                self.default = itr

        if self.default:
            sel = self.objects.locale_view.get_selection()
            sel.select_iter(self.default)

            GObject.idle_add(self.objects.locale_view.scroll_to_cell,
                             sel.get_selected_rows()[1][0])

    def on_locked(self, unlockbar):
        """
		Fired when the scene has been locked.
		"""

        GObject.idle_add(self.objects.content.set_sensitive, False)

    def on_unlocked(self, unlockbar):
        """
		Fired when the scene has been unlocked.
		"""

        GObject.idle_add(self.objects.content.set_sensitive, True)

    def prepare_scene(self):
        """ Called when doing the scene setup. """

        self.scene_container = self.objects.main

        # Check for savespace...
        if os.path.exists("/etc/dpkg/dpkg.cfg.d/keeptalking"):
            self.objects.savespace_enable.set_active(True)

        # Create unlockbar
        self.unlockbar = UnlockBar(
            "org.semplicelinux.keeptalking2.change-locale")
        self.unlockbar.connect("locked", self.on_locked)
        self.unlockbar.connect("unlocked", self.on_unlocked)
        self.objects.main.pack_start(self.unlockbar, False, False, 0)

        self.Locale = Locale()

        self.default = None

        # Make the locale_view treeview working...
        locale_renderer = Gtk.CellRendererText()
        self.locale_column = Gtk.TreeViewColumn("Locale",
                                                locale_renderer,
                                                text=1)
        self.objects.locales.set_sort_column_id(1, Gtk.SortType.ASCENDING)
        self.objects.locale_view.append_column(self.locale_column)

        type_renderer = Gtk.CellRendererText()
        self.type_column = Gtk.TreeViewColumn("Type", type_renderer, text=2)
        self.objects.locale_view.append_column(self.type_column)

        # Populate the locale list
        GObject.idle_add(self.build_locale_list)

    def on_scene_called(self):
        """
		Show the scene!
		"""

        # We are locked
        self.unlockbar.emit("locked")

        self.cancellable = Gio.Cancellable()
        self.RebootDialog = RebootDialog(self.cancellable)
        self.RebootDialog.bind_property("visible", self.scene_container,
                                        "sensitive",
                                        GObject.BindingFlags.INVERT_BOOLEAN)

    def on_scene_asked_to_close(self):
        """
		Do some cleanup before returning home
		"""

        self.unlockbar.cancel_authorization()
        self.cancellable.cancel()

        return True
Ejemplo n.º 5
0
class Scene(quickstart.scenes.BaseScene):
    """
	Keyboard settings.
	"""

    events = {
        "changed": ("model_combo", ),
        "cursor-changed": ("layout_view", "variant_view"),
    }

    def setxkbmap(self):
        """
		Invokes setxkbmap and sets the currently selected layout, variant
		and model.
		"""

        subprocess.call([
            "setxkbmap", self.Keyboard.default_layout,
            self.Keyboard.default_variant if self.Keyboard.default_variant else
            "", "-model" if self.Keyboard.default_model else "",
            self.Keyboard.default_model if self.Keyboard.default_model else ""
        ])

    def on_model_combo_changed(self, combobox):
        """
		Fired when the user changes the keyboard model.
		"""

        itr = self.objects.model_combo.get_active_iter()
        if not itr: return

        selected = self.objects.models.get_value(itr, 0)
        if selected == self.Keyboard.default_variant: return

        try:
            self.Keyboard.set(model=selected)
            self.default_model = itr

            self.setxkbmap()
        except:
            self.objects.model_combo.set_active_iter(self.default_model)

    def on_variant_view_cursor_changed(self, treeview):
        """
		Fired when the user changes the variant.
		"""

        if self.building_list: return

        # selection
        sel = treeview.get_selection()
        if not sel: return

        # iter
        model, itr = sel.get_selected()
        if not itr: return

        selected = self.objects.variants.get_value(itr, 0)
        if selected == self.Keyboard.default_variant: return

        try:
            self.Keyboard.set(variant=selected)
            self.default_variant = itr

            self.setxkbmap()
        except:
            sel.select_iter(self.default_variant)

    def on_layout_view_cursor_changed(self, treeview):
        """
		Fired when the user changes the locale.
		"""

        # selection
        sel = treeview.get_selection()
        if not sel: return

        # iter
        model, itr = sel.get_selected()
        if not itr: return

        selected = self.objects.layouts.get_value(itr, 0)
        if selected == self.Keyboard.default_layout: return

        # Reset default variant
        self.default_variant = None

        try:
            self.Keyboard.set(layout=selected, variant='')
            self.default = itr

            self.setxkbmap()
        except:
            sel.select_iter(self.default)

        # Rebuild variant list
        GObject.idle_add(self.build_variant_list, selected)

    def build_variant_list(self, layout):
        """
		Populates the variant list.
		"""

        self.building_list = True

        self.objects.variants.clear()

        models, layouts = self.Keyboard.supported()
        variants = layouts[layout]["variants"]

        for variant in variants:
            for item, key in variant.items():

                itr = self.objects.variants.append([item, key])

                # Save the default variant
                if item == self.Keyboard.default_variant:
                    self.default_variant = itr

        # No variant
        reciter = self.objects.variants.prepend(
            ['', layouts[layout]["description"]])

        sel = self.objects.variant_view.get_selection()
        if self.default_variant:
            sel.select_iter(self.default)
        else:
            # No variant, use reciter
            sel.select_iter(reciter)

        GObject.idle_add(self.objects.variant_view.scroll_to_cell,
                         sel.get_selected_rows()[1][0])

        self.building_list = False

    def build_model_list(self):
        """
		Populates the model list.
		"""

        pc105 = None

        self.objects.models.clear()

        models = self.Keyboard.supported()[0]

        for item, key in models.items():
            itr = self.objects.models.append([item, key])

            # Save the default model
            if item == self.Keyboard.default_model:
                self.default_model = itr
            elif item == "pc105":
                # Fallback
                pc105 = itr

        if self.default_model:
            self.objects.model_combo.set_active_iter(self.default_model)
        else:
            self.objects.model_combo.set_active_iter(pc105)

    def build_layout_list(self):
        """
		Populates the layout list.
		"""

        self.objects.layouts.clear()

        models, layouts = self.Keyboard.supported()

        for item, key in layouts.items():
            itr = self.objects.layouts.append([item, key["description"]])

            # Save iter if it's the default...
            if item == self.Keyboard.default_layout:
                self.default = itr

        if self.default:
            sel = self.objects.layout_view.get_selection()
            sel.select_iter(self.default)

            GObject.idle_add(self.objects.layout_view.scroll_to_cell,
                             sel.get_selected_rows()[1][0])

            # Also build the variant view!
            GObject.idle_add(self.build_variant_list,
                             self.Keyboard.default_layout)

    def on_locked(self, unlockbar):
        """
		Fired when the scene has been locked.
		"""

        GObject.idle_add(self.objects.content.set_sensitive, False)

    def on_unlocked(self, unlockbar):
        """
		Fired when the scene has been unlocked.
		"""

        GObject.idle_add(self.objects.content.set_sensitive, True)

    def prepare_scene(self):
        """ Called when doing the scene setup. """

        self.scene_container = self.objects.main

        # Create unlockbar
        self.unlockbar = UnlockBar("org.freedesktop.locale1.set-keyboard")
        self.unlockbar.connect("locked", self.on_locked)
        self.unlockbar.connect("unlocked", self.on_unlocked)
        self.objects.main.pack_start(self.unlockbar, False, False, 0)

        self.Keyboard = Keyboard()

        self.building_list = False

        self.default = None
        self.default_variant = None
        self.default_model = None

        # Make the layout_view treeview working...
        layout_renderer = Gtk.CellRendererText()
        self.layout_column = Gtk.TreeViewColumn("Layout",
                                                layout_renderer,
                                                text=1)
        self.objects.layouts.set_sort_column_id(1, Gtk.SortType.ASCENDING)
        self.objects.layout_view.append_column(self.layout_column)

        # Do the same for the variant_view...
        variant_renderer = Gtk.CellRendererText()
        self.variant_column = Gtk.TreeViewColumn("Variant",
                                                 variant_renderer,
                                                 text=1)
        self.objects.variants.set_sort_column_id(1, Gtk.SortType.ASCENDING)
        self.objects.variant_view.append_column(self.variant_column)

        # And something similar for the model combobox...
        model_renderer = Gtk.CellRendererText()
        self.objects.model_combo.pack_start(model_renderer, True)
        self.objects.model_combo.add_attribute(model_renderer, "text", 1)
        self.objects.models.set_sort_column_id(1, Gtk.SortType.ASCENDING)

        # Populate the layout list
        GObject.idle_add(self.build_layout_list)

        # Populate the model list
        GObject.idle_add(self.build_model_list)

    def on_scene_called(self):
        """
		Show the scene!
		"""

        # We are locked
        self.unlockbar.emit("locked")

    def on_scene_asked_to_close(self):
        """
		Do some cleanup before returning home
		"""

        self.unlockbar.cancel_authorization()

        return True
Ejemplo n.º 6
0
class Scene(quickstart.scenes.BaseScene):
	""" Desktop preferences. """
	
	events = {
		"value-changed" : ["brightness_scale"],
	}
	
	building = False
	
	client = Up.Client.new()
	with_battery = False
	
	@quickstart.threads.thread
	def set_value(self, obj, value):
		"""
		Sets the value
		"""
		
		obj('(sb)', value, True)
	
	def on_brightness_scale_value_changed(self, scale):
		"""
		Fired when the brightness level has been changed.
		"""
		
		if not self.building:
			self.VeraPowerManager.SetBrightness('(i)', int(scale.get_value())+1)
	
	def on_brightness_level_changed_external(self, params=None):
		"""
		Fired when the brightness level has been changed from the outside.
		"""
		
		if not params: params = (self.VeraPowerManager.GetBrightness(),)
		
		self.building = True
		self.objects.brightness_level.set_value(float(params[0]))
		self.building = False
	
	def on_combobox_changed(self, combobox):
		"""
		Fired when a combobox has been changed.
		"""
		
		if self.building: return
		
		if combobox == self.objects.lid_switch_action:
			# Lid switch
			obj = self.VeraPowerManager.SetHandleLidSwitch
		elif combobox == self.objects.power_button_action:
			# Power button
			obj = self.VeraPowerManager.SetHandlePowerKey
		
		self.set_value(obj, ACTIONS[combobox.get_active()])
	
	def prepare_scene(self):
		""" Called when doing the scene setup. """
		
		self.scene_container = self.objects.main
		
		# g-signals
		self.signal_handlers = {
			"BrightnessChanged" : self.on_brightness_level_changed_external,
		}
		
		# Create unlockbar
		self.unlockbar = UnlockBar("org.semplicelinux.vera.powermanager.modify-logind")
		self.objects.main.pack_start(self.unlockbar, False, False, 0)
				
		# Search for batteries
		for device in self.client.get_devices():
			if device.props.is_present and device.props.power_supply and device.props.kind == Up.DeviceKind.BATTERY:
				# Found a power supply, and we'll show this in the UI.
				self.with_battery = device
				
				# We show only one battery, otherwise the UI will be a mess
				break
		
		# If there is a battery, bind properties to get live updates on the status
		if self.with_battery:
			# Show the battery frame
			self.objects.battery_frame.show()
			
			# Name
			self.objects.battery_name.set_text(
				"%s %s" % (
					self.with_battery.props.vendor, self.with_battery.props.model
				)
			)
			
			# Percentage on charge_bar
			self.with_battery.bind_property(
				"percentage",
				self.objects.charge_bar,
				"value",
				GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
			)
			
			# Percentage on label
			# Currently python-gi doesn't support bind_property_full(), which is
			# a shame because it's fantastic.
			# So we are using a connection here.
			percentage_callback = lambda x, y: self.objects.battery_percentage.set_text("%s%%" % int(self.with_battery.props.percentage))
			self.with_battery.connect(
				"notify::percentage",
				percentage_callback
			)
			percentage_callback(None, None)
			
			# Status on label
			# Unforunately, same as above.
			status_callback = lambda x, y: self.objects.battery_status.set_text("%s, " % BATTERY_STATE[self.with_battery.props.state])
			self.with_battery.connect(
				"notify::state",
				status_callback
			)
			status_callback(None, None)
		
		# Check for lid switch
		if os.path.exists("/proc/acpi/button/lid"):
			# That's a pretty dirty check
			self.objects.lid_switch_container.show()
		
		# Create cells for the comboboxes
		for combo in (self.objects.power_button_action, self.objects.lid_switch_action):
			cellrenderer = Gtk.CellRendererText()
			combo.pack_start(cellrenderer, True)
			combo.add_attribute(cellrenderer, "text", 1)
			
			combo.connect("changed", self.on_combobox_changed)
		
		# Disable "Buttons" frame when locked
		self.unlockbar.bind_property(
			"lock",
			self.objects.buttons_frame,
			"sensitive",
			GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN
		)
	
	def on_scene_called(self):
		"""
		Fired when the scene has been called.
		"""
		
		# Locked
		self.unlockbar.emit("locked")

		# Enter in the bus
		self.bus_cancellable = Gio.Cancellable()
		self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
		self.VeraPowerManager = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/vera/powermanager",
			BUS_NAME,
			self.bus_cancellable
		)
		# connect signals
		self.VeraPowerManager.connect(
			"g-signal",
			lambda proxy, sender, signal, params: self.signal_handlers[signal](params) if signal in self.signal_handlers else None
		)
		
		# Check for backlight support
		if self.VeraPowerManager.IsBacklightSupported():
			self.objects.display_frame.show()
			self.on_brightness_level_changed_external()
		else:
			self.objects.display_frame.hide()
		
		# Update comboboxes
		self.building = True
		self.objects.power_button_action.set_active(
			ACTIONS.index(self.VeraPowerManager.GetHandlePowerKey())
		)
		self.objects.lid_switch_action.set_active(
			ACTIONS.index(self.VeraPowerManager.GetHandleLidSwitch())
		)
		self.building = False
	
	def on_scene_asked_to_close(self):
		"""
		Fired when the scene has been asked to close.
		"""
		
		# Close dbus connection
		self.bus_cancellable.cancel()
		
		return True
Ejemplo n.º 7
0
class Scene(quickstart.scenes.BaseScene):
	"""
	Keyboard settings.
	"""
	
	events = {
		"changed": ("model_combo", ),
		"cursor-changed": ("layout_view", "variant_view"),
	}
	
	def setxkbmap(self):
		"""
		Invokes setxkbmap and sets the currently selected layout, variant
		and model.
		"""
		
		subprocess.call(
			[
				"setxkbmap",
				self.Keyboard.default_layout,
				self.Keyboard.default_variant if self.Keyboard.default_variant else "",
				"-model" if self.Keyboard.default_model else "",
				self.Keyboard.default_model if self.Keyboard.default_model else ""
			]
		)
	
	def on_model_combo_changed(self, combobox):
		"""
		Fired when the user changes the keyboard model.
		"""
		
		itr = self.objects.model_combo.get_active_iter()
		if not itr: return
		
		selected = self.objects.models.get_value(itr, 0)
		if selected == self.Keyboard.default_variant: return
		
		try:
			self.Keyboard.set(model=selected)
			self.default_model = itr
			
			self.setxkbmap()
		except:
			self.objects.model_combo.set_active_iter(self.default_model)
	
	def on_variant_view_cursor_changed(self, treeview):
		"""
		Fired when the user changes the variant.
		"""
		
		if self.building_list: return
		
		# selection
		sel = treeview.get_selection()
		if not sel: return
		
		# iter
		model, itr = sel.get_selected()
		if not itr: return
		
		selected = self.objects.variants.get_value(itr, 0)
		if selected == self.Keyboard.default_variant: return

		try:
			self.Keyboard.set(variant=selected)
			self.default_variant = itr

			self.setxkbmap()
		except:
			sel.select_iter(self.default_variant)		
	
	def on_layout_view_cursor_changed(self, treeview):
		"""
		Fired when the user changes the locale.
		"""
		
		# selection
		sel = treeview.get_selection()
		if not sel: return
		
		# iter
		model, itr = sel.get_selected()
		if not itr: return
		
		selected = self.objects.layouts.get_value(itr, 0)
		if selected == self.Keyboard.default_layout: return

		# Reset default variant
		self.default_variant = None

		try:
			self.Keyboard.set(layout=selected, variant='')
			self.default = itr
			
			self.setxkbmap()
		except:
			sel.select_iter(self.default)

		# Rebuild variant list
		GObject.idle_add(self.build_variant_list, selected)
	
	def build_variant_list(self, layout):
		"""
		Populates the variant list.
		"""
		
		self.building_list = True
				
		self.objects.variants.clear()
		
		models, layouts = self.Keyboard.supported()
		variants = layouts[layout]["variants"]
		
		for variant in variants:
			for item, key in variant.items():
				
				itr = self.objects.variants.append([item, key])
				
				# Save the default variant
				if item == self.Keyboard.default_variant:
					self.default_variant = itr
		
		# No variant
		reciter = self.objects.variants.prepend(['', layouts[layout]["description"]])
		
		sel = self.objects.variant_view.get_selection()
		if self.default_variant:
			sel.select_iter(self.default)
		else:
			# No variant, use reciter
			sel.select_iter(reciter)
		
		GObject.idle_add(self.objects.variant_view.scroll_to_cell, sel.get_selected_rows()[1][0])
		
		self.building_list = False
	
	def build_model_list(self):
		"""
		Populates the model list.
		"""
		
		pc105 = None
		
		self.objects.models.clear()
		
		models = self.Keyboard.supported()[0]
		
		for item, key in models.items():
			itr = self.objects.models.append([item, key])
			
			# Save the default model
			if item == self.Keyboard.default_model:
				self.default_model = itr
			elif item == "pc105":
				# Fallback
				pc105 = itr
		
		if self.default_model:
			self.objects.model_combo.set_active_iter(self.default_model)
		else:
			self.objects.model_combo.set_active_iter(pc105)

	
	def build_layout_list(self):
		"""
		Populates the layout list.
		"""
		
		self.objects.layouts.clear()
		
		models, layouts = self.Keyboard.supported()
		
		for item, key in layouts.items():
			itr = self.objects.layouts.append([item, key["description"]])
			
			# Save iter if it's the default...
			if item == self.Keyboard.default_layout:
				self.default = itr
		
		if self.default:
			sel = self.objects.layout_view.get_selection()
			sel.select_iter(self.default)
						
			GObject.idle_add(self.objects.layout_view.scroll_to_cell, sel.get_selected_rows()[1][0])
			
			# Also build the variant view!
			GObject.idle_add(self.build_variant_list, self.Keyboard.default_layout)
	
	def on_locked(self, unlockbar):
		"""
		Fired when the scene has been locked.
		"""
		
		GObject.idle_add(self.objects.content.set_sensitive, False)
	
	def on_unlocked(self, unlockbar):
		"""
		Fired when the scene has been unlocked.
		"""
		
		GObject.idle_add(self.objects.content.set_sensitive, True)
	
	def prepare_scene(self):
		""" Called when doing the scene setup. """
		
		self.scene_container = self.objects.main
		
		# Create unlockbar
		self.unlockbar = UnlockBar("org.freedesktop.locale1.set-keyboard")
		self.unlockbar.connect("locked", self.on_locked)
		self.unlockbar.connect("unlocked", self.on_unlocked)
		self.objects.main.pack_start(self.unlockbar, False, False, 0)		

		self.Keyboard = Keyboard()
		
		self.building_list = False
		
		self.default = None
		self.default_variant = None
		self.default_model = None

		# Make the layout_view treeview working...
		layout_renderer = Gtk.CellRendererText()
		self.layout_column = Gtk.TreeViewColumn("Layout", layout_renderer, text=1)
		self.objects.layouts.set_sort_column_id(1, Gtk.SortType.ASCENDING)
		self.objects.layout_view.append_column(self.layout_column)
		
		# Do the same for the variant_view...
		variant_renderer = Gtk.CellRendererText()
		self.variant_column = Gtk.TreeViewColumn("Variant", variant_renderer, text=1)
		self.objects.variants.set_sort_column_id(1, Gtk.SortType.ASCENDING)
		self.objects.variant_view.append_column(self.variant_column)
		
		# And something similar for the model combobox...
		model_renderer = Gtk.CellRendererText()
		self.objects.model_combo.pack_start(model_renderer, True)
		self.objects.model_combo.add_attribute(model_renderer, "text", 1)
		self.objects.models.set_sort_column_id(1, Gtk.SortType.ASCENDING)

		# Populate the layout list
		GObject.idle_add(self.build_layout_list)
		
		# Populate the model list
		GObject.idle_add(self.build_model_list)
	
	def on_scene_called(self):
		"""
		Show the scene!
		"""
		
		# We are locked
		self.unlockbar.emit("locked")

	def on_scene_asked_to_close(self):
		"""
		Do some cleanup before returning home
		"""
		
		self.unlockbar.cancel_authorization()
				
		return True
class Scene(quickstart.scenes.BaseScene):
	""" Desktop preferences. """
	
	events = {
		"changed" : ["selected_channel"],
		"toggled" : ["enable_proposed_updates", "enable_development_updates", "show_details_button"],
		"clicked" : ["refresh_button", "download_button", "install_button"],
		"realize" : ["install_scene"],
		"size-allocate" : ["details_textview"],
	}
	
	# Used to define the currently-enabled semplice-base channel. 
	base_channel_enabled = None
	
	# Current variant
	current_variant = "current"
	
	building = False
	
	package_transactions = {}
	
	def on_scene_asked_to_close(self):
		"""
		Do some cleanup
		"""
		
		# If we are in the install scene, return to the main one,
		# but only if the installation has been completed
		if self.objects.main.get_visible_child() == self.objects.install_scene and not self.handler.props.installing:
			self.objects.main.set_visible_child(self.objects.summary)
			
			# Clear list
			self.update_list.clear()
			
			# Check for updates
			self.handler.check(force=True)
			
			return False
		
		# This is relatively safe
		self.bus_cancellable.cancel()
		
		return True
	
	def prepare_scene(self):
		"""
		Fired when doing the scene setup.
		"""
				
		self.scene_container = self.objects.main
		
		# Connect to the handler
		self.handler = UpdateHandler()
		
		# Convenience variable that houses the "installing" state.
		# Used when checking whether to continue following the apt log,
		# without flooding the DBus bus.
		self._installing = False
		
		# Determines if we are following the log or not
		self.following_log = False
		
		# APT_LOG follower (FIXME: should move it elsewhere)
		self.follower = None
		
		# Determines if the log has been fully written to the buffer
		self.log_written = False

		# Set appropriate font size and weight for the "Distribution upgrades" label
		context = self.objects.distribution_upgrade_label.create_pango_context()
		desc = context.get_font_description()
		desc.set_weight(Pango.Weight.LIGHT) # Weight
		desc.set_size(Pango.SCALE*13) # Size
		self.objects.distribution_upgrade_label.override_font(desc)
		
		# Do the same for the "Application updates" label
		self.objects.application_updates_label.override_font(desc)
		
		# ...and for the "The software is up-to-date" one
		self.objects.application_updates_status.override_font(desc)
		
		# ..and for the error_description
		desc.set_size(Pango.SCALE*10)
		self.objects.error_description.override_font(desc)
		
		# ...and for the "Semplice is installing the updates" one
		desc.set_size(Pango.SCALE*20)
		self.objects.install_label.override_font(desc)

		# Create unlockbar
		self.unlockbar = UnlockBar("org.semplicelinux.channels.manage")
		self.objects.summary.pack_start(self.unlockbar, False, False, 0)

		# Set-up channel combobox
		renderer = Gtk.CellRendererText()
		self.objects.selected_channel.pack_start(renderer, True)
		self.objects.selected_channel.add_attribute(renderer, "text", 1)
		self.objects.selected_channel.add_attribute(renderer, "sensitive", 2)
		
		# Create update list
		self.update_list = UpdateList()
		self.objects.application_updates_scroll.add(self.update_list)
		#self.objects.application_updates_content.pack_start(self.update_list, True, True, 5)
		#self.update_list.prepend(Gtk.Label("HAAA"))
		#print(len(self.update_list), self.update_list.props.empty, self.update_list.props.selection_mode)
		self.update_list.show_all()
		
		# Open AppStream database
		# FIXME pending AppStream API update. See #4
		#Database.open()
		
		#for item in ["glade", "pokerth", "banshee", "gnome-software"]:
		#	self.update_list.add_item(-1, item, "XX", "upgrade", True)
		
		# FIXME
		self.objects.distribution_upgrade.hide()
		
		# Connect to status-toggled
		self.update_list.connect("status-toggled", self.on_status_toggled)
		
		# Connect to lock failed:
		self.handler.connect("lock-failed", self.on_lock_failed)
		
		# Connect to generic failure:
		self.handler.connect("generic-failure", self.on_generic_failure)
		
		# Connect to update found:
		self.handler.connect("update-found", self.on_update_found)
		
		# Connect to package-status-changed
		self.handler.connect("package-status-changed", self.on_package_status_changed)
		
		# Connect to package-fetch signals
		self.handler.connect("package-fetch-started", self.on_package_fetch_started)
		self.handler.connect("package-fetch-failed", self.on_package_fetch_failed)
		self.handler.connect("package-fetch-finished", self.on_package_fetch_finished)

		# React when checking changes
		self.handler.connect("notify::checking", self.on_checking_changed)
		
		# React when refreshing
		self.handler.connect("notify::refreshing", self.on_refreshing_changed)
		
		# React when downloading
		self.handler.connect("notify::downloading", self.on_downloading_changed)
		
		# React when installing
		self.handler.connect("notify::installing", self.on_installing_changed)
		
		# React when we know the total size to download
		self.handler.connect("notify::update-required-download", self.on_update_required_download_changed)
		
		# Ensure that the updates_frame is not sensitive when the settings
		# are locked...
		self.unlockbar.bind_property(
			"lock",
			self.objects.settings_frame,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN
		)
		
		# Show the up-to-date container if the update list is empty
		self.update_list.bind_property(
			"empty",
			self.objects.application_updates_status,
			"visible",
			GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"status-scene",
			self.objects.application_updates_status,
			"visible_child_name",
			GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
		)
		#self.handler.bind_property(
		#	"cache-operation",
		#	self.objects.application_updates_checking_container,
		#	"visible",
		#	GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
		#)
		#self.objects.application_updates_checking_container.bind_property(
		#	"visible",
		#	self.objects.software_uptodate_container,
		#	"visible",
		#	GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		#)
		self.update_list.bind_property(
			"empty",
			self.objects.application_updates_scroll,
			"visible",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		self.update_list.bind_property(
			"empty",
			self.objects.application_updates_actions,
			"visible",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
				
		# Show download indicators when the cache is refreshing
		self.handler.bind_property(
			"cache-operation",
			self.objects.spinner,
			"active",
			GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"download-operation",
			self.objects.download_rate,
			"visible",
			GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"download-operation",
			self.objects.download_eta,
			"visible",
			GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"download-rate",
			self.objects.download_rate,
			"label",
			GObject.BindingFlags.DEFAULT
		)
		self.handler.bind_property(
			"download-eta",
			self.objects.download_eta,
			"label",
			GObject.BindingFlags.DEFAULT
		)
		self.handler.bind_property(
			"download-current-item",
			self.objects.download_rate,
			"tooltip_text",
			GObject.BindingFlags.DEFAULT
		)
		self.handler.bind_property(
			"cache-operation",
			self.objects.refresh_button,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"cache-operation",
			self.update_list,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"cache-operation",
			self.objects.application_updates_actions,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		
		# Remove sensitiveness on checkbuttons when downloading
		self.handler.bind_property(
			"downloading",
			self.update_list.package_checkbox,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		
		# Update labels in the action buttons accordingly to the current mode
		self.handler.bind_property(
			"download-operation-label",
			self.objects.download_button,
			"label",
			GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"install-operation-label",
			self.objects.install_button,
			"label",
			GObject.BindingFlags.SYNC_CREATE
		)
		
		# Update sensitiveness of the install_button when downloading
		self.handler.bind_property(
			"downloading",
			self.objects.install_button,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		
		# Update the installation_scene label
		self.handler.bind_property(
			"install-scene-label",
			self.objects.install_label,
			"label",
			GObject.BindingFlags.SYNC_CREATE
		)
		
		# Show the log textview when "Show details" has been pressed
		self.objects.show_details_button.bind_property(
			"active",
			self.objects.details_scrolled,
			"visible",
			GObject.BindingFlags.SYNC_CREATE
		)
		
		# Switch into the installation mode if channels is already installing
		if self.handler.props.installing:
			self.on_installing_changed()
		else:
			# Check for updates
			if not self.handler.props.refreshing:
				self.handler.check()
			
			# Downloading? Enable the mode
			if self.handler.props.downloading:
				#self.on_downloading_changed()
				self.update_list.enable_downloading_mode()
	
	def on_install_scene_realize(self, widget):
		"""
		Fired when the install_progress is going to be realized.
		"""
				
		# Create a circular progress bar, and add it to the stack
		self.install_progress = CircularProgressBar()
		self.objects.progress_stack.add_named(self.install_progress, "progress")
		
		self.handler.bind_property(
			"install-progress",
			self.install_progress,
			"fraction",
			GObject.BindingFlags.SYNC_CREATE
		)
		
		# Resize the CircularProgressBar so that it has the same requested
		# width and height of the details_scrolled
		width, height = self.objects.details_scrolled.get_size_request()
		self.install_progress.set_size_request(width, height)
		
		# Ensure that the CircularProgressBar stays visible when the
		# details textview is not
		self.objects.details_scrolled.bind_property(
			"visible",
			self.install_progress,
			"visible",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		
		
		#self.install_progress.show()
		
		#self.objects.show_details_button.set_active(True)
	
	def on_show_details_button_toggled(self, button):
		"""
		Fired when the show_details_button has been toggled.
		"""
		
		if button.props.active and not self.following_log and not self.log_written:
			self.follow_log()
	
	@quickstart.threads.on_idle
	def append_text(self, line):
		"""
		Appends the text to the details_buffer.
		"""
		
		self.objects.details_buffer.insert(
			self.objects.details_buffer.get_end_iter(),
			line
		)
	
	@quickstart.threads.thread
	def follow_log(self):
		"""
		Follows the APT logfile
		"""
		
		self.following_log = True
		
		# Wait until it has been properly created
		while not os.path.exists(APT_LOGFILE) and self._installing:
			time.sleep(1)
		
		if self._installing:
			self.follower = Follower(APT_LOGFILE)
			for line in self.follower:
				Gdk.threads_enter()
				self.append_text(line)
				Gdk.threads_leave()
		else:
			# Not installed anymore, the upgrade process completed
			# So directly dump the entire file:
			with open(APT_LOGFILE) as f:
				for line in f:
					Gdk.threads_enter()
					self.append_text(line)
					Gdk.threads_leave()
			self.log_written = True
		
		self.following_log = False
	
	def on_details_textview_size_allocate(self, textview, allocation):
		"""
		Fired when the new text has been properly allocated.
		"""
		
		adj = self.objects.details_scrolled.get_vadjustment()
		adj.set_value(adj.props.upper - adj.props.page_size)

	
	def on_installing_changed(self, handler=None, value=None):
		"""
		Fired when the service began the package installation.
		"""
		
		# FIXME: Remove that once proper DBus proprieties caching
		# has been implemented
		self._installing = self.handler.props.installing
		
		# FIXME: Should move that elsewhere
		if not self._installing and self.following_log and self.follower:
			# Stop the follower
			print("Stopping follower")
			self.follower.stop()
			self.log_written = True
			self.follower = None
		
		if self._installing:
			# New installation, ensure that we are in a somewhat clean state
			self.log_written = False
			
			# Hide the details scrolled for now
			self.objects.details_scrolled.hide()
			
			# Finally, show the scene!
			self.scene_container.set_visible_child(self.objects.install_scene)

	def on_generic_failure(self, handler, error, description):
		"""
		Fired when the APT Lock failed.
		"""
		
		dialog = Gtk.MessageDialog(
			self.objects.main.get_toplevel(),
			Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
			Gtk.MessageType.ERROR,
			Gtk.ButtonsType.CLOSE,
			error
		)
		dialog.set_title(_("Error"))
		dialog.format_secondary_text(description)
		
		dialog.run()
		dialog.destroy()

	def on_lock_failed(self, handler):
		"""
		Fired when the APT Lock failed.
		"""
		
		return self.on_generic_failure(
			handler,
			_("Unable to lock the APT database"),
			_("Please close the active package managers.")
		)
	
	@quickstart.threads.on_idle
	def on_update_found(self, handler, id, name, version, reason, status, size):
		"""
		Fired when an update has been found.
		"""
		
		self.update_list.add_item(id, name, version, reason, status, size)
	
	@quickstart.threads.on_idle
	def on_package_status_changed(self, handler, id, reason):
		"""
		Fired when a package status has been changed.
		"""
		
		print("STATUS!")
		self.update_list.update_status(id, reason)
	
	def on_package_fetch_started(self, handler, transaction_id, description, shortdesc):
		"""
		Fired when a package is being fetched.
		"""
		
		if not shortdesc in self.update_list.names_with_id:
			# wat?
			return

		# Get package id from the UpdateList			
		id = self.update_list.names_with_id[shortdesc]
		
		# Associate transaction with the id
		self.package_transactions[transaction_id] = id
		
		# Finally update the status on the list
		self.update_list.set_downloading(id, True)
	
	def on_package_fetch_failed(self, handler, transaction_id):
		"""
		Fired when a package failed to download.
		"""
		
		# FIXME: should notify the user!
		
		if not transaction_id in self.package_transactions:
			return
		
		self.update_list.set_downloading(self.package_transactions[transaction_id], False)

	def on_package_fetch_finished(self, handler, transaction_id):
		"""
		Fired when a package has been downloaded.
		"""
				
		if not transaction_id in self.package_transactions:
			return
		
		self.update_list.set_downloading(self.package_transactions[transaction_id], False)
	
	def on_downloading_changed(self, handler, value):
		"""
		Fired when handler's downloading property changed.
		"""
		
		if handler.props.downloading:
			self.update_list.enable_downloading_mode()
		else:
			self.update_list.disable_downloading_mode()
			self.package_transactions = {}
	
	def on_status_toggled(self, updatelist, id, reason):
		"""
		Fired when a package status changed locally.
		"""
		
		print(id, reason)
		
		self.handler.change_status(id, reason)
	
	def on_download_button_clicked(self, button):
		"""
		Fired when the download button has been clicked.
		"""
		
		if not self.handler.props.downloading:
			# Start fetching
			self.handler.fetch()
		else:
			# Stop fetching
			self.handler.fetch_stop()
	
	def on_install_button_clicked(self, button):
		"""
		Fired when the install button has been clicked.
		"""
		
		if not self.handler.props.downloading:
			# Start fetching
			self.handler.fetch(trigger_installation=True)
	
	def on_refresh_button_clicked(self, button):
		"""
		Fired when the refresh button has been clicked.
		"""
		
		# Clear list
		self.update_list.clear()
		
		self.handler.refresh()
	
	def on_checking_changed(self, handler, value):
		"""
		Fired when handler's checking property changed.
		"""
		
		print("Checking changed!")
		
		if not self.handler.props.checking:
			# Expand the update_list
			self.update_list.expand_all()
	
	def on_refreshing_changed(self, handler, value):
		"""
		Fired when handler's refreshing property changed.
		"""
		
		if not self.handler.props.refreshing:
			# Check for updates
			self.handler.check()
	
	def on_update_required_download_changed(self, handler, value):
		"""
		Fired when handler's update-required-download property changed.
		"""
		
		self.objects.download_size.show()
		self.objects.download_size.set_text(
			_("Total download size: %s") % self.handler.props.update_required_download
		)

	def on_selected_channel_changed(self, combobox):
		"""
		Fired when the selected channel has been changed.
		"""
		
		if self.building:
			return
		
		new_channel = self.objects.semplice_base_channels.get_value(combobox.get_active_iter(), 0)
		
		self.Channels.Enable("(s)", new_channel)
		
		# Store the new choice
		self.base_channel_enabled = new_channel
		
		# Check for features
		self.check_for_features()
		

	def on_enable_proposed_updates_toggled(self, checkbutton):
		"""
		Fired when the Enable proposed updates checkbutton has been toggled.
		"""
		
		if self.building:
			return
		
		(self.Channels.EnableComponent if checkbutton.get_active() else self.Channels.DisableComponent)("(ss)", self.base_channel_enabled, PROPOSED_COMPONENT)
	
	def on_enable_development_updates_toggled(self, checkbutton):
		"""
		Fired when the Enable development updates checkbutton has been toggled.
		"""
		
		if self.building:
			return
		
		(self.Channels.Enable if checkbutton.get_active() else self.Channels.Disable)("(s)", DEVELOPMENT_CHANNEL)

	@quickstart.threads.on_idle
	def check_for_features(self):
		"""
		"Enable proposed updates" and "Enable development updates" are
		available only on semplice-current.
		
		This method ensures that those checkbuttons are not sensitive if
		the selected base channel is not semplice-current and loads their
		settings if it is.
		"""
		
		self.building = True
		
		if self.base_channel_enabled == CURRENT_CHANNEL:			
			self.objects.enable_development_updates.set_sensitive(True)
			if self.Channels.GetEnabled("(s)", DEVELOPMENT_CHANNEL):
				self.objects.enable_development_updates.set_active(True)
			else:
				self.objects.enable_development_updates.set_active(False)
		else:
			self.objects.enable_development_updates.set_sensitive(False)
			self.objects.enable_development_updates.set_active(False)

		# Check if the "proposed" channel is available
		if self.Channels.HasComponent("(ss)", self.base_channel_enabled, PROPOSED_COMPONENT):
			self.objects.enable_proposed_updates.set_sensitive(True)
			
			if self.Channels.GetComponentEnabled("(ss)", self.base_channel_enabled, PROPOSED_COMPONENT):
				self.objects.enable_proposed_updates.set_active(True)
			else:
				self.objects.enable_proposed_updates.set_active(False)
		else:
			self.objects.enable_proposed_updates.set_sensitive(False)
			self.objects.enable_proposed_updates.set_active(False)
		
		self.building = False
		
	@quickstart.threads.on_idle
	def load(self):
		"""
		Reloads the semplice_base_channels ListStore.
		"""
		
		self.building = True
		
		self.objects.semplice_base_channels.clear()
		
		for channel in self.Providers.WhatProvides("(s)", BASE_PROVIDER):
			details = self.Channels.GetDetails("(sas)", channel, ["name"])
			itr = self.objects.semplice_base_channels.append(
				[
					channel,
					details["name"] if "name" in details else channel,
					False if self.current_variant == "current" and channel != CURRENT_CHANNEL else True
				]
			)
			
			if self.Channels.GetEnabled("(s)", channel):
				self.objects.selected_channel.set_active_iter(itr)
				self.base_channel_enabled = channel
		
		self.check_for_features()
		
		# self.building will be restored by check_for_features()

	def on_scene_called(self):
		"""
		Fired when the scene has been called.
		"""
		
		# Load current variant
		if os.path.exists(VARIANT_FILE):
			with open(VARIANT_FILE, "r") as f:
				self.current_variant = f.read().strip()

		# Enter in the bus
		self.bus_cancellable = Gio.Cancellable()
		self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
		self.Channels = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/channels/channels",
			"org.semplicelinux.channels.channels",
			self.bus_cancellable
		)
		self.Providers = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/channels/providers",
			"org.semplicelinux.channels.providers",
			self.bus_cancellable
		)

		# We are locked
		self.unlockbar.emit("locked")
		
		self.load()
Ejemplo n.º 9
0
class Scene(quickstart.scenes.BaseScene):
	""" Desktop preferences. """
	
	events = {
		"clicked": (
			"change_fullname_button",
			"change_password_button",
			"change_groups_button",
			"delete_user_button",
		),
		"response": (
			"delete_user_dialog",
			"groups_dialog",
		),
		"delete-event": (
			"delete_user_dialog",
			"groups_dialog",
		),
		"row_selected": ("user_list",),
	}
	
	on_edit_mode = False
	
	current_user = None
	current_user_properties = None
	current_user_box = None
	caller_user_box = None
	
	def handle_delete_events(self, window, event):
		"""
		Handles a dialog delete-event.
		"""
		
		return True
	
	on_delete_user_dialog_delete_event = on_groups_dialog_delete_event = handle_delete_events
	
	def on_locked(self, unlockbar):
		"""
		Fired when the Polkit auth has been revoked.
		"""
		
		# Switch again to the caller user
		self.objects.user_list.select_row(self.caller_user_box)
	
	def on_unlocked(self, unlockbar):
		"""
		Fired when the Polkit auth has been given.
		"""
		
		pass
	
	@quickstart.threads.on_idle
	def restore_edit_mode(self):
		"""
		Restores the full "edit" mode on the user details part.
		"""
		
		self.objects.fullname.show()
		self.objects.new_fullname.hide()
		
		fullname = self.current_user_properties.Get("(ss)",
			USER_IFACE,
			"Fullname"
		)
		self.objects.fullname.set_markup(fullname if fullname else NO_FULLNAME_STRING)
		
		self.objects.change_fullname_button.set_image(self.objects.edit_image)
		
		self.on_edit_mode = False
	
	def on_change_fullname_button_clicked(self, button):
		"""
		Fired when the 'Edit fullname' button has been clicked.
		"""
		
		if not self.on_edit_mode:
			self.objects.fullname.hide()
			self.objects.new_fullname.show()
			
			self.objects.new_fullname.set_text(
				self.objects.fullname.get_text() if self.objects.fullname.get_text() != NO_FULLNAME_STRING.replace("<i>","").replace("</i>","") else ""
			)
			self.objects.new_fullname.grab_focus()
			
			self.objects.change_fullname_button.set_image(self.objects.apply_image)
			
			self.on_edit_mode = True
		else:
			# Save changes
			self.current_user_properties.Set("(sss)",
				USER_IFACE,
				"Fullname",
				self.objects.new_fullname.get_text()
			)
			
			self.current_user_box.set_username_and_fullname(
				self.current_user_box.user.get_text(),
				self.objects.new_fullname.get_text()
			)
			
			self.restore_edit_mode()
	
	def on_change_password_button_clicked(self, button):
		"""
		Fired when the 'Click to change' button on the password field
		has been clicked.
		"""
		
		self.current_user.ChangePassword('(s)', os.environ["DISPLAY"])
	
	def on_delete_user_button_clicked(self, button):
		"""
		Fired when the delete user button has been clicked.
		"""
		
		self.objects.delete_user_dialog.show()
	
	def on_delete_user_dialog_response(self, dialog, response_id):
		"""
		Fired when the delete user dialog got a response.
		"""
		
		if response_id == Gtk.ResponseType.OK:
			# Delete user
			# FIXME: error checking?
			self.current_user.DeleteUser("(b)", self.objects.delete_user_with_home.get_active())
		
		# Reset delete_user_with_home
		self.objects.delete_user_with_home.set_active(False)
		dialog.hide()
		return False
	
	def on_change_groups_button_clicked(self, dialog):
		"""
		Fired when the change groups button has been clicked.
		"""
		
		self.objects.groups_store.clear()
		
		# Build group list
		user_in = self.GroupConfig.GetGroupsForUser('(s)', self.current_user_box._user)
		for group, name in self.GroupConfig.GetGroups().items():
			name = name[0]
			if name in ("sudo",):
				# Skip
				continue
			self.objects.groups_store.insert_with_valuesv(-1, [0, 1, 2], [group, (name in user_in), name])
		
		self.objects.groups_dialog.show()
	
	def on_groups_dialog_response(self, dialog, response_id):
		"""
		Fired when the groups dialog got a response.
		"""
		
		dialog.hide()
		return False
	
	def on_group_enabled_toggle_toggled(self, toggle, path):
		"""
		Fired when a toggle in the groups dialog has been... toggled.
		"""
		
		itr = self.objects.groups_store.get_iter(path)
		
		# Connect to group object
		group_properties = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/usersd/group/%s" % self.objects.groups_store.get_value(itr, 0),
			"org.freedesktop.DBus.Properties",
			self.bus_cancellable
		)
		
		# Get current group members
		members = group_properties.Get('(ss)', 'org.semplicelinux.usersd.group', 'Members')
		
		if not toggle.get_active():
			# Add user
			if not self.current_user_box._user in members: members.append(self.current_user_box._user)
		else:
			# Remove user
			if self.current_user_box._user in members: members.remove(self.current_user_box._user)
		
		# Set the new list
		try:
			group_properties.Set('(ssas)', 'org.semplicelinux.usersd.group', 'Members', members)
		except:
			# Failed/Aborted
			return
		
		# Finally set the enabled boolean in the row
		self.objects.groups_store.set_value(itr, 1, not toggle.get_active())
			
	@quickstart.threads.on_idle
	def build_user_list(self):
		"""
		Builds the user list.
		"""
		
		# Clear
		self.objects.user_list.foreach(lambda x: x.destroy())
		
		# "Add new"
		self.objects.user_list.add(AddNewBox())
		
		try:
			for uid, details in self.UsersConfig.GetUsers().items():
				if uid < 1000 or uid == 65534:
					# Skip system groups or nobody
					continue
				
				box = Gtk.ListBoxRow()
				box.add(UserBox(uid, details[0], details[1], details[2]))
				box.show()
				self.objects.user_list.add(box)
				
				# Current user?
				if uid == CURRENT_UID:
					# Yes!
					self.caller_user_box = box
					self.objects.user_list.select_row(self.caller_user_box)
		except:
			# Unable to build user list, probably caused by a non-working DBus connection
			self.objects.main.set_sensitive(False)
			self.objects.content.hide()
			self.objects.unable_to_connect_warning.show()
			
	def on_user_list_row_selected(self, listbox, row):
		"""
		Fired when a user has been selected.
		"""
		
		if row == None: return
		
		userbox = row.get_child()
		
		if userbox.add_new:
			return self.UsersConfig.ShowUserCreationUI('(sas)', os.environ["DISPLAY"], DEFAULT_GROUPS)
		
		self.current_user_box = userbox
		
		# Caller users cannot remove themselves
		if row == self.caller_user_box:
			self.objects.delete_user_button.hide()
		else:
			self.objects.delete_user_button.show()
		
		# Obtain DBus object for the current user
		self.current_user = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/usersd/user/%s" % userbox.uid,
			USER_IFACE,
			self.bus_cancellable
		)
		self.current_user_properties = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/usersd/user/%s" % userbox.uid,
			"org.freedesktop.DBus.Properties",
			self.bus_cancellable
		)
		
		self.objects.fullname.set_markup(userbox.fullname.get_text() if userbox.fullname.get_text() else NO_FULLNAME_STRING)
		self.objects.user.set_text(userbox.user.get_text())
		
		# Administrator switch
		if self.SudoGroup:
			self.objects.administrator.set_active(
				(userbox.user.get_text() in self.SudoGroup.Get('(ss)', 'org.semplicelinux.usersd.group', 'Members'))
			)
	
	def determine_row_sorting(self, row1, row2):
		"""
		Used to sort the user list by UIDs.
		"""
		
		child1 = row1.get_child()
		child2 = row2.get_child()
		
		if child2.add_new:
			return 1
		elif child1.add_new:
			return 0
		elif child1.uid > child2.uid:
			return 1
		else:
			return 0
	
	def prepare_scene(self):
		"""
		Fired when the module has just been loaded and we should setup
		things.
		"""
		
		self.scene_container = self.objects.main
		
		# g-signals
		self.signal_handlers = {
			"UserListChanged": self.build_user_list
		}

		# Create unlockbar
		self.unlockbar = UnlockBar("org.semplicelinux.usersd.manage")
		self.unlockbar.connect("locked", self.on_locked)
		self.unlockbar.connect("unlocked", self.on_unlocked)
		self.objects.main.pack_start(self.unlockbar, False, False, 0)
		
		# Bind the locked state to the sensitiveness of the use_scrolled ScrolledWindow
		self.unlockbar.bind_property(
			"lock",
			self.objects.user_scrolled,
			"sensitive",
			GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN
		)
		
		# ListBox sort
		self.objects.user_list.set_sort_func(self.determine_row_sorting)
		
		# Delete user dialog
		self.objects.delete_user_dialog.bind_property(
			"visible",
			self.objects.main,
			"sensitive",
			GObject.BindingFlags.DEFAULT | GObject.BindingFlags.INVERT_BOOLEAN
		)
		self.objects.delete_user_dialog.add_buttons(
			_("_Cancel"),
			Gtk.ResponseType.CANCEL,
			_("_Delete user"),
			Gtk.ResponseType.OK
		)
		self.objects.delete_user_dialog.get_widget_for_response(Gtk.ResponseType.OK).get_style_context().add_class("destructive-action")
		
		# Groups dialog
		self.objects.groups_dialog.bind_property(
			"visible",
			self.objects.main,
			"sensitive",
			GObject.BindingFlags.DEFAULT | GObject.BindingFlags.INVERT_BOOLEAN
		)
		self.objects.groups_dialog.add_buttons(
			_("_Close"),
			Gtk.ResponseType.CLOSE
		)

		self.group_enabled_toggle = Gtk.CellRendererToggle()
		self.objects.groups_treeview.append_column(
			Gtk.TreeViewColumn(
				"Enabled",
				self.group_enabled_toggle,
				active=1
			)
		)
		self.group_enabled_toggle.connect("toggled", self.on_group_enabled_toggle_toggled)
		self.objects.groups_treeview.append_column(
			Gtk.TreeViewColumn(
				"Group description",
				Gtk.CellRendererText(),
				text=2
			)
		)
		self.objects.groups_store.set_sort_column_id(0, Gtk.SortType.ASCENDING)
		
		self.objects.administrator.connect("notify::active", self.on_administrator_changed)
	
	def on_administrator_changed(self, widget, param):
		"""
		Fired when the user wants to change the sudo membership of the selected user.
		"""
		
		members = self.SudoGroup.Get('(ss)', 'org.semplicelinux.usersd.group', 'Members')
		
		changed = False
		if widget.get_active():
			# Add
			if not self.current_user_box._user in members:
				members.append(self.current_user_box._user)
				changed = True
		else:
			# Remove
			if self.current_user_box._user in members:
				members.remove(self.current_user_box._user)
				changed = True
		
		if changed:
			# Commit changes
			self.SudoGroup.Set('(ssas)', 'org.semplicelinux.usersd.group', 'Members', members)		

	def on_scene_called(self):
		"""
		Fired when the user wants to see this scene.
		"""
		
		# Recover from a failed connection state
		self.objects.main.set_sensitive(True)
		self.objects.content.show()
		self.objects.unable_to_connect_warning.hide()
		
		# We are locked!
		self.unlockbar.emit("locked")
		
		# Estabilish DBus connection
		self.bus_cancellable = Gio.Cancellable()
		self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
		self.UsersConfig = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/usersd",
			"org.semplicelinux.usersd.user",
			self.bus_cancellable
		)
		self.UsersConfig.connect(
			"g-signal",
			lambda proxy, sender, signal, params: self.signal_handlers[signal]() if signal in self.signal_handlers else None
		)
		self.GroupConfig = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/usersd",
			"org.semplicelinux.usersd.group",
			self.bus_cancellable
		)
		
		# Estabilish DBus connection to the "sudo" group
		try:
			self.SudoGroup = Gio.DBusProxy.new_sync(
				self.bus,
				0,
				None,
				BUS_NAME,
				self.GroupConfig.LookupGroup("(s)", "sudo"),
				"org.freedesktop.DBus.Properties",
				self.bus_cancellable
			)
		except:
			self.SudoGroup = None
		
		if not self.SudoGroup:
			self.objects.administrator.set_sensitive(False)
		else:
			self.objects.administrator.set_sensitive(True)
		
		self.build_user_list()			
Ejemplo n.º 10
0
class Scene(quickstart.scenes.BaseScene):
    """ Desktop preferences. """

    events = {
        "clicked": (
            "change_fullname_button",
            "change_password_button",
            "change_groups_button",
            "delete_user_button",
        ),
        "response": (
            "delete_user_dialog",
            "groups_dialog",
        ),
        "delete-event": (
            "delete_user_dialog",
            "groups_dialog",
        ),
        "row_selected": ("user_list", ),
    }

    on_edit_mode = False

    current_user = None
    current_user_properties = None
    current_user_box = None
    caller_user_box = None

    def handle_delete_events(self, window, event):
        """
		Handles a dialog delete-event.
		"""

        return True

    on_delete_user_dialog_delete_event = on_groups_dialog_delete_event = handle_delete_events

    def on_locked(self, unlockbar):
        """
		Fired when the Polkit auth has been revoked.
		"""

        # Switch again to the caller user
        self.objects.user_list.select_row(self.caller_user_box)

    def on_unlocked(self, unlockbar):
        """
		Fired when the Polkit auth has been given.
		"""

        pass

    @quickstart.threads.on_idle
    def restore_edit_mode(self):
        """
		Restores the full "edit" mode on the user details part.
		"""

        self.objects.fullname.show()
        self.objects.new_fullname.hide()

        fullname = self.current_user_properties.Get("(ss)", USER_IFACE,
                                                    "Fullname")
        self.objects.fullname.set_markup(
            fullname if fullname else NO_FULLNAME_STRING)

        self.objects.change_fullname_button.set_image(self.objects.edit_image)

        self.on_edit_mode = False

    def on_change_fullname_button_clicked(self, button):
        """
		Fired when the 'Edit fullname' button has been clicked.
		"""

        if not self.on_edit_mode:
            self.objects.fullname.hide()
            self.objects.new_fullname.show()

            self.objects.new_fullname.set_text(
                self.objects.fullname.get_text(
                ) if self.objects.fullname.get_text() != NO_FULLNAME_STRING.
                replace("<i>", "").replace("</i>", "") else "")
            self.objects.new_fullname.grab_focus()

            self.objects.change_fullname_button.set_image(
                self.objects.apply_image)

            self.on_edit_mode = True
        else:
            # Save changes
            self.current_user_properties.Set(
                "(sss)", USER_IFACE, "Fullname",
                self.objects.new_fullname.get_text())

            self.current_user_box.set_username_and_fullname(
                self.current_user_box.user.get_text(),
                self.objects.new_fullname.get_text())

            self.restore_edit_mode()

    def on_change_password_button_clicked(self, button):
        """
		Fired when the 'Click to change' button on the password field
		has been clicked.
		"""

        self.current_user.ChangePassword('(s)', os.environ["DISPLAY"])

    def on_delete_user_button_clicked(self, button):
        """
		Fired when the delete user button has been clicked.
		"""

        self.objects.delete_user_dialog.show()

    def on_delete_user_dialog_response(self, dialog, response_id):
        """
		Fired when the delete user dialog got a response.
		"""

        if response_id == Gtk.ResponseType.OK:
            # Delete user
            # FIXME: error checking?
            self.current_user.DeleteUser(
                "(b)", self.objects.delete_user_with_home.get_active())

        # Reset delete_user_with_home
        self.objects.delete_user_with_home.set_active(False)
        dialog.hide()
        return False

    def on_change_groups_button_clicked(self, dialog):
        """
		Fired when the change groups button has been clicked.
		"""

        self.objects.groups_store.clear()

        # Build group list
        user_in = self.GroupConfig.GetGroupsForUser(
            '(s)', self.current_user_box._user)
        for group, name in self.GroupConfig.GetGroups().items():
            name = name[0]
            if name in ("sudo", ):
                # Skip
                continue
            self.objects.groups_store.insert_with_valuesv(
                -1, [0, 1, 2], [group, (name in user_in), name])

        self.objects.groups_dialog.show()

    def on_groups_dialog_response(self, dialog, response_id):
        """
		Fired when the groups dialog got a response.
		"""

        dialog.hide()
        return False

    def on_group_enabled_toggle_toggled(self, toggle, path):
        """
		Fired when a toggle in the groups dialog has been... toggled.
		"""

        itr = self.objects.groups_store.get_iter(path)

        # Connect to group object
        group_properties = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME, "/org/semplicelinux/usersd/group/%s" %
            self.objects.groups_store.get_value(itr, 0),
            "org.freedesktop.DBus.Properties", self.bus_cancellable)

        # Get current group members
        members = group_properties.Get('(ss)',
                                       'org.semplicelinux.usersd.group',
                                       'Members')

        if not toggle.get_active():
            # Add user
            if not self.current_user_box._user in members:
                members.append(self.current_user_box._user)
        else:
            # Remove user
            if self.current_user_box._user in members:
                members.remove(self.current_user_box._user)

        # Set the new list
        try:
            group_properties.Set('(ssas)', 'org.semplicelinux.usersd.group',
                                 'Members', members)
        except:
            # Failed/Aborted
            return

        # Finally set the enabled boolean in the row
        self.objects.groups_store.set_value(itr, 1, not toggle.get_active())

    @quickstart.threads.on_idle
    def build_user_list(self):
        """
		Builds the user list.
		"""

        # Clear
        self.objects.user_list.foreach(lambda x: x.destroy())

        # "Add new"
        self.objects.user_list.add(AddNewBox())

        try:
            for uid, details in self.UsersConfig.GetUsers().items():
                if uid < 1000 or uid == 65534:
                    # Skip system groups or nobody
                    continue

                box = Gtk.ListBoxRow()
                box.add(UserBox(uid, details[0], details[1], details[2]))
                box.show()
                self.objects.user_list.add(box)

                # Current user?
                if uid == CURRENT_UID:
                    # Yes!
                    self.caller_user_box = box
                    self.objects.user_list.select_row(self.caller_user_box)
        except:
            # Unable to build user list, probably caused by a non-working DBus connection
            self.objects.main.set_sensitive(False)
            self.objects.content.hide()
            self.objects.unable_to_connect_warning.show()

    def on_user_list_row_selected(self, listbox, row):
        """
		Fired when a user has been selected.
		"""

        if row == None: return

        userbox = row.get_child()

        if userbox.add_new:
            return self.UsersConfig.ShowUserCreationUI('(sas)',
                                                       os.environ["DISPLAY"],
                                                       DEFAULT_GROUPS)

        self.current_user_box = userbox

        # Caller users cannot remove themselves
        if row == self.caller_user_box:
            self.objects.delete_user_button.hide()
        else:
            self.objects.delete_user_button.show()

        # Obtain DBus object for the current user
        self.current_user = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME,
            "/org/semplicelinux/usersd/user/%s" % userbox.uid, USER_IFACE,
            self.bus_cancellable)
        self.current_user_properties = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME,
            "/org/semplicelinux/usersd/user/%s" % userbox.uid,
            "org.freedesktop.DBus.Properties", self.bus_cancellable)

        self.objects.fullname.set_markup(userbox.fullname.get_text(
        ) if userbox.fullname.get_text() else NO_FULLNAME_STRING)
        self.objects.user.set_text(userbox.user.get_text())

        # Administrator switch
        if self.SudoGroup:
            self.objects.administrator.set_active(
                (userbox.user.get_text()
                 in self.SudoGroup.Get('(ss)',
                                       'org.semplicelinux.usersd.group',
                                       'Members')))

    def determine_row_sorting(self, row1, row2):
        """
		Used to sort the user list by UIDs.
		"""

        child1 = row1.get_child()
        child2 = row2.get_child()

        if child2.add_new:
            return 1
        elif child1.add_new:
            return 0
        elif child1.uid > child2.uid:
            return 1
        else:
            return 0

    def prepare_scene(self):
        """
		Fired when the module has just been loaded and we should setup
		things.
		"""

        self.scene_container = self.objects.main

        # g-signals
        self.signal_handlers = {"UserListChanged": self.build_user_list}

        # Create unlockbar
        self.unlockbar = UnlockBar("org.semplicelinux.usersd.manage")
        self.unlockbar.connect("locked", self.on_locked)
        self.unlockbar.connect("unlocked", self.on_unlocked)
        self.objects.main.pack_start(self.unlockbar, False, False, 0)

        # Bind the locked state to the sensitiveness of the use_scrolled ScrolledWindow
        self.unlockbar.bind_property(
            "lock", self.objects.user_scrolled, "sensitive",
            GObject.BindingFlags.SYNC_CREATE
            | GObject.BindingFlags.INVERT_BOOLEAN)

        # ListBox sort
        self.objects.user_list.set_sort_func(self.determine_row_sorting)

        # Delete user dialog
        self.objects.delete_user_dialog.bind_property(
            "visible", self.objects.main, "sensitive",
            GObject.BindingFlags.DEFAULT | GObject.BindingFlags.INVERT_BOOLEAN)
        self.objects.delete_user_dialog.add_buttons(_("_Cancel"),
                                                    Gtk.ResponseType.CANCEL,
                                                    _("_Delete user"),
                                                    Gtk.ResponseType.OK)
        self.objects.delete_user_dialog.get_widget_for_response(
            Gtk.ResponseType.OK).get_style_context().add_class(
                "destructive-action")

        # Groups dialog
        self.objects.groups_dialog.bind_property(
            "visible", self.objects.main, "sensitive",
            GObject.BindingFlags.DEFAULT | GObject.BindingFlags.INVERT_BOOLEAN)
        self.objects.groups_dialog.add_buttons(_("_Close"),
                                               Gtk.ResponseType.CLOSE)

        self.group_enabled_toggle = Gtk.CellRendererToggle()
        self.objects.groups_treeview.append_column(
            Gtk.TreeViewColumn("Enabled", self.group_enabled_toggle, active=1))
        self.group_enabled_toggle.connect("toggled",
                                          self.on_group_enabled_toggle_toggled)
        self.objects.groups_treeview.append_column(
            Gtk.TreeViewColumn("Group description",
                               Gtk.CellRendererText(),
                               text=2))
        self.objects.groups_store.set_sort_column_id(0, Gtk.SortType.ASCENDING)

        self.objects.administrator.connect("notify::active",
                                           self.on_administrator_changed)

    def on_administrator_changed(self, widget, param):
        """
		Fired when the user wants to change the sudo membership of the selected user.
		"""

        members = self.SudoGroup.Get('(ss)', 'org.semplicelinux.usersd.group',
                                     'Members')

        changed = False
        if widget.get_active():
            # Add
            if not self.current_user_box._user in members:
                members.append(self.current_user_box._user)
                changed = True
        else:
            # Remove
            if self.current_user_box._user in members:
                members.remove(self.current_user_box._user)
                changed = True

        if changed:
            # Commit changes
            self.SudoGroup.Set('(ssas)', 'org.semplicelinux.usersd.group',
                               'Members', members)

    def on_scene_called(self):
        """
		Fired when the user wants to see this scene.
		"""

        # Recover from a failed connection state
        self.objects.main.set_sensitive(True)
        self.objects.content.show()
        self.objects.unable_to_connect_warning.hide()

        # We are locked!
        self.unlockbar.emit("locked")

        # Estabilish DBus connection
        self.bus_cancellable = Gio.Cancellable()
        self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
        self.UsersConfig = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME, "/org/semplicelinux/usersd",
            "org.semplicelinux.usersd.user", self.bus_cancellable)
        self.UsersConfig.connect(
            "g-signal",
            lambda proxy, sender, signal, params: self.signal_handlers[signal]
            () if signal in self.signal_handlers else None)
        self.GroupConfig = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME, "/org/semplicelinux/usersd",
            "org.semplicelinux.usersd.group", self.bus_cancellable)

        # Estabilish DBus connection to the "sudo" group
        try:
            self.SudoGroup = Gio.DBusProxy.new_sync(
                self.bus, 0, None, BUS_NAME,
                self.GroupConfig.LookupGroup("(s)", "sudo"),
                "org.freedesktop.DBus.Properties", self.bus_cancellable)
        except:
            self.SudoGroup = None

        if not self.SudoGroup:
            self.objects.administrator.set_sensitive(False)
        else:
            self.objects.administrator.set_sensitive(True)

        self.build_user_list()
Ejemplo n.º 11
0
class Scene(quickstart.scenes.BaseScene):
    """
	Time & Date settings.
	"""

    events = {
        "delete-event": ("select_timezone_dialog", ),
        "realize": ("select_timezone_dialog", ),
        "button-press-event": ("main", ),
        "clicked": ("time_button", "location_button", "apply_timezone"),
        "toggled": ("calendar_button", "ntp_enabled"),
        "value-changed":
        ("hours_adjustment", "minutes_adjustment", "seconds_adjustment"),
        "wrapped": ("hours", ),
        "output": ("hours", "minutes", "seconds")
    }

    @quickstart.threads.on_idle
    def build_timezone_list(self):
        """
		Builds the timezone list.
		"""

        self.objects.timezones.clear()

        for item, key in self.TimeZone.supported.items():
            for zone in key:
                zone1 = "%s/%s" % (item, zone)
                itr = self.objects.timezones.append([zone1, zone])

                # Save the iter if it's the default
                if zone1 == self.TimeZone.default:
                    # save the iter! ;)
                    self.default = itr

        if self.default:
            sel = self.objects.timezone_treeview.get_selection()
            sel.select_iter(self.default)

            GObject.idle_add(self.objects.timezone_treeview.scroll_to_cell,
                             sel.get_selected_rows()[1][0])

        GObject.idle_add(self.objects.timezone_treeview.grab_focus)

    def on_apply_timezone_clicked(self, button):
        """
		Fired when the apply_timezone button has been clicked.
		"""

        GObject.idle_add(self.objects.select_timezone_dialog.hide)

        # Get selected
        sel = self.objects.timezone_treeview.get_selection()
        if not sel: return

        model, itr = sel.get_selected()
        if not itr: return

        selected = self.objects.timezones.get_value(itr, 0)
        if selected == self.objects.location_button.get_label():
            return  # Ugly!

        try:
            self.TimeDate.SetTimezone('(sb)', selected, True)

            self.default = itr
            self.current_datetime = datetime.datetime.now()
        except:
            sel.select_iter(self.default)

        self.objects.location_button.set_label(
            self.objects.timezones.get_value(self.default, 0))

    def on_select_timezone_dialog_realize(self, widget):
        """
		Fired when the select_timezone_dialog is going to be shown.
		"""

        self.build_timezone_list()

    def on_select_timezone_dialog_delete_event(self, widget, event):
        """
		Fired when the select_timezone_dialog is going to be destroyed.
		"""

        widget.hide()

        return True  # do not destroy

    def on_main_button_press_event(self, eventbox, event):
        """
		Fired when the user has clicked on the eventbox.
		
		We use this call to return to the 'read-only' mode on the time
		view.
		"""

        if self.objects.time_modify.props.visible and event.type == Gdk.EventType.BUTTON_PRESS:
            self.refresh_infos()
            self.objects.time_modify.hide()
            self.objects.time_button.show()

    def on_spinbutton_output(self, spinbutton):
        """
		Fired when a spinbutton has been changed.
		
		We use this method to ensure to have two digits everytime.
		Code comes from http://stackoverflow.com/a/9998968 (thanks!)
		
		(not connected to anything, due to a current limitation of quickstart
		we will associate on_*widget*_output to this method later)
		"""

        spinbutton.set_text('{:02d}'.format(
            int(spinbutton.get_adjustment().get_value())))

        return True

    on_hours_output = on_spinbutton_output
    on_minutes_output = on_spinbutton_output
    on_seconds_output = on_spinbutton_output

    def on_hours_wrapped(self, spinbutton):
        """
		Fired when the hours spinbutton has been wrapped.
		"""

        if self.timezone12:
            if int(spinbutton.get_text()) == 1:
                # +1
                self.hour_offset = 0 if self.hour_offset == 12 else 12
            else:
                # -1
                self.hour_offset = 12 if self.hour_offset == 0 else 0

            # Reset time
            self.on_hours_adjustment_value_changed(spinbutton.get_adjustment())

    def set_time(self):
        """
		Actually sets the time.
		"""

        print("Setting time...")

        self.TimeDate.SetTime(
            '(xbb)',
            time.mktime(self.current_datetime.timetuple()) * 1000000, False,
            False)

    def on_hours_adjustment_value_changed(self, adjustment):
        """
		Fired when the hours adjustment has been changed.
		"""

        if self.hours_input:
            value = int(adjustment.get_value())
            if self.timezone12:
                value += self.hour_offset

            self.current_datetime = self.current_datetime.replace(
                hour=value if not self.timezone12 else (
                    0 if value == 24 else value))
            if self.timezone12:
                self.objects.timezone12_edit.set_text(
                    self.current_datetime.strftime("%p"))

            self.set_time()

    def on_minutes_adjustment_value_changed(self, adjustment):
        """
		Fired when the minutes adjustment has been changed.
		"""

        if self.minutes_input:
            self.current_datetime = self.current_datetime.replace(
                minute=int(adjustment.get_value()))
            self.set_time()

    def on_seconds_adjustment_value_changed(self, adjustment):
        """
		Fired when the seconds adjustment has been changed.
		"""

        if self.seconds_input:
            self.current_datetime = self.current_datetime.replace(
                second=int(adjustment.get_value()))
            self.set_time()

    def on_location_button_clicked(self, button):
        """
		Fired when the location_button has been clicked.
		"""

        GObject.idle_add(self.objects.select_timezone_dialog.present)

    def on_time_button_clicked(self, button):
        """
		Fired when the time_button has been clicked.
		"""

        self.hours_input = self.minutes_input = self.seconds_input = False

        button.hide()
        self.objects.time_modify.show()

        # Load the adjustments with current data
        time = self.current_datetime.time()
        # Handle 12-hour timezones
        if not self.timezone12:
            # 24 hour
            self.objects.hours_adjustment.set_value(time.hour)
            self.objects.timezone12_edit.hide()
        else:
            # 12 hour
            self.objects.hours_adjustment.set_value(int(time.strftime("%I")))
            self.objects.timezone12_edit.set_text(
                self.current_datetime.strftime("%p"))
        self.objects.minutes_adjustment.set_value(time.minute)
        self.objects.seconds_adjustment.set_value(time.second)

    def on_calendar_button_toggled(self, button):
        """
		Fired when the calendar_button has been toggled.
		"""

        if button.get_active(): self.calendar_popover.show_all()

    def on_day_selected(self, calendar):
        """
		Fired when a day in the calendar has been selected.
		"""

        date = calendar.get_date()
        self.current_datetime = self.current_datetime.replace(year=date[0],
                                                              month=date[1] +
                                                              1,
                                                              day=date[2])

        self.objects.calendar_button.set_label(
            self.current_datetime.date().strftime("%A %d %B %Y"))

        # Update time if we should
        if self.date_input:
            self.set_time()

    def on_ntp_enabled_toggled(self, checkbox):
        """
		Fired when the ntp_enabled checkbox has been toggled.
		"""

        if self.unlockbar.current_state == ActionResponse.UNLOCK:
            print("Setting NTP to %s" % checkbox.get_active())
            try:
                self.TimeDate.SetNTP('(bb)', checkbox.get_active(), False)
            except GLib.GError as error:
                # FIXME? Here it raises FileNotFound error, I think
                # is some Debian bug?
                pass

        # Set sensitivity on the manual container
        self.objects.manual_container.set_sensitive(not checkbox.get_active())

    @quickstart.threads.on_idle
    def refresh_infos(self):
        """
		Refreshes the information displayed.
		"""

        _datetime = datetime.datetime.now()

        self.objects.time.set_text(_datetime.time().strftime("%X"))

    @quickstart.threads.on_idle
    def update_date(self):
        """
		Updates the date.
		"""

        date = self.current_datetime.date()

        self.date_input = False
        self.calendar.select_month(date.month - 1, date.year)
        self.calendar.select_day(date.day)
        self.date_input = True

    @quickstart.threads.on_idle
    def update_time(self):
        """
		Updates the time.
		"""

        self.current_datetime += datetime.timedelta(seconds=1)

        time = self.current_datetime.time()
        if time.hour == 00 and time.minute == 00 and time.second == 00:
            # Day changed
            self.update_date()

        # Update the current visible time object
        if self.objects.time_modify.props.visible:
            if time.minute == 00 and time.second == 00:
                self.hours_input = False
                self.objects.hours_adjustment.set_value(time.hour)
            if time.second == 00:
                self.minutes_input = False
                self.objects.minutes_adjustment.set_value(time.minute)
            self.seconds_input = False
            self.objects.seconds_adjustment.set_value(time.second)

            self.hours_input = self.minutes_input = self.seconds_input = True
        else:
            self.objects.time.set_text(time.strftime("%X"))

    def on_locked(self, unlockbar):
        """
		Fired when the user (or policykit) has locked the special
		temporary privilegies.
		"""

        # Locked, disable sensitivity on everything...
        self.objects.container.set_sensitive(False)

    def on_unlocked(self, unlockbar):
        """
		Fired when the user has got the special temporary privilegies.
		"""

        # Unlocked, re-set sensitivity
        self.objects.container.set_sensitive(True)

    def prepare_scene(self):
        """
		Called when doing the scene setup.
		"""

        self.scene_container = self.objects.main

        self.default = None

        self.TimeZone = TimeZone()
        self.hour_offset = 0
        self.timezone12 = None

        # Create unlockbar
        self.unlockbar = UnlockBar("org.freedesktop.timedate1.set-time")
        self.unlockbar.connect("locked", self.on_locked)
        self.unlockbar.connect("unlocked", self.on_unlocked)
        self.objects.main_box.pack_start(self.unlockbar, False, False, 0)

        # Enter in the bus
        self.bus_cancellable = Gio.Cancellable()
        self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
        self.TimeDate = Gio.DBusProxy.new_sync(self.bus, 0, None, BUS_NAME,
                                               "/org/freedesktop/timedate1",
                                               BUS_NAME, self.bus_cancellable)
        self.TimeDateProperties = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME, "/org/freedesktop/timedate1",
            "org.freedesktop.DBus.Properties", self.bus_cancellable
        )  # Really we should create a new proxy to get the properties?!

        #self.refresh_infos()

        # Set-up select timezone dialog
        self.objects.timezone_treeview.append_column(
            Gtk.TreeViewColumn("Timezone", Gtk.CellRendererText(), text=0))
        self.objects.timezones.set_sort_column_id(0, Gtk.SortType.ASCENDING)

        # Create calendar popover
        self.calendar = Gtk.Calendar()
        self.calendar.connect("day-selected", self.on_day_selected)
        self.calendar_popover = Gtk.Popover.new(self.objects.calendar_button)
        self.calendar_popover.set_modal(True)
        self.calendar_popover.connect(
            "closed", lambda x: self.objects.calendar_button.set_active(False))
        self.calendar_popover.add(self.calendar)

        # Set appropriate font size and weight
        context = self.objects.time.create_pango_context()
        desc = context.get_font_description()
        desc.set_weight(Pango.Weight.LIGHT)  # Weight
        desc.set_size(Pango.SCALE * 80)  # Size

        self.objects.time.override_font(desc)
        self.objects.hours.override_font(desc)
        self.objects.minutes.override_font(desc)
        self.objects.seconds.override_font(desc)
        self.objects.timezone12_edit.override_font(desc)

        # Set mask on the main eventbox
        self.objects.main.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)

        # Styling
        self.objects.time_button.get_style_context().add_class("no-borders")
        self.objects.location_button.get_style_context().add_class(
            "no-borders")
        self.objects.calendar_button.get_style_context().add_class(
            "no-borders")

    def on_scene_called(self):
        """
		Called when switching to this scene.
		
		We will handle here all timeouts to ensure we syncronize the
		time label with the actual time.
		"""

        self.objects.main.show_all()
        self.objects.time_modify.hide()

        # We are locked
        self.unlockbar.emit("locked")

        self.current_datetime = datetime.datetime.now()
        self.timezone12 = (not self.current_datetime.strftime("%p") == "")
        adj = self.objects.hours.get_adjustment()
        if self.timezone12:
            adj.set_upper(12)
            adj.set_lower(1)
        else:
            adj.set_upper(23)
            adj.set_lower(0)
        if self.current_datetime.time().hour > 12: self.hour_offset = 12

        self.update_date()
        self.update_time()

        # NTP
        ntp = bool(self.TimeDateProperties.Get('(ss)', BUS_NAME, 'NTP'))
        self.objects.ntp_enabled.set_active(ntp)

        # Timezone
        self.objects.location_button.set_label(
            self.TimeDateProperties.Get('(ss)', BUS_NAME, 'Timezone'))

        self.label_timeout = GLib.timeout_add_seconds(1, self.update_time)

    def on_scene_asked_to_close(self):
        """
		Do some cleanup before returning home
		"""

        GLib.source_remove(self.label_timeout)
        self.unlockbar.cancel_authorization()

        self.bus_cancellable.cancel()

        return True
Ejemplo n.º 12
0
class Scene(quickstart.scenes.BaseScene):
	"""
	Time & Date settings.
	"""
	
	events = {
		"delete-event" : ("select_timezone_dialog",),
		"realize": ("select_timezone_dialog",),
		"button-press-event" : ("main",),
		"clicked" : ("time_button", "location_button", "apply_timezone"),
		"toggled" : ("calendar_button", "ntp_enabled"),
		"value-changed" : ("hours_adjustment", "minutes_adjustment", "seconds_adjustment"),
		"wrapped" : ("hours",),
		"output" : ("hours", "minutes", "seconds")
	}
	
	@quickstart.threads.on_idle
	def build_timezone_list(self):
		"""
		Builds the timezone list.
		"""
		
		self.objects.timezones.clear()
		
		for item, key in self.TimeZone.supported.items():
			for zone in key:
				zone1 = "%s/%s" % (item, zone)
				itr = self.objects.timezones.append([zone1, zone])
				
				# Save the iter if it's the default
				if zone1 == self.TimeZone.default:
					# save the iter! ;)
					self.default = itr
		
		if self.default:
			sel = self.objects.timezone_treeview.get_selection()
			sel.select_iter(self.default)
			
			GObject.idle_add(self.objects.timezone_treeview.scroll_to_cell, sel.get_selected_rows()[1][0])
		
		GObject.idle_add(self.objects.timezone_treeview.grab_focus)
	
	def on_apply_timezone_clicked(self, button):
		"""
		Fired when the apply_timezone button has been clicked.
		"""
		
		GObject.idle_add(self.objects.select_timezone_dialog.hide)
		
		# Get selected
		sel = self.objects.timezone_treeview.get_selection()
		if not sel: return
		
		model, itr = sel.get_selected()
		if not itr: return
		
		selected = self.objects.timezones.get_value(itr, 0)
		if selected == self.objects.location_button.get_label(): return # Ugly!
		
		try:
			self.TimeDate.SetTimezone(
				'(sb)',
				selected,
				True
			)
			
			self.default = itr
			self.current_datetime = datetime.datetime.now()
		except:
			sel.select_iter(self.default)
		
		self.objects.location_button.set_label(self.objects.timezones.get_value(self.default, 0))
	
	def on_select_timezone_dialog_realize(self, widget):
		"""
		Fired when the select_timezone_dialog is going to be shown.
		"""
		
		self.build_timezone_list()
	
	def on_select_timezone_dialog_delete_event(self, widget, event):
		"""
		Fired when the select_timezone_dialog is going to be destroyed.
		"""
		
		widget.hide()
		
		return True # do not destroy
	
	def on_main_button_press_event(self, eventbox, event):
		"""
		Fired when the user has clicked on the eventbox.
		
		We use this call to return to the 'read-only' mode on the time
		view.
		"""
		
		if self.objects.time_modify.props.visible and event.type == Gdk.EventType.BUTTON_PRESS:
			self.refresh_infos()
			self.objects.time_modify.hide()
			self.objects.time_button.show()
	
	def on_spinbutton_output(self, spinbutton):
		"""
		Fired when a spinbutton has been changed.
		
		We use this method to ensure to have two digits everytime.
		Code comes from http://stackoverflow.com/a/9998968 (thanks!)
		
		(not connected to anything, due to a current limitation of quickstart
		we will associate on_*widget*_output to this method later)
		"""
		
		spinbutton.set_text('{:02d}'.format(int(spinbutton.get_adjustment().get_value())))
		
		return True

	on_hours_output = on_spinbutton_output
	on_minutes_output = on_spinbutton_output
	on_seconds_output = on_spinbutton_output
	
	def on_hours_wrapped(self, spinbutton):
		"""
		Fired when the hours spinbutton has been wrapped.
		"""
				
		if self.timezone12:
			if int(spinbutton.get_text()) == 1:
				# +1
				self.hour_offset = 0 if self.hour_offset == 12 else 12
			else:
				# -1
				self.hour_offset = 12 if self.hour_offset == 0 else 0
			
			# Reset time
			self.on_hours_adjustment_value_changed(spinbutton.get_adjustment())
	
	def set_time(self):
		"""
		Actually sets the time.
		"""
		
		print("Setting time...")
		
		self.TimeDate.SetTime('(xbb)', time.mktime(self.current_datetime.timetuple()) * 1000000, False, False)
		
	def on_hours_adjustment_value_changed(self, adjustment):
		"""
		Fired when the hours adjustment has been changed.
		"""
		
		if self.hours_input:
			value = int(adjustment.get_value())
			if self.timezone12:
				value += self.hour_offset
			
			self.current_datetime = self.current_datetime.replace(
				hour=value if not self.timezone12 else (
					0 if value == 24 else value
				)
			)
			if self.timezone12:
				self.objects.timezone12_edit.set_text(self.current_datetime.strftime("%p"))
			
			self.set_time()
			
					
	def on_minutes_adjustment_value_changed(self, adjustment):
		"""
		Fired when the minutes adjustment has been changed.
		"""
		
		if self.minutes_input:
			self.current_datetime = self.current_datetime.replace(minute=int(adjustment.get_value()))
			self.set_time()
			

	def on_seconds_adjustment_value_changed(self, adjustment):
		"""
		Fired when the seconds adjustment has been changed.
		"""
		
		if self.seconds_input:
			self.current_datetime = self.current_datetime.replace(second=int(adjustment.get_value()))
			self.set_time()
	
	def on_location_button_clicked(self, button):
		"""
		Fired when the location_button has been clicked.
		"""

		GObject.idle_add(self.objects.select_timezone_dialog.present)		
	
	def on_time_button_clicked(self, button):
		"""
		Fired when the time_button has been clicked.
		"""
		
		self.hours_input = self.minutes_input = self.seconds_input = False
		
		button.hide()
		self.objects.time_modify.show()
				
		# Load the adjustments with current data
		time = self.current_datetime.time()
		# Handle 12-hour timezones
		if not self.timezone12:
			# 24 hour
			self.objects.hours_adjustment.set_value(time.hour)
			self.objects.timezone12_edit.hide()
		else:
			# 12 hour
			self.objects.hours_adjustment.set_value(int(time.strftime("%I")))
			self.objects.timezone12_edit.set_text(self.current_datetime.strftime("%p"))
		self.objects.minutes_adjustment.set_value(time.minute)
		self.objects.seconds_adjustment.set_value(time.second)
	
	def on_calendar_button_toggled(self, button):
		"""
		Fired when the calendar_button has been toggled.
		"""
				
		if button.get_active(): self.calendar_popover.show_all()
	
	def on_day_selected(self, calendar):
		"""
		Fired when a day in the calendar has been selected.
		"""
		
		date = calendar.get_date()
		self.current_datetime = self.current_datetime.replace(
			year=date[0],
			month=date[1]+1,
			day=date[2]
		)
		
		self.objects.calendar_button.set_label(self.current_datetime.date().strftime("%A %d %B %Y"))
		
		# Update time if we should
		if self.date_input:
			self.set_time()
	
	def on_ntp_enabled_toggled(self, checkbox):
		"""
		Fired when the ntp_enabled checkbox has been toggled.
		"""
		
		if self.unlockbar.current_state == ActionResponse.UNLOCK:
			print("Setting NTP to %s" % checkbox.get_active())
			try:
				self.TimeDate.SetNTP('(bb)', checkbox.get_active(), False)
			except GLib.GError as error:
				# FIXME? Here it raises FileNotFound error, I think
				# is some Debian bug?
				pass
		
		# Set sensitivity on the manual container
		self.objects.manual_container.set_sensitive(not checkbox.get_active())
	
	@quickstart.threads.on_idle
	def refresh_infos(self):
		"""
		Refreshes the information displayed.
		"""
		
		_datetime = datetime.datetime.now()
		
		self.objects.time.set_text(_datetime.time().strftime("%X"))
	
	@quickstart.threads.on_idle
	def update_date(self):
		"""
		Updates the date.
		"""
		
		date = self.current_datetime.date()
		
		self.date_input = False
		self.calendar.select_month(date.month-1, date.year)
		self.calendar.select_day(date.day)
		self.date_input = True
	
	@quickstart.threads.on_idle
	def update_time(self):
		"""
		Updates the time.
		"""
		
		self.current_datetime += datetime.timedelta(seconds=1)
		
		time = self.current_datetime.time()
		if time.hour == 00 and time.minute == 00 and time.second == 00:
			# Day changed
			self.update_date()
		
		# Update the current visible time object
		if self.objects.time_modify.props.visible:
			if time.minute == 00 and time.second == 00: 
				self.hours_input = False
				self.objects.hours_adjustment.set_value(time.hour)
			if time.second == 00:
				self.minutes_input = False
				self.objects.minutes_adjustment.set_value(time.minute)
			self.seconds_input = False
			self.objects.seconds_adjustment.set_value(time.second)
			
			self.hours_input = self.minutes_input = self.seconds_input = True
		else:
			self.objects.time.set_text(time.strftime("%X"))
	
	def on_locked(self, unlockbar):
		"""
		Fired when the user (or policykit) has locked the special
		temporary privilegies.
		"""
		
		# Locked, disable sensitivity on everything...
		self.objects.container.set_sensitive(False)
	
	def on_unlocked(self, unlockbar):
		"""
		Fired when the user has got the special temporary privilegies.
		"""
		
		# Unlocked, re-set sensitivity
		self.objects.container.set_sensitive(True)
		
			
	def prepare_scene(self):
		"""
		Called when doing the scene setup.
		"""
		
		self.scene_container = self.objects.main
		
		self.default = None
		
		self.TimeZone = TimeZone()
		self.hour_offset = 0
		self.timezone12 = None
		
		# Create unlockbar
		self.unlockbar = UnlockBar("org.freedesktop.timedate1.set-time")
		self.unlockbar.connect("locked", self.on_locked)
		self.unlockbar.connect("unlocked", self.on_unlocked)
		self.objects.main_box.pack_start(self.unlockbar, False, False, 0)
		
		# Enter in the bus
		self.bus_cancellable = Gio.Cancellable()
		self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
		self.TimeDate = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/freedesktop/timedate1",
			BUS_NAME,
			self.bus_cancellable
		)
		self.TimeDateProperties = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/freedesktop/timedate1",
			"org.freedesktop.DBus.Properties",
			self.bus_cancellable
		) # Really we should create a new proxy to get the properties?!
		
		#self.refresh_infos()
		
		# Set-up select timezone dialog
		self.objects.timezone_treeview.append_column(
			Gtk.TreeViewColumn(
				"Timezone",
				Gtk.CellRendererText(),
				text=0
			)
		)
		self.objects.timezones.set_sort_column_id(0, Gtk.SortType.ASCENDING)
		
		# Create calendar popover
		self.calendar = Gtk.Calendar()
		self.calendar.connect("day-selected", self.on_day_selected)
		self.calendar_popover = Gtk.Popover.new(self.objects.calendar_button)
		self.calendar_popover.set_modal(True)
		self.calendar_popover.connect("closed", lambda x: self.objects.calendar_button.set_active(False))
		self.calendar_popover.add(self.calendar)
		
		# Set appropriate font size and weight
		context = self.objects.time.create_pango_context()
		desc = context.get_font_description()
		desc.set_weight(Pango.Weight.LIGHT) # Weight
		desc.set_size(Pango.SCALE*80) # Size
		
		self.objects.time.override_font(desc)
		self.objects.hours.override_font(desc)
		self.objects.minutes.override_font(desc)
		self.objects.seconds.override_font(desc)
		self.objects.timezone12_edit.override_font(desc)
		
		# Set mask on the main eventbox
		self.objects.main.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
		
		# Styling
		self.objects.time_button.get_style_context().add_class("no-borders")
		self.objects.location_button.get_style_context().add_class("no-borders")
		self.objects.calendar_button.get_style_context().add_class("no-borders")
		
	
	def on_scene_called(self):
		"""
		Called when switching to this scene.
		
		We will handle here all timeouts to ensure we syncronize the
		time label with the actual time.
		"""

		self.objects.main.show_all()
		self.objects.time_modify.hide()
		
		# We are locked
		self.unlockbar.emit("locked")
		
		self.current_datetime = datetime.datetime.now()
		self.timezone12 = (not self.current_datetime.strftime("%p") == "")
		adj = self.objects.hours.get_adjustment()
		if self.timezone12:
			adj.set_upper(12)
			adj.set_lower(1)
		else:
			adj.set_upper(23)
			adj.set_lower(0)
		if self.current_datetime.time().hour > 12: self.hour_offset = 12
			
		self.update_date()
		self.update_time()
		
		# NTP
		ntp = bool(self.TimeDateProperties.Get('(ss)', BUS_NAME, 'NTP'))
		self.objects.ntp_enabled.set_active(ntp)
		
		# Timezone
		self.objects.location_button.set_label(self.TimeDateProperties.Get('(ss)', BUS_NAME, 'Timezone'))
		
		self.label_timeout = GLib.timeout_add_seconds(1, self.update_time)
	
	def on_scene_asked_to_close(self):
		"""
		Do some cleanup before returning home
		"""
		
		GLib.source_remove(self.label_timeout)
		self.unlockbar.cancel_authorization()
		
		self.bus_cancellable.cancel()
		
		return True