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()
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()
class NTPConfigDialog(GUIObject): builderObjects = ["ntpConfigDialog", "serversStore"] mainWidgetName = "ntpConfigDialog" uiFile = "spokes/lib/ntp_dialog.glade" def __init__(self, data, servers, states): GUIObject.__init__(self, data) self._servers = servers self._active_server = None self._states = states # self.window.set_size_request(500, 400) working_column = self.builder.get_object("workingColumn") working_renderer = self.builder.get_object("workingRenderer") override_cell_property(working_column, working_renderer, "icon-name", self._render_working) self._serversView = self.builder.get_object("serversView") self._serversStore = self.builder.get_object("serversStore") self._update_timer = Timer() def _render_working(self, column, renderer, model, itr, user_data=None): value = self._serversStore[itr][SERVER_WORKING] if value == constants.NTP_SERVER_QUERY: return "dialog-question" elif value == constants.NTP_SERVER_OK: return "emblem-default" else: return "dialog-error" def refresh(self): # Update the store. self._serversStore.clear() for server in self._servers: self._add_row(server) # Start to update the status. self._update_timer.timeout_sec(1, self._update_rows) def run(self): self.window.show() rc = self.window.run() self.window.hide() # OK clicked if rc == 1: # Clean up unedited entries self._cleanup_unedited_entry() # Restart the NTP service. if conf.system.can_set_time_synchronization: ntp.save_servers_to_config(self._servers) util.restart_service(NTP_SERVICE) return rc def _cleanup_unedited_entry(self): """Clean up unedited entry. There can be only one, at the very end. """ if self._servers[-1].hostname == SERVER_STARTING_STRING: pass num = len(self._serversStore) path = Gtk.TreePath(num - 1) itr = self._serversStore.get_iter(path) self._serversStore.remove(itr) del self._servers[-1] def _add_row(self, server): """Add a new row for the given NTP server. :param server: an NTP server :type server: an instance of TimeSourceData """ itr = self._serversStore.append( ["", False, False, constants.NTP_SERVER_QUERY, server]) self._refresh_row(itr) def _refresh_row(self, itr): """Refresh the given row.""" server = self._serversStore[itr][SERVER_OBJECT] self._serversStore.set_value(itr, SERVER_HOSTNAME, server.hostname) self._serversStore.set_value(itr, SERVER_POOL, server.type == TIME_SOURCE_POOL) self._serversStore.set_value(itr, SERVER_NTS, "nts" in server.options) def _update_rows(self): """Periodically update the status of all rows. :return: True to repeat, otherwise False """ for row in self._serversStore: server = row[SERVER_OBJECT] if server is self._active_server: continue status = self._states.get_status(server) row[SERVER_WORKING] = status return True def on_add_button_clicked(self, *args): """Handler for Add button. Tries to add a new server for editing, or reuse an existing server that was not edited after adding. """ # check if there is any unedited server # exactly zero or one such server can exist, at last position only if not self._servers[-1].hostname == SERVER_STARTING_STRING: # no unedited leftover, so make a new server with a reasonable guess about the defaults server = TimeSourceData() server.type = TIME_SOURCE_SERVER server.hostname = SERVER_STARTING_STRING server.options = ["iburst"] # add the (still invalid) server self._servers.append(server) self._states.check_status(server) self._add_row(server) # select the correct row - it is always the last one num = len(self._serversStore) path = Gtk.TreePath(num - 1) itr = self._serversStore.get_iter(path) selection = self._serversView.get_selection() selection.select_iter(itr) self._serversView.grab_focus() # start editing the newly added server hostname # it is already selected so just "press" the edit button self.on_edit_button_clicked(*args) def on_edit_button_clicked(self, *args): """Handler for Edit button""" selection = self._serversView.get_selection() store, items = selection.get_selected_rows() # pylint: disable=unused-variable path = items[-1] # take only the last item column = self._serversView.get_column( 0) # first column is server/hostname self._serversView.set_cursor(path, column, True) def on_remove_button_clicked(self, *args): """Handler for Remove button""" selection = self._serversView.get_selection() store, items = selection.get_selected_rows() for path in reversed(items): itr = store.get_iter(path) server = store[itr][SERVER_OBJECT] store.remove(itr) self._servers.remove(server) def on_pool_toggled(self, renderer, path, *args): itr = self._serversStore.get_iter(path) server = self._serversStore[itr][SERVER_OBJECT] if server.type == TIME_SOURCE_SERVER: server.type = TIME_SOURCE_POOL else: server.type = TIME_SOURCE_SERVER self._refresh_row(itr) def on_nts_toggled(self, renderer, path, *args): itr = self._serversStore.get_iter(path) server = self._serversStore[itr][SERVER_OBJECT] if "nts" in server.options: server.options.remove("nts") else: server.options.append("nts") self._states.check_status(server) self._refresh_row(itr) def on_server_editing_started(self, renderer, editable, path): itr = self._serversStore.get_iter(path) self._active_server = self._serversStore[itr][SERVER_OBJECT] def on_server_editing_canceled(self, renderer): self._active_server = None def on_server_edited(self, renderer, path, new_text, *args): self._active_server = None if not path: return (valid, error) = network.is_valid_hostname(new_text) if not valid: log.error("'%s' is not a valid hostname: %s", new_text, error) return itr = self._serversStore.get_iter(path) server = self._serversStore[itr][SERVER_OBJECT] if server.hostname == new_text: return server.hostname = new_text self._states.check_status(server) self._refresh_row(itr)
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)
class NTPConfigDialog(GUIObject, GUIDialogInputCheckHandler): builderObjects = ["ntpConfigDialog", "addImage", "serversStore"] mainWidgetName = "ntpConfigDialog" uiFile = "spokes/datetime_spoke.glade" def __init__(self, data, servers, states): GUIObject.__init__(self, data) self._servers = servers self._active_server = None self._states = states # Use GUIDIalogInputCheckHandler to manipulate the sensitivity of the # add button, and check for valid input in on_entry_activated add_button = self.builder.get_object("addButton") GUIDialogInputCheckHandler.__init__(self, add_button) self.window.set_size_request(500, 400) working_column = self.builder.get_object("workingColumn") working_renderer = self.builder.get_object("workingRenderer") override_cell_property(working_column, working_renderer, "icon-name", self._render_working) self._serverEntry = self.builder.get_object("serverEntry") self._serversStore = self.builder.get_object("serversStore") self._addButton = self.builder.get_object("addButton") self._poolCheckButton = self.builder.get_object("poolCheckButton") self._ntsCheckButton = self.builder.get_object("ntsCheckButton") self._serverCheck = self.add_check(self._serverEntry, self._validate_server) self._serverCheck.update_check_status() self._update_timer = Timer() def _render_working(self, column, renderer, model, itr, user_data=None): value = self._serversStore[itr][SERVER_WORKING] if value == constants.NTP_SERVER_QUERY: return "dialog-question" elif value == constants.NTP_SERVER_OK: return "emblem-default" else: return "dialog-error" def _validate_server(self, inputcheck): server = self.get_input(inputcheck.input_obj) # If not set, fail the check to keep the button insensitive, but don't # display an error if not server: return InputCheck.CHECK_SILENT (valid, error) = network.is_valid_hostname(server) if not valid: return "'%s' is not a valid hostname: %s" % (server, error) else: return InputCheck.CHECK_OK def refresh(self): # Update the store. self._serversStore.clear() for server in self._servers: self._add_row(server) # Start to update the status. self._update_timer.timeout_sec(1, self._update_rows) # Focus on the server entry. self._serverEntry.grab_focus() def run(self): self.window.show() rc = self.window.run() self.window.hide() # OK clicked if rc == 1: # Remove servers. for row in self._serversStore: if not row[SERVER_USE]: server = row[SERVER_OBJECT] self._servers.remove(server) # Restart the NTP service. if conf.system.can_set_time_synchronization: ntp.save_servers_to_config(self._servers) util.restart_service(NTP_SERVICE) return rc def _add_row(self, server): """Add a new row for the given NTP server. :param server: an NTP server :type server: an instance of TimeSourceData """ itr = self._serversStore.append( ["", False, False, constants.NTP_SERVER_QUERY, True, server]) self._refresh_row(itr) def _refresh_row(self, itr): """Refresh the given row.""" server = self._serversStore[itr][SERVER_OBJECT] self._serversStore.set_value(itr, SERVER_HOSTNAME, server.hostname) self._serversStore.set_value(itr, SERVER_POOL, server.type == TIME_SOURCE_POOL) self._serversStore.set_value(itr, SERVER_NTS, "nts" in server.options) def _update_rows(self): """Periodically update the status of all rows. :return: True to repeat, otherwise False """ for row in self._serversStore: server = row[SERVER_OBJECT] if server is self._active_server: continue status = self._states.get_status(server) row[SERVER_WORKING] = status return True def on_entry_activated(self, entry, *args): # Check that the input check has passed if self._serverCheck.check_status != InputCheck.CHECK_OK: return server = TimeSourceData() if self._poolCheckButton.get_active(): server.type = TIME_SOURCE_POOL else: server.type = TIME_SOURCE_SERVER server.hostname = entry.get_text() server.options = ["iburst"] if self._ntsCheckButton.get_active(): server.options.append("nts") self._servers.append(server) self._states.check_status(server) self._add_row(server) entry.set_text("") self._poolCheckButton.set_active(False) self._ntsCheckButton.set_active(False) def on_add_clicked(self, *args): self._serverEntry.emit("activate") def on_use_server_toggled(self, renderer, path, *args): itr = self._serversStore.get_iter(path) old_value = self._serversStore[itr][SERVER_USE] self._serversStore.set_value(itr, SERVER_USE, not old_value) def on_pool_toggled(self, renderer, path, *args): itr = self._serversStore.get_iter(path) server = self._serversStore[itr][SERVER_OBJECT] if server.type == TIME_SOURCE_SERVER: server.type = TIME_SOURCE_POOL else: server.type = TIME_SOURCE_SERVER self._refresh_row(itr) def on_nts_toggled(self, renderer, path, *args): itr = self._serversStore.get_iter(path) server = self._serversStore[itr][SERVER_OBJECT] if "nts" in server.options: server.options.remove("nts") else: server.options.append("nts") self._states.check_status(server) self._refresh_row(itr) def on_server_editing_started(self, renderer, editable, path): itr = self._serversStore.get_iter(path) self._active_server = self._serversStore[itr][SERVER_OBJECT] def on_server_editing_canceled(self, renderer): self._active_server = None def on_server_edited(self, renderer, path, new_text, *args): self._active_server = None if not path: return (valid, error) = network.is_valid_hostname(new_text) if not valid: log.error("'%s' is not a valid hostname: %s", new_text, error) return itr = self._serversStore.get_iter(path) server = self._serversStore[itr][SERVER_OBJECT] if server.hostname == new_text: return server.hostname = new_text self._states.check_status(server) self._refresh_row(itr)
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()
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()