예제 #1
0
class ProgressHub(Hub):
    """
       .. inheritance-diagram:: ProgressHub
          :parts: 3
    """
    builderObjects = ["progressWindow"]
    mainWidgetName = "progressWindow"
    uiFile = "hubs/progress.glade"
    helpFile = "ProgressHub.xml"

    def __init__(self, data, storage, payload):
        super().__init__(data, storage, payload)

        self._totalSteps = 0
        self._currentStep = 0
        self._configurationDone = False
        self._update_progress_timer = Timer()
        self._cycle_rnotes_timer = Timer()

    def _do_configuration(self, widget=None, reenable_ransom=True):
        from pyanaconda.installation import doConfiguration
        from pyanaconda.threading import threadMgr, AnacondaThread

        assert self._configurationDone == False

        self._configurationDone = True

        # Disable all personalization spokes
        self.builder.get_object("progressWindow-scroll").set_sensitive(False)

        if reenable_ransom:
            self._start_ransom_notes()

        self._restart_spinner()

        self._update_progress_timer.timeout_msec(250, self._update_progress,
                                                 self._configuration_done)
        threadMgr.add(AnacondaThread(name=THREAD_CONFIGURATION, target=doConfiguration,
                                     args=(self.storage, self.payload, self.data)))

    def _start_ransom_notes(self):
        # Adding this as a timeout below means it'll get called after 60
        # seconds, so we need to do the first call manually.
        self._cycle_rnotes()
        self._cycle_rnotes_timer.timeout_sec(60, self._cycle_rnotes)

    def _update_progress(self, callback=None):
        from pyanaconda.progress import progressQ
        import queue

        q = progressQ.q

        # Grab all messages may have appeared since last time this method ran.
        while True:
            # Attempt to get a message out of the queue for how we should update
            # the progress bar.  If there's no message, don't error out.
            try:
                (code, args) = q.get(False)
            except queue.Empty:
                break

            if code == progressQ.PROGRESS_CODE_INIT:
                self._init_progress_bar(args[0])
            elif code == progressQ.PROGRESS_CODE_STEP:
                self._step_progress_bar()
            elif code == progressQ.PROGRESS_CODE_MESSAGE:
                self._update_progress_message(args[0])
            elif code == progressQ.PROGRESS_CODE_COMPLETE:
                q.task_done()

                # we are done, stop the progress indication
                gtk_call_once(self._progressBar.set_fraction, 1.0)
                gtk_call_once(self._progressLabel.set_text, _("Complete!"))
                gtk_call_once(self._spinner.stop)
                gtk_call_once(self._spinner.hide)

                if callback:
                    callback()

                # There shouldn't be any more progress bar updates, so return False
                # to indicate this method should be removed from the idle loop.
                return False
            elif code == progressQ.PROGRESS_CODE_QUIT:
                sys.exit(args[0])

            q.task_done()

        return True


    def _configuration_done(self):
        # Configuration done, remove ransom notes timer
        # and switch to the Reboot page

        self._cycle_rnotes_timer.cancel()
        self._progressNotebook.set_current_page(1)
        self.window.set_may_continue(True)

        util.ipmi_report(IPMI_FINISHED)

        if conf.license.eula:
            self.set_warning(_("Use of this product is subject to the license agreement "
                               "found at %s") % conf.license.eula)
            self.window.show_all()

        # kickstart install, continue automatically if reboot or shutdown selected
        if flags.automatedInstall and self.data.reboot.action in [KS_REBOOT, KS_SHUTDOWN]:
            self.window.emit("continue-clicked")

    def _install_done(self):
        # package installation done, check personalization spokes
        # and start the configuration step if all is ready
        if not self._inSpoke and self.continuePossible:
            self._do_configuration(reenable_ransom=False)
        else:
            # some mandatory spokes are not ready
            # switch to configure and finish page
            self._cycle_rnotes_timer.cancel()
            self._progressNotebook.set_current_page(0)

    def _do_globs(self, path):
        return glob.glob(path + "/*.png") + \
               glob.glob(path + "/*.jpg") + \
               glob.glob(path + "/*.svg")

    def _get_rnotes(self):
        # We first look for rnotes in paths containing the language, then in
        # directories without the language component.  You know, just in case.

        paths = ["/tmp/updates/pixmaps/rnotes/",
                 "/tmp/product/pixmaps/rnotes/",
                 "/usr/share/anaconda/pixmaps/rnotes/"]

        all_lang_pixmaps = []
        for path in paths:
            all_lang_pixmaps += self._do_globs(path + "/*")

        pixmap_langs = [pixmap.split(os.path.sep)[-2] for pixmap in all_lang_pixmaps]
        best_lang = find_best_locale_match(os.environ["LANG"], pixmap_langs)

        if not best_lang:
            # nothing found, try the default language
            best_lang = find_best_locale_match(DEFAULT_LANG, pixmap_langs)

        if not best_lang:
            # nothing found even for the default language, try non-localized rnotes
            non_localized = []
            for path in paths:
                non_localized += self._do_globs(path)

            return non_localized

        best_lang_pixmaps = []
        for path in paths:
            best_lang_pixmaps += self._do_globs(path + best_lang)

        return best_lang_pixmaps

    def _cycle_rnotes(self):
        # Change the ransom notes image every minute by grabbing the next
        # image's filename.  Note that self._rnotesPages is an infinite list,
        # so this will cycle through the images indefinitely.
        try:
            nxt = next(self._rnotesPages)
        except StopIteration:
            # there are no rnotes
            pass
        else:
            self._progressNotebook.set_current_page(nxt)

        return True

    def initialize(self):
        super().initialize()

        if not conf.system.can_reboot and conf.target.is_hardware:
            continueText = self.builder.get_object("rebootLabel")
            continueText.set_text(_("%s is now successfully installed on your system and ready "
                                    "for you to use!  When you are ready, reboot your system to start using it!"))
            continueText.set_line_wrap(True)
            self.window.get_continue_button().set_label(C_("GUI|Progress", "_Quit"))

        self._progressBar = self.builder.get_object("progressBar")
        self._progressLabel = self.builder.get_object("progressLabel")
        self._progressNotebook = self.builder.get_object("progressNotebook")
        self._spinner = self.builder.get_object("progressSpinner")

        lbl = self.builder.get_object("configurationLabel")
        lbl.set_text(_("%s is now successfully installed, but some configuration still needs to be done.\n"
            "Finish it and then click the Finish configuration button please.") %
            productName)

        lbl = self.builder.get_object("rebootLabel")
        lbl.set_text(_("%s is now successfully installed and ready for you to use!\n"
                "Go ahead and reboot to start using it!") % productName)

        rnotes = self._get_rnotes()
        # Get the start of the pages we're about to add to the notebook
        rnotes_start = self._progressNotebook.get_n_pages()
        if rnotes:
            # Add a new page in the notebook for each ransom note image.
            for f in rnotes:
                img = Gtk.Image.new_from_file(f)
                img.show()
                self._progressNotebook.append_page(img, None)

            # An infinite list of the page numbers containing ransom notes images.
            self._rnotesPages = itertools.cycle(range(rnotes_start,
                self._progressNotebook.get_n_pages()))
        else:
            # Add a blank page to the notebook and we'll just cycle to that
            # over and over again.
            blank = Gtk.Box()
            blank.show()
            self._progressNotebook.append_page(blank, None)
            self._rnotesPages = itertools.cycle([rnotes_start])

    def refresh(self):
        from pyanaconda.installation import doInstall
        from pyanaconda.threading import threadMgr, AnacondaThread

        super().refresh()

        self._start_ransom_notes()
        self._update_progress_timer.timeout_msec(250, self._update_progress, self._install_done)
        threadMgr.add(AnacondaThread(name=THREAD_INSTALL, target=doInstall,
                                     args=(self.storage, self.payload, self.data)))

    def _updateContinueButton(self):
        if self._configurationDone:
            self.window.set_may_continue(self.continuePossible)
        else:
            self.builder.get_object("configureButton").set_sensitive(self.continuePossible)

    def _init_progress_bar(self, steps):
        self._totalSteps = steps
        self._currentStep = 0

        gtk_call_once(self._progressBar.set_fraction, 0.0)

    def _step_progress_bar(self):
        if not self._totalSteps:
            return

        self._currentStep += 1
        gtk_call_once(self._progressBar.set_fraction, self._currentStep/self._totalSteps)

    def _update_progress_message(self, message):
        if not self._totalSteps:
            return

        gtk_call_once(self._progressLabel.set_text, message)

    @async_action_nowait
    def _restart_spinner(self):
        self._spinner.show()
        self._spinner.start()
