def __init__(self, cfg, updater, ttreeview, ttree_paths, info_bar, theme, dot_size): super(TreeUpdater, self).__init__() self.action_required = False self.quit = False self.cleared = True self.autoexpand = True self.count = 0 self.cfg = cfg self.updater = updater self.info_bar = info_bar self.last_update_time = None self.ancestors = {} self.descendants = [] self.fam_state_summary = {} self._prev_id_named_paths = {} self._prev_data = {} self._prev_fam_data = {} self.autoexpand_states = [ 'queued', 'ready', 'expired', 'submitted', 'running', 'failed' ] self._last_autoexpand_me = [] # Dict of paths vs all descendant node states self.ttree_paths = ttree_paths self.should_group_families = ("text" not in self.cfg.ungrouped_views) self.ttreeview = ttreeview # Hierarchy of models: view <- sorted <- filtered <- base model self.ttreestore = ttreeview.get_model().get_model().get_model() self._prev_tooltip_task_id = None if hasattr(self.ttreeview, "set_has_tooltip"): self.ttreeview.set_has_tooltip(True) try: self.ttreeview.connect('query-tooltip', self.on_query_tooltip) except TypeError: # Lower PyGTK version. pass # Cache the latest ETC calculation for active ids. self._id_tetc_cache = {} # Generate task state icons. dotm = DotMaker(theme, size=dot_size) self.dots = dotm.get_dots()
def __init__(self, cfg, updater, ttreeview, ttree_paths, info_bar, theme, dot_size): super(TreeUpdater, self).__init__() self.action_required = False self.quit = False self.cleared = True self.autoexpand = True self.count = 0 self.cfg = cfg self.updater = updater self.info_bar = info_bar self.last_update_time = None self.ancestors = {} self.descendants = [] self.fam_state_summary = {} self._prev_id_named_paths = {} self._prev_data = {} self._prev_fam_data = {} self.autoexpand_states = [ 'queued', 'ready', 'expired', 'submitted', 'running', 'failed'] self._last_autoexpand_me = [] # Dict of paths vs all descendant node states self.ttree_paths = ttree_paths self.should_group_families = ("text" not in self.cfg.ungrouped_views) self.ttreeview = ttreeview # Hierarchy of models: view <- sorted <- filtered <- base model self.ttreestore = ttreeview.get_model().get_model().get_model() self._prev_tooltip_task_id = None if hasattr(self.ttreeview, "set_has_tooltip"): self.ttreeview.set_has_tooltip(True) try: self.ttreeview.connect('query-tooltip', self.on_query_tooltip) except TypeError: # Lower PyGTK version. pass # Cache the latest ETC calculation for active ids. self._id_tetc_cache = {} # Generate task state icons. dotm = DotMaker(theme, size=dot_size) self.dots = dotm.get_dots()
def __init__(self, hosts, dot_hbox, gcylc_image, is_compact, owner=None, poll_interval=None): self.quit = True self.dot_hbox = dot_hbox self.gcylc_image = gcylc_image self.is_compact = is_compact self._set_gcylc_image_tooltip() self.gcylc_image.set_sensitive(False) self.theme_name = gcfg.get(['use theme']) self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) self.hosts_suites_info = {} self.stopped_hosts_suites_info = {} self._set_exception_hook() super(ScanPanelAppletUpdater, self).__init__( hosts, owner=owner, poll_interval=poll_interval)
def __init__(self, cfg, updater, treeview, info_bar, theme, dot_size): super(DotUpdater, self).__init__() self.quit = False self.cleared = True self.action_required = False self.autoexpand = True self.should_hide_headings = False self.should_group_families = ("dot" not in cfg.ungrouped_views) self.should_transpose_view = False self.is_transposed = False self.defn_order_on = True self.cfg = cfg self.updater = updater self.theme = theme self.info_bar = info_bar self.last_update_time = None self.state_summary = {} self.fam_state_summary = {} self.ancestors_pruned = {} self.descendants = [] self.point_strings = [] self.led_headings = [] self.led_treeview = treeview self.led_treestore = treeview.get_model() self._prev_tooltip_task_id = None if hasattr(self.led_treeview, "set_has_tooltip"): self.led_treeview.set_has_tooltip(True) try: self.led_treeview.connect('query-tooltip', self.on_query_tooltip) except TypeError: # Lower PyGTK version. pass self.task_list = [] self.family_tree = {} self.expanded_rows = [] self.selected_rows = [] # generate task state icons dotm = DotMaker(theme, size=dot_size) self.dots = dotm.get_dots()
def __init__(self, dot_hbox, gcylc_image, is_compact): self.hosts = [] self.dot_hbox = dot_hbox self.gcylc_image = gcylc_image self.is_compact = is_compact self.prev_full_update = None self.prev_norm_update = None self.quit = True self._set_gcylc_image_tooltip() self.gcylc_image.set_sensitive(False) self.interval_full = gsfg.get(['suite listing update interval']) self.interval_part = gsfg.get(['suite status update interval']) self.theme_name = gcfg.get(['use theme']) self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) self.suite_info_map = {} self._set_exception_hook() self.owner_pattern = None
def __init__(self, hosts, dot_hbox, gcylc_image, is_compact, poll_interval=None): self.hosts = hosts self.dot_hbox = dot_hbox self.gcylc_image = gcylc_image self.is_compact = is_compact if poll_interval is None: poll_interval = self.POLL_INTERVAL self.poll_interval = poll_interval self._should_force_update = False self._last_running_time = None self.quit = True self.last_update_time = None self._set_gcylc_image_tooltip() self.gcylc_image.set_sensitive(False) self.theme_name = gcfg.get(['use theme']) self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) self.suite_info_map = {} self._set_exception_hook() self.owner_pattern = re.compile(r'\A%s\Z' % USER)
class ScanApp(object): """Summarize running suite statuses for a given set of hosts.""" def __init__(self, hosts=None, owner=None, poll_interval=None): gobject.threads_init() set_exception_hook_dialog("cylc gscan") setup_icons() if not hosts: hosts = GLOBAL_CFG.get(["suite host scanning", "hosts"]) self.hosts = hosts if owner is None: owner = USER self.owner = owner self.window = gtk.Window() self.window.set_title("cylc gscan") self.window.set_icon(get_icon()) self.vbox = gtk.VBox() self.vbox.show() self.theme_name = gcfg.get(["use theme"]) self.theme = gcfg.get(["themes", self.theme_name]) self.dots = DotMaker(self.theme) suite_treemodel = gtk.TreeStore(str, str, bool, str, int, str, str) self._prev_tooltip_location_id = None self.suite_treeview = gtk.TreeView(suite_treemodel) # Construct the host column. host_name_column = gtk.TreeViewColumn("Host") cell_text_host = gtk.CellRendererText() host_name_column.pack_start(cell_text_host, expand=False) host_name_column.set_cell_data_func(cell_text_host, self._set_cell_text_host) host_name_column.set_sort_column_id(0) host_name_column.set_visible("host" in gsfg.get(["columns"])) host_name_column.set_resizable(True) # Construct the suite name column. suite_name_column = gtk.TreeViewColumn("Suite") cell_text_name = gtk.CellRendererText() suite_name_column.pack_start(cell_text_name, expand=False) suite_name_column.set_cell_data_func(cell_text_name, self._set_cell_text_name) suite_name_column.set_sort_column_id(1) suite_name_column.set_visible("suite" in gsfg.get(["columns"])) suite_name_column.set_resizable(True) # Construct the suite title column. suite_title_column = gtk.TreeViewColumn("Title") cell_text_title = gtk.CellRendererText() suite_title_column.pack_start(cell_text_title, expand=False) suite_title_column.set_cell_data_func(cell_text_title, self._set_cell_text_title) suite_title_column.set_sort_column_id(3) suite_title_column.set_visible("title" in gsfg.get(["columns"])) suite_title_column.set_resizable(True) # Construct the update time column. time_column = gtk.TreeViewColumn("Updated") cell_text_time = gtk.CellRendererText() time_column.pack_start(cell_text_time, expand=False) time_column.set_cell_data_func(cell_text_time, self._set_cell_text_time) time_column.set_sort_column_id(4) time_column.set_visible("updated" in gsfg.get(["columns"])) time_column.set_resizable(True) self.suite_treeview.append_column(host_name_column) self.suite_treeview.append_column(suite_name_column) self.suite_treeview.append_column(suite_title_column) self.suite_treeview.append_column(time_column) # Construct the status column. status_column = gtk.TreeViewColumn("Status") status_column.set_sort_column_id(5) status_column.set_visible("status" in gsfg.get(["columns"])) status_column.set_resizable(True) status_column_info = 6 cycle_column_info = 5 cell_text_cycle = gtk.CellRendererText() status_column.pack_start(cell_text_cycle, expand=False) status_column.set_cell_data_func(cell_text_cycle, self._set_cell_text_cycle, cycle_column_info) self.suite_treeview.append_column(status_column) for i in range(len(TASK_STATUSES_ORDERED)): cell_pixbuf_state = gtk.CellRendererPixbuf() status_column.pack_start(cell_pixbuf_state, expand=False) status_column.set_cell_data_func(cell_pixbuf_state, self._set_cell_pixbuf_state, (status_column_info, i)) self.suite_treeview.show() if hasattr(self.suite_treeview, "set_has_tooltip"): self.suite_treeview.set_has_tooltip(True) try: self.suite_treeview.connect("query-tooltip", self._on_query_tooltip) except TypeError: # Lower PyGTK version. pass self.suite_treeview.connect("button-press-event", self._on_button_press_event) scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.add(self.suite_treeview) scrolled_window.show() self.vbox.pack_start(scrolled_window, expand=True, fill=True) self.updater = ScanAppUpdater( self.hosts, suite_treemodel, self.suite_treeview, owner=self.owner, poll_interval=poll_interval ) self.updater.start() self.window.add(self.vbox) self.window.connect("destroy", self._on_destroy_event) self.window.set_default_size(300, 150) self.suite_treeview.grab_focus() self.window.show() def _on_button_press_event(self, treeview, event): # DISPLAY MENU ONLY ON RIGHT CLICK ONLY if event.type != gtk.gdk._2BUTTON_PRESS and event.button != 3: return False treemodel = treeview.get_model() x = int(event.x) y = int(event.y) time = event.time pth = treeview.get_path_at_pos(x, y) suite_host_tuples = [] if pth is not None: # Add a gcylc launcher item. path, col, cellx, celly = pth iter_ = treemodel.get_iter(path) host, suite = treemodel.get(iter_, 0, 1) if suite is None: # On an expanded cycle point row, so get from parent. host, suite = treemodel.get(treemodel.iter_parent(iter_), 0, 1) suite_host_tuples.append((suite, host)) if event.type == gtk.gdk._2BUTTON_PRESS: if suite_host_tuples: launch_gcylc(host, suite, owner=self.owner) return False has_stopped_suites = bool(self.updater.stopped_hosts_suites_info) view_item = gtk.ImageMenuItem("View Column...") img = gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_MENU) view_item.set_image(img) view_item.show() view_menu = gtk.Menu() view_item.set_submenu(view_menu) for column_index, column in enumerate(treeview.get_columns()): name = column.get_title() is_visible = column.get_visible() column_item = gtk.CheckMenuItem(name.replace("_", "__")) column_item._connect_args = (column_index, is_visible) column_item.set_active(is_visible) column_item.connect("toggled", self._on_toggle_column_visible) column_item.show() view_menu.append(column_item) menu = get_scan_menu( suite_host_tuples, self.theme_name, self._set_theme, has_stopped_suites, self.updater.clear_stopped_suites, self.hosts, self.updater.set_hosts, self.updater.update_now, self.updater.start, program_name="cylc gscan", extra_items=[view_item], owner=self.owner, ) menu.popup(None, None, None, event.button, event.time) return False def _on_destroy_event(self, widget): self.updater.quit = True gtk.main_quit() return False def _on_query_tooltip(self, widget, x, y, kbd_ctx, tooltip): """Handle a tooltip creation request.""" tip_context = self.suite_treeview.get_tooltip_context(x, y, kbd_ctx) if tip_context is None: self._prev_tooltip_location_id = None return False x, y = self.suite_treeview.convert_widget_to_bin_window_coords(x, y) path, column, cell_x, cell_y = self.suite_treeview.get_path_at_pos(x, y) model = self.suite_treeview.get_model() iter_ = model.get_iter(path) parent_iter = model.iter_parent(iter_) if parent_iter is None: host = model.get_value(iter_, 0) suite = model.get_value(iter_, 1) child_row_number = None else: host = model.get_value(parent_iter, 0) suite = model.get_value(parent_iter, 1) child_row_number = path[-1] suite_update_time = model.get_value(iter_, 4) location_id = (host, suite, suite_update_time, column.get_title(), child_row_number) if location_id != self._prev_tooltip_location_id: self._prev_tooltip_location_id = location_id tooltip.set_text(None) return False if column.get_title() in ["Host", "Suite"]: tooltip.set_text(suite + " - " + host) return True if column.get_title() == "Updated": suite_update_point = get_timepoint_from_seconds_since_unix_epoch(suite_update_time) if self.updater.last_update_time is not None and suite_update_time != int(self.updater.last_update_time): retrieval_point = get_timepoint_from_seconds_since_unix_epoch(int(self.updater.last_update_time)) text = "Last changed at %s\n" % suite_update_point text += "Last scanned at %s" % retrieval_point else: # An older suite (or before any updates are made?) text = "Last scanned at %s" % suite_update_point tooltip.set_text(text) return True if column.get_title() != "Status": tooltip.set_text(None) return False state_texts = [] status_column_info = 6 state_text = model.get_value(iter_, status_column_info) if state_text is None: tooltip.set_text(None) return False info = re.findall(r"\D+\d+", state_text) for status_number in info: status, number = status_number.rsplit(" ", 1) state_texts.append(number + " " + status.strip()) text = "Tasks: " + ", ".join(state_texts) tooltip.set_text(text) return True def _on_toggle_column_visible(self, menu_item): column_index, is_visible = menu_item._connect_args column = self.suite_treeview.get_columns()[column_index] column.set_visible(not is_visible) return False def _set_cell_pixbuf_state(self, column, cell, model, iter_, index_tuple): status_column_info, index = index_tuple state_info = model.get_value(iter_, status_column_info) if state_info is not None: is_stopped = model.get_value(iter_, 2) info = re.findall(r"\D+\d+", state_info) if index < len(info): state = info[index].rsplit(" ", 1)[0] icon = self.dots.get_icon(state.strip(), is_stopped=is_stopped) cell.set_property("visible", True) else: icon = None cell.set_property("visible", False) else: icon = None cell.set_property("visible", False) cell.set_property("pixbuf", icon) def _set_cell_text_host(self, column, cell, model, iter_): host = model.get_value(iter_, 0) is_stopped = model.get_value(iter_, 2) cell.set_property("sensitive", not is_stopped) cell.set_property("text", host) def _set_cell_text_name(self, column, cell, model, iter_): name = model.get_value(iter_, 1) is_stopped = model.get_value(iter_, 2) cell.set_property("sensitive", not is_stopped) cell.set_property("text", name) def _set_cell_text_title(self, column, cell, model, iter_): title = model.get_value(iter_, 3) is_stopped = model.get_value(iter_, 2) cell.set_property("sensitive", not is_stopped) cell.set_property("text", title) def _set_cell_text_time(self, column, cell, model, iter_): suite_update_time = model.get_value(iter_, 4) time_point = get_timepoint_from_seconds_since_unix_epoch(suite_update_time) time_point.set_time_zone_to_local() current_time = time.time() current_point = get_timepoint_from_seconds_since_unix_epoch(current_time) if str(time_point).split("T")[0] == str(current_point).split("T")[0]: time_string = str(time_point).split("T")[1] else: time_string = str(time_point) is_stopped = model.get_value(iter_, 2) cell.set_property("sensitive", not is_stopped) cell.set_property("text", time_string) def _set_cell_text_cycle(self, column, cell, model, iter_, active_cycle): cycle = model.get_value(iter_, active_cycle) is_stopped = model.get_value(iter_, 2) cell.set_property("sensitive", not is_stopped) cell.set_property("text", cycle) def _set_theme(self, new_theme_name): self.theme_name = new_theme_name self.theme = gcfg.get(["themes", self.theme_name]) self.dots = DotMaker(self.theme) def _set_tooltip(self, widget, text): tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(widget, text)
def __init__(self, hosts=None, owner=None, poll_interval=None): gobject.threads_init() set_exception_hook_dialog("cylc gscan") setup_icons() if not hosts: hosts = GLOBAL_CFG.get(["suite host scanning", "hosts"]) self.hosts = hosts if owner is None: owner = user self.owner = owner self.window = gtk.Window() self.window.set_title("cylc gscan") self.window.set_icon(get_icon()) self.vbox = gtk.VBox() self.vbox.show() self.theme_name = gcfg.get(['use theme']) self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) suite_treemodel = gtk.TreeStore(str, str, bool, str, int, str, str) self._prev_tooltip_location_id = None self.suite_treeview = gtk.TreeView(suite_treemodel) # Construct the host column. host_name_column = gtk.TreeViewColumn("Host") cell_text_host = gtk.CellRendererText() host_name_column.pack_start(cell_text_host, expand=False) host_name_column.set_cell_data_func(cell_text_host, self._set_cell_text_host) host_name_column.set_sort_column_id(0) host_name_column.set_visible(False) host_name_column.set_resizable(True) # Construct the suite name column. suite_name_column = gtk.TreeViewColumn("Suite") cell_text_name = gtk.CellRendererText() suite_name_column.pack_start(cell_text_name, expand=False) suite_name_column.set_cell_data_func(cell_text_name, self._set_cell_text_name) suite_name_column.set_sort_column_id(1) suite_name_column.set_resizable(True) # Construct the suite title column. suite_title_column = gtk.TreeViewColumn("Title") cell_text_title = gtk.CellRendererText() suite_title_column.pack_start(cell_text_title, expand=False) suite_title_column.set_cell_data_func(cell_text_title, self._set_cell_text_title) suite_title_column.set_sort_column_id(3) suite_title_column.set_visible(False) suite_title_column.set_resizable(True) # Construct the update time column. time_column = gtk.TreeViewColumn("Updated") cell_text_time = gtk.CellRendererText() time_column.pack_start(cell_text_time, expand=False) time_column.set_cell_data_func(cell_text_time, self._set_cell_text_time) time_column.set_sort_column_id(4) time_column.set_visible(False) time_column.set_resizable(True) self.suite_treeview.append_column(host_name_column) self.suite_treeview.append_column(suite_name_column) self.suite_treeview.append_column(suite_title_column) self.suite_treeview.append_column(time_column) # Construct the status column. status_column = gtk.TreeViewColumn("Status") status_column.set_sort_column_id(5) status_column.set_resizable(True) status_column_info = 6 cycle_column_info = 5 cell_text_cycle = gtk.CellRendererText() status_column.pack_start(cell_text_cycle, expand=False) status_column.set_cell_data_func(cell_text_cycle, self._set_cell_text_cycle, cycle_column_info) self.suite_treeview.append_column(status_column) distinct_states = len(task_state.legal) for i in range(distinct_states): cell_pixbuf_state = gtk.CellRendererPixbuf() status_column.pack_start(cell_pixbuf_state, expand=False) status_column.set_cell_data_func(cell_pixbuf_state, self._set_cell_pixbuf_state, (status_column_info, i)) self.suite_treeview.show() if hasattr(self.suite_treeview, "set_has_tooltip"): self.suite_treeview.set_has_tooltip(True) try: self.suite_treeview.connect('query-tooltip', self._on_query_tooltip) except TypeError: # Lower PyGTK version. pass self.suite_treeview.connect("button-press-event", self._on_button_press_event) scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.add(self.suite_treeview) scrolled_window.show() self.vbox.pack_start(scrolled_window, expand=True, fill=True) self.updater = ScanAppUpdater(self.hosts, suite_treemodel, self.suite_treeview, owner=self.owner, poll_interval=poll_interval) self.updater.start() self.window.add(self.vbox) self.window.connect("destroy", self._on_destroy_event) self.window.set_default_size(300, 150) self.suite_treeview.grab_focus() self.window.show()
class ScanApp(object): """Summarize running suite statuses for a given set of hosts.""" def __init__(self, hosts=None, owner=None, poll_interval=None): gobject.threads_init() set_exception_hook_dialog("cylc gscan") setup_icons() if not hosts: hosts = GLOBAL_CFG.get(["suite host scanning", "hosts"]) self.hosts = hosts if owner is None: owner = user self.owner = owner self.window = gtk.Window() self.window.set_title("cylc gscan") self.window.set_icon(get_icon()) self.vbox = gtk.VBox() self.vbox.show() self.theme_name = gcfg.get(['use theme']) self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) suite_treemodel = gtk.TreeStore(str, str, bool, str, int, str, str) self._prev_tooltip_location_id = None self.suite_treeview = gtk.TreeView(suite_treemodel) # Construct the host column. host_name_column = gtk.TreeViewColumn("Host") cell_text_host = gtk.CellRendererText() host_name_column.pack_start(cell_text_host, expand=False) host_name_column.set_cell_data_func(cell_text_host, self._set_cell_text_host) host_name_column.set_sort_column_id(0) host_name_column.set_visible(False) host_name_column.set_resizable(True) # Construct the suite name column. suite_name_column = gtk.TreeViewColumn("Suite") cell_text_name = gtk.CellRendererText() suite_name_column.pack_start(cell_text_name, expand=False) suite_name_column.set_cell_data_func(cell_text_name, self._set_cell_text_name) suite_name_column.set_sort_column_id(1) suite_name_column.set_resizable(True) # Construct the suite title column. suite_title_column = gtk.TreeViewColumn("Title") cell_text_title = gtk.CellRendererText() suite_title_column.pack_start(cell_text_title, expand=False) suite_title_column.set_cell_data_func(cell_text_title, self._set_cell_text_title) suite_title_column.set_sort_column_id(3) suite_title_column.set_visible(False) suite_title_column.set_resizable(True) # Construct the update time column. time_column = gtk.TreeViewColumn("Updated") cell_text_time = gtk.CellRendererText() time_column.pack_start(cell_text_time, expand=False) time_column.set_cell_data_func(cell_text_time, self._set_cell_text_time) time_column.set_sort_column_id(4) time_column.set_visible(False) time_column.set_resizable(True) self.suite_treeview.append_column(host_name_column) self.suite_treeview.append_column(suite_name_column) self.suite_treeview.append_column(suite_title_column) self.suite_treeview.append_column(time_column) # Construct the status column. status_column = gtk.TreeViewColumn("Status") status_column.set_sort_column_id(5) status_column.set_resizable(True) status_column_info = 6 cycle_column_info = 5 cell_text_cycle = gtk.CellRendererText() status_column.pack_start(cell_text_cycle, expand=False) status_column.set_cell_data_func(cell_text_cycle, self._set_cell_text_cycle, cycle_column_info) self.suite_treeview.append_column(status_column) distinct_states = len(task_state.legal) for i in range(distinct_states): cell_pixbuf_state = gtk.CellRendererPixbuf() status_column.pack_start(cell_pixbuf_state, expand=False) status_column.set_cell_data_func(cell_pixbuf_state, self._set_cell_pixbuf_state, (status_column_info, i)) self.suite_treeview.show() if hasattr(self.suite_treeview, "set_has_tooltip"): self.suite_treeview.set_has_tooltip(True) try: self.suite_treeview.connect('query-tooltip', self._on_query_tooltip) except TypeError: # Lower PyGTK version. pass self.suite_treeview.connect("button-press-event", self._on_button_press_event) scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.add(self.suite_treeview) scrolled_window.show() self.vbox.pack_start(scrolled_window, expand=True, fill=True) self.updater = ScanAppUpdater(self.hosts, suite_treemodel, self.suite_treeview, owner=self.owner, poll_interval=poll_interval) self.updater.start() self.window.add(self.vbox) self.window.connect("destroy", self._on_destroy_event) self.window.set_default_size(300, 150) self.suite_treeview.grab_focus() self.window.show() def _on_button_press_event(self, treeview, event): # DISPLAY MENU ONLY ON RIGHT CLICK ONLY if (event.type != gtk.gdk._2BUTTON_PRESS and event.button != 3): return False treemodel = treeview.get_model() x = int(event.x) y = int(event.y) time = event.time pth = treeview.get_path_at_pos(x, y) suite_host_tuples = [] if pth is not None: # Add a gcylc launcher item. path, col, cellx, celly = pth iter_ = treemodel.get_iter(path) host, suite = treemodel.get(iter_, 0, 1) if suite is None: # On an expanded cycle point row, so get from parent. host, suite = treemodel.get(treemodel.iter_parent(iter_), 0, 1) suite_host_tuples.append((suite, host)) if event.type == gtk.gdk._2BUTTON_PRESS: if suite_host_tuples: launch_gcylc(host, suite, owner=self.owner) return False has_stopped_suites = bool(self.updater.stopped_hosts_suites_info) view_item = gtk.ImageMenuItem("View Column...") img = gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_MENU) view_item.set_image(img) view_item.show() view_menu = gtk.Menu() view_item.set_submenu(view_menu) for column_index, column in enumerate(treeview.get_columns()): name = column.get_title() is_visible = column.get_visible() column_item = gtk.CheckMenuItem(name.replace("_", "__")) column_item._connect_args = (column_index, is_visible) column_item.set_active(is_visible) column_item.connect("toggled", self._on_toggle_column_visible) column_item.show() view_menu.append(column_item) menu = get_scan_menu(suite_host_tuples, self.theme_name, self._set_theme, has_stopped_suites, self.updater.clear_stopped_suites, self.hosts, self.updater.set_hosts, self.updater.update_now, self.updater.start, program_name="cylc gscan", extra_items=[view_item], owner=self.owner) menu.popup(None, None, None, event.button, event.time) return False def _on_destroy_event(self, widget): self.updater.quit = True gtk.main_quit() return False def _on_query_tooltip(self, widget, x, y, kbd_ctx, tooltip): """Handle a tooltip creation request.""" tip_context = self.suite_treeview.get_tooltip_context(x, y, kbd_ctx) if tip_context is None: self._prev_tooltip_location_id = None return False x, y = self.suite_treeview.convert_widget_to_bin_window_coords(x, y) path, column, cell_x, cell_y = (self.suite_treeview.get_path_at_pos( x, y)) model = self.suite_treeview.get_model() iter_ = model.get_iter(path) parent_iter = model.iter_parent(iter_) if parent_iter is None: host = model.get_value(iter_, 0) suite = model.get_value(iter_, 1) child_row_number = None else: host = model.get_value(parent_iter, 0) suite = model.get_value(parent_iter, 1) child_row_number = path[-1] suite_update_time = model.get_value(iter_, 4) location_id = (host, suite, suite_update_time, column.get_title(), child_row_number) if location_id != self._prev_tooltip_location_id: self._prev_tooltip_location_id = location_id tooltip.set_text(None) return False if column.get_title() in ["Host", "Suite"]: tooltip.set_text(suite + " - " + host) return True if column.get_title() == "Updated": suite_update_point = get_timepoint_from_seconds_since_unix_epoch( suite_update_time) if (self.updater.last_update_time is not None and suite_update_time != int(self.updater.last_update_time)): retrieval_point = get_timepoint_from_seconds_since_unix_epoch( int(self.updater.last_update_time)) text = "Last changed at %s\n" % suite_update_point text += "Last scanned at %s" % retrieval_point else: # An older suite (or before any updates are made?) text = "Last scanned at %s" % suite_update_point tooltip.set_text(text) return True if column.get_title() != "Status": tooltip.set_text(None) return False state_texts = [] status_column_info = 6 state_text = model.get_value(iter_, status_column_info) if state_text is None: tooltip.set_text(None) return False info = re.findall('\D+\d+', state_text) for status_number in info: status, number = status_number.rsplit(" ", 1) state_texts.append(number + " " + status.strip()) text = "Tasks: " + ", ".join(state_texts) tooltip.set_text(text) return True def _on_toggle_column_visible(self, menu_item): column_index, is_visible = menu_item._connect_args column = self.suite_treeview.get_columns()[column_index] column.set_visible(not is_visible) return False def _set_cell_pixbuf_state(self, column, cell, model, iter_, index_tuple): status_column_info, index = index_tuple state_info = model.get_value(iter_, status_column_info) if state_info is not None: is_stopped = model.get_value(iter_, 2) info = re.findall('\D+\d+', state_info) if index < len(info): state, num_tasks = info[index].rsplit(" ", 1) icon = self.dots.get_icon(state.strip(), is_stopped=is_stopped) cell.set_property("visible", True) else: icon = None cell.set_property("visible", False) else: icon = None cell.set_property("visible", False) cell.set_property("pixbuf", icon) def _set_cell_text_host(self, column, cell, model, iter_): host = model.get_value(iter_, 0) is_stopped = model.get_value(iter_, 2) cell.set_property("sensitive", not is_stopped) cell.set_property("text", host) def _set_cell_text_name(self, column, cell, model, iter_): name = model.get_value(iter_, 1) is_stopped = model.get_value(iter_, 2) cell.set_property("sensitive", not is_stopped) cell.set_property("text", name) def _set_cell_text_title(self, column, cell, model, iter_): title = model.get_value(iter_, 3) is_stopped = model.get_value(iter_, 2) cell.set_property("sensitive", not is_stopped) cell.set_property("text", title) def _set_cell_text_time(self, column, cell, model, iter_): suite_update_time = model.get_value(iter_, 4) time_point = get_timepoint_from_seconds_since_unix_epoch( suite_update_time) time_point.set_time_zone_to_local() current_time = time.time() current_point = ( get_timepoint_from_seconds_since_unix_epoch(current_time)) if str(time_point).split("T")[0] == str(current_point).split("T")[0]: time_string = str(time_point).split("T")[1] else: time_string = str(time_point) is_stopped = model.get_value(iter_, 2) cell.set_property("sensitive", not is_stopped) cell.set_property("text", time_string) def _set_cell_text_cycle(self, column, cell, model, iter_, active_cycle): cycle = model.get_value(iter_, active_cycle) is_stopped = model.get_value(iter_, 2) cell.set_property("sensitive", not is_stopped) cell.set_property("text", cycle) def _set_theme(self, new_theme_name): self.theme_name = new_theme_name self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) def _set_tooltip(self, widget, text): tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(widget, text)
def __init__(self, hosts=None, owner=None, poll_interval=None): gobject.threads_init() set_exception_hook_dialog("cylc gscan") setup_icons() if not hosts: hosts = GLOBAL_CFG.get(["suite host scanning", "hosts"]) self.hosts = hosts if owner is None: owner = USER self.owner = owner self.window = gtk.Window() self.window.set_title("cylc gscan") self.window.set_icon(get_icon()) self.vbox = gtk.VBox() self.vbox.show() self.warnings = {} self.theme_name = gcfg.get(['use theme']) self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) suite_treemodel = gtk.TreeStore( str, str, bool, str, int, str, str, str) self._prev_tooltip_location_id = None self.suite_treeview = gtk.TreeView(suite_treemodel) # Construct the host column. host_name_column = gtk.TreeViewColumn("Host") cell_text_host = gtk.CellRendererText() host_name_column.pack_start(cell_text_host, expand=False) host_name_column.set_cell_data_func( cell_text_host, self._set_cell_text_host) host_name_column.set_sort_column_id(0) host_name_column.set_visible("host" in gsfg.get(["columns"])) host_name_column.set_resizable(True) # Construct the suite name column. suite_name_column = gtk.TreeViewColumn("Suite") cell_text_name = gtk.CellRendererText() suite_name_column.pack_start(cell_text_name, expand=False) suite_name_column.set_cell_data_func( cell_text_name, self._set_cell_text_name) suite_name_column.set_sort_column_id(1) suite_name_column.set_visible("suite" in gsfg.get(["columns"])) suite_name_column.set_resizable(True) # Construct the suite title column. suite_title_column = gtk.TreeViewColumn("Title") cell_text_title = gtk.CellRendererText() suite_title_column.pack_start(cell_text_title, expand=False) suite_title_column.set_cell_data_func( cell_text_title, self._set_cell_text_title) suite_title_column.set_sort_column_id(3) suite_title_column.set_visible("title" in gsfg.get( ["columns"])) suite_title_column.set_resizable(True) # Construct the update time column. time_column = gtk.TreeViewColumn("Updated") cell_text_time = gtk.CellRendererText() time_column.pack_start(cell_text_time, expand=False) time_column.set_cell_data_func( cell_text_time, self._set_cell_text_time) time_column.set_sort_column_id(4) time_column.set_visible("updated" in gsfg.get(["columns"])) time_column.set_resizable(True) self.suite_treeview.append_column(host_name_column) self.suite_treeview.append_column(suite_name_column) self.suite_treeview.append_column(suite_title_column) self.suite_treeview.append_column(time_column) # Construct the status column. status_column = gtk.TreeViewColumn("Status") status_column.set_sort_column_id(5) status_column.set_visible("status" in gsfg.get(["columns"])) status_column.set_resizable(True) cell_text_cycle = gtk.CellRendererText() status_column.pack_start(cell_text_cycle, expand=False) status_column.set_cell_data_func( cell_text_cycle, self._set_cell_text_cycle, self.CYCLE_COLUMN) self.suite_treeview.append_column(status_column) # Warning icon. warn_icon = gtk.CellRendererPixbuf() image = gtk.Image() pixbuf = image.render_icon( gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_LARGE_TOOLBAR) self.warn_icon_colour = pixbuf.scale_simple( # colour warn icon pixbuf self.ICON_SIZE, self.ICON_SIZE, gtk.gdk.INTERP_HYPER) self.warn_icon_grey = pixbuf.scale_simple( self.ICON_SIZE, self.ICON_SIZE, gtk.gdk.INTERP_HYPER) self.warn_icon_colour.saturate_and_pixelate( self.warn_icon_grey, 0, False) # b&w warn icon pixbuf status_column.pack_start(warn_icon, expand=False) status_column.set_cell_data_func(warn_icon, self._set_error_icon_state) self.warn_icon_blank = gtk.gdk.Pixbuf( # Transparent pixbuff. gtk.gdk.COLORSPACE_RGB, True, 8, self.ICON_SIZE, self.ICON_SIZE ).fill(0x00000000) # Task status icons. for i in range(len(TASK_STATUSES_ORDERED)): cell_pixbuf_state = gtk.CellRendererPixbuf() status_column.pack_start(cell_pixbuf_state, expand=False) status_column.set_cell_data_func( cell_pixbuf_state, self._set_cell_pixbuf_state, i) self.suite_treeview.show() if hasattr(self.suite_treeview, "set_has_tooltip"): self.suite_treeview.set_has_tooltip(True) try: self.suite_treeview.connect('query-tooltip', self._on_query_tooltip) except TypeError: # Lower PyGTK version. pass self.suite_treeview.connect("button-press-event", self._on_button_press_event) scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.add(self.suite_treeview) scrolled_window.show() self.vbox.pack_start(scrolled_window, expand=True, fill=True) self.updater = ScanAppUpdater( self.hosts, suite_treemodel, self.suite_treeview, owner=self.owner, poll_interval=poll_interval ) self.updater.start() self.window.add(self.vbox) self.window.connect("destroy", self._on_destroy_event) self.window.set_default_size(300, 150) self.suite_treeview.grab_focus() self.window.show() self.warning_icon_shown = []
class ScanPanelAppletUpdater(object): """Update the scan panel applet - subclass of gscan equivalent.""" IDLE_STOPPED_TIME = 3600 # 1 hour. MAX_INDIVIDUAL_SUITES = 5 def __init__(self, dot_hbox, gcylc_image, is_compact): self.hosts = [] self.dot_hbox = dot_hbox self.gcylc_image = gcylc_image self.is_compact = is_compact self.prev_full_update = None self.prev_norm_update = None self.quit = True self._set_gcylc_image_tooltip() self.gcylc_image.set_sensitive(False) self.interval_full = gsfg.get(['suite listing update interval']) self.interval_part = gsfg.get(['suite status update interval']) self.theme_name = gcfg.get(['use theme']) self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) self.suite_info_map = {} self._set_exception_hook() self.owner_pattern = None def clear_stopped_suites(self): """Clear stopped suite information that may have built up.""" for key, result in self.suite_info_map.copy().items(): if KEY_PORT not in result: del self.suite_info_map[key] gobject.idle_add(self.update) def has_stopped_suites(self): """Return True if we have any stopped suite information.""" for result in self.suite_info_map.copy().values(): if KEY_PORT not in result: return True return False def run(self): """Extract running suite information at particular intervals.""" if self.quit: return False now = time() if (self.prev_norm_update is not None and self.IDLE_STOPPED_TIME is not None and now > self.prev_norm_update + self.IDLE_STOPPED_TIME): self.stop() return True full_mode = ( self.prev_full_update is None or now >= self.prev_full_update + self.interval_full) if (full_mode or self.prev_norm_update is None or now >= self.prev_norm_update + self.interval_part): # Get new information. self.suite_info_map = update_suites_info(self, full_mode=True) self.prev_norm_update = time() if full_mode: self.prev_full_update = self.prev_norm_update gobject.idle_add(self.update) return True def set_hosts(self, new_hosts): del self.hosts[:] self.hosts.extend(new_hosts) self.update_now() def start(self): self.gcylc_image.set_sensitive(True) self.quit = False self.prev_full_update = None self.prev_norm_update = None gobject.timeout_add(1000, self.run) self._set_gcylc_image_tooltip() def stop(self): self.gcylc_image.set_sensitive(False) self.quit = True self._set_gcylc_image_tooltip() def launch_context_menu(self, event, suite_keys=None, extra_items=None): if suite_keys is None: suite_keys = [] if extra_items is None: extra_items = [] gscan_item = gtk.ImageMenuItem("Launch cylc gscan") img = gtk.image_new_from_stock("gcylc", gtk.ICON_SIZE_MENU) gscan_item.set_image(img) gscan_item.show() gscan_item.connect("button-press-event", self._on_button_press_event_gscan) extra_items.append(gscan_item) menu = get_gpanel_scan_menu(suite_keys, self.theme_name, self._set_theme, self.has_stopped_suites(), self.clear_stopped_suites, self.hosts, self.set_hosts, self.update_now, self.start, program_name="cylc gpanel", extra_items=extra_items, is_stopped=self.quit) menu.popup(None, None, None, event.button, event.time) return False def update(self): """Update the Applet.""" for child in self.dot_hbox.get_children(): self.dot_hbox.remove(child) number_mode = ( not self.is_compact and len(self.suite_info_map) > self.MAX_INDIVIDUAL_SUITES) suite_statuses = {} compact_suite_statuses = [] for key, suite_info in sorted(self.suite_info_map.items(), key=lambda details: details[0][2]): if KEY_STATES not in suite_info: continue host, _, suite = key is_stopped = KEY_PORT not in suite_info status = extract_group_state( suite_info[KEY_STATES][0].keys(), is_stopped=is_stopped) status_map = suite_info[KEY_STATES][0] if number_mode: suite_statuses.setdefault(is_stopped, {}) suite_statuses[is_stopped].setdefault(status, []) suite_statuses[is_stopped][status].append( (suite, host, status_map.items())) elif self.is_compact: compact_suite_statuses.append( (suite, host, status, status_map.items(), is_stopped)) else: self._add_image_box( [(suite, host, status, status_map.items(), is_stopped)]) if number_mode: for is_stopped, status_map in sorted(suite_statuses.items()): # Sort by number of suites in this state. statuses = status_map.items() statuses.sort(lambda x, y: cmp(len(y[1]), len(x[1]))) for status, suite_host_states_tuples in statuses: label = gtk.Label(str(len(suite_host_states_tuples)) + ":") label.show() self.dot_hbox.pack_start(label, expand=False, fill=False) suite_info_tuples = [] for suite, host, task_states in suite_host_states_tuples: suite_info_tuples.append( (suite, host, status, task_states, is_stopped)) self._add_image_box(suite_info_tuples) if self.is_compact: if not compact_suite_statuses: # No suites running or stopped. self.gcylc_image.show() return False self.gcylc_image.hide() self._add_image_box(compact_suite_statuses) return False def update_now(self): """Force an update as soon as possible.""" self.prev_full_update = None self.prev_norm_update = None def _add_image_box(self, suite_host_info_tuples): image_eb = gtk.EventBox() image_eb.show() running_status_list = [] status_list = [] suite_keys = [] for info_tuple in suite_host_info_tuples: suite, host, status, _, is_stopped = info_tuple suite_keys.append((host, get_user(), suite)) if not is_stopped: running_status_list.append(status) status_list.append(status) if running_status_list: status = extract_group_state(running_status_list, is_stopped=False) image = self.dots.get_image(status, is_stopped=False) else: status = extract_group_state(status_list, is_stopped=True) image = self.dots.get_image(status, is_stopped=True) image.show() image_eb.add(image) image_eb._connect_args = suite_keys image_eb.connect("button-press-event", self._on_button_press_event) text_format = "%s - %s - %s" long_text_format = text_format + "\n %s\n" text = "" tip_vbox = gtk.VBox() # Only used in PyGTK 2.12+ tip_vbox.show() for info_tuple in suite_host_info_tuples: suite, host, status, state_counts, is_stopped = info_tuple state_counts.sort(lambda x, y: cmp(y[1], x[1])) tip_hbox = gtk.HBox() tip_hbox.show() state_info = [] for state_name, number in state_counts: state_info.append("%d %s" % (number, state_name)) image = self.dots.get_image(state_name, is_stopped=is_stopped) image.show() tip_hbox.pack_start(image, expand=False, fill=False) states_text = ", ".join(state_info) if status is None: suite_summary = "?" else: suite_summary = status if is_stopped: suite_summary = "stopped with " + suite_summary tip_label = gtk.Label(text_format % (suite, suite_summary, host)) tip_label.show() tip_hbox.pack_start(tip_label, expand=False, fill=False, padding=5) tip_vbox.pack_start(tip_hbox, expand=False, fill=False) text += long_text_format % ( suite, suite_summary, host, states_text) text = text.rstrip() if hasattr(gtk, "Tooltip"): image_eb.set_has_tooltip(True) image_eb.connect("query-tooltip", self._on_img_tooltip_query, tip_vbox) else: self._set_tooltip(image_eb, text) self.dot_hbox.pack_start(image_eb, expand=False, fill=False, padding=1) def launch_gscan(self): """Launch gscan.""" if cylc.flags.debug: stdout = sys.stdout stderr = sys.stderr command = ["cylc", "gscan", "--debug"] else: stdout = open(os.devnull, "w") stderr = STDOUT command = ["cylc", "gscan"] if self.hosts: command += self.hosts Popen(command, stdin=open(os.devnull), stdout=stdout, stderr=stderr) def _on_button_press_event(self, widget, event): if event.button == 1: self.launch_context_menu(event, suite_keys=widget._connect_args) return False def _on_button_press_event_gscan(self, widget, event): self.launch_gscan() @staticmethod def _on_img_tooltip_query(widget, x, y, kbd, tooltip, tip_widget): tooltip.set_custom(tip_widget) return True def _set_exception_hook(self): """Handle an uncaught exception.""" sys.excepthook = lambda e_type, e_value, e_traceback: ( self._handle_exception( e_type, e_value, e_traceback, sys.excepthook)) def _handle_exception(self, e_type, e_value, e_traceback, old_hook): self.gcylc_image.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_MENU) exc_lines = traceback.format_exception(e_type, e_value, e_traceback) exc_text = "".join(exc_lines) info = "cylc gpanel has a problem.\n\n%s" % exc_text self._set_tooltip(self.gcylc_image, info.rstrip()) if old_hook is not None: old_hook(e_type, e_value, e_traceback) def _set_gcylc_image_tooltip(self): if self.quit: self._set_tooltip(self.gcylc_image, "Cylc Applet - Off") else: self._set_tooltip(self.gcylc_image, "Cylc Applet - Active") def _set_theme(self, new_theme_name): self.theme_name = new_theme_name self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) def _set_tooltip(self, widget, text): tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(widget, text)
def _set_theme(self, new_theme_name): self.theme_name = new_theme_name self.theme = GcylcConfig.get_inst().get(['themes', self.theme_name]) self.dots = DotMaker(self.theme)
class ScanApp(object): """Summarize running suite statuses for a given set of hosts.""" WARNINGS_COLUMN = 7 STATUS_COLUMN = 6 CYCLE_COLUMN = 5 UPDATE_TIME_COLUMN = 4 TITLE_COLUMN = 3 STOPPED_COLUMN = 2 SUITE_COLUMN = 1 HOST_COLUMN = 0 ICON_SIZE = 17 def __init__(self, hosts=None, owner=None, poll_interval=None): gobject.threads_init() set_exception_hook_dialog("cylc gscan") setup_icons() if not hosts: hosts = GLOBAL_CFG.get(["suite host scanning", "hosts"]) self.hosts = hosts if owner is None: owner = USER self.owner = owner self.window = gtk.Window() self.window.set_title("cylc gscan") self.window.set_icon(get_icon()) self.vbox = gtk.VBox() self.vbox.show() self.warnings = {} self.theme_name = gcfg.get(['use theme']) self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) suite_treemodel = gtk.TreeStore( str, str, bool, str, int, str, str, str) self._prev_tooltip_location_id = None self.suite_treeview = gtk.TreeView(suite_treemodel) # Construct the host column. host_name_column = gtk.TreeViewColumn("Host") cell_text_host = gtk.CellRendererText() host_name_column.pack_start(cell_text_host, expand=False) host_name_column.set_cell_data_func( cell_text_host, self._set_cell_text_host) host_name_column.set_sort_column_id(0) host_name_column.set_visible("host" in gsfg.get(["columns"])) host_name_column.set_resizable(True) # Construct the suite name column. suite_name_column = gtk.TreeViewColumn("Suite") cell_text_name = gtk.CellRendererText() suite_name_column.pack_start(cell_text_name, expand=False) suite_name_column.set_cell_data_func( cell_text_name, self._set_cell_text_name) suite_name_column.set_sort_column_id(1) suite_name_column.set_visible("suite" in gsfg.get(["columns"])) suite_name_column.set_resizable(True) # Construct the suite title column. suite_title_column = gtk.TreeViewColumn("Title") cell_text_title = gtk.CellRendererText() suite_title_column.pack_start(cell_text_title, expand=False) suite_title_column.set_cell_data_func( cell_text_title, self._set_cell_text_title) suite_title_column.set_sort_column_id(3) suite_title_column.set_visible("title" in gsfg.get( ["columns"])) suite_title_column.set_resizable(True) # Construct the update time column. time_column = gtk.TreeViewColumn("Updated") cell_text_time = gtk.CellRendererText() time_column.pack_start(cell_text_time, expand=False) time_column.set_cell_data_func( cell_text_time, self._set_cell_text_time) time_column.set_sort_column_id(4) time_column.set_visible("updated" in gsfg.get(["columns"])) time_column.set_resizable(True) self.suite_treeview.append_column(host_name_column) self.suite_treeview.append_column(suite_name_column) self.suite_treeview.append_column(suite_title_column) self.suite_treeview.append_column(time_column) # Construct the status column. status_column = gtk.TreeViewColumn("Status") status_column.set_sort_column_id(5) status_column.set_visible("status" in gsfg.get(["columns"])) status_column.set_resizable(True) cell_text_cycle = gtk.CellRendererText() status_column.pack_start(cell_text_cycle, expand=False) status_column.set_cell_data_func( cell_text_cycle, self._set_cell_text_cycle, self.CYCLE_COLUMN) self.suite_treeview.append_column(status_column) # Warning icon. warn_icon = gtk.CellRendererPixbuf() image = gtk.Image() pixbuf = image.render_icon( gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_LARGE_TOOLBAR) self.warn_icon_colour = pixbuf.scale_simple( # colour warn icon pixbuf self.ICON_SIZE, self.ICON_SIZE, gtk.gdk.INTERP_HYPER) self.warn_icon_grey = pixbuf.scale_simple( self.ICON_SIZE, self.ICON_SIZE, gtk.gdk.INTERP_HYPER) self.warn_icon_colour.saturate_and_pixelate( self.warn_icon_grey, 0, False) # b&w warn icon pixbuf status_column.pack_start(warn_icon, expand=False) status_column.set_cell_data_func(warn_icon, self._set_error_icon_state) self.warn_icon_blank = gtk.gdk.Pixbuf( # Transparent pixbuff. gtk.gdk.COLORSPACE_RGB, True, 8, self.ICON_SIZE, self.ICON_SIZE ).fill(0x00000000) # Task status icons. for i in range(len(TASK_STATUSES_ORDERED)): cell_pixbuf_state = gtk.CellRendererPixbuf() status_column.pack_start(cell_pixbuf_state, expand=False) status_column.set_cell_data_func( cell_pixbuf_state, self._set_cell_pixbuf_state, i) self.suite_treeview.show() if hasattr(self.suite_treeview, "set_has_tooltip"): self.suite_treeview.set_has_tooltip(True) try: self.suite_treeview.connect('query-tooltip', self._on_query_tooltip) except TypeError: # Lower PyGTK version. pass self.suite_treeview.connect("button-press-event", self._on_button_press_event) scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.add(self.suite_treeview) scrolled_window.show() self.vbox.pack_start(scrolled_window, expand=True, fill=True) self.updater = ScanAppUpdater( self.hosts, suite_treemodel, self.suite_treeview, owner=self.owner, poll_interval=poll_interval ) self.updater.start() self.window.add(self.vbox) self.window.connect("destroy", self._on_destroy_event) self.window.set_default_size(300, 150) self.suite_treeview.grab_focus() self.window.show() self.warning_icon_shown = [] def _on_button_press_event(self, treeview, event): x = int(event.x) y = int(event.y) pth = treeview.get_path_at_pos(x, y) treemodel = treeview.get_model() # Dismiss warnings by clicking on a warning icon. if (event.button == 1): if not pth: return False path, column, cell_x, cell_y = (pth) if column.get_title() == "Status": dot_offset, dot_width = tuple(column.cell_get_position( column.get_cell_renderers()[1])) if not dot_width: return False cell_index = (cell_x - dot_offset) // dot_width if cell_index == 0: iter_ = treemodel.get_iter(path) host, suite = treemodel.get(iter_, self.HOST_COLUMN, self.SUITE_COLUMN) self.updater.clear_warnings(suite, host) treemodel.set(iter_, self.WARNINGS_COLUMN, '') return True # Display menu on right click. if (event.type != gtk.gdk._2BUTTON_PRESS and event.button != 3): return False suite_host_tuples = [] if pth is not None: # Add a gcylc launcher item. path, col, cellx, celly = pth iter_ = treemodel.get_iter(path) host, suite = treemodel.get(iter_, self.HOST_COLUMN, self.SUITE_COLUMN) if suite is None: # On an expanded cycle point row, so get from parent. host, suite = treemodel.get(treemodel.iter_parent(iter_), self.HOST_COLUMN, self.SUITE_COLUMN) suite_host_tuples.append((suite, host)) if event.type == gtk.gdk._2BUTTON_PRESS: if suite_host_tuples: launch_gcylc(host, suite, owner=self.owner) return False has_stopped_suites = bool(self.updater.stopped_hosts_suites_info) view_item = gtk.ImageMenuItem("View Column...") img = gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_MENU) view_item.set_image(img) view_item.show() view_menu = gtk.Menu() view_item.set_submenu(view_menu) for column_index, column in enumerate(treeview.get_columns()): name = column.get_title() is_visible = column.get_visible() column_item = gtk.CheckMenuItem(name.replace("_", "__")) column_item._connect_args = (column_index, is_visible) column_item.set_active(is_visible) column_item.connect("toggled", self._on_toggle_column_visible) column_item.show() view_menu.append(column_item) menu = get_scan_menu( suite_host_tuples, self.theme_name, self._set_theme, has_stopped_suites, self.updater.clear_stopped_suites, self.hosts, self.updater.set_hosts, self.updater.update_now, self.updater.start, program_name="cylc gscan", extra_items=[view_item], owner=self.owner ) menu.popup(None, None, None, event.button, event.time) return False def _on_destroy_event(self, widget): self.updater.quit = True gtk.main_quit() return False def _on_query_tooltip(self, widget, x, y, kbd_ctx, tooltip): """Handle a tooltip creation request.""" tip_context = self.suite_treeview.get_tooltip_context(x, y, kbd_ctx) if tip_context is None: self._prev_tooltip_location_id = None return False x, y = self.suite_treeview.convert_widget_to_bin_window_coords(x, y) path, column, cell_x, cell_y = ( self.suite_treeview.get_path_at_pos(x, y)) model = self.suite_treeview.get_model() iter_ = model.get_iter(path) parent_iter = model.iter_parent(iter_) if parent_iter is None: host = model.get_value(iter_, self.HOST_COLUMN) suite = model.get_value(iter_, self.SUITE_COLUMN) child_row_number = None else: host = model.get_value(parent_iter, self.HOST_COLUMN) suite = model.get_value(parent_iter, self.SUITE_COLUMN) child_row_number = path[-1] suite_update_time = model.get_value(iter_, self.UPDATE_TIME_COLUMN) location_id = (host, suite, suite_update_time, column.get_title(), child_row_number) if location_id != self._prev_tooltip_location_id: self._prev_tooltip_location_id = location_id tooltip.set_text(None) return False if column.get_title() in ["Host", "Suite"]: tooltip.set_text(suite + " - " + host) return True if column.get_title() == "Updated": suite_update_point = get_timepoint_from_seconds_since_unix_epoch( suite_update_time) if (self.updater.last_update_time is not None and suite_update_time != int(self.updater.last_update_time)): retrieval_point = get_timepoint_from_seconds_since_unix_epoch( int(self.updater.last_update_time)) text = "Last changed at %s\n" % suite_update_point text += "Last scanned at %s" % retrieval_point else: # An older suite (or before any updates are made?) text = "Last scanned at %s" % suite_update_point tooltip.set_text(text) return True if column.get_title() != "Status": tooltip.set_text(None) return False # Generate text for the number of tasks in each state state_texts = [] state_text = model.get_value(iter_, self.STATUS_COLUMN) if state_text is None: tooltip.set_text(None) return False info = re.findall(r'\D+\d+', state_text) for status_number in info: status, number = status_number.rsplit(" ", 1) state_texts.append(number + " " + status.strip()) tooltip_prefix = ( "<span foreground=\"#777777\">Tasks: " + ", ".join(state_texts) + "</span>" ) # If hovering over a status indicator set tooltip to show most recent # tasks. dot_offset, dot_width = tuple(column.cell_get_position( column.get_cell_renderers()[2])) cell_index = ((cell_x - dot_offset) // dot_width) + 1 if cell_index >= 0: # NOTE: TreeViewColumn.get_cell_renderers() does not always return # cell renderers for the correct row. if cell_index == 0: # Hovering over the error symbol. point_string = model.get(iter_, self.CYCLE_COLUMN)[0] if point_string: return False if (suite, host) not in self.warnings or not self.warnings[ (suite, host)]: return False tooltip.set_markup( tooltip_prefix + '\n<b>New failures</b> (<i>last 5</i>) <i><span ' + 'foreground="#2222BB">click to dismiss</span></i>\n' + self.warnings[(suite, host)]) return True else: # Hovering over a status indicator. info = re.findall(r'\D+\d+', model.get(iter_, self.STATUS_COLUMN)[0]) if cell_index > len(info): return False state = info[cell_index - 1].strip().split(' ')[0] point_string = model.get(iter_, self.CYCLE_COLUMN)[0] tasks = self.updater.get_last_n_tasks( suite, host, state, point_string) tooltip.set_markup(tooltip_prefix + ( '\n<b>Recent {state} tasks</b>\n{tasks}').format( state=state, tasks='\n'.join(tasks)) ) return True # Set the tooltip to a generic status for this suite. tooltip.set_markup(tooltip_prefix) return True def _on_toggle_column_visible(self, menu_item): column_index, is_visible = menu_item._connect_args column = self.suite_treeview.get_columns()[column_index] column.set_visible(not is_visible) return False def _set_cell_pixbuf_state(self, column, cell, model, iter_, index): state_info = model.get_value(iter_, self.STATUS_COLUMN) if state_info is not None: is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) info = re.findall(r'\D+\d+', state_info) if index < len(info): state = info[index].rsplit(" ", self.SUITE_COLUMN)[0].strip() icon = self.dots.get_icon(state.strip(), is_stopped=is_stopped) cell.set_property("visible", True) else: icon = None cell.set_property("visible", False) else: icon = None cell.set_property("visible", False) cell.set_property("pixbuf", icon) def _set_error_icon_state(self, column, cell, model, iter_): """Update the state of the warning icon.""" host, suite, warnings, point_string = model.get( iter_, self.HOST_COLUMN, self.SUITE_COLUMN, self.WARNINGS_COLUMN, self.CYCLE_COLUMN) if point_string: # Error icon only for first row. cell.set_property('pixbuf', self.warn_icon_blank) elif warnings: cell.set_property('pixbuf', self.warn_icon_colour) self.warning_icon_shown.append((suite, host)) self.warnings[(suite, host)] = warnings else: cell.set_property('pixbuf', self.warn_icon_grey) self.warnings[(suite, host)] = None if not (suite, host) in self.warning_icon_shown: cell.set_property('pixbuf', self.warn_icon_blank) def _set_cell_text_host(self, column, cell, model, iter_): host = model.get_value(iter_, self.HOST_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", host) def _set_cell_text_name(self, column, cell, model, iter_): name = model.get_value(iter_, self.SUITE_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", name) def _set_cell_text_title(self, column, cell, model, iter_): title = model.get_value(iter_, self.TITLE_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", title) def _set_cell_text_time(self, column, cell, model, iter_): suite_update_time = model.get_value(iter_, self.UPDATE_TIME_COLUMN) time_point = get_timepoint_from_seconds_since_unix_epoch( suite_update_time) time_point.set_time_zone_to_local() current_time = time.time() current_point = ( get_timepoint_from_seconds_since_unix_epoch(current_time)) if str(time_point).split("T")[0] == str(current_point).split("T")[0]: time_string = str(time_point).split("T")[1] else: time_string = str(time_point) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", time_string) def _set_cell_text_cycle(self, column, cell, model, iter_, active_cycle): cycle = model.get_value(iter_, active_cycle) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", cycle) def _set_theme(self, new_theme_name): self.theme_name = new_theme_name self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) def _set_tooltip(self, widget, text): tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(widget, text)
def _set_key_liststore(self): dotm = DotMaker(self._theme, self._dot_size) self._key_liststore.clear() for state in task_state.legal: dot = dotm.get_icon(state) self._key_liststore.append([state, dot])
def _set_key_liststore(self): dotm = DotMaker(self._theme, self._dot_size) self._key_liststore.clear() for state in TASK_STATUSES_ORDERED: dot = dotm.get_icon(state) self._key_liststore.append([state, dot])
class ScanApp(object): """Summarize running suite statuses for a given set of hosts.""" WARNINGS_COLUMN = 9 STATUS_COLUMN = 8 CYCLE_COLUMN = 7 UPDATE_TIME_COLUMN = 6 TITLE_COLUMN = 5 STOPPED_COLUMN = 4 SUITE_COLUMN = 3 OWNER_COLUMN = 2 HOST_COLUMN = 1 GROUP_COLUMN = 0 ICON_SIZE = 17 def __init__( self, hosts=None, patterns_name=None, patterns_owner=None, comms_timeout=None, poll_interval=None): gobject.threads_init() set_exception_hook_dialog("cylc gscan") setup_icons() if not hosts: hosts = GLOBAL_CFG.get(["suite host scanning", "hosts"]) self.hosts = hosts self.window = gtk.Window() title = "cylc gscan" for opt, items, skip in [ ("-n", patterns_name, None), ("-o", patterns_owner, USER)]: if items: for pattern in items: if pattern != skip: title += " %s %s" % (opt, pattern) self.window.set_title(title) self.window.set_icon(get_icon()) self.vbox = gtk.VBox() self.vbox.show() self.warnings = {} self.theme_name = gcfg.get(['use theme']) self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) suite_treemodel = gtk.TreeStore( str, # group str, # host str, # owner str, # suite bool, # is_stopped str, # title int, # update_time str, # states str, # states_text str) # warning_text self._prev_tooltip_location_id = None self.suite_treeview = gtk.TreeView(suite_treemodel) # Visibility of columns vis_cols = gsfg.get(["columns"]) # Doesn't make any sense without suite name column if gsfg.COL_SUITE not in vis_cols: vis_cols.append(gsfg.COL_SUITE.lower()) # In multiple host environment, add host column by default if hosts: vis_cols.append(gsfg.COL_HOST.lower()) # In multiple owner environment, add owner column by default if patterns_owner != [USER]: vis_cols.append(gsfg.COL_OWNER.lower()) # Construct the group, host, owner, suite, title, update time column. for col_title, col_id, col_cell_text_setter in [ (gsfg.COL_GROUP, self.GROUP_COLUMN, self._set_cell_text_group), (gsfg.COL_HOST, self.HOST_COLUMN, self._set_cell_text_host), (gsfg.COL_OWNER, self.OWNER_COLUMN, self._set_cell_text_owner), (gsfg.COL_SUITE, self.SUITE_COLUMN, self._set_cell_text_name), (gsfg.COL_TITLE, self.TITLE_COLUMN, self._set_cell_text_title), (gsfg.COL_UPDATED, self.UPDATE_TIME_COLUMN, self._set_cell_text_time), ]: column = gtk.TreeViewColumn(col_title) cell_text = gtk.CellRendererText() column.pack_start(cell_text, expand=False) column.set_cell_data_func(cell_text, col_cell_text_setter) column.set_sort_column_id(col_id) column.set_visible(col_title.lower() in vis_cols) column.set_resizable(True) self.suite_treeview.append_column(column) # Construct the status column. status_column = gtk.TreeViewColumn(gsfg.COL_STATUS) status_column.set_sort_column_id(self.STATUS_COLUMN) status_column.set_visible(gsfg.COL_STATUS.lower() in vis_cols) status_column.set_resizable(True) cell_text_cycle = gtk.CellRendererText() status_column.pack_start(cell_text_cycle, expand=False) status_column.set_cell_data_func( cell_text_cycle, self._set_cell_text_cycle, self.CYCLE_COLUMN) self.suite_treeview.append_column(status_column) # Warning icon. warn_icon = gtk.CellRendererPixbuf() image = gtk.Image() pixbuf = image.render_icon( gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_LARGE_TOOLBAR) self.warn_icon_colour = pixbuf.scale_simple( # colour warn icon pixbuf self.ICON_SIZE, self.ICON_SIZE, gtk.gdk.INTERP_HYPER) self.warn_icon_grey = pixbuf.scale_simple( self.ICON_SIZE, self.ICON_SIZE, gtk.gdk.INTERP_HYPER) self.warn_icon_colour.saturate_and_pixelate( self.warn_icon_grey, 0, False) # b&w warn icon pixbuf status_column.pack_start(warn_icon, expand=False) status_column.set_cell_data_func(warn_icon, self._set_error_icon_state) self.warn_icon_blank = gtk.gdk.Pixbuf( # Transparent pixbuff. gtk.gdk.COLORSPACE_RGB, True, 8, self.ICON_SIZE, self.ICON_SIZE ).fill(0x00000000) # Task status icons. for i in range(len(TASK_STATUSES_ORDERED)): cell_pixbuf_state = gtk.CellRendererPixbuf() status_column.pack_start(cell_pixbuf_state, expand=False) status_column.set_cell_data_func( cell_pixbuf_state, self._set_cell_pixbuf_state, i) self.suite_treeview.show() if hasattr(self.suite_treeview, "set_has_tooltip"): self.suite_treeview.set_has_tooltip(True) try: self.suite_treeview.connect('query-tooltip', self._on_query_tooltip) except TypeError: # Lower PyGTK version. pass self.suite_treeview.connect("button-press-event", self._on_button_press_event) scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.add(self.suite_treeview) scrolled_window.show() self.vbox.pack_start(scrolled_window, expand=True, fill=True) patterns = {"name": None, "owner": None} for label, items in [ ("owner", patterns_owner), ("name", patterns_name)]: if items: patterns[label] = r"\A(?:" + r")|(?:".join(items) + r")\Z" try: patterns[label] = re.compile(patterns[label]) except re.error: raise ValueError("Invalid %s pattern: %s" % (label, items)) self.updater = ScanAppUpdater( self.window, self.hosts, suite_treemodel, self.suite_treeview, comms_timeout=comms_timeout, poll_interval=poll_interval, group_column_id=self.GROUP_COLUMN, name_pattern=patterns["name"], owner_pattern=patterns["owner"]) self.updater.start() self.window.add(self.vbox) self.window.connect("destroy", self._on_destroy_event) self.window.set_default_size(300, 150) self.suite_treeview.grab_focus() self.window.show() self.warning_icon_shown = [] def _on_button_press_event(self, treeview, event): """Tree view button press callback.""" x = int(event.x) y = int(event.y) pth = treeview.get_path_at_pos(x, y) treemodel = treeview.get_model() # Dismiss warnings by clicking on a warning icon. if event.button == 1: if not pth: return False path, column, cell_x, _ = pth if column.get_title() == gsfg.COL_STATUS: dot_offset, dot_width = tuple(column.cell_get_position( column.get_cell_renderers()[1])) if not dot_width: return False try: cell_index = (cell_x - dot_offset) // dot_width except ZeroDivisionError: return False if cell_index == 0: iter_ = treemodel.get_iter(path) host, owner, suite = treemodel.get( iter_, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) self.updater.clear_warnings(host, owner, suite) treemodel.set(iter_, self.WARNINGS_COLUMN, '') return True # Display menu on right click. if event.type != gtk.gdk._2BUTTON_PRESS and event.button != 3: return False suite_keys = [] if pth is not None: # Add a gcylc launcher item. path = pth[0] iter_ = treemodel.get_iter(path) host, owner, suite = treemodel.get( iter_, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) if suite is not None: suite_keys.append((host, owner, suite)) elif suite is None: # On an expanded cycle point row, so get from parent. try: host, owner, suite = treemodel.get( treemodel.iter_parent(iter_), self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) suite_keys.append((host, owner, suite)) except: # Now iterate over the children instead. # We need to iterate over the children as there can be more # than one suite in a group of suites. # Get a TreeIter pointing to the first child of parent iter suite_iter = treemodel.iter_children(iter_) # Iterate over the children until you get to end while suite_iter is not None: host, owner, suite = treemodel.get(suite_iter, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) suite_keys.append((host, owner, suite)) # Advance to the next pointer in the treemodel suite_iter = treemodel.iter_next(suite_iter) if event.type == gtk.gdk._2BUTTON_PRESS: if suite_keys: launch_gcylc(suite_keys[0]) return False view_item = gtk.ImageMenuItem("View Column...") img = gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_MENU) view_item.set_image(img) view_item.show() view_menu = gtk.Menu() view_item.set_submenu(view_menu) for column_index, column in enumerate(treeview.get_columns()): name = column.get_title() is_visible = column.get_visible() column_item = gtk.CheckMenuItem(name.replace("_", "__")) column_item._connect_args = (column_index, is_visible) column_item.set_active(is_visible) column_item.connect("toggled", self._on_toggle_column_visible) column_item.show() view_menu.append(column_item) menu = get_scan_menu( suite_keys, self.theme_name, self._set_theme, self.updater.has_stopped_suites(), self.updater.clear_stopped_suites, self.hosts, self.updater.set_hosts, self.updater.update_now, self.updater.start, program_name="cylc gscan", extra_items=[view_item], ) menu.popup(None, None, None, event.button, event.time) return False def _on_destroy_event(self, _): """Callback on destroy of main window.""" self.updater.quit = True gtk.main_quit() return False def _on_query_tooltip(self, _, x, y, kbd_ctx, tooltip): """Handle a tooltip creation request.""" tip_context = self.suite_treeview.get_tooltip_context(x, y, kbd_ctx) if tip_context is None: self._prev_tooltip_location_id = None return False x, y = self.suite_treeview.convert_widget_to_bin_window_coords(x, y) path, column, cell_x, _ = ( self.suite_treeview.get_path_at_pos(x, y)) model = self.suite_treeview.get_model() iter_ = model.get_iter(path) parent_iter = model.iter_parent(iter_) if parent_iter is None or parent_iter and model.iter_has_child(iter_): host, owner, suite = model.get( iter_, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) child_row_number = None else: host, owner, suite = model.get( parent_iter, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) child_row_number = path[-1] suite_update_time = model.get_value(iter_, self.UPDATE_TIME_COLUMN) location_id = ( host, owner, suite, suite_update_time, column.get_title(), child_row_number) if location_id != self._prev_tooltip_location_id: self._prev_tooltip_location_id = location_id tooltip.set_text(None) return False if column.get_title() in [ gsfg.COL_HOST, gsfg.COL_OWNER, gsfg.COL_SUITE]: tooltip.set_text("%s - %s:%s" % (suite, owner, host)) return True if column.get_title() == gsfg.COL_UPDATED: suite_update_point = timepoint_from_epoch(suite_update_time) if (self.updater.last_update_time is not None and suite_update_time != int(self.updater.last_update_time)): retrieval_point = timepoint_from_epoch( int(self.updater.last_update_time)) text = "Last changed at %s\n" % suite_update_point text += "Last scanned at %s" % retrieval_point else: # An older suite (or before any updates are made?) text = "Last scanned at %s" % suite_update_point tooltip.set_text(text) return True if column.get_title() != gsfg.COL_STATUS: tooltip.set_text(None) return False # Generate text for the number of tasks in each state state_texts = [] state_text = model.get_value(iter_, self.STATUS_COLUMN) if state_text is None: tooltip.set_text(None) return False info = re.findall(r'\D+\d+', state_text) for status_number in info: status, number = status_number.rsplit(" ", 1) state_texts.append(number + " " + status.strip()) tooltip_prefix = ( "<span foreground=\"#777777\">Tasks: " + ", ".join(state_texts) + "</span>" ) # If hovering over a status indicator set tooltip to show most recent # tasks. dot_offset, dot_width = tuple(column.cell_get_position( column.get_cell_renderers()[2])) try: cell_index = ((cell_x - dot_offset) // dot_width) + 1 except ZeroDivisionError: return False if cell_index >= 0: # NOTE: TreeViewColumn.get_cell_renderers() does not always return # cell renderers for the correct row. if cell_index == 0: # Hovering over the error symbol. point_string = model.get(iter_, self.CYCLE_COLUMN)[0] if point_string: return False if not self.warnings.get((host, owner, suite)): return False tooltip.set_markup( tooltip_prefix + '\n<b>New failures</b> (<i>last 5</i>) <i><span ' + 'foreground="#2222BB">click to dismiss</span></i>\n' + self.warnings[(host, owner, suite)]) return True else: # Hovering over a status indicator. info = re.findall(r'\D+\d+', model.get(iter_, self.STATUS_COLUMN)[0]) if cell_index > len(info): return False state = info[cell_index - 1].strip().split(' ')[0] point_string = model.get(iter_, self.CYCLE_COLUMN)[0] tooltip_text = tooltip_prefix if suite: tasks = self.updater.get_last_n_tasks( host, owner, suite, state, point_string) tooltip_text += ( '\n<b>Recent {state} tasks</b>\n{tasks}').format( state=state, tasks='\n'.join(tasks)) tooltip.set_markup(tooltip_text) return True # Set the tooltip to a generic status for this suite. tooltip.set_markup(tooltip_prefix) return True def _on_toggle_column_visible(self, menu_item): """Toggle column visibility callback.""" column_index, is_visible = menu_item._connect_args column = self.suite_treeview.get_columns()[column_index] column.set_visible(not is_visible) self.updater.update() return False def _set_cell_pixbuf_state(self, _, cell, model, iter_, index): """State info pixbuf.""" state_info = model.get_value(iter_, self.STATUS_COLUMN) if state_info is not None: is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) info = re.findall(r'\D+\d+', state_info) if index < len(info): state = info[index].strip().rsplit( " ", self.SUITE_COLUMN)[0].strip() icon = self.dots.get_icon(state, is_stopped=is_stopped) cell.set_property("visible", True) else: icon = None cell.set_property("visible", False) else: icon = None cell.set_property("visible", False) cell.set_property("pixbuf", icon) def _set_error_icon_state(self, _, cell, model, iter_): """Update the state of the warning icon.""" host, owner, suite, warnings, point_string = model.get( iter_, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN, self.WARNINGS_COLUMN, self.CYCLE_COLUMN) key = (host, owner, suite) if point_string: # Error icon only for first row. cell.set_property('pixbuf', self.warn_icon_blank) elif warnings: cell.set_property('pixbuf', self.warn_icon_colour) self.warning_icon_shown.append(key) self.warnings[key] = warnings else: cell.set_property('pixbuf', self.warn_icon_grey) self.warnings[key] = None if key not in self.warning_icon_shown: cell.set_property('pixbuf', self.warn_icon_blank) def _set_cell_text_group(self, _, cell, model, iter_): """Set cell text for "group" column.""" group = model.get_value(iter_, self.GROUP_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", group) def _set_cell_text_host(self, _, cell, model, iter_): """Set cell text for "host" column.""" host = model.get_value(iter_, self.HOST_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", host) def _set_cell_text_owner(self, _, cell, model, iter_): """Set cell text for "owner" column.""" value = model.get_value(iter_, self.OWNER_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", value) def _set_cell_text_name(self, _, cell, model, iter_): """Set cell text for (suite name) "name" column.""" name = model.get_value(iter_, self.SUITE_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", name) def _set_cell_text_title(self, _, cell, model, iter_): """Set cell text for "title" column.""" title = model.get_value(iter_, self.TITLE_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", title) def _set_cell_text_time(self, _, cell, model, iter_): """Set cell text for "update-time" column.""" suite_update_time = model.get_value(iter_, self.UPDATE_TIME_COLUMN) time_point = timepoint_from_epoch(suite_update_time) time_point.set_time_zone_to_local() current_time = time.time() current_point = timepoint_from_epoch(current_time) if str(time_point).split("T")[0] == str(current_point).split("T")[0]: time_string = str(time_point).split("T")[1] else: time_string = str(time_point) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", time_string) def _set_cell_text_cycle(self, _, cell, model, iter_, active_cycle): """Set cell text for "cycle" column.""" cycle = model.get_value(iter_, active_cycle) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", cycle) def _set_theme(self, new_theme_name): """Set GUI theme.""" self.theme_name = new_theme_name self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) @staticmethod def _set_tooltip(widget, text): """Set tooltip for a widget.""" tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(widget, text)
class ScanApp(object): """Summarize running suite statuses for a given set of hosts.""" WARNINGS_COLUMN = 9 STATUS_COLUMN = 8 CYCLE_COLUMN = 7 UPDATE_TIME_COLUMN = 6 TITLE_COLUMN = 5 STOPPED_COLUMN = 4 SUITE_COLUMN = 3 OWNER_COLUMN = 2 HOST_COLUMN = 1 GROUP_COLUMN = 0 ICON_SIZE = 17 def __init__( self, hosts=None, patterns_name=None, patterns_owner=None, comms_timeout=None, poll_interval=None): gobject.threads_init() set_exception_hook_dialog("cylc gscan") setup_icons() if not hosts: hosts = GLOBAL_CFG.get(["suite host scanning", "hosts"]) self.hosts = hosts self.window = gtk.Window() title = "cylc gscan" for opt, items, skip in [ ("-n", patterns_name, None), ("-o", patterns_owner, USER)]: if items: for pattern in items: if pattern != skip: title += " %s %s" % (opt, pattern) self.window.set_title(title) self.window.set_icon(get_icon()) self.vbox = gtk.VBox() self.vbox.show() self.warnings = {} self.theme_name = gcfg.get(['use theme']) self.theme = gcfg.get(['themes', self.theme_name]) suite_treemodel = gtk.TreeStore( str, # group str, # host str, # owner str, # suite bool, # is_stopped str, # title int, # update_time str, # states str, # states_text str) # warning_text self._prev_tooltip_location_id = None self.treeview = gtk.TreeView(suite_treemodel) # Visibility of columns vis_cols = gsfg.get(["columns"]) # Doesn't make any sense without suite name column if gsfg.COL_SUITE not in vis_cols: vis_cols.append(gsfg.COL_SUITE.lower()) # In multiple host environment, add host column by default if hosts: vis_cols.append(gsfg.COL_HOST.lower()) # In multiple owner environment, add owner column by default if patterns_owner != [USER]: vis_cols.append(gsfg.COL_OWNER.lower()) # Construct the group, host, owner, suite, title, update time column. for col_title, col_id, col_cell_text_setter in [ (gsfg.COL_GROUP, self.GROUP_COLUMN, self._set_cell_text_group), (gsfg.COL_HOST, self.HOST_COLUMN, self._set_cell_text_host), (gsfg.COL_OWNER, self.OWNER_COLUMN, self._set_cell_text_owner), (gsfg.COL_SUITE, self.SUITE_COLUMN, self._set_cell_text_name), (gsfg.COL_TITLE, self.TITLE_COLUMN, self._set_cell_text_title), (gsfg.COL_UPDATED, self.UPDATE_TIME_COLUMN, self._set_cell_text_time), ]: column = gtk.TreeViewColumn(col_title) cell_text = gtk.CellRendererText() column.pack_start(cell_text, expand=False) column.set_cell_data_func(cell_text, col_cell_text_setter) column.set_sort_column_id(col_id) column.set_visible(col_title.lower() in vis_cols) column.set_resizable(True) self.treeview.append_column(column) # Construct the status column. status_column = gtk.TreeViewColumn(gsfg.COL_STATUS) status_column.set_sort_column_id(self.STATUS_COLUMN) status_column.set_visible(gsfg.COL_STATUS.lower() in vis_cols) status_column.set_resizable(True) cell_text_cycle = gtk.CellRendererText() status_column.pack_start(cell_text_cycle, expand=False) status_column.set_cell_data_func( cell_text_cycle, self._set_cell_text_cycle, self.CYCLE_COLUMN) self.treeview.append_column(status_column) # Warning icon. warn_icon = gtk.CellRendererPixbuf() image = gtk.Image() pixbuf = image.render_icon( gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_LARGE_TOOLBAR) self.warn_icon_colour = pixbuf.scale_simple( # colour warn icon pixbuf self.ICON_SIZE, self.ICON_SIZE, gtk.gdk.INTERP_HYPER) self.warn_icon_grey = pixbuf.scale_simple( self.ICON_SIZE, self.ICON_SIZE, gtk.gdk.INTERP_HYPER) self.warn_icon_colour.saturate_and_pixelate( self.warn_icon_grey, 0, False) # b&w warn icon pixbuf status_column.pack_start(warn_icon, expand=False) status_column.set_cell_data_func(warn_icon, self._set_error_icon_state) self.warn_icon_blank = gtk.gdk.Pixbuf( # Transparent pixbuff. gtk.gdk.COLORSPACE_RGB, True, 8, self.ICON_SIZE, self.ICON_SIZE ).fill(0x00000000) # Task status icons. for i in range(len(TASK_STATUSES_ORDERED)): cell_pixbuf_state = gtk.CellRendererPixbuf() status_column.pack_start(cell_pixbuf_state, expand=False) status_column.set_cell_data_func( cell_pixbuf_state, self._set_cell_pixbuf_state, i) self.treeview.show() if hasattr(self.treeview, "set_has_tooltip"): self.treeview.set_has_tooltip(True) try: self.treeview.connect('query-tooltip', self._on_query_tooltip) except TypeError: # Lower PyGTK version. pass self.treeview.connect("button-press-event", self._on_button_press_event) patterns = {"name": None, "owner": None} for label, items in [ ("owner", patterns_owner), ("name", patterns_name)]: if items: patterns[label] = r"\A(?:" + r")|(?:".join(items) + r")\Z" try: patterns[label] = re.compile(patterns[label]) except re.error: raise ValueError("Invalid %s pattern: %s" % (label, items)) self.updater = ScanAppUpdater( self.window, self.hosts, suite_treemodel, self.treeview, comms_timeout=comms_timeout, poll_interval=poll_interval, group_column_id=self.GROUP_COLUMN, name_pattern=patterns["name"], owner_pattern=patterns["owner"]) self.updater.start() self.dot_size = gcfg.get(['dot icon size']) self._set_dots() self.create_menubar() accelgroup = gtk.AccelGroup() self.window.add_accel_group(accelgroup) key, modifier = gtk.accelerator_parse('<Alt>m') accelgroup.connect_group( key, modifier, gtk.ACCEL_VISIBLE, self._toggle_hide_menu_bar) self.create_tool_bar() self.menu_hbox = gtk.HBox() self.menu_hbox.pack_start(self.menu_bar, expand=True, fill=True) self.menu_hbox.pack_start(self.tool_bar, expand=True, fill=True) self.menu_hbox.show_all() self.menu_hbox.hide_all() scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.add(self.treeview) scrolled_window.show() self.vbox.pack_start(self.menu_hbox, expand=False) self.vbox.pack_start(scrolled_window, expand=True, fill=True) self.window.add(self.vbox) self.window.connect("destroy", self._on_destroy_event) wsize = gsfg.get(['window size']) self.window.set_default_size(*wsize) self.treeview.grab_focus() self.window.show() self.theme_legend_window = None self.warning_icon_shown = [] def popup_theme_legend(self, widget=None): """Popup a theme legend window.""" if self.theme_legend_window is None: self.theme_legend_window = ThemeLegendWindow( self.window, self.theme) self.theme_legend_window.connect( "destroy", self.destroy_theme_legend) else: self.theme_legend_window.present() def update_theme_legend(self): """Update the theme legend window, if it exists.""" if self.theme_legend_window is not None: self.theme_legend_window.update(self.theme) def destroy_theme_legend(self, widget): """Handle a destroy of the theme legend window.""" self.theme_legend_window = None def create_menubar(self): """Create the main menu.""" self.menu_bar = gtk.MenuBar() file_menu = gtk.Menu() file_menu_root = gtk.MenuItem('_File') file_menu_root.set_submenu(file_menu) exit_item = gtk.ImageMenuItem('E_xit') img = gtk.image_new_from_stock(gtk.STOCK_QUIT, gtk.ICON_SIZE_MENU) exit_item.set_image(img) exit_item.show() exit_item.connect("activate", self._on_destroy_event) file_menu.append(exit_item) view_menu = gtk.Menu() view_menu_root = gtk.MenuItem('_View') view_menu_root.set_submenu(view_menu) col_item = gtk.ImageMenuItem("_Columns...") img = gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_MENU) col_item.set_image(img) col_item.show() col_menu = gtk.Menu() for column_index, column in enumerate(self.treeview.get_columns()): name = column.get_title() is_visible = column.get_visible() column_item = gtk.CheckMenuItem(name.replace("_", "__")) column_item._connect_args = column_index column_item.set_active(is_visible) column_item.connect("toggled", self._on_toggle_column_visible) column_item.show() col_menu.append(column_item) col_item.set_submenu(col_menu) col_item.show_all() view_menu.append(col_item) view_menu.append(gtk.SeparatorMenuItem()) # Construct theme chooser items (same as cylc.gui.app_main). theme_item = gtk.ImageMenuItem('Theme...') img = gtk.image_new_from_stock( gtk.STOCK_SELECT_COLOR, gtk.ICON_SIZE_MENU) theme_item.set_image(img) thememenu = gtk.Menu() theme_item.set_submenu(thememenu) theme_item.show() theme_items = {} theme = "default" theme_items[theme] = gtk.RadioMenuItem(label=theme) thememenu.append(theme_items[theme]) theme_items[theme].theme_name = theme for theme in gcfg.get(['themes']): if theme == "default": continue theme_items[theme] = gtk.RadioMenuItem( group=theme_items['default'], label=theme) thememenu.append(theme_items[theme]) theme_items[theme].theme_name = theme # set_active then connect, to avoid causing an unnecessary toggle now. theme_items[self.theme_name].set_active(True) for theme in gcfg.get(['themes']): theme_items[theme].show() theme_items[theme].connect( 'toggled', lambda i: (i.get_active() and self._set_theme(i.theme_name))) view_menu.append(theme_item) theme_legend_item = gtk.ImageMenuItem("Show task state key") img = gtk.image_new_from_stock( gtk.STOCK_SELECT_COLOR, gtk.ICON_SIZE_MENU) theme_legend_item.set_image(img) theme_legend_item.show() theme_legend_item.connect("activate", self.popup_theme_legend) view_menu.append(theme_legend_item) view_menu.append(gtk.SeparatorMenuItem()) # Construct a configure scanned hosts item. hosts_item = gtk.ImageMenuItem("Configure Hosts") img = gtk.image_new_from_stock( gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) hosts_item.set_image(img) hosts_item.show() hosts_item.connect( "button-press-event", lambda b, e: launch_hosts_dialog( self.hosts, self.updater.set_hosts)) view_menu.append(hosts_item) sep_item = gtk.SeparatorMenuItem() sep_item.show() help_menu = gtk.Menu() help_menu_root = gtk.MenuItem('_Help') help_menu_root.set_submenu(help_menu) self.menu_bar.append(file_menu_root) self.menu_bar.append(view_menu_root) self.menu_bar.append(help_menu_root) # Construct an about dialog item. info_item = gtk.ImageMenuItem("About") img = gtk.image_new_from_stock(gtk.STOCK_ABOUT, gtk.ICON_SIZE_MENU) info_item.set_image(img) info_item.show() info_item.connect( "button-press-event", lambda b, e: launch_about_dialog("cylc gscan", self.hosts) ) help_menu.append(info_item) self.menu_bar.show_all() def _set_dots(self): self.dots = DotMaker(self.theme, size=self.dot_size) def create_tool_bar(self): """Create the tool bar for the GUI.""" self.tool_bar = gtk.Toolbar() update_now_button = gtk.ToolButton( icon_widget=gtk.image_new_from_stock( gtk.STOCK_REFRESH, gtk.ICON_SIZE_SMALL_TOOLBAR)) update_now_button.set_label("Update") tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(update_now_button, "Update now") update_now_button.connect("clicked", self.updater.update_now) clear_stopped_button = gtk.ToolButton( icon_widget=gtk.image_new_from_stock( gtk.STOCK_CLEAR, gtk.ICON_SIZE_SMALL_TOOLBAR)) clear_stopped_button.set_label("Clear") tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(clear_stopped_button, "Clear stopped suites") clear_stopped_button.connect("clicked", self.updater.clear_stopped_suites) expand_button = gtk.ToolButton( icon_widget=gtk.image_new_from_stock( gtk.STOCK_ADD, gtk.ICON_SIZE_SMALL_TOOLBAR)) expand_button.set_label("Expand all") tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(expand_button, "Expand all rows") expand_button.connect( "clicked", lambda e: self.treeview.expand_all()) collapse_button = gtk.ToolButton( icon_widget=gtk.image_new_from_stock( gtk.STOCK_REMOVE, gtk.ICON_SIZE_SMALL_TOOLBAR)) collapse_button.set_label("Expand all") tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(collapse_button, "Collapse all rows") collapse_button.connect( "clicked", lambda e: self.treeview.collapse_all()) self.tool_bar.insert(update_now_button, 0) self.tool_bar.insert(clear_stopped_button, 0) self.tool_bar.insert(collapse_button, 0) self.tool_bar.insert(expand_button, 0) separator = gtk.SeparatorToolItem() separator.set_expand(True) self.tool_bar.insert(separator, 0) self.tool_bar.show_all() def _on_button_press_event(self, treeview, event): """Tree view button press callback.""" x = int(event.x) y = int(event.y) pth = treeview.get_path_at_pos(x, y) treemodel = treeview.get_model() # Dismiss warnings by clicking on a warning icon. if event.button == 1: if not pth: return False path, column, cell_x, _ = pth if column.get_title() == gsfg.COL_STATUS: dot_offset, dot_width = tuple(column.cell_get_position( column.get_cell_renderers()[1])) if not dot_width: return False try: cell_index = (cell_x - dot_offset) // dot_width except ZeroDivisionError: return False if cell_index == 0: iter_ = treemodel.get_iter(path) host, owner, suite = treemodel.get( iter_, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) self.updater.clear_warnings(host, owner, suite) treemodel.set(iter_, self.WARNINGS_COLUMN, '') return True # Display menu on right click. if event.type != gtk.gdk._2BUTTON_PRESS and event.button != 3: return False suite_keys = [] if pth is not None: # Add a gcylc launcher item. path = pth[0] iter_ = treemodel.get_iter(path) host, owner, suite = treemodel.get( iter_, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) if suite is not None: suite_keys.append((host, owner, suite)) elif suite is None: # On an expanded cycle point row, so get from parent. try: host, owner, suite = treemodel.get( treemodel.iter_parent(iter_), self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) suite_keys.append((host, owner, suite)) except: # Now iterate over the children instead. # We need to iterate over the children as there can be more # than one suite in a group of suites. # Get a TreeIter pointing to the first child of parent iter suite_iter = treemodel.iter_children(iter_) # Iterate over the children until you get to end while suite_iter is not None: host, owner, suite = treemodel.get(suite_iter, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) suite_keys.append((host, owner, suite)) # Advance to the next pointer in the treemodel suite_iter = treemodel.iter_next(suite_iter) if event.type == gtk.gdk._2BUTTON_PRESS: if suite_keys: launch_gcylc(suite_keys[0]) return False menu = get_scan_menu(suite_keys, self._toggle_hide_menu_bar) menu.popup(None, None, None, event.button, event.time) return False def _on_destroy_event(self, _): """Callback on destroy of main window.""" try: self.updater.quit = True gtk.main_quit() except RuntimeError: pass return False def _toggle_hide_menu_bar(self, *_): if self.menu_hbox.get_property("visible"): self.menu_hbox.hide_all() else: self.menu_hbox.show_all() def _on_query_tooltip(self, _, x, y, kbd_ctx, tooltip): """Handle a tooltip creation request.""" tip_context = self.treeview.get_tooltip_context(x, y, kbd_ctx) if tip_context is None: self._prev_tooltip_location_id = None return False x, y = self.treeview.convert_widget_to_bin_window_coords(x, y) path, column, cell_x, _ = ( self.treeview.get_path_at_pos(x, y)) model = self.treeview.get_model() iter_ = model.get_iter(path) parent_iter = model.iter_parent(iter_) if parent_iter is None or parent_iter and model.iter_has_child(iter_): host, owner, suite = model.get( iter_, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) child_row_number = None else: host, owner, suite = model.get( parent_iter, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) child_row_number = path[-1] suite_update_time = model.get_value(iter_, self.UPDATE_TIME_COLUMN) location_id = ( host, owner, suite, suite_update_time, column.get_title(), child_row_number) if location_id != self._prev_tooltip_location_id: self._prev_tooltip_location_id = location_id tooltip.set_text(None) return False if column.get_title() in [ gsfg.COL_HOST, gsfg.COL_OWNER, gsfg.COL_SUITE]: tooltip.set_text("%s - %s:%s" % (suite, owner, host)) return True if column.get_title() == gsfg.COL_UPDATED: suite_update_point = timepoint_from_epoch(suite_update_time) if (self.updater.last_update_time is not None and suite_update_time != int(self.updater.last_update_time)): retrieval_point = timepoint_from_epoch( int(self.updater.last_update_time)) text = "Last changed at %s\n" % suite_update_point text += "Last scanned at %s" % retrieval_point else: # An older suite (or before any updates are made?) text = "Last scanned at %s" % suite_update_point tooltip.set_text(text) return True if column.get_title() != gsfg.COL_STATUS: tooltip.set_text(None) return False # Generate text for the number of tasks in each state state_texts = [] state_text = model.get_value(iter_, self.STATUS_COLUMN) if state_text is None: tooltip.set_text(None) return False info = re.findall(r'\D+\d+', state_text) for status_number in info: status, number = status_number.rsplit(" ", 1) state_texts.append(number + " " + status.strip()) tooltip_prefix = ( "<span foreground=\"#777777\">Tasks: " + ", ".join(state_texts) + "</span>" ) # If hovering over a status indicator set tooltip to show most recent # tasks. dot_offset, dot_width = tuple(column.cell_get_position( column.get_cell_renderers()[2])) try: cell_index = ((cell_x - dot_offset) // dot_width) + 1 except ZeroDivisionError: return False if cell_index >= 0: # NOTE: TreeViewColumn.get_cell_renderers() does not always return # cell renderers for the correct row. if cell_index == 0: # Hovering over the error symbol. point_string = model.get(iter_, self.CYCLE_COLUMN)[0] if point_string: return False if not self.warnings.get((host, owner, suite)): return False tooltip.set_markup( tooltip_prefix + '\n<b>New failures</b> (<i>last 5</i>) <i><span ' + 'foreground="#2222BB">click to dismiss</span></i>\n' + self.warnings[(host, owner, suite)]) return True else: # Hovering over a status indicator. info = re.findall(r'\D+\d+', model.get(iter_, self.STATUS_COLUMN)[0]) if cell_index > len(info): return False state = info[cell_index - 1].strip().split(' ')[0] point_string = model.get(iter_, self.CYCLE_COLUMN)[0] tooltip_text = tooltip_prefix if suite: tasks = self.updater.get_last_n_tasks( host, owner, suite, state, point_string) tooltip_text += ( '\n<b>Recent {state} tasks</b>\n{tasks}').format( state=state, tasks='\n'.join(tasks)) tooltip.set_markup(tooltip_text) return True # Set the tooltip to a generic status for this suite. tooltip.set_markup(tooltip_prefix) return True def _on_toggle_column_visible(self, menu_item): """Toggle column visibility callback.""" column_index = menu_item._connect_args column = self.treeview.get_columns()[column_index] is_visible = column.get_visible() column.set_visible(not is_visible) self.updater.update() return False def _set_cell_pixbuf_state(self, _, cell, model, iter_, index): """State info pixbuf.""" state_info = model.get_value(iter_, self.STATUS_COLUMN) if state_info is not None: is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) info = re.findall(r'\D+\d+', state_info) if index < len(info): state = info[index].strip().rsplit( " ", self.SUITE_COLUMN)[0].strip() icon = self.dots.get_icon(state, is_stopped=is_stopped) cell.set_property("visible", True) else: icon = None cell.set_property("visible", False) else: icon = None cell.set_property("visible", False) cell.set_property("pixbuf", icon) def _set_error_icon_state(self, _, cell, model, iter_): """Update the state of the warning icon.""" host, owner, suite, warnings, point_string = model.get( iter_, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN, self.WARNINGS_COLUMN, self.CYCLE_COLUMN) key = (host, owner, suite) if point_string: # Error icon only for first row. cell.set_property('pixbuf', self.warn_icon_blank) elif warnings: cell.set_property('pixbuf', self.warn_icon_colour) self.warning_icon_shown.append(key) self.warnings[key] = warnings else: cell.set_property('pixbuf', self.warn_icon_grey) self.warnings[key] = None if key not in self.warning_icon_shown: cell.set_property('pixbuf', self.warn_icon_blank) def _set_cell_text_group(self, _, cell, model, iter_): """Set cell text for "group" column.""" group = model.get_value(iter_, self.GROUP_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", group) def _set_cell_text_host(self, _, cell, model, iter_): """Set cell text for "host" column.""" host = model.get_value(iter_, self.HOST_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", host) def _set_cell_text_owner(self, _, cell, model, iter_): """Set cell text for "owner" column.""" value = model.get_value(iter_, self.OWNER_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", value) def _set_cell_text_name(self, _, cell, model, iter_): """Set cell text for (suite name) "name" column.""" name = model.get_value(iter_, self.SUITE_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", name) def _set_cell_text_title(self, _, cell, model, iter_): """Set cell text for "title" column.""" title = model.get_value(iter_, self.TITLE_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", title) def _set_cell_text_time(self, _, cell, model, iter_): """Set cell text for "update-time" column.""" suite_update_time = model.get_value(iter_, self.UPDATE_TIME_COLUMN) time_point = timepoint_from_epoch(suite_update_time) time_point.set_time_zone_to_local() current_point = timepoint_from_epoch(time()) if str(time_point).split("T")[0] == str(current_point).split("T")[0]: time_string = str(time_point).split("T")[1] else: time_string = str(time_point) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", time_string) def _set_cell_text_cycle(self, _, cell, model, iter_, active_cycle): """Set cell text for "cycle" column.""" cycle = model.get_value(iter_, active_cycle) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", cycle) def _set_theme(self, new_theme_name): """Set GUI theme.""" self.theme_name = new_theme_name self.theme = gcfg.get(['themes', self.theme_name]) self._set_dots() self.updater.update() self.update_theme_legend() @staticmethod def _set_tooltip(widget, text): """Set tooltip for a widget.""" tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(widget, text)
def _set_dots(self): self.dots = DotMaker(self.theme, size=self.dot_size)
def __init__(self, hosts=None, owner=None, poll_interval=None): gobject.threads_init() set_exception_hook_dialog("cylc gscan") setup_icons() if not hosts: hosts = GLOBAL_CFG.get(["suite host scanning", "hosts"]) self.hosts = hosts if owner is None: owner = USER self.owner = owner self.window = gtk.Window() self.window.set_title("cylc gscan") self.window.set_icon(get_icon()) self.vbox = gtk.VBox() self.vbox.show() self.theme_name = gcfg.get(["use theme"]) self.theme = gcfg.get(["themes", self.theme_name]) self.dots = DotMaker(self.theme) suite_treemodel = gtk.TreeStore(str, str, bool, str, int, str, str) self._prev_tooltip_location_id = None self.suite_treeview = gtk.TreeView(suite_treemodel) # Construct the host column. host_name_column = gtk.TreeViewColumn("Host") cell_text_host = gtk.CellRendererText() host_name_column.pack_start(cell_text_host, expand=False) host_name_column.set_cell_data_func(cell_text_host, self._set_cell_text_host) host_name_column.set_sort_column_id(0) host_name_column.set_visible("host" in gsfg.get(["columns"])) host_name_column.set_resizable(True) # Construct the suite name column. suite_name_column = gtk.TreeViewColumn("Suite") cell_text_name = gtk.CellRendererText() suite_name_column.pack_start(cell_text_name, expand=False) suite_name_column.set_cell_data_func(cell_text_name, self._set_cell_text_name) suite_name_column.set_sort_column_id(1) suite_name_column.set_visible("suite" in gsfg.get(["columns"])) suite_name_column.set_resizable(True) # Construct the suite title column. suite_title_column = gtk.TreeViewColumn("Title") cell_text_title = gtk.CellRendererText() suite_title_column.pack_start(cell_text_title, expand=False) suite_title_column.set_cell_data_func(cell_text_title, self._set_cell_text_title) suite_title_column.set_sort_column_id(3) suite_title_column.set_visible("title" in gsfg.get(["columns"])) suite_title_column.set_resizable(True) # Construct the update time column. time_column = gtk.TreeViewColumn("Updated") cell_text_time = gtk.CellRendererText() time_column.pack_start(cell_text_time, expand=False) time_column.set_cell_data_func(cell_text_time, self._set_cell_text_time) time_column.set_sort_column_id(4) time_column.set_visible("updated" in gsfg.get(["columns"])) time_column.set_resizable(True) self.suite_treeview.append_column(host_name_column) self.suite_treeview.append_column(suite_name_column) self.suite_treeview.append_column(suite_title_column) self.suite_treeview.append_column(time_column) # Construct the status column. status_column = gtk.TreeViewColumn("Status") status_column.set_sort_column_id(5) status_column.set_visible("status" in gsfg.get(["columns"])) status_column.set_resizable(True) status_column_info = 6 cycle_column_info = 5 cell_text_cycle = gtk.CellRendererText() status_column.pack_start(cell_text_cycle, expand=False) status_column.set_cell_data_func(cell_text_cycle, self._set_cell_text_cycle, cycle_column_info) self.suite_treeview.append_column(status_column) for i in range(len(TASK_STATUSES_ORDERED)): cell_pixbuf_state = gtk.CellRendererPixbuf() status_column.pack_start(cell_pixbuf_state, expand=False) status_column.set_cell_data_func(cell_pixbuf_state, self._set_cell_pixbuf_state, (status_column_info, i)) self.suite_treeview.show() if hasattr(self.suite_treeview, "set_has_tooltip"): self.suite_treeview.set_has_tooltip(True) try: self.suite_treeview.connect("query-tooltip", self._on_query_tooltip) except TypeError: # Lower PyGTK version. pass self.suite_treeview.connect("button-press-event", self._on_button_press_event) scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.add(self.suite_treeview) scrolled_window.show() self.vbox.pack_start(scrolled_window, expand=True, fill=True) self.updater = ScanAppUpdater( self.hosts, suite_treemodel, self.suite_treeview, owner=self.owner, poll_interval=poll_interval ) self.updater.start() self.window.add(self.vbox) self.window.connect("destroy", self._on_destroy_event) self.window.set_default_size(300, 150) self.suite_treeview.grab_focus() self.window.show()
class ScanPanelAppletUpdater(BaseScanTimeoutUpdater): """Update the scan panel applet - subclass of gscan equivalent.""" IDLE_STOPPED_TIME = 3600 # 1 hour. MAX_INDIVIDUAL_SUITES = 5 def __init__(self, hosts, dot_hbox, gcylc_image, is_compact, owner=None, poll_interval=None): self.quit = True self.dot_hbox = dot_hbox self.gcylc_image = gcylc_image self.is_compact = is_compact self._set_gcylc_image_tooltip() self.gcylc_image.set_sensitive(False) self.theme_name = gcfg.get(['use theme']) self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) self.hosts_suites_info = {} self.stopped_hosts_suites_info = {} self._set_exception_hook() super(ScanPanelAppletUpdater, self).__init__( hosts, owner=owner, poll_interval=poll_interval) def clear_stopped_suites(self): """Clear stopped suite information that may have built up.""" self.stopped_hosts_suites_info.clear() gobject.idle_add(self.update) def start(self): self.gcylc_image.set_sensitive(True) super(ScanPanelAppletUpdater, self).start() self._set_gcylc_image_tooltip() def stop(self): self.gcylc_image.set_sensitive(False) super(ScanPanelAppletUpdater, self).stop() self._set_gcylc_image_tooltip() def launch_context_menu(self, event, suite_host_tuples=None, extra_items=None): has_stopped_suites = bool(self.stopped_hosts_suites_info) if suite_host_tuples is None: suite_host_tuples = [] if extra_items is None: extra_items = [] gscan_item = gtk.ImageMenuItem("Launch cylc gscan") img = gtk.image_new_from_stock("gcylc", gtk.ICON_SIZE_MENU) gscan_item.set_image(img) gscan_item.show() gscan_item.connect("button-press-event", self._on_button_press_event_gscan) extra_items.append(gscan_item) menu = get_scan_menu(suite_host_tuples, self.theme_name, self._set_theme, has_stopped_suites, self.clear_stopped_suites, self.hosts, self.set_hosts, self.update_now, self.start, program_name="cylc gpanel", extra_items=extra_items, owner=self.owner, is_stopped=self.quit) menu.popup(None, None, None, event.button, event.time) return False def update(self): """Update the Applet.""" info = copy.deepcopy(self.hosts_suites_info) stop_info = copy.deepcopy(self.stopped_hosts_suites_info) suite_host_tuples = [] for host in self.hosts: suites = (info.get(host, {}).keys() + stop_info.get(host, {}).keys()) for suite in suites: if (suite, host) not in suite_host_tuples: suite_host_tuples.append((suite, host)) suite_host_tuples.sort() for child in self.dot_hbox.get_children(): self.dot_hbox.remove(child) number_mode = (not self.is_compact and len(suite_host_tuples) > self.MAX_INDIVIDUAL_SUITES) suite_statuses = {} compact_suite_statuses = [] for suite, host in suite_host_tuples: if suite in info.get(host, {}): suite_info = info[host][suite] is_stopped = False else: suite_info = stop_info[host][suite] is_stopped = True if "states" not in suite_info: continue status = extract_group_state(suite_info['states'].keys(), is_stopped=is_stopped) status_map = suite_info['states'] if number_mode: suite_statuses.setdefault(is_stopped, {}) suite_statuses[is_stopped].setdefault(status, []) suite_statuses[is_stopped][status].append( (suite, host, status_map.items())) elif self.is_compact: compact_suite_statuses.append((suite, host, status, status_map.items(), is_stopped)) else: self._add_image_box([(suite, host, status, status_map.items(), is_stopped)]) if number_mode: for is_stopped in sorted(suite_statuses.keys()): statuses = suite_statuses[is_stopped].items() # Sort by number of suites in this state. statuses.sort(lambda x, y: cmp(len(y[1]), len(x[1]))) for status, suite_host_states_tuples in statuses: label = gtk.Label(str(len(suite_host_states_tuples)) + ":") label.show() self.dot_hbox.pack_start(label, expand=False, fill=False) suite_info_tuples = [] for suite, host, task_states in suite_host_states_tuples: suite_info_tuples.append((suite, host, status, task_states, is_stopped)) self._add_image_box(suite_info_tuples) if self.is_compact: if not compact_suite_statuses: # No suites running or stopped. self.gcylc_image.show() return False self.gcylc_image.hide() self._add_image_box(compact_suite_statuses) return False def _add_image_box(self, suite_host_info_tuples): image_eb = gtk.EventBox() image_eb.show() is_all_stopped = False running_status_list = [] status_list = [] suite_host_tuples = [] for info_tuple in suite_host_info_tuples: suite, host, status, task_states, is_stopped = info_tuple suite_host_tuples.append((suite, host)) if not is_stopped: running_status_list.append(status) status_list.append(status) if running_status_list: status = extract_group_state(running_status_list, is_stopped=False) image = self.dots.get_image(status, is_stopped=False) else: status = extract_group_state(status_list, is_stopped=True) image = self.dots.get_image(status, is_stopped=True) image.show() image_eb.add(image) image_eb._connect_args = suite_host_tuples image_eb.connect("button-press-event", self._on_button_press_event) text_format = "%s - %s - %s" long_text_format = text_format + "\n %s\n" text = "" tip_vbox = gtk.VBox() # Only used in PyGTK 2.12+ tip_vbox.show() for info_tuple in suite_host_info_tuples: suite, host, status, state_counts, is_stopped = info_tuple state_counts.sort(lambda x, y: cmp(y[1], x[1])) tip_hbox = gtk.HBox() tip_hbox.show() state_info = [] for state_name, number in state_counts: state_info.append("%d %s" % (number, state_name)) image = self.dots.get_image(state_name, is_stopped=is_stopped) image.show() tip_hbox.pack_start(image, expand=False, fill=False) states_text = ", ".join(state_info) if status is None: suite_summary = "?" else: suite_summary = status if is_stopped: suite_summary = "stopped with " + suite_summary tip_label = gtk.Label(text_format % (suite, suite_summary, host)) tip_label.show() tip_hbox.pack_start(tip_label, expand=False, fill=False, padding=5) tip_vbox.pack_start(tip_hbox, expand=False, fill=False) text += long_text_format % ( suite, suite_summary, host, states_text) text = text.rstrip() if hasattr(gtk, "Tooltip"): image_eb.set_has_tooltip(True) image_eb.connect("query-tooltip", self._on_img_tooltip_query, tip_vbox) else: self._set_tooltip(image_eb, text) self.dot_hbox.pack_start(image_eb, expand=False, fill=False, padding=1) def _on_button_press_event(self, widget, event): if event.button == 1: self.launch_context_menu(event, suite_host_tuples=widget._connect_args) return False def _on_button_press_event_gscan(self, widget, event): launch_gscan(hosts=self.hosts, owner=self.owner) def _on_img_tooltip_query(self, widget, x, y, kbd, tooltip, tip_widget): tooltip.set_custom(tip_widget) return True def _set_exception_hook(self): # Handle an uncaught exception. old_hook = sys.excepthook sys.excepthook = (lambda *a: self._handle_exception(*a, old_hook=old_hook)) def _handle_exception(self, e_type, e_value, e_traceback, old_hook=None): self.gcylc_image.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_MENU) exc_lines = traceback.format_exception(e_type, e_value, e_traceback) exc_text = "".join(exc_lines) info = "cylc gpanel has a problem.\n\n%s" % exc_text self._set_tooltip(self.gcylc_image, info.rstrip()) if old_hook is not None: old_hook(e_type, e_value, e_traceback) def _set_gcylc_image_tooltip(self): if self.quit: self._set_tooltip(self.gcylc_image, "Cylc Applet - Off") else: self._set_tooltip(self.gcylc_image, "Cylc Applet - Active") def _set_theme(self, new_theme_name): self.theme_name = new_theme_name self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) def _set_tooltip(self, widget, text): tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(widget, text)
class ScanApp(object): """Summarize running suite statuses for a given set of hosts.""" WARNINGS_COLUMN = 10 STATUS_COLUMN = 9 CYCLE_COLUMN = 8 UPDATE_TIME_COLUMN = 7 TITLE_COLUMN = 6 STOPPED_COLUMN = 5 VERSION_COLUMN = 4 SUITE_COLUMN = 3 OWNER_COLUMN = 2 HOST_COLUMN = 1 GROUP_COLUMN = 0 ICON_SIZE = 17 def __init__(self, hosts=None, patterns_name=None, patterns_owner=None, comms_timeout=None, interval=None): gobject.threads_init() set_exception_hook_dialog("cylc gscan") setup_icons() self.window = gtk.Window() title = "cylc gscan" for opt, items in [("-n", patterns_name), ("-o", patterns_owner)]: if items: for pattern in items: if pattern is not None: title += " %s %s" % (opt, pattern) self.window.set_title(title) self.window.set_icon(get_icon()) self.vbox = gtk.VBox() self.vbox.show() self.warnings = {} self.theme_name = GcylcConfig.get_inst().get(['use theme']) self.theme = GcylcConfig.get_inst().get(['themes', self.theme_name]) suite_treemodel = gtk.TreeStore( str, # group str, # host str, # owner str, # suite str, # version bool, # is_stopped str, # title int, # update_time str, # states str, # states_text str) # warning_text self._prev_tooltip_location_id = None self.treeview = gtk.TreeView(suite_treemodel) # Visibility of columns gsfg = GScanConfig.get_inst() vis_cols = gsfg.get(["columns"]) hide_main_menu_bar = gsfg.get(["hide main menubar"]) # Doesn't make any sense without suite name column if gsfg.COL_SUITE not in vis_cols: vis_cols.append(gsfg.COL_SUITE.lower()) # Construct the group, host, owner, suite, title, update time column. for col_title, col_id, col_cell_text_setter in [ (gsfg.COL_GROUP, self.GROUP_COLUMN, self._set_cell_text_group), (gsfg.COL_HOST, self.HOST_COLUMN, self._set_cell_text_host), (gsfg.COL_OWNER, self.OWNER_COLUMN, self._set_cell_text_owner), (gsfg.COL_SUITE, self.SUITE_COLUMN, self._set_cell_text_name), (gsfg.COL_VERSION, self.VERSION_COLUMN, self._set_cell_text_version), (gsfg.COL_TITLE, self.TITLE_COLUMN, self._set_cell_text_title), (gsfg.COL_UPDATED, self.UPDATE_TIME_COLUMN, self._set_cell_text_time), ]: column = gtk.TreeViewColumn(col_title) cell_text = gtk.CellRendererText() column.pack_start(cell_text, expand=False) column.set_cell_data_func(cell_text, col_cell_text_setter) column.set_sort_column_id(col_id) column.set_visible(col_title.lower() in vis_cols) column.set_resizable(True) self.treeview.append_column(column) # Construct the status column. status_column = gtk.TreeViewColumn(gsfg.COL_STATUS) status_column.set_sort_column_id(self.STATUS_COLUMN) status_column.set_visible(gsfg.COL_STATUS.lower() in vis_cols) status_column.set_resizable(True) cell_text_cycle = gtk.CellRendererText() status_column.pack_start(cell_text_cycle, expand=False) status_column.set_cell_data_func(cell_text_cycle, self._set_cell_text_cycle, self.CYCLE_COLUMN) self.treeview.append_column(status_column) # Warning icon. warn_icon = gtk.CellRendererPixbuf() image = gtk.Image() pixbuf = image.render_icon(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_LARGE_TOOLBAR) self.warn_icon_colour = pixbuf.scale_simple( # colour warn icon pixbuf self.ICON_SIZE, self.ICON_SIZE, gtk.gdk.INTERP_HYPER) self.warn_icon_grey = pixbuf.scale_simple(self.ICON_SIZE, self.ICON_SIZE, gtk.gdk.INTERP_HYPER) self.warn_icon_colour.saturate_and_pixelate( self.warn_icon_grey, 0, False) # b&w warn icon pixbuf status_column.pack_start(warn_icon, expand=False) status_column.set_cell_data_func(warn_icon, self._set_error_icon_state) self.warn_icon_blank = gtk.gdk.Pixbuf( # Transparent pixbuff. gtk.gdk.COLORSPACE_RGB, True, 8, self.ICON_SIZE, self.ICON_SIZE).fill(0x00000000) # Task status icons. for i in range(len(TASK_STATUSES_ORDERED)): cell_pixbuf_state = gtk.CellRendererPixbuf() status_column.pack_start(cell_pixbuf_state, expand=False) status_column.set_cell_data_func(cell_pixbuf_state, self._set_cell_pixbuf_state, i) self.treeview.show() if hasattr(self.treeview, "set_has_tooltip"): self.treeview.set_has_tooltip(True) try: self.treeview.connect('query-tooltip', self._on_query_tooltip) except TypeError: # Lower PyGTK version. pass self.treeview.connect("button-press-event", self._on_button_press_event) cre_owner, cre_name = re_compile_filters(patterns_owner, patterns_name) self.updater = ScanAppUpdater(self.window, hosts, suite_treemodel, self.treeview, comms_timeout, interval, self.GROUP_COLUMN, cre_owner, cre_name) self.updater.start() self.dot_size = GcylcConfig.get_inst().get(['dot icon size']) self.dots = None self._set_dots() self.menu_bar = None self.create_menubar() accelgroup = gtk.AccelGroup() self.window.add_accel_group(accelgroup) key, modifier = gtk.accelerator_parse('<Alt>m') accelgroup.connect_group(key, modifier, gtk.ACCEL_VISIBLE, self._toggle_hide_menu_bar) self.tool_bar = None self.create_tool_bar() self.menu_hbox = gtk.HBox() self.menu_hbox.pack_start(self.menu_bar, expand=True, fill=True) self.menu_hbox.pack_start(self.tool_bar, expand=True, fill=True) self.menu_hbox.show_all() if hide_main_menu_bar: self.menu_hbox.hide_all() scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.add(self.treeview) scrolled_window.show() self.vbox.pack_start(self.menu_hbox, expand=False) self.vbox.pack_start(scrolled_window, expand=True, fill=True) self.window.add(self.vbox) self.window.connect("destroy", self._on_destroy_event) wsize = gsfg.get(['window size']) self.window.set_default_size(*wsize) self.treeview.grab_focus() self.window.show() self.theme_legend_window = None self.warning_icon_shown = [] def popup_theme_legend(self, widget=None): """Popup a theme legend window.""" if self.theme_legend_window is None: self.theme_legend_window = ThemeLegendWindow( self.window, self.theme) self.theme_legend_window.connect("destroy", self.destroy_theme_legend) else: self.theme_legend_window.present() def update_theme_legend(self): """Update the theme legend window, if it exists.""" if self.theme_legend_window is not None: self.theme_legend_window.update(self.theme) def destroy_theme_legend(self, widget): """Handle a destroy of the theme legend window.""" self.theme_legend_window = None def create_menubar(self): """Create the main menu.""" self.menu_bar = gtk.MenuBar() file_menu = gtk.Menu() file_menu_root = gtk.MenuItem('_File') file_menu_root.set_submenu(file_menu) exit_item = gtk.ImageMenuItem('E_xit') img = gtk.image_new_from_stock(gtk.STOCK_QUIT, gtk.ICON_SIZE_MENU) exit_item.set_image(img) exit_item.show() exit_item.connect("activate", self._on_destroy_event) file_menu.append(exit_item) view_menu = gtk.Menu() view_menu_root = gtk.MenuItem('_View') view_menu_root.set_submenu(view_menu) col_item = gtk.ImageMenuItem("_Columns...") img = gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_MENU) col_item.set_image(img) col_item.show() col_menu = gtk.Menu() for column_index, column in enumerate(self.treeview.get_columns()): name = column.get_title() is_visible = column.get_visible() column_item = gtk.CheckMenuItem(name.replace("_", "__")) column_item._connect_args = column_index column_item.set_active(is_visible) column_item.connect("toggled", self._on_toggle_column_visible) column_item.show() col_menu.append(column_item) col_item.set_submenu(col_menu) col_item.show_all() view_menu.append(col_item) view_menu.append(gtk.SeparatorMenuItem()) # Construct theme chooser items (same as cylc.gui.app_main). theme_item = gtk.ImageMenuItem('Theme...') img = gtk.image_new_from_stock(gtk.STOCK_SELECT_COLOR, gtk.ICON_SIZE_MENU) theme_item.set_image(img) thememenu = gtk.Menu() theme_item.set_submenu(thememenu) theme_item.show() theme_items = {} theme = "default" theme_items[theme] = gtk.RadioMenuItem(label=theme) thememenu.append(theme_items[theme]) theme_items[theme].theme_name = theme for theme in GcylcConfig.get_inst().get(['themes']): if theme == "default": continue theme_items[theme] = gtk.RadioMenuItem( group=theme_items['default'], label=theme) thememenu.append(theme_items[theme]) theme_items[theme].theme_name = theme # set_active then connect, to avoid causing an unnecessary toggle now. theme_items[self.theme_name].set_active(True) for theme in GcylcConfig.get_inst().get(['themes']): theme_items[theme].show() theme_items[theme].connect( 'toggled', lambda i: (i.get_active() and self._set_theme(i.theme_name))) view_menu.append(theme_item) theme_legend_item = gtk.ImageMenuItem("Show task state key") img = gtk.image_new_from_stock(gtk.STOCK_SELECT_COLOR, gtk.ICON_SIZE_MENU) theme_legend_item.set_image(img) theme_legend_item.show() theme_legend_item.connect("activate", self.popup_theme_legend) view_menu.append(theme_legend_item) view_menu.append(gtk.SeparatorMenuItem()) # Construct a configure scanned hosts item. hosts_item = gtk.ImageMenuItem("Configure Hosts") img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) hosts_item.set_image(img) hosts_item.show() hosts_item.connect( "activate", lambda w: launch_hosts_dialog(self.updater.hosts, self. updater.set_hosts)) view_menu.append(hosts_item) sep_item = gtk.SeparatorMenuItem() sep_item.show() help_menu = gtk.Menu() help_menu_root = gtk.MenuItem('_Help') help_menu_root.set_submenu(help_menu) self.menu_bar.append(file_menu_root) self.menu_bar.append(view_menu_root) self.menu_bar.append(help_menu_root) # Construct an about dialog item. info_item = gtk.ImageMenuItem("About") img = gtk.image_new_from_stock(gtk.STOCK_ABOUT, gtk.ICON_SIZE_MENU) info_item.set_image(img) info_item.show() info_item.connect( "activate", lambda w: launch_about_dialog("cylc gscan", self.updater.hosts)) help_menu.append(info_item) self.menu_bar.show_all() def _set_dots(self): self.dots = DotMaker(self.theme, size=self.dot_size) def create_tool_bar(self): """Create the tool bar for the GUI.""" self.tool_bar = gtk.Toolbar() update_now_button = gtk.ToolButton( icon_widget=gtk.image_new_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_SMALL_TOOLBAR)) update_now_button.set_label("Update Listing") tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(update_now_button, "Update Suite Listing Now") update_now_button.connect("clicked", self.updater.set_update_listing) clear_stopped_button = gtk.ToolButton( icon_widget=gtk.image_new_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_SMALL_TOOLBAR)) clear_stopped_button.set_label("Clear") tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(clear_stopped_button, "Clear stopped suites") clear_stopped_button.connect("clicked", self.updater.clear_stopped_suites) expand_button = gtk.ToolButton(icon_widget=gtk.image_new_from_stock( gtk.STOCK_ADD, gtk.ICON_SIZE_SMALL_TOOLBAR)) expand_button.set_label("Expand all") tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(expand_button, "Expand all rows") expand_button.connect("clicked", lambda e: self.treeview.expand_all()) collapse_button = gtk.ToolButton(icon_widget=gtk.image_new_from_stock( gtk.STOCK_REMOVE, gtk.ICON_SIZE_SMALL_TOOLBAR)) collapse_button.set_label("Collapse all") tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(collapse_button, "Collapse all rows") collapse_button.connect("clicked", lambda e: self.treeview.collapse_all()) self.tool_bar.insert(update_now_button, 0) self.tool_bar.insert(clear_stopped_button, 0) self.tool_bar.insert(collapse_button, 0) self.tool_bar.insert(expand_button, 0) separator = gtk.SeparatorToolItem() separator.set_expand(True) self.tool_bar.insert(separator, 0) self.tool_bar.show_all() def _on_button_press_event(self, treeview, event): """Tree view button press callback.""" x = int(event.x) y = int(event.y) pth = treeview.get_path_at_pos(x, y) treemodel = treeview.get_model() # Dismiss warnings by clicking on a warning icon. if event.button == 1: if not pth: return False path, column, cell_x = pth[:3] if column.get_title() == GScanConfig.get_inst().COL_STATUS: dot_offset, dot_width = tuple( column.cell_get_position(column.get_cell_renderers()[1])) if not dot_width: return False try: cell_index = (cell_x - dot_offset) // dot_width except ZeroDivisionError: return False if cell_index == 0: iter_ = treemodel.get_iter(path) host, owner, suite = treemodel.get(iter_, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) self.updater.clear_warnings(host, owner, suite) treemodel.set(iter_, self.WARNINGS_COLUMN, '') return True # Display menu on right click. if event.type != gtk.gdk._2BUTTON_PRESS and event.button != 3: return False suite_keys = [] if pth is not None: # Add a gcylc launcher item. path = pth[0] iter_ = treemodel.get_iter(path) host, owner, suite = treemodel.get(iter_, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) if suite is not None: suite_keys.append((host, owner, suite)) elif suite is None: # On an expanded cycle point row, so get from parent. try: host, owner, suite = treemodel.get( treemodel.iter_parent(iter_), self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) suite_keys.append((host, owner, suite)) except TypeError: # Now iterate over the children instead. # We need to iterate over the children as there can be more # than one suite in a group of suites. # Get a TreeIter pointing to the first child of parent iter suite_iter = treemodel.iter_children(iter_) # Iterate over the children until you get to end while suite_iter is not None: host, owner, suite = treemodel.get( suite_iter, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) suite_keys.append((host, owner, suite)) # Advance to the next pointer in the treemodel suite_iter = treemodel.iter_next(suite_iter) if event.type == gtk.gdk._2BUTTON_PRESS: if suite_keys: launch_gcylc(suite_keys[0]) return False menu = get_scan_menu(suite_keys, self._toggle_hide_menu_bar) menu.popup(None, None, None, event.button, event.time) return False def _on_destroy_event(self, _): """Callback on destroy of main window.""" try: self.updater.quit = True gtk.main_quit() except RuntimeError: pass return False def _toggle_hide_menu_bar(self, *_): if self.menu_hbox.get_property("visible"): self.menu_hbox.hide_all() else: self.menu_hbox.show_all() def _on_query_tooltip(self, _, x, y, kbd_ctx, tooltip): """Handle a tooltip creation request.""" tip_context = self.treeview.get_tooltip_context(x, y, kbd_ctx) if tip_context is None: self._prev_tooltip_location_id = None return False x, y = self.treeview.convert_widget_to_bin_window_coords(x, y) path, column, cell_x = (self.treeview.get_path_at_pos(x, y))[:3] model = self.treeview.get_model() iter_ = model.get_iter(path) parent_iter = model.iter_parent(iter_) if parent_iter is None or parent_iter and model.iter_has_child(iter_): host, owner, suite = model.get(iter_, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) child_row_number = None else: host, owner, suite = model.get(parent_iter, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN) child_row_number = path[-1] suite_update_time = model.get_value(iter_, self.UPDATE_TIME_COLUMN) location_id = (host, owner, suite, suite_update_time, column.get_title(), child_row_number) if location_id != self._prev_tooltip_location_id: self._prev_tooltip_location_id = location_id tooltip.set_text(None) return False gsfg = GScanConfig.get_inst() if column.get_title() in [ gsfg.COL_HOST, gsfg.COL_OWNER, gsfg.COL_SUITE ]: tooltip.set_text("%s - %s:%s" % (suite, owner, host)) return True if column.get_title() == gsfg.COL_UPDATED: suite_update_point = timepoint_from_epoch(suite_update_time) if (self.updater.prev_norm_update is not None and suite_update_time != int(self.updater.prev_norm_update)): text = "Last changed at %s\n" % suite_update_point text += "Last scanned at %s" % timepoint_from_epoch( int(self.updater.prev_norm_update)) else: # An older suite (or before any updates are made?) text = "Last scanned at %s" % suite_update_point tooltip.set_text(text) return True if column.get_title() != gsfg.COL_STATUS: tooltip.set_text(None) return False # Generate text for the number of tasks in each state state_texts = [] state_text = model.get_value(iter_, self.STATUS_COLUMN) if state_text is None: tooltip.set_text(None) return False info = re.findall(r'\D+\d+', state_text) for status_number in info: status, number = status_number.rsplit(" ", 1) state_texts.append(number + " " + status.strip()) tooltip_prefix = ("<span foreground=\"#777777\">Tasks: " + ", ".join(state_texts) + "</span>") # If hovering over a status indicator set tooltip to show most recent # tasks. dot_offset, dot_width = tuple( column.cell_get_position(column.get_cell_renderers()[2])) try: cell_index = ((cell_x - dot_offset) // dot_width) + 1 except ZeroDivisionError: return False if cell_index >= 0: # NOTE: TreeViewColumn.get_cell_renderers() does not always return # cell renderers for the correct row. if cell_index == 0: # Hovering over the error symbol. point_string = model.get(iter_, self.CYCLE_COLUMN)[0] if point_string: return False if not self.warnings.get((host, owner, suite)): return False tooltip.set_markup( tooltip_prefix + '\n<b>New failures</b> (<i>last 5</i>) <i><span ' + 'foreground="#2222BB">click to dismiss</span></i>\n' + self.warnings[(host, owner, suite)]) return True else: # Hovering over a status indicator. info = re.findall(r'\D+\d+', model.get(iter_, self.STATUS_COLUMN)[0]) if cell_index > len(info): return False state = info[cell_index - 1].strip().split(' ')[0] point_string = model.get(iter_, self.CYCLE_COLUMN)[0] tooltip_text = tooltip_prefix if suite: tasks = self.updater.get_last_n_tasks( host, owner, suite, state, point_string) tooltip_text += ( '\n<b>Recent {state} tasks</b>\n{tasks}').format( state=state, tasks='\n'.join(tasks)) tooltip.set_markup(tooltip_text) return True # Set the tooltip to a generic status for this suite. tooltip.set_markup(tooltip_prefix) return True def _on_toggle_column_visible(self, menu_item): """Toggle column visibility callback.""" column_index = menu_item._connect_args column = self.treeview.get_columns()[column_index] is_visible = column.get_visible() column.set_visible(not is_visible) self.updater.update() return False def _set_cell_pixbuf_state(self, _, cell, model, iter_, index): """State info pixbuf.""" state_info = model.get_value(iter_, self.STATUS_COLUMN) if state_info is not None: is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) info = re.findall(r'\D+\d+', state_info) if index < len(info): state = info[index].strip().rsplit( " ", self.SUITE_COLUMN)[0].strip() icon = self.dots.get_icon(state, is_stopped=is_stopped) cell.set_property("visible", True) else: icon = None cell.set_property("visible", False) else: icon = None cell.set_property("visible", False) cell.set_property("pixbuf", icon) def _set_error_icon_state(self, _, cell, model, iter_): """Update the state of the warning icon.""" host, owner, suite, warnings, point_string = model.get( iter_, self.HOST_COLUMN, self.OWNER_COLUMN, self.SUITE_COLUMN, self.WARNINGS_COLUMN, self.CYCLE_COLUMN) key = (host, owner, suite) if point_string: # Error icon only for first row. cell.set_property('pixbuf', self.warn_icon_blank) elif warnings: cell.set_property('pixbuf', self.warn_icon_colour) self.warning_icon_shown.append(key) self.warnings[key] = warnings else: cell.set_property('pixbuf', self.warn_icon_grey) self.warnings[key] = None if key not in self.warning_icon_shown: cell.set_property('pixbuf', self.warn_icon_blank) def _set_cell_text_group(self, _, cell, model, iter_): """Set cell text for "group" column.""" group = model.get_value(iter_, self.GROUP_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", group) def _set_cell_text_host(self, _, cell, model, iter_): """Set cell text for "host" column.""" host = model.get_value(iter_, self.HOST_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", host) def _set_cell_text_owner(self, _, cell, model, iter_): """Set cell text for "owner" column.""" value = model.get_value(iter_, self.OWNER_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", value) def _set_cell_text_name(self, _, cell, model, iter_): """Set cell text for (suite name) "name" column.""" name = model.get_value(iter_, self.SUITE_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", name) def _set_cell_text_version(self, _, cell, model, iter_): """Set cell text for (suite version) "version" column.""" value = model.get_value(iter_, self.VERSION_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", value) def _set_cell_text_title(self, _, cell, model, iter_): """Set cell text for "title" column.""" title = model.get_value(iter_, self.TITLE_COLUMN) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", title) def _set_cell_text_time(self, _, cell, model, iter_): """Set cell text for "update-time" column.""" suite_update_time = model.get_value(iter_, self.UPDATE_TIME_COLUMN) time_point = timepoint_from_epoch(suite_update_time) time_point.set_time_zone_to_local() current_point = timepoint_from_epoch(time()) if str(time_point).split("T")[0] == str(current_point).split("T")[0]: time_string = str(time_point).split("T")[1] else: time_string = str(time_point) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", time_string) def _set_cell_text_cycle(self, _, cell, model, iter_, active_cycle): """Set cell text for "cycle" column.""" cycle = model.get_value(iter_, active_cycle) is_stopped = model.get_value(iter_, self.STOPPED_COLUMN) cell.set_property("sensitive", not is_stopped) cell.set_property("text", cycle) def _set_theme(self, new_theme_name): """Set GUI theme.""" self.theme_name = new_theme_name self.theme = GcylcConfig.get_inst().get(['themes', self.theme_name]) self._set_dots() self.updater.update() self.update_theme_legend() @staticmethod def _set_tooltip(widget, text): """Set tooltip for a widget.""" tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(widget, text)
def _set_theme(self, new_theme_name): self.theme_name = new_theme_name self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme)
class ScanPanelAppletUpdater(object): """Update the scan panel applet - subclass of gscan equivalent.""" IDLE_STOPPED_TIME = 3600 # 1 hour. MAX_INDIVIDUAL_SUITES = 5 def __init__(self, dot_hbox, gcylc_image, is_compact): self.hosts = [] self.dot_hbox = dot_hbox self.gcylc_image = gcylc_image self.is_compact = is_compact self.prev_full_update = None self.prev_norm_update = None self.quit = True self._set_gcylc_image_tooltip() self.gcylc_image.set_sensitive(False) gsfg = GScanConfig.get_inst() self.interval_full = gsfg.get(['suite listing update interval']) self.interval_part = gsfg.get(['suite status update interval']) gcfg = GcylcConfig.get_inst() self.theme_name = gcfg.get(['use theme']) self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) self.suite_info_map = {} self._set_exception_hook() self.owner_pattern = None def clear_stopped_suites(self): """Clear stopped suite information that may have built up.""" for key, result in self.suite_info_map.copy().items(): if KEY_PORT not in result: del self.suite_info_map[key] gobject.idle_add(self.update) def has_stopped_suites(self): """Return True if we have any stopped suite information.""" for result in self.suite_info_map.copy().values(): if KEY_PORT not in result: return True return False def run(self): """Extract running suite information at particular intervals.""" if self.quit: return False now = time() if (self.prev_norm_update is not None and self.IDLE_STOPPED_TIME is not None and now > self.prev_norm_update + self.IDLE_STOPPED_TIME): self.stop() return True full_mode = (self.prev_full_update is None or now >= self.prev_full_update + self.interval_full) if (full_mode or self.prev_norm_update is None or now >= self.prev_norm_update + self.interval_part): # Get new information. self.suite_info_map = update_suites_info(self, full_mode=True) self.prev_norm_update = time() if full_mode: self.prev_full_update = self.prev_norm_update gobject.idle_add(self.update) return True def set_hosts(self, new_hosts): del self.hosts[:] self.hosts.extend(new_hosts) self.update_now() def start(self): self.gcylc_image.set_sensitive(True) self.quit = False self.prev_full_update = None self.prev_norm_update = None gobject.timeout_add(1000, self.run) self._set_gcylc_image_tooltip() def stop(self): self.gcylc_image.set_sensitive(False) self.quit = True self._set_gcylc_image_tooltip() def launch_context_menu(self, event, suite_keys=None, extra_items=None): if suite_keys is None: suite_keys = [] if extra_items is None: extra_items = [] gscan_item = gtk.ImageMenuItem("Launch cylc gscan") img = gtk.image_new_from_stock("gcylc", gtk.ICON_SIZE_MENU) gscan_item.set_image(img) gscan_item.show() gscan_item.connect("button-press-event", self._on_button_press_event_gscan) extra_items.append(gscan_item) menu = get_gpanel_scan_menu(suite_keys, self.theme_name, self._set_theme, self.has_stopped_suites(), self.clear_stopped_suites, self.hosts, self.set_hosts, self.update_now, self.start, program_name="cylc gpanel", extra_items=extra_items, is_stopped=self.quit) menu.popup(None, None, None, event.button, event.time) return False def update(self): """Update the Applet.""" for child in self.dot_hbox.get_children(): self.dot_hbox.remove(child) number_mode = (not self.is_compact and len(self.suite_info_map) > self.MAX_INDIVIDUAL_SUITES) suite_statuses = {} compact_suite_statuses = [] for key, suite_info in sorted(self.suite_info_map.items(), key=lambda details: details[0][2]): if KEY_STATES not in suite_info: continue host, _, suite = key is_stopped = KEY_PORT not in suite_info status = extract_group_state(suite_info[KEY_STATES][0].keys(), is_stopped=is_stopped) status_map = suite_info[KEY_STATES][0] if number_mode: suite_statuses.setdefault(is_stopped, {}) suite_statuses[is_stopped].setdefault(status, []) suite_statuses[is_stopped][status].append( (suite, host, status_map.items())) elif self.is_compact: compact_suite_statuses.append( (suite, host, status, status_map.items(), is_stopped)) else: self._add_image_box([(suite, host, status, status_map.items(), is_stopped)]) if number_mode: for is_stopped, status_map in sorted(suite_statuses.items()): # Sort by number of suites in this state. statuses = status_map.items() statuses.sort(lambda x, y: cmp(len(y[1]), len(x[1]))) for status, suite_host_states_tuples in statuses: label = gtk.Label(str(len(suite_host_states_tuples)) + ":") label.show() self.dot_hbox.pack_start(label, expand=False, fill=False) suite_info_tuples = [] for suite, host, task_states in suite_host_states_tuples: suite_info_tuples.append( (suite, host, status, task_states, is_stopped)) self._add_image_box(suite_info_tuples) if self.is_compact: if not compact_suite_statuses: # No suites running or stopped. self.gcylc_image.show() return False self.gcylc_image.hide() self._add_image_box(compact_suite_statuses) return False def update_now(self): """Force an update as soon as possible.""" self.prev_full_update = None self.prev_norm_update = None def _add_image_box(self, suite_host_info_tuples): image_eb = gtk.EventBox() image_eb.show() running_status_list = [] status_list = [] suite_keys = [] for info_tuple in suite_host_info_tuples: suite, host, status, _, is_stopped = info_tuple suite_keys.append((host, get_user(), suite)) if not is_stopped: running_status_list.append(status) status_list.append(status) if running_status_list: status = extract_group_state(running_status_list, is_stopped=False) image = self.dots.get_image(status, is_stopped=False) else: status = extract_group_state(status_list, is_stopped=True) image = self.dots.get_image(status, is_stopped=True) image.show() image_eb.add(image) image_eb._connect_args = suite_keys image_eb.connect("button-press-event", self._on_button_press_event) text_format = "%s - %s - %s" long_text_format = text_format + "\n %s\n" text = "" tip_vbox = gtk.VBox() # Only used in PyGTK 2.12+ tip_vbox.show() for info_tuple in suite_host_info_tuples: suite, host, status, state_counts, is_stopped = info_tuple state_counts.sort(lambda x, y: cmp(y[1], x[1])) tip_hbox = gtk.HBox() tip_hbox.show() state_info = [] for state_name, number in state_counts: state_info.append("%d %s" % (number, state_name)) image = self.dots.get_image(state_name, is_stopped=is_stopped) image.show() tip_hbox.pack_start(image, expand=False, fill=False) states_text = ", ".join(state_info) if status is None: suite_summary = "?" else: suite_summary = status if is_stopped: suite_summary = "stopped with " + suite_summary tip_label = gtk.Label(text_format % (suite, suite_summary, host)) tip_label.show() tip_hbox.pack_start(tip_label, expand=False, fill=False, padding=5) tip_vbox.pack_start(tip_hbox, expand=False, fill=False) text += long_text_format % (suite, suite_summary, host, states_text) text = text.rstrip() if hasattr(gtk, "Tooltip"): image_eb.set_has_tooltip(True) image_eb.connect("query-tooltip", self._on_img_tooltip_query, tip_vbox) else: self._set_tooltip(image_eb, text) self.dot_hbox.pack_start(image_eb, expand=False, fill=False, padding=1) def launch_gscan(self): """Launch gscan.""" if cylc.flags.debug: stdout = sys.stdout stderr = sys.stderr command = ["cylc", "gscan", "--debug"] else: stdout = open(os.devnull, "w") stderr = STDOUT command = ["cylc", "gscan"] if self.hosts: command += self.hosts Popen(command, stdin=open(os.devnull), stdout=stdout, stderr=stderr) def _on_button_press_event(self, widget, event): if event.button == 1: self.launch_context_menu(event, suite_keys=widget._connect_args) return False def _on_button_press_event_gscan(self, widget, event): self.launch_gscan() @staticmethod def _on_img_tooltip_query(widget, x, y, kbd, tooltip, tip_widget): tooltip.set_custom(tip_widget) return True def _set_exception_hook(self): """Handle an uncaught exception.""" sys.excepthook = lambda e_type, e_value, e_traceback: ( self._handle_exception(e_type, e_value, e_traceback, sys.excepthook )) def _handle_exception(self, e_type, e_value, e_traceback, old_hook): self.gcylc_image.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_MENU) exc_lines = traceback.format_exception(e_type, e_value, e_traceback) exc_text = "".join(exc_lines) info = "cylc gpanel has a problem.\n\n%s" % exc_text self._set_tooltip(self.gcylc_image, info.rstrip()) if old_hook is not None: old_hook(e_type, e_value, e_traceback) def _set_gcylc_image_tooltip(self): if self.quit: self._set_tooltip(self.gcylc_image, "Cylc Applet - Off") else: self._set_tooltip(self.gcylc_image, "Cylc Applet - Active") def _set_theme(self, new_theme_name): self.theme_name = new_theme_name self.theme = GcylcConfig.get_inst().get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) def _set_tooltip(self, widget, text): tooltip = gtk.Tooltips() tooltip.enable() tooltip.set_tip(widget, text)
def __init__( self, hosts=None, patterns_name=None, patterns_owner=None, comms_timeout=None, poll_interval=None): gobject.threads_init() set_exception_hook_dialog("cylc gscan") setup_icons() if not hosts: hosts = GLOBAL_CFG.get(["suite host scanning", "hosts"]) self.hosts = hosts self.window = gtk.Window() title = "cylc gscan" for opt, items, skip in [ ("-n", patterns_name, None), ("-o", patterns_owner, USER)]: if items: for pattern in items: if pattern != skip: title += " %s %s" % (opt, pattern) self.window.set_title(title) self.window.set_icon(get_icon()) self.vbox = gtk.VBox() self.vbox.show() self.warnings = {} self.theme_name = gcfg.get(['use theme']) self.theme = gcfg.get(['themes', self.theme_name]) self.dots = DotMaker(self.theme) suite_treemodel = gtk.TreeStore( str, # group str, # host str, # owner str, # suite bool, # is_stopped str, # title int, # update_time str, # states str, # states_text str) # warning_text self._prev_tooltip_location_id = None self.suite_treeview = gtk.TreeView(suite_treemodel) # Visibility of columns vis_cols = gsfg.get(["columns"]) # Doesn't make any sense without suite name column if gsfg.COL_SUITE not in vis_cols: vis_cols.append(gsfg.COL_SUITE.lower()) # In multiple host environment, add host column by default if hosts: vis_cols.append(gsfg.COL_HOST.lower()) # In multiple owner environment, add owner column by default if patterns_owner != [USER]: vis_cols.append(gsfg.COL_OWNER.lower()) # Construct the group, host, owner, suite, title, update time column. for col_title, col_id, col_cell_text_setter in [ (gsfg.COL_GROUP, self.GROUP_COLUMN, self._set_cell_text_group), (gsfg.COL_HOST, self.HOST_COLUMN, self._set_cell_text_host), (gsfg.COL_OWNER, self.OWNER_COLUMN, self._set_cell_text_owner), (gsfg.COL_SUITE, self.SUITE_COLUMN, self._set_cell_text_name), (gsfg.COL_TITLE, self.TITLE_COLUMN, self._set_cell_text_title), (gsfg.COL_UPDATED, self.UPDATE_TIME_COLUMN, self._set_cell_text_time), ]: column = gtk.TreeViewColumn(col_title) cell_text = gtk.CellRendererText() column.pack_start(cell_text, expand=False) column.set_cell_data_func(cell_text, col_cell_text_setter) column.set_sort_column_id(col_id) column.set_visible(col_title.lower() in vis_cols) column.set_resizable(True) self.suite_treeview.append_column(column) # Construct the status column. status_column = gtk.TreeViewColumn(gsfg.COL_STATUS) status_column.set_sort_column_id(self.STATUS_COLUMN) status_column.set_visible(gsfg.COL_STATUS.lower() in vis_cols) status_column.set_resizable(True) cell_text_cycle = gtk.CellRendererText() status_column.pack_start(cell_text_cycle, expand=False) status_column.set_cell_data_func( cell_text_cycle, self._set_cell_text_cycle, self.CYCLE_COLUMN) self.suite_treeview.append_column(status_column) # Warning icon. warn_icon = gtk.CellRendererPixbuf() image = gtk.Image() pixbuf = image.render_icon( gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_LARGE_TOOLBAR) self.warn_icon_colour = pixbuf.scale_simple( # colour warn icon pixbuf self.ICON_SIZE, self.ICON_SIZE, gtk.gdk.INTERP_HYPER) self.warn_icon_grey = pixbuf.scale_simple( self.ICON_SIZE, self.ICON_SIZE, gtk.gdk.INTERP_HYPER) self.warn_icon_colour.saturate_and_pixelate( self.warn_icon_grey, 0, False) # b&w warn icon pixbuf status_column.pack_start(warn_icon, expand=False) status_column.set_cell_data_func(warn_icon, self._set_error_icon_state) self.warn_icon_blank = gtk.gdk.Pixbuf( # Transparent pixbuff. gtk.gdk.COLORSPACE_RGB, True, 8, self.ICON_SIZE, self.ICON_SIZE ).fill(0x00000000) # Task status icons. for i in range(len(TASK_STATUSES_ORDERED)): cell_pixbuf_state = gtk.CellRendererPixbuf() status_column.pack_start(cell_pixbuf_state, expand=False) status_column.set_cell_data_func( cell_pixbuf_state, self._set_cell_pixbuf_state, i) self.suite_treeview.show() if hasattr(self.suite_treeview, "set_has_tooltip"): self.suite_treeview.set_has_tooltip(True) try: self.suite_treeview.connect('query-tooltip', self._on_query_tooltip) except TypeError: # Lower PyGTK version. pass self.suite_treeview.connect("button-press-event", self._on_button_press_event) scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.add(self.suite_treeview) scrolled_window.show() self.vbox.pack_start(scrolled_window, expand=True, fill=True) patterns = {"name": None, "owner": None} for label, items in [ ("owner", patterns_owner), ("name", patterns_name)]: if items: patterns[label] = r"\A(?:" + r")|(?:".join(items) + r")\Z" try: patterns[label] = re.compile(patterns[label]) except re.error: raise ValueError("Invalid %s pattern: %s" % (label, items)) self.updater = ScanAppUpdater( self.window, self.hosts, suite_treemodel, self.suite_treeview, comms_timeout=comms_timeout, poll_interval=poll_interval, group_column_id=self.GROUP_COLUMN, name_pattern=patterns["name"], owner_pattern=patterns["owner"]) self.updater.start() self.window.add(self.vbox) self.window.connect("destroy", self._on_destroy_event) self.window.set_default_size(300, 150) self.suite_treeview.grab_focus() self.window.show() self.warning_icon_shown = []