Example #1
0
    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()
Example #2
0
    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()
Example #3
0
 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)
Example #4
0
    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()
Example #5
0
    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()
Example #6
0
 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
Example #7
0
 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)
Example #8
0
 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
Example #9
0
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)
Example #10
0
File: gscan.py Project: kaday/cylc
    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()
Example #11
0
File: gscan.py Project: kaday/cylc
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)
Example #12
0
    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 = []
Example #13
0
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)
Example #14
0
 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)
Example #15
0
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)
Example #16
0
File: legend.py Project: kaday/cylc
 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])
Example #17
0
 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])
Example #18
0
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)
Example #19
0
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)
Example #20
0
 def _set_dots(self):
     self.dots = DotMaker(self.theme, size=self.dot_size)
Example #21
0
 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])
Example #22
0
    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()
Example #23
0
 def _set_dots(self):
     self.dots = DotMaker(self.theme, size=self.dot_size)
Example #24
0
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)
Example #25
0
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)
Example #26
0
 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)
Example #27
0
 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])
Example #28
0
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)
Example #29
0
    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 = []