예제 #2
0
class DatetimeSpoke(FirstbootSpokeMixIn, NormalSpoke):
    """
       .. inheritance-diagram:: DatetimeSpoke
          :parts: 3
    """
    builderObjects = [
        "datetimeWindow",
        "days",
        "months",
        "years",
        "regions",
        "cities",
        "upImage",
        "upImage1",
        "upImage2",
        "downImage",
        "downImage1",
        "downImage2",
        "downImage3",
        "configImage",
        "citiesFilter",
        "daysFilter",
        "cityCompletion",
        "regionCompletion",
    ]

    mainWidgetName = "datetimeWindow"
    uiFile = "spokes/datetime_spoke.glade"
    helpFile = "DateTimeSpoke.xml"

    category = LocalizationCategory

    icon = "preferences-system-time-symbolic"
    title = CN_("GUI|Spoke", "_Time & Date")

    # Hack to get libtimezonemap loaded for GtkBuilder
    # see https://bugzilla.gnome.org/show_bug.cgi?id=712184
    _hack = TimezoneMap.TimezoneMap()
    del (_hack)

    def __init__(self, *args):
        NormalSpoke.__init__(self, *args)

        # taking values from the kickstart file?
        self._kickstarted = flags.flags.automatedInstall

        self._update_datetime_timer = None
        self._start_updating_timer = None
        self._shown = False
        self._tz = None

        self._timezone_module = TIMEZONE.get_observer()
        self._timezone_module.connect()
        self._network_module = NETWORK.get_observer()
        self._network_module.connect()

    def initialize(self):
        NormalSpoke.initialize(self)
        self.initialize_start()
        self._daysStore = self.builder.get_object("days")
        self._monthsStore = self.builder.get_object("months")
        self._yearsStore = self.builder.get_object("years")
        self._regionsStore = self.builder.get_object("regions")
        self._citiesStore = self.builder.get_object("cities")
        self._tzmap = self.builder.get_object("tzmap")
        self._dateBox = self.builder.get_object("dateBox")

        # we need to know it the new value is the same as previous or not
        self._old_region = None
        self._old_city = None

        self._regionCombo = self.builder.get_object("regionCombobox")
        self._cityCombo = self.builder.get_object("cityCombobox")

        self._daysFilter = self.builder.get_object("daysFilter")
        self._daysFilter.set_visible_func(self.existing_date, None)

        self._citiesFilter = self.builder.get_object("citiesFilter")
        self._citiesFilter.set_visible_func(self.city_in_region, None)

        self._hoursLabel = self.builder.get_object("hoursLabel")
        self._minutesLabel = self.builder.get_object("minutesLabel")
        self._amPmUp = self.builder.get_object("amPmUpButton")
        self._amPmDown = self.builder.get_object("amPmDownButton")
        self._amPmLabel = self.builder.get_object("amPmLabel")
        self._radioButton24h = self.builder.get_object("timeFormatRB")
        self._amPmRevealer = self.builder.get_object("amPmRevealer")

        # Set the entry completions.
        # The text_column property needs to be set here. If we set
        # it in the glade file, the completion doesn't show text.
        region_completion = self.builder.get_object("regionCompletion")
        region_completion.set_text_column(0)

        city_completion = self.builder.get_object("cityCompletion")
        city_completion.set_text_column(0)

        # create widgets for displaying/configuring date
        day_box, self._dayCombo, day_label = _new_date_field_box(
            self._daysFilter)
        self._dayCombo.connect("changed", self.on_day_changed)
        month_box, self._monthCombo, month_label = _new_date_field_box(
            self._monthsStore)
        self._monthCombo.connect("changed", self.on_month_changed)
        year_box, self._yearCombo, year_label = _new_date_field_box(
            self._yearsStore)
        self._yearCombo.connect("changed", self.on_year_changed)

        # get the right order for date widgets and respective formats and put
        # widgets in place
        widgets, formats = resolve_date_format(year_box, month_box, day_box)
        for widget in widgets:
            self._dateBox.pack_start(widget, False, False, 0)

        self._day_format, suffix = formats[widgets.index(day_box)]
        day_label.set_text(suffix)
        self._month_format, suffix = formats[widgets.index(month_box)]
        month_label.set_text(suffix)
        self._year_format, suffix = formats[widgets.index(year_box)]
        year_label.set_text(suffix)

        self._ntpSwitch = self.builder.get_object("networkTimeSwitch")

        self._regions_zones = get_all_regions_and_timezones()

        # Set the initial sensitivity of the AM/PM toggle based on the time-type selected
        self._radioButton24h.emit("toggled")

        if not conf.system.can_set_system_clock:
            self._set_date_time_setting_sensitive(False)

        self._config_dialog = NTPconfigDialog(self.data, self._timezone_module)
        self._config_dialog.initialize()

        threadMgr.add(
            AnacondaThread(name=constants.THREAD_DATE_TIME,
                           target=self._initialize))

    def _initialize(self):
        # a bit hacky way, but should return the translated strings
        for i in range(1, 32):
            day = datetime.date(2000, 1, i).strftime(self._day_format)
            self.add_to_store_idx(self._daysStore, i, day)

        for i in range(1, 13):
            month = datetime.date(2000, i, 1).strftime(self._month_format)
            self.add_to_store_idx(self._monthsStore, i, month)

        for i in range(1990, 2051):
            year = datetime.date(i, 1, 1).strftime(self._year_format)
            self.add_to_store_idx(self._yearsStore, i, year)

        cities = set()
        xlated_regions = ((region, get_xlated_timezone(region))
                          for region in self._regions_zones.keys())
        for region, xlated in sorted(
                xlated_regions, key=functools.cmp_to_key(_compare_regions)):
            self.add_to_store_xlated(self._regionsStore, region, xlated)
            for city in self._regions_zones[region]:
                cities.add((city, get_xlated_timezone(city)))

        for city, xlated in sorted(cities,
                                   key=functools.cmp_to_key(_compare_cities)):
            self.add_to_store_xlated(self._citiesStore, city, xlated)

        self._update_datetime_timer = None
        kickstart_timezone = self._timezone_module.proxy.Timezone
        if is_valid_timezone(kickstart_timezone):
            self._set_timezone(kickstart_timezone)
        elif not flags.flags.automatedInstall:
            log.warning(
                "%s is not a valid timezone, falling back to default (%s)",
                kickstart_timezone, DEFAULT_TZ)
            self._set_timezone(DEFAULT_TZ)
            self._timezone_module.proxy.SetTimezone(DEFAULT_TZ)

        time_init_thread = threadMgr.get(constants.THREAD_TIME_INIT)
        if time_init_thread is not None:
            hubQ.send_message(self.__class__.__name__,
                              _("Restoring hardware time..."))
            threadMgr.wait(constants.THREAD_TIME_INIT)

        hubQ.send_ready(self.__class__.__name__, False)

        # report that we are done
        self.initialize_done()

    @property
    def status(self):
        kickstart_timezone = self._timezone_module.proxy.Timezone

        if kickstart_timezone:
            if is_valid_timezone(kickstart_timezone):
                return _("%s timezone") % get_xlated_timezone(
                    kickstart_timezone)
            else:
                return _("Invalid timezone")
        else:
            location = self._tzmap.get_location()
            if location and location.get_property("zone"):
                return _("%s timezone") % get_xlated_timezone(
                    location.get_property("zone"))
            else:
                return _("Nothing selected")

    def apply(self):
        self._shown = False

        # we could use self._tzmap.get_timezone() here, but it returns "" if
        # Etc/XXXXXX timezone is selected
        region = self._get_active_region()
        city = self._get_active_city()
        # nothing selected, just leave the spoke and
        # return to hub without changing anything
        if not region or not city:
            return

        old_tz = self._timezone_module.proxy.Timezone
        new_tz = region + "/" + city

        self._timezone_module.proxy.SetTimezone(new_tz)

        if old_tz != new_tz:
            # new values, not from kickstart
            # TODO: seen should be set from the module
            self._kickstarted = False

        self._timezone_module.proxy.SetNTPEnabled(self._ntpSwitch.get_active())

    def execute(self):
        if self._update_datetime_timer is not None:
            self._update_datetime_timer.cancel()
        self._update_datetime_timer = None

    @property
    def ready(self):
        return not threadMgr.get("AnaDateTimeThread")

    @property
    def completed(self):
        if self._kickstarted and not self._timezone_module.proxy.Kickstarted:
            # taking values from kickstart, but not specified
            return False
        else:
            return is_valid_timezone(self._timezone_module.proxy.Timezone)

    @property
    def mandatory(self):
        return True

    def refresh(self):
        self._shown = True

        # update the displayed time
        self._update_datetime_timer = Timer()
        self._update_datetime_timer.timeout_sec(1, self._update_datetime)
        self._start_updating_timer = None

        kickstart_timezone = self._timezone_module.proxy.Timezone

        if is_valid_timezone(kickstart_timezone):
            self._tzmap.set_timezone(kickstart_timezone)
            time.tzset()

        self._update_datetime()

        has_active_network = self._network_module.proxy.Connected
        if not has_active_network:
            self._show_no_network_warning()
        else:
            self.clear_info()
            gtk_call_once(self._config_dialog.refresh_servers_state)

        if conf.system.can_set_time_synchronization:
            ntp_working = has_active_network and util.service_running(
                NTP_SERVICE)
        else:
            ntp_working = self._timezone_module.proxy.NTPEnabled

        self._ntpSwitch.set_active(ntp_working)

    @async_action_wait
    def _set_timezone(self, timezone):
        """
        Sets timezone to the city/region comboboxes and the timezone map.

        :param timezone: timezone to set
        :type timezone: str
        :return: if successfully set or not
        :rtype: bool

        """

        parts = timezone.split("/", 1)
        if len(parts) != 2:
            # invalid timezone cannot be set
            return False

        region, city = parts
        self._set_combo_selection(self._regionCombo, region)
        self._set_combo_selection(self._cityCombo, city)

        return True

    @async_action_nowait
    def add_to_store_xlated(self, store, item, xlated):
        store.append([item, xlated])

    @async_action_nowait
    def add_to_store_idx(self, store, idx, item):
        store.append([idx, item])

    def existing_date(self, days_model, days_iter, user_data=None):
        if not days_iter:
            return False
        day = days_model[days_iter][0]

        #days 1-28 are in every month every year
        if day < 29:
            return True

        months_model = self._monthCombo.get_model()
        months_iter = self._monthCombo.get_active_iter()
        if not months_iter:
            return True

        years_model = self._yearCombo.get_model()
        years_iter = self._yearCombo.get_active_iter()
        if not years_iter:
            return True

        try:
            datetime.date(years_model[years_iter][0],
                          months_model[months_iter][0], day)
            return True
        except ValueError:
            return False

    def _get_active_city(self):
        cities_model = self._cityCombo.get_model()
        cities_iter = self._cityCombo.get_active_iter()
        if not cities_iter:
            return None

        return cities_model[cities_iter][0]

    def _get_active_region(self):
        regions_model = self._regionCombo.get_model()
        regions_iter = self._regionCombo.get_active_iter()
        if not regions_iter:
            return None

        return regions_model[regions_iter][0]

    def city_in_region(self, model, itr, user_data=None):
        if not itr:
            return False
        city = model[itr][0]

        region = self._get_active_region()
        if not region:
            return False

        return city in self._regions_zones[region]

    def _set_amPm_part_sensitive(self, sensitive):

        for widget in (self._amPmUp, self._amPmDown, self._amPmLabel):
            widget.set_sensitive(sensitive)

    def _to_amPm(self, hours):
        if hours >= 12:
            day_phase = _("PM")
        else:
            day_phase = _("AM")

        new_hours = ((hours - 1) % 12) + 1

        return (new_hours, day_phase)

    def _to_24h(self, hours, day_phase):
        correction = 0

        if day_phase == _("AM") and hours == 12:
            correction = -12

        elif day_phase == _("PM") and hours != 12:
            correction = 12

        return (hours + correction) % 24

    def _update_datetime(self):
        now = datetime.datetime.now(self._tz)
        if self._radioButton24h.get_active():
            self._hoursLabel.set_text("%0.2d" % now.hour)
        else:
            hours, amPm = self._to_amPm(now.hour)
            self._hoursLabel.set_text("%0.2d" % hours)
            self._amPmLabel.set_text(amPm)

        self._minutesLabel.set_text("%0.2d" % now.minute)

        self._set_combo_selection(self._dayCombo, now.day)
        self._set_combo_selection(self._monthCombo, now.month)
        self._set_combo_selection(self._yearCombo, now.year)

        #GLib's timer is driven by the return value of the function.
        #It runs the fuction periodically while the returned value
        #is True.
        return True

    def _save_system_time(self):
        """
        Returning False from this method removes the timer that would
        otherwise call it again and again.

        """

        self._start_updating_timer = None

        if not conf.system.can_set_system_clock:
            return False

        month = self._get_combo_selection(self._monthCombo)[0]
        if not month:
            return False

        year = self._get_combo_selection(self._yearCombo)[0]
        if not year:
            return False

        hours = int(self._hoursLabel.get_text())
        if not self._radioButton24h.get_active():
            hours = self._to_24h(hours, self._amPmLabel.get_text())

        minutes = int(self._minutesLabel.get_text())

        day = self._get_combo_selection(self._dayCombo)[0]
        #day may be None if there is no such in the selected year and month
        if day:
            isys.set_system_date_time(year,
                                      month,
                                      day,
                                      hours,
                                      minutes,
                                      tz=self._tz)

        #start the timer only when the spoke is shown
        if self._shown and not self._update_datetime_timer:
            self._update_datetime_timer = Timer()
            self._update_datetime_timer.timeout_sec(1, self._update_datetime)

        #run only once (after first 2 seconds of inactivity)
        return False

    def _stop_and_maybe_start_time_updating(self, interval=2):
        """
        This method is called in every date/time-setting button's callback.
        It removes the timer for updating displayed date/time (do not want to
        change it while user does it manually) and allows us to set new system
        date/time only after $interval seconds long idle on time-setting buttons.
        This is done by the _start_updating_timer that is reset in this method.
        So when there is $interval seconds long idle on date/time-setting
        buttons, self._save_system_time method is invoked. Since it returns
        False, this timer is then removed and only reactivated in this method
        (thus in some date/time-setting button's callback).

        """

        #do not start timers if the spoke is not shown
        if not self._shown:
            self._update_datetime()
            self._save_system_time()
            return

        #stop time updating
        if self._update_datetime_timer:
            self._update_datetime_timer.cancel()
            self._update_datetime_timer = None

        #stop previous $interval seconds timer (see below)
        if self._start_updating_timer:
            self._start_updating_timer.cancel()

        #let the user change date/time and after $interval seconds of inactivity
        #save it as the system time and start updating the displayed date/time
        self._start_updating_timer = Timer()
        self._start_updating_timer.timeout_sec(interval,
                                               self._save_system_time)

    def _set_combo_selection(self, combo, item):
        model = combo.get_model()
        if not model:
            return False

        itr = model.get_iter_first()
        while itr:
            if model[itr][0] == item:
                combo.set_active_iter(itr)
                return True

            itr = model.iter_next(itr)

        return False

    def _get_combo_selection(self, combo):
        """
        Get the selected item of the combobox.

        :return: selected item or None

        """

        model = combo.get_model()
        itr = combo.get_active_iter()
        if not itr or not model:
            return None, None

        return model[itr][0], model[itr][1]

    def _restore_old_city_region(self):
        """Restore stored "old" (or last valid) values."""
        # check if there are old values to go back to
        if self._old_region and self._old_city:
            self._set_timezone(self._old_region + "/" + self._old_city)

    def on_up_hours_clicked(self, *args):
        self._stop_and_maybe_start_time_updating()

        hours = int(self._hoursLabel.get_text())

        if self._radioButton24h.get_active():
            new_hours = (hours + 1) % 24
        else:
            amPm = self._amPmLabel.get_text()
            #let's not deal with magical AM/PM arithmetics
            new_hours = self._to_24h(hours, amPm)
            new_hours, new_amPm = self._to_amPm((new_hours + 1) % 24)
            self._amPmLabel.set_text(new_amPm)

        new_hours_str = "%0.2d" % new_hours
        self._hoursLabel.set_text(new_hours_str)

    def on_down_hours_clicked(self, *args):
        self._stop_and_maybe_start_time_updating()

        hours = int(self._hoursLabel.get_text())

        if self._radioButton24h.get_active():
            new_hours = (hours - 1) % 24
        else:
            amPm = self._amPmLabel.get_text()
            #let's not deal with magical AM/PM arithmetics
            new_hours = self._to_24h(hours, amPm)
            new_hours, new_amPm = self._to_amPm((new_hours - 1) % 24)
            self._amPmLabel.set_text(new_amPm)

        new_hours_str = "%0.2d" % new_hours
        self._hoursLabel.set_text(new_hours_str)

    def on_up_minutes_clicked(self, *args):
        self._stop_and_maybe_start_time_updating()

        minutes = int(self._minutesLabel.get_text())
        minutes_str = "%0.2d" % ((minutes + 1) % 60)
        self._minutesLabel.set_text(minutes_str)

    def on_down_minutes_clicked(self, *args):
        self._stop_and_maybe_start_time_updating()

        minutes = int(self._minutesLabel.get_text())
        minutes_str = "%0.2d" % ((minutes - 1) % 60)
        self._minutesLabel.set_text(minutes_str)

    def on_updown_ampm_clicked(self, *args):
        self._stop_and_maybe_start_time_updating()

        if self._amPmLabel.get_text() == _("AM"):
            self._amPmLabel.set_text(_("PM"))
        else:
            self._amPmLabel.set_text(_("AM"))

    def on_region_changed(self, combo, *args):
        """
        :see: on_city_changed

        """

        region = self._get_active_region()

        if not region or region == self._old_region:
            # region entry being edited or old_value chosen, no action needed
            # @see: on_city_changed
            return

        self._citiesFilter.refilter()

        # Set the city to the first one available in this newly selected region.
        zone = self._regions_zones[region]
        firstCity = sorted(list(zone))[0]

        self._set_combo_selection(self._cityCombo, firstCity)
        self._old_region = region
        self._old_city = firstCity

    def on_city_changed(self, combo, *args):
        """
        ComboBox emits ::changed signal not only when something is selected, but
        also when its entry's text is changed. We need to distinguish between
        those two cases ('London' typed in the entry => no action until ENTER is
        hit etc.; 'London' chosen in the expanded combobox => update timezone
        map and do all necessary actions). Fortunately when entry is being
        edited, self._get_active_city returns None.

        """

        timezone = None

        region = self._get_active_region()
        city = self._get_active_city()

        if not region or not city or (region == self._old_region
                                      and city == self._old_city):
            # entry being edited or no change, no actions needed
            return

        if city and region:
            timezone = region + "/" + city
        else:
            # both city and region are needed to form a valid timezone
            return

        if region == "Etc":
            # Etc timezones cannot be displayed on the map, so let's reset the
            # location and manually set a highlight with no location pin.
            self._tzmap.clear_location()
            if city in ("GMT", "UTC"):
                offset = 0.0
            # The tzdb data uses POSIX-style signs for the GMT zones, which is
            # the opposite of whatever everyone else expects. GMT+4 indicates a
            # zone four hours west of Greenwich; i.e., four hours before. Reverse
            # the sign to match the libtimezone map.
            else:
                # Take the part after "GMT"
                offset = -float(city[3:])

            self._tzmap.set_selected_offset(offset)
            time.tzset()
        else:
            # we don't want the timezone-changed signal to be emitted
            self._tzmap.set_timezone(timezone)
            time.tzset()

        # update "old" values
        self._old_city = city

    def on_entry_left(self, entry, *args):
        # user clicked somewhere else or hit TAB => finished editing
        entry.emit("activate")

    def on_city_region_key_released(self, entry, event, *args):
        if event.type == Gdk.EventType.KEY_RELEASE and \
                event.keyval == Gdk.KEY_Escape:
            # editing canceled
            self._restore_old_city_region()

    def on_completion_match_selected(self, combo, model, itr):
        item = None
        if model and itr:
            item = model[itr][0]
        if item:
            self._set_combo_selection(combo, item)

    def on_city_region_text_entry_activated(self, entry):
        combo = entry.get_parent()

        # It's gotta be up there somewhere, right? right???
        while not isinstance(combo, Gtk.ComboBox):
            combo = combo.get_parent()

        model = combo.get_model()
        entry_text = entry.get_text().lower()

        for row in model:
            if entry_text == row[0].lower():
                self._set_combo_selection(combo, row[0])
                return

        # non-matching value entered, reset to old values
        self._restore_old_city_region()

    def on_month_changed(self, *args):
        self._stop_and_maybe_start_time_updating(interval=5)
        self._daysFilter.refilter()

    def on_day_changed(self, *args):
        self._stop_and_maybe_start_time_updating(interval=5)

    def on_year_changed(self, *args):
        self._stop_and_maybe_start_time_updating(interval=5)
        self._daysFilter.refilter()

    def on_location_changed(self, tz_map, location):
        if not location:
            return

        timezone = location.get_property('zone')

        # Updating the timezone will update the region/city combo boxes to match.
        # The on_city_changed handler will attempt to convert the timezone back
        # to a location and set it in the map, which we don't want, since we
        # already have a location. That's why we're here.
        with blockedHandler(self._cityCombo, self.on_city_changed):
            if self._set_timezone(timezone):
                # timezone successfully set
                self._tz = get_timezone(timezone)
                self._update_datetime()

    def on_timeformat_changed(self, button24h, *args):
        hours = int(self._hoursLabel.get_text())
        amPm = self._amPmLabel.get_text()

        #connected to 24-hour radio button
        if button24h.get_active():
            self._set_amPm_part_sensitive(False)
            new_hours = self._to_24h(hours, amPm)
            self._amPmRevealer.set_reveal_child(False)
        else:
            self._set_amPm_part_sensitive(True)
            new_hours, new_amPm = self._to_amPm(hours)
            self._amPmLabel.set_text(new_amPm)
            self._amPmRevealer.set_reveal_child(True)

        self._hoursLabel.set_text("%0.2d" % new_hours)

    def _set_date_time_setting_sensitive(self, sensitive):
        #contains all date/time setting widgets
        footer_alignment = self.builder.get_object("footerAlignment")
        footer_alignment.set_sensitive(sensitive)

    def _show_no_network_warning(self):
        self.set_warning(_("You need to set up networking first if you "\
                           "want to use NTP"))

    def _show_no_ntp_server_warning(self):
        self.set_warning(_("You have no working NTP server configured"))

    def on_ntp_switched(self, switch, *args):
        if switch.get_active():
            #turned ON
            if not conf.system.can_set_time_synchronization:
                #cannot touch runtime system, not much to do here
                return

            if not self._network_module.proxy.Connected:
                self._show_no_network_warning()
                switch.set_active(False)
                return
            else:
                self.clear_info()

                working_server = self._config_dialog.working_server
                if working_server is None:
                    self._show_no_ntp_server_warning()
                else:
                    #we need a one-time sync here, because chronyd would not change
                    #the time as drastically as we need
                    ntp.one_time_sync_async(working_server)

            ret = util.start_service(NTP_SERVICE)
            self._set_date_time_setting_sensitive(False)

            #if starting chronyd failed and chronyd is not running,
            #set switch back to OFF
            if (ret != 0) and not util.service_running(NTP_SERVICE):
                switch.set_active(False)

        else:
            #turned OFF
            if not conf.system.can_set_time_synchronization:
                #cannot touch runtime system, nothing to do here
                return

            self._set_date_time_setting_sensitive(True)
            ret = util.stop_service(NTP_SERVICE)

            #if stopping chronyd failed and chronyd is running,
            #set switch back to ON
            if (ret != 0) and util.service_running(NTP_SERVICE):
                switch.set_active(True)

            self.clear_info()

    def on_ntp_config_clicked(self, *args):
        self._config_dialog.refresh()

        with self.main_window.enlightbox(self._config_dialog.window):
            response = self._config_dialog.run()

        if response == 1:
            pools, servers = self._config_dialog.pools_servers
            self._timezone_module.proxy.SetNTPServers(
                ntp.pools_servers_to_internal(pools, servers))

            if self._config_dialog.working_server is None:
                self._show_no_ntp_server_warning()
            else:
                self.clear_info()
예제 #3
0
class ProgressHub(Hub):
    """
       .. inheritance-diagram:: ProgressHub
          :parts: 3
    """
    builderObjects = ["progressWindow"]
    mainWidgetName = "progressWindow"
    uiFile = "hubs/progress.glade"
    helpFile = "ProgressHub.xml"

    def __init__(self, data, storage, payload, instclass):
        Hub.__init__(self, data, storage, payload, instclass)

        self._totalSteps = 0
        self._currentStep = 0
        self._configurationDone = False
        self._update_progress_timer = Timer()
        self._cycle_rnotes_timer = Timer()

    def _do_configuration(self, widget=None, reenable_ransom=True):
        from pyanaconda.installation import doConfiguration
        from pyanaconda.threading import threadMgr, AnacondaThread

        assert self._configurationDone == False

        self._configurationDone = True

        # Disable all personalization spokes
        self.builder.get_object("progressWindow-scroll").set_sensitive(False)

        if reenable_ransom:
            self._start_ransom_notes()

        self._restart_spinner()

        self._update_progress_timer.timeout_msec(250, self._update_progress,
                                                 self._configuration_done)
        threadMgr.add(
            AnacondaThread(name=THREAD_CONFIGURATION,
                           target=doConfiguration,
                           args=(self.storage, self.payload, self.data,
                                 self.instclass)))

    def _start_ransom_notes(self):
        # Adding this as a timeout below means it'll get called after 60
        # seconds, so we need to do the first call manually.
        self._cycle_rnotes()
        self._cycle_rnotes_timer.timeout_sec(60, self._cycle_rnotes)

    def _update_progress(self, callback=None):
        from pyanaconda.progress import progressQ
        import queue

        q = progressQ.q

        # Grab all messages may have appeared since last time this method ran.
        while True:
            # Attempt to get a message out of the queue for how we should update
            # the progress bar.  If there's no message, don't error out.
            try:
                (code, args) = q.get(False)
            except queue.Empty:
                break

            if code == progressQ.PROGRESS_CODE_INIT:
                self._init_progress_bar(args[0])
            elif code == progressQ.PROGRESS_CODE_STEP:
                self._step_progress_bar()
            elif code == progressQ.PROGRESS_CODE_MESSAGE:
                self._update_progress_message(args[0])
            elif code == progressQ.PROGRESS_CODE_COMPLETE:
                q.task_done()

                # we are done, stop the progress indication
                gtk_call_once(self._progressBar.set_fraction, 1.0)
                gtk_call_once(self._progressLabel.set_text, _("Complete!"))
                gtk_call_once(self._spinner.stop)
                gtk_call_once(self._spinner.hide)

                if callback:
                    callback()

                # There shouldn't be any more progress bar updates, so return False
                # to indicate this method should be removed from the idle loop.
                return False
            elif code == progressQ.PROGRESS_CODE_QUIT:
                sys.exit(args[0])

            q.task_done()

        return True

    def _configuration_done(self):
        # Configuration done, remove ransom notes timer
        # and switch to the Reboot page

        self._cycle_rnotes_timer.cancel()
        self._progressNotebook.set_current_page(1)
        self.window.set_may_continue(True)

        util.ipmi_report(IPMI_FINISHED)

        # kickstart install, continue automatically if reboot or shutdown selected
        if flags.automatedInstall and self.data.reboot.action in [
                KS_REBOOT, KS_SHUTDOWN
        ]:
            self.window.emit("continue-clicked")

    def _install_done(self):
        # package installation done, check personalization spokes
        # and start the configuration step if all is ready
        if not self._inSpoke and self.continuePossible:
            self._do_configuration(reenable_ransom=False)
        else:
            # some mandatory spokes are not ready
            # switch to configure and finish page
            self._cycle_rnotes_timer.cancel()
            self._progressNotebook.set_current_page(0)

    def _do_globs(self, path):
        return glob.glob(path + "/*.png") + \
               glob.glob(path + "/*.jpg") + \
               glob.glob(path + "/*.svg")

    def _get_rnotes(self):
        # We first look for rnotes in paths containing the language, then in
        # directories without the language component.  You know, just in case.

        paths = [
            "/tmp/updates/pixmaps/rnotes/", "/tmp/product/pixmaps/rnotes/",
            "/usr/share/anaconda/pixmaps/rnotes/"
        ]

        all_lang_pixmaps = []
        for path in paths:
            all_lang_pixmaps += self._do_globs(path + "/*")

        pixmap_langs = [
            pixmap.split(os.path.sep)[-2] for pixmap in all_lang_pixmaps
        ]
        best_lang = find_best_locale_match(os.environ["LANG"], pixmap_langs)

        if not best_lang:
            # nothing found, try the default language
            best_lang = find_best_locale_match(DEFAULT_LANG, pixmap_langs)

        if not best_lang:
            # nothing found even for the default language, try non-localized rnotes
            non_localized = []
            for path in paths:
                non_localized += self._do_globs(path)

            return non_localized

        best_lang_pixmaps = []
        for path in paths:
            best_lang_pixmaps += self._do_globs(path + best_lang)

        return best_lang_pixmaps

    def _cycle_rnotes(self):
        # Change the ransom notes image every minute by grabbing the next
        # image's filename.  Note that self._rnotesPages is an infinite list,
        # so this will cycle through the images indefinitely.
        try:
            nxt = next(self._rnotesPages)
        except StopIteration:
            # there are no rnotes
            pass
        else:
            self._progressNotebook.set_current_page(nxt)

        return True

    def initialize(self):
        Hub.initialize(self)

        if flags.livecdInstall:
            continueText = self.builder.get_object("rebootLabel")
            continueText.set_text(
                _("%s is now successfully installed on your system and ready "
                  "for you to use!  When you are ready, reboot your system to start using it!"
                  ))
            continueText.set_line_wrap(True)
            self.window.get_continue_button().set_label(
                C_("GUI|Progress", "_Quit"))

        self._progressBar = self.builder.get_object("progressBar")
        self._progressLabel = self.builder.get_object("progressLabel")
        self._progressNotebook = self.builder.get_object("progressNotebook")
        self._spinner = self.builder.get_object("progressSpinner")

        lbl = self.builder.get_object("configurationLabel")
        lbl.set_text(
            _("%s is now successfully installed, but some configuration still needs to be done.\n"
              "Finish it and then click the Finish configuration button please."
              ) % productName)

        lbl = self.builder.get_object("rebootLabel")
        lbl.set_text(
            _("%s is now successfully installed and ready for you to use!\n"
              "Go ahead and reboot to start using it!") % productName)

        rnotes = self._get_rnotes()
        # Get the start of the pages we're about to add to the notebook
        rnotes_start = self._progressNotebook.get_n_pages()
        if rnotes:
            # Add a new page in the notebook for each ransom note image.
            for f in rnotes:
                img = Gtk.Image.new_from_file(f)
                img.show()
                self._progressNotebook.append_page(img, None)

            # An infinite list of the page numbers containing ransom notes images.
            self._rnotesPages = itertools.cycle(
                range(rnotes_start, self._progressNotebook.get_n_pages()))
        else:
            # Add a blank page to the notebook and we'll just cycle to that
            # over and over again.
            blank = Gtk.Box()
            blank.show()
            self._progressNotebook.append_page(blank, None)
            self._rnotesPages = itertools.cycle([rnotes_start])

    def refresh(self):
        from pyanaconda.installation import doInstall
        from pyanaconda.threading import threadMgr, AnacondaThread

        Hub.refresh(self)

        self._start_ransom_notes()
        self._update_progress_timer.timeout_msec(250, self._update_progress,
                                                 self._install_done)
        threadMgr.add(
            AnacondaThread(name=THREAD_INSTALL,
                           target=doInstall,
                           args=(self.storage, self.payload, self.data,
                                 self.instclass)))

    def _updateContinueButton(self):
        if self._configurationDone:
            self.window.set_may_continue(self.continuePossible)
        else:
            self.builder.get_object("configureButton").set_sensitive(
                self.continuePossible)

    def _init_progress_bar(self, steps):
        self._totalSteps = steps
        self._currentStep = 0

        gtk_call_once(self._progressBar.set_fraction, 0.0)

    def _step_progress_bar(self):
        if not self._totalSteps:
            return

        self._currentStep += 1
        gtk_call_once(self._progressBar.set_fraction,
                      self._currentStep / self._totalSteps)

    def _update_progress_message(self, message):
        if not self._totalSteps:
            return

        gtk_call_once(self._progressLabel.set_text, message)

    @async_action_nowait
    def _restart_spinner(self):
        self._spinner.show()
        self._spinner.start()
예제 #4
0
class RefreshDialog(GUIObject):
    builderObjects = ["refreshDialog"]
    mainWidgetName = "refreshDialog"
    uiFile = "spokes/lib/refresh.glade"

    def __init__(self, data, storage):
        super().__init__(data)

        self.storage = storage

        self._notebook = self.builder.get_object("refreshNotebook")
        self._cancel_button = self.builder.get_object("refreshCancelButton")
        self._ok_button = self.builder.get_object("refreshOKButton")

        self._elapsed = 0
        self._rescan_timer = Timer()

    def run(self):
        rc = self.window.run()
        self.window.destroy()
        return rc

    def _check_rescan(self, *args):
        if threadMgr.get(constants.THREAD_STORAGE):
            self._elapsed += 1

            # If more than five seconds has elapsed since the rescan started,
            # give the user the option to return to the hub.
            if self._elapsed >= 5:
                self._notebook.set_current_page(2)

            return True

        # Seems a little silly to have this warning text still displayed
        # when everything is done.
        box = self.builder.get_object("warningBox")
        box.set_visible(False)

        self._cancel_button.set_sensitive(False)
        self._ok_button.set_sensitive(True)
        self._notebook.set_current_page(3)
        return False

    def return_to_hub_link_clicked(self, label, uri):
        # The user clicked on the link that takes them back to the hub.  We need
        # to kill the _check_rescan watcher and then emit a special response ID
        # indicating the user did not press OK.
        #
        # NOTE: There is no button with response_id=2.
        self._rescan_timer.cancel()
        self.window.response(2)

    def on_rescan_clicked(self, button):
        # Once the rescan button is clicked, the option to cancel expires.
        # We also need to display the spinner showing activity.
        self._cancel_button.set_sensitive(False)
        self._ok_button.set_sensitive(False)
        self._notebook.set_current_page(1)

        # And now to fire up the storage reinitialization.
        threadMgr.add(AnacondaThread(name=constants.THREAD_STORAGE,
                                     target=reset_storage,
                                     args=(self.storage, ),
                                     kwargs={"scan_all": True}))

        self._elapsed = 0

        # This watches for the rescan to be finished and updates the dialog when
        # that happens.
        self._rescan_timer.timeout_sec(1, self._check_rescan)
예제 #5
0
class DatetimeSpoke(FirstbootSpokeMixIn, NormalSpoke):
    """
       .. inheritance-diagram:: DatetimeSpoke
          :parts: 3
    """
    builderObjects = ["datetimeWindow",
                      "days", "months", "years", "regions", "cities",
                      "upImage", "upImage1", "upImage2", "downImage",
                      "downImage1", "downImage2", "downImage3", "configImage",
                      "citiesFilter", "daysFilter",
                      "cityCompletion", "regionCompletion",
                      ]

    mainWidgetName = "datetimeWindow"
    uiFile = "spokes/datetime_spoke.glade"
    helpFile = "DateTimeSpoke.xml"

    category = LocalizationCategory

    icon = "preferences-system-time-symbolic"
    title = CN_("GUI|Spoke", "_Time & Date")

    # Hack to get libtimezonemap loaded for GtkBuilder
    # see https://bugzilla.gnome.org/show_bug.cgi?id=712184
    _hack = TimezoneMap.TimezoneMap()
    del(_hack)

    def __init__(self, *args):
        NormalSpoke.__init__(self, *args)

        # taking values from the kickstart file?
        self._kickstarted = flags.flags.automatedInstall

        self._update_datetime_timer = None
        self._start_updating_timer = None
        self._shown = False
        self._tz = None

        self._timezone_module = TIMEZONE.get_observer()
        self._timezone_module.connect()
        self._network_module = NETWORK.get_observer()
        self._network_module.connect()

    def initialize(self):
        NormalSpoke.initialize(self)
        self.initialize_start()
        self._daysStore = self.builder.get_object("days")
        self._monthsStore = self.builder.get_object("months")
        self._yearsStore = self.builder.get_object("years")
        self._regionsStore = self.builder.get_object("regions")
        self._citiesStore = self.builder.get_object("cities")
        self._tzmap = self.builder.get_object("tzmap")
        self._dateBox = self.builder.get_object("dateBox")

        # we need to know it the new value is the same as previous or not
        self._old_region = None
        self._old_city = None

        self._regionCombo = self.builder.get_object("regionCombobox")
        self._cityCombo = self.builder.get_object("cityCombobox")

        self._daysFilter = self.builder.get_object("daysFilter")
        self._daysFilter.set_visible_func(self.existing_date, None)

        self._citiesFilter = self.builder.get_object("citiesFilter")
        self._citiesFilter.set_visible_func(self.city_in_region, None)

        self._hoursLabel = self.builder.get_object("hoursLabel")
        self._minutesLabel = self.builder.get_object("minutesLabel")
        self._amPmUp = self.builder.get_object("amPmUpButton")
        self._amPmDown = self.builder.get_object("amPmDownButton")
        self._amPmLabel = self.builder.get_object("amPmLabel")
        self._radioButton24h = self.builder.get_object("timeFormatRB")
        self._amPmRevealer = self.builder.get_object("amPmRevealer")

        # Set the entry completions.
        # The text_column property needs to be set here. If we set
        # it in the glade file, the completion doesn't show text.
        region_completion = self.builder.get_object("regionCompletion")
        region_completion.set_text_column(0)

        city_completion = self.builder.get_object("cityCompletion")
        city_completion.set_text_column(0)

        # create widgets for displaying/configuring date
        day_box, self._dayCombo, day_label = _new_date_field_box(self._daysFilter)
        self._dayCombo.connect("changed", self.on_day_changed)
        month_box, self._monthCombo, month_label = _new_date_field_box(self._monthsStore)
        self._monthCombo.connect("changed", self.on_month_changed)
        year_box, self._yearCombo, year_label = _new_date_field_box(self._yearsStore)
        self._yearCombo.connect("changed", self.on_year_changed)

        # get the right order for date widgets and respective formats and put
        # widgets in place
        widgets, formats = resolve_date_format(year_box, month_box, day_box)
        for widget in widgets:
            self._dateBox.pack_start(widget, False, False, 0)

        self._day_format, suffix = formats[widgets.index(day_box)]
        day_label.set_text(suffix)
        self._month_format, suffix = formats[widgets.index(month_box)]
        month_label.set_text(suffix)
        self._year_format, suffix = formats[widgets.index(year_box)]
        year_label.set_text(suffix)

        self._ntpSwitch = self.builder.get_object("networkTimeSwitch")

        self._regions_zones = get_all_regions_and_timezones()

        # Set the initial sensitivity of the AM/PM toggle based on the time-type selected
        self._radioButton24h.emit("toggled")

        if not conf.system.can_set_system_clock:
            self._set_date_time_setting_sensitive(False)

        self._config_dialog = NTPconfigDialog(self.data, self._timezone_module)
        self._config_dialog.initialize()

        threadMgr.add(AnacondaThread(name=constants.THREAD_DATE_TIME,
                                     target=self._initialize))

    def _initialize(self):
        # a bit hacky way, but should return the translated strings
        for i in range(1, 32):
            day = datetime.date(2000, 1, i).strftime(self._day_format)
            self.add_to_store_idx(self._daysStore, i, day)

        for i in range(1, 13):
            month = datetime.date(2000, i, 1).strftime(self._month_format)
            self.add_to_store_idx(self._monthsStore, i, month)

        for i in range(1990, 2051):
            year = datetime.date(i, 1, 1).strftime(self._year_format)
            self.add_to_store_idx(self._yearsStore, i, year)

        cities = set()
        xlated_regions = ((region, get_xlated_timezone(region))
                          for region in self._regions_zones.keys())
        for region, xlated in sorted(xlated_regions, key=functools.cmp_to_key(_compare_regions)):
            self.add_to_store_xlated(self._regionsStore, region, xlated)
            for city in self._regions_zones[region]:
                cities.add((city, get_xlated_timezone(city)))

        for city, xlated in sorted(cities, key=functools.cmp_to_key(_compare_cities)):
            self.add_to_store_xlated(self._citiesStore, city, xlated)

        self._update_datetime_timer = None
        kickstart_timezone = self._timezone_module.proxy.Timezone
        if is_valid_timezone(kickstart_timezone):
            self._set_timezone(kickstart_timezone)
        elif not flags.flags.automatedInstall:
            log.warning("%s is not a valid timezone, falling back to default (%s)",
                        kickstart_timezone, DEFAULT_TZ)
            self._set_timezone(DEFAULT_TZ)
            self._timezone_module.proxy.SetTimezone(DEFAULT_TZ)

        time_init_thread = threadMgr.get(constants.THREAD_TIME_INIT)
        if time_init_thread is not None:
            hubQ.send_message(self.__class__.__name__,
                             _("Restoring hardware time..."))
            threadMgr.wait(constants.THREAD_TIME_INIT)

        hubQ.send_ready(self.__class__.__name__, False)

        # report that we are done
        self.initialize_done()

    @property
    def status(self):
        kickstart_timezone = self._timezone_module.proxy.Timezone

        if kickstart_timezone:
            if is_valid_timezone(kickstart_timezone):
                return _("%s timezone") % get_xlated_timezone(kickstart_timezone)
            else:
                return _("Invalid timezone")
        else:
            location = self._tzmap.get_location()
            if location and location.get_property("zone"):
                return _("%s timezone") % get_xlated_timezone(location.get_property("zone"))
            else:
                return _("Nothing selected")

    def apply(self):
        self._shown = False

        # we could use self._tzmap.get_timezone() here, but it returns "" if
        # Etc/XXXXXX timezone is selected
        region = self._get_active_region()
        city = self._get_active_city()
        # nothing selected, just leave the spoke and
        # return to hub without changing anything
        if not region or not city:
            return

        old_tz = self._timezone_module.proxy.Timezone
        new_tz = region + "/" + city

        self._timezone_module.proxy.SetTimezone(new_tz)

        if old_tz != new_tz:
            # new values, not from kickstart
            # TODO: seen should be set from the module
            self._kickstarted = False

        self._timezone_module.proxy.SetNTPEnabled(self._ntpSwitch.get_active())

    def execute(self):
        if self._update_datetime_timer is not None:
            self._update_datetime_timer.cancel()
        self._update_datetime_timer = None

    @property
    def ready(self):
        return not threadMgr.get("AnaDateTimeThread")

    @property
    def completed(self):
        if self._kickstarted and not self._timezone_module.proxy.Kickstarted:
            # taking values from kickstart, but not specified
            return False
        else:
            return is_valid_timezone(self._timezone_module.proxy.Timezone)

    @property
    def mandatory(self):
        return True

    def refresh(self):
        self._shown = True

        # update the displayed time
        self._update_datetime_timer = Timer()
        self._update_datetime_timer.timeout_sec(1, self._update_datetime)
        self._start_updating_timer = None

        kickstart_timezone = self._timezone_module.proxy.Timezone

        if is_valid_timezone(kickstart_timezone):
            self._tzmap.set_timezone(kickstart_timezone)
            time.tzset()

        self._update_datetime()

        has_active_network = self._network_module.proxy.Connected
        if not has_active_network:
            self._show_no_network_warning()
        else:
            self.clear_info()
            gtk_call_once(self._config_dialog.refresh_servers_state)

        if conf.system.can_set_time_synchronization:
            ntp_working = has_active_network and util.service_running(NTP_SERVICE)
        else:
            ntp_working = self._timezone_module.proxy.NTPEnabled

        self._ntpSwitch.set_active(ntp_working)

    @async_action_wait
    def _set_timezone(self, timezone):
        """
        Sets timezone to the city/region comboboxes and the timezone map.

        :param timezone: timezone to set
        :type timezone: str
        :return: if successfully set or not
        :rtype: bool

        """

        parts = timezone.split("/", 1)
        if len(parts) != 2:
            # invalid timezone cannot be set
            return False

        region, city = parts
        self._set_combo_selection(self._regionCombo, region)
        self._set_combo_selection(self._cityCombo, city)

        return True

    @async_action_nowait
    def add_to_store_xlated(self, store, item, xlated):
        store.append([item, xlated])

    @async_action_nowait
    def add_to_store_idx(self, store, idx, item):
        store.append([idx, item])

    def existing_date(self, days_model, days_iter, user_data=None):
        if not days_iter:
            return False
        day = days_model[days_iter][0]

        #days 1-28 are in every month every year
        if day < 29:
            return True

        months_model = self._monthCombo.get_model()
        months_iter = self._monthCombo.get_active_iter()
        if not months_iter:
            return True

        years_model = self._yearCombo.get_model()
        years_iter = self._yearCombo.get_active_iter()
        if not years_iter:
            return True

        try:
            datetime.date(years_model[years_iter][0],
                          months_model[months_iter][0], day)
            return True
        except ValueError:
            return False

    def _get_active_city(self):
        cities_model = self._cityCombo.get_model()
        cities_iter = self._cityCombo.get_active_iter()
        if not cities_iter:
            return None

        return cities_model[cities_iter][0]

    def _get_active_region(self):
        regions_model = self._regionCombo.get_model()
        regions_iter = self._regionCombo.get_active_iter()
        if not regions_iter:
            return None

        return regions_model[regions_iter][0]

    def city_in_region(self, model, itr, user_data=None):
        if not itr:
            return False
        city = model[itr][0]

        region = self._get_active_region()
        if not region:
            return False

        return city in self._regions_zones[region]

    def _set_amPm_part_sensitive(self, sensitive):

        for widget in (self._amPmUp, self._amPmDown, self._amPmLabel):
            widget.set_sensitive(sensitive)

    def _to_amPm(self, hours):
        if hours >= 12:
            day_phase = _("PM")
        else:
            day_phase = _("AM")

        new_hours = ((hours - 1) % 12) + 1

        return (new_hours, day_phase)

    def _to_24h(self, hours, day_phase):
        correction = 0

        if day_phase == _("AM") and hours == 12:
            correction = -12

        elif day_phase == _("PM") and hours != 12:
            correction = 12

        return (hours + correction) % 24

    def _update_datetime(self):
        now = datetime.datetime.now(self._tz)
        if self._radioButton24h.get_active():
            self._hoursLabel.set_text("%0.2d" % now.hour)
        else:
            hours, amPm = self._to_amPm(now.hour)
            self._hoursLabel.set_text("%0.2d" % hours)
            self._amPmLabel.set_text(amPm)

        self._minutesLabel.set_text("%0.2d" % now.minute)

        self._set_combo_selection(self._dayCombo, now.day)
        self._set_combo_selection(self._monthCombo, now.month)
        self._set_combo_selection(self._yearCombo, now.year)

        #GLib's timer is driven by the return value of the function.
        #It runs the fuction periodically while the returned value
        #is True.
        return True

    def _save_system_time(self):
        """
        Returning False from this method removes the timer that would
        otherwise call it again and again.

        """

        self._start_updating_timer = None

        if not conf.system.can_set_system_clock:
            return False

        month = self._get_combo_selection(self._monthCombo)[0]
        if not month:
            return False

        year = self._get_combo_selection(self._yearCombo)[0]
        if not year:
            return False

        hours = int(self._hoursLabel.get_text())
        if not self._radioButton24h.get_active():
            hours = self._to_24h(hours, self._amPmLabel.get_text())

        minutes = int(self._minutesLabel.get_text())

        day = self._get_combo_selection(self._dayCombo)[0]
        #day may be None if there is no such in the selected year and month
        if day:
            isys.set_system_date_time(year, month, day, hours, minutes, tz=self._tz)

        #start the timer only when the spoke is shown
        if self._shown and not self._update_datetime_timer:
            self._update_datetime_timer = Timer()
            self._update_datetime_timer.timeout_sec(1, self._update_datetime)

        #run only once (after first 2 seconds of inactivity)
        return False

    def _stop_and_maybe_start_time_updating(self, interval=2):
        """
        This method is called in every date/time-setting button's callback.
        It removes the timer for updating displayed date/time (do not want to
        change it while user does it manually) and allows us to set new system
        date/time only after $interval seconds long idle on time-setting buttons.
        This is done by the _start_updating_timer that is reset in this method.
        So when there is $interval seconds long idle on date/time-setting
        buttons, self._save_system_time method is invoked. Since it returns
        False, this timer is then removed and only reactivated in this method
        (thus in some date/time-setting button's callback).

        """

        #do not start timers if the spoke is not shown
        if not self._shown:
            self._update_datetime()
            self._save_system_time()
            return

        #stop time updating
        if self._update_datetime_timer:
            self._update_datetime_timer.cancel()
            self._update_datetime_timer = None

        #stop previous $interval seconds timer (see below)
        if self._start_updating_timer:
            self._start_updating_timer.cancel()

        #let the user change date/time and after $interval seconds of inactivity
        #save it as the system time and start updating the displayed date/time
        self._start_updating_timer = Timer()
        self._start_updating_timer.timeout_sec(interval, self._save_system_time)

    def _set_combo_selection(self, combo, item):
        model = combo.get_model()
        if not model:
            return False

        itr = model.get_iter_first()
        while itr:
            if model[itr][0] == item:
                combo.set_active_iter(itr)
                return True

            itr = model.iter_next(itr)

        return False

    def _get_combo_selection(self, combo):
        """
        Get the selected item of the combobox.

        :return: selected item or None

        """

        model = combo.get_model()
        itr = combo.get_active_iter()
        if not itr or not model:
            return None, None

        return model[itr][0], model[itr][1]

    def _restore_old_city_region(self):
        """Restore stored "old" (or last valid) values."""
        # check if there are old values to go back to
        if self._old_region and self._old_city:
            self._set_timezone(self._old_region + "/" + self._old_city)

    def on_up_hours_clicked(self, *args):
        self._stop_and_maybe_start_time_updating()

        hours = int(self._hoursLabel.get_text())

        if self._radioButton24h.get_active():
            new_hours = (hours + 1) % 24
        else:
            amPm = self._amPmLabel.get_text()
            #let's not deal with magical AM/PM arithmetics
            new_hours = self._to_24h(hours, amPm)
            new_hours, new_amPm = self._to_amPm((new_hours + 1) % 24)
            self._amPmLabel.set_text(new_amPm)

        new_hours_str = "%0.2d" % new_hours
        self._hoursLabel.set_text(new_hours_str)

    def on_down_hours_clicked(self, *args):
        self._stop_and_maybe_start_time_updating()

        hours = int(self._hoursLabel.get_text())

        if self._radioButton24h.get_active():
            new_hours = (hours - 1) % 24
        else:
            amPm = self._amPmLabel.get_text()
            #let's not deal with magical AM/PM arithmetics
            new_hours = self._to_24h(hours, amPm)
            new_hours, new_amPm = self._to_amPm((new_hours - 1) % 24)
            self._amPmLabel.set_text(new_amPm)

        new_hours_str = "%0.2d" % new_hours
        self._hoursLabel.set_text(new_hours_str)

    def on_up_minutes_clicked(self, *args):
        self._stop_and_maybe_start_time_updating()

        minutes = int(self._minutesLabel.get_text())
        minutes_str = "%0.2d" % ((minutes + 1) % 60)
        self._minutesLabel.set_text(minutes_str)

    def on_down_minutes_clicked(self, *args):
        self._stop_and_maybe_start_time_updating()

        minutes = int(self._minutesLabel.get_text())
        minutes_str = "%0.2d" % ((minutes - 1) % 60)
        self._minutesLabel.set_text(minutes_str)

    def on_updown_ampm_clicked(self, *args):
        self._stop_and_maybe_start_time_updating()

        if self._amPmLabel.get_text() == _("AM"):
            self._amPmLabel.set_text(_("PM"))
        else:
            self._amPmLabel.set_text(_("AM"))

    def on_region_changed(self, combo, *args):
        """
        :see: on_city_changed

        """

        region = self._get_active_region()

        if not region or region == self._old_region:
            # region entry being edited or old_value chosen, no action needed
            # @see: on_city_changed
            return

        self._citiesFilter.refilter()

        # Set the city to the first one available in this newly selected region.
        zone = self._regions_zones[region]
        firstCity = sorted(list(zone))[0]

        self._set_combo_selection(self._cityCombo, firstCity)
        self._old_region = region
        self._old_city = firstCity

    def on_city_changed(self, combo, *args):
        """
        ComboBox emits ::changed signal not only when something is selected, but
        also when its entry's text is changed. We need to distinguish between
        those two cases ('London' typed in the entry => no action until ENTER is
        hit etc.; 'London' chosen in the expanded combobox => update timezone
        map and do all necessary actions). Fortunately when entry is being
        edited, self._get_active_city returns None.

        """

        timezone = None

        region = self._get_active_region()
        city = self._get_active_city()

        if not region or not city or (region == self._old_region and
                                      city == self._old_city):
            # entry being edited or no change, no actions needed
            return

        if city and region:
            timezone = region + "/" + city
        else:
            # both city and region are needed to form a valid timezone
            return

        if region == "Etc":
            # Etc timezones cannot be displayed on the map, so let's reset the
            # location and manually set a highlight with no location pin.
            self._tzmap.clear_location()
            if city in ("GMT", "UTC"):
                offset = 0.0
            # The tzdb data uses POSIX-style signs for the GMT zones, which is
            # the opposite of whatever everyone else expects. GMT+4 indicates a
            # zone four hours west of Greenwich; i.e., four hours before. Reverse
            # the sign to match the libtimezone map.
            else:
                # Take the part after "GMT"
                offset = -float(city[3:])

            self._tzmap.set_selected_offset(offset)
            time.tzset()
        else:
            # we don't want the timezone-changed signal to be emitted
            self._tzmap.set_timezone(timezone)
            time.tzset()

        # update "old" values
        self._old_city = city

    def on_entry_left(self, entry, *args):
        # user clicked somewhere else or hit TAB => finished editing
        entry.emit("activate")

    def on_city_region_key_released(self, entry, event, *args):
        if event.type == Gdk.EventType.KEY_RELEASE and \
                event.keyval == Gdk.KEY_Escape:
            # editing canceled
            self._restore_old_city_region()

    def on_completion_match_selected(self, combo, model, itr):
        item = None
        if model and itr:
            item = model[itr][0]
        if item:
            self._set_combo_selection(combo, item)

    def on_city_region_text_entry_activated(self, entry):
        combo = entry.get_parent()

        # It's gotta be up there somewhere, right? right???
        while not isinstance(combo, Gtk.ComboBox):
            combo = combo.get_parent()

        model = combo.get_model()
        entry_text = entry.get_text().lower()

        for row in model:
            if entry_text == row[0].lower():
                self._set_combo_selection(combo, row[0])
                return

        # non-matching value entered, reset to old values
        self._restore_old_city_region()

    def on_month_changed(self, *args):
        self._stop_and_maybe_start_time_updating(interval=5)
        self._daysFilter.refilter()

    def on_day_changed(self, *args):
        self._stop_and_maybe_start_time_updating(interval=5)

    def on_year_changed(self, *args):
        self._stop_and_maybe_start_time_updating(interval=5)
        self._daysFilter.refilter()

    def on_location_changed(self, tz_map, location):
        if not location:
            return

        timezone = location.get_property('zone')

        # Updating the timezone will update the region/city combo boxes to match.
        # The on_city_changed handler will attempt to convert the timezone back
        # to a location and set it in the map, which we don't want, since we
        # already have a location. That's why we're here.
        with blockedHandler(self._cityCombo, self.on_city_changed):
            if self._set_timezone(timezone):
                # timezone successfully set
                self._tz = get_timezone(timezone)
                self._update_datetime()

    def on_timeformat_changed(self, button24h, *args):
        hours = int(self._hoursLabel.get_text())
        amPm = self._amPmLabel.get_text()

        #connected to 24-hour radio button
        if button24h.get_active():
            self._set_amPm_part_sensitive(False)
            new_hours = self._to_24h(hours, amPm)
            self._amPmRevealer.set_reveal_child(False)
        else:
            self._set_amPm_part_sensitive(True)
            new_hours, new_amPm = self._to_amPm(hours)
            self._amPmLabel.set_text(new_amPm)
            self._amPmRevealer.set_reveal_child(True)

        self._hoursLabel.set_text("%0.2d" % new_hours)

    def _set_date_time_setting_sensitive(self, sensitive):
        #contains all date/time setting widgets
        footer_alignment = self.builder.get_object("footerAlignment")
        footer_alignment.set_sensitive(sensitive)

    def _show_no_network_warning(self):
        self.set_warning(_("You need to set up networking first if you "\
                           "want to use NTP"))

    def _show_no_ntp_server_warning(self):
        self.set_warning(_("You have no working NTP server configured"))

    def on_ntp_switched(self, switch, *args):
        if switch.get_active():
            #turned ON
            if not conf.system.can_set_time_synchronization:
                #cannot touch runtime system, not much to do here
                return

            if not self._network_module.proxy.Connected:
                self._show_no_network_warning()
                switch.set_active(False)
                return
            else:
                self.clear_info()

                working_server = self._config_dialog.working_server
                if working_server is None:
                    self._show_no_ntp_server_warning()
                else:
                    #we need a one-time sync here, because chronyd would not change
                    #the time as drastically as we need
                    ntp.one_time_sync_async(working_server)

            ret = util.start_service(NTP_SERVICE)
            self._set_date_time_setting_sensitive(False)

            #if starting chronyd failed and chronyd is not running,
            #set switch back to OFF
            if (ret != 0) and not util.service_running(NTP_SERVICE):
                switch.set_active(False)

        else:
            #turned OFF
            if not conf.system.can_set_time_synchronization:
                #cannot touch runtime system, nothing to do here
                return

            self._set_date_time_setting_sensitive(True)
            ret = util.stop_service(NTP_SERVICE)

            #if stopping chronyd failed and chronyd is running,
            #set switch back to ON
            if (ret != 0) and util.service_running(NTP_SERVICE):
                switch.set_active(True)

            self.clear_info()

    def on_ntp_config_clicked(self, *args):
        self._config_dialog.refresh()

        with self.main_window.enlightbox(self._config_dialog.window):
            response = self._config_dialog.run()

        if response == 1:
            pools, servers = self._config_dialog.pools_servers
            self._timezone_module.proxy.SetNTPServers(ntp.pools_servers_to_internal(pools, servers))

            if self._config_dialog.working_server is None:
                self._show_no_ntp_server_warning()
            else:
                self.clear_info()