KGRKJGETMRETU895U-589TY5MIGM5JGB5SDFESFREWTGR54TY
Server : Apache/2.2.17 (Unix) mod_ssl/2.2.17 OpenSSL/0.9.8e-fips-rhel5 DAV/2 PHP/5.2.17
System : Linux localhost 2.6.18-419.el5 #1 SMP Fri Feb 24 22:47:42 UTC 2017 x86_64
User : nobody ( 99)
PHP Version : 5.2.17
Disable Function : NONE
Directory :  /proc/21573/root/usr/lib/python2.4/site-packages/setroubleshoot/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : //proc/21573/root/usr/lib/python2.4/site-packages/setroubleshoot/browser.py
# Authors: John Dennis <jdennis@redhat.com>
#
# Copyright (C) 2006,2007,2008 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

__all__ = ['BrowserApplet',
           ]

# This import must come before importing gtk to silence warnings
from setroubleshoot.gui_utils import *

import os
import sys
import pygtk
pygtk.require("2.0")
import gtk
import pango
import re
import time
import traceback
from types import *

import gobject 
import gnome.ui    
import gtkhtml2

from setroubleshoot.log import *
from setroubleshoot.analyze import *
from setroubleshoot.config import get_config
from setroubleshoot.email_dialog import *
from setroubleshoot.errcode import *
from setroubleshoot.signature import *
from setroubleshoot.util import *
from setroubleshoot.html_util import *
from setroubleshoot.rpc import *
from setroubleshoot.rpc_interfaces import *

#------------------------------------------------------------------------------

NUM_COLUMNS = 7
(PYOBJECT_COLUMN, FILTER_TYPE_COLUMN, DATE_COLUMN, HOST_COLUMN, COUNT_COLUMN, CATEGORY_COLUMN,
 SUMMARY_COLUMN) = range(NUM_COLUMNS)

tv_column_info = {
    FILTER_TYPE_COLUMN: {'name' : 'Filter',   'title' : _('Quiet'),},
    DATE_COLUMN:        {'name' : 'Date',     'title' : _('Date'),},
    HOST_COLUMN:        {'name' : 'Host',     'title' : _('Host'),},
    COUNT_COLUMN:       {'name' : 'Count',    'title' : _('Count'),},
    CATEGORY_COLUMN:    {'name' : 'Category', 'title' : _('Category'),},
    SUMMARY_COLUMN:     {'name' : 'Summary',  'title' : _('Summary'),},
}

#------------------------------------------------------------------------------

def get_siginfo_from_model_path(model, path):
    if type(path) is StringType:
        iter = model.get_iter_from_string(path)
    else:
        iter = model.get_iter(path)
    siginfo = model.get_value(iter, PYOBJECT_COLUMN)
    return siginfo

def get_model_path_from_local_id(model, local_id):
    iter = model.get_iter_first()
    while iter:
        row_siginfo = model.get_value(iter, PYOBJECT_COLUMN)
        if row_siginfo.local_id == local_id:
            return model.get_path(iter)
        iter = model.iter_next(iter)
    return None

def get_local_id_from_model_path(model, path):
    iter = model.get_iter(path)
    if iter is None:
        return None
    siginfo = model.get_value(iter, PYOBJECT_COLUMN)
    return siginfo.local_id
    

def validate_siginfo(siginfo):
    timestamp = None

    if siginfo.first_seen_date is None:
        if timestamp is None:
            timestamp = TimeStamp()
        siginfo.first_seen_date = timestamp
    if siginfo.last_seen_date is None:
        if timestamp is None:
            timestamp = TimeStamp()
        siginfo.last_seen_date = timestamp

def convert_paths_to_local_ids(model, paths):
    local_ids = []
    if model is None:
        return local_ids

    for path in paths:
        local_id = get_local_id_from_model_path(model, path)
        if local_id is None:
            log_gui_data.error("unable to lookup local_id of row %s in model", path)
            continue
        local_ids.append(local_id)
    return local_ids

def convert_local_ids_to_paths(model, local_ids):
    paths = []
    if model is None:
        return paths
    for local_id in local_ids:
        path = get_model_path_from_local_id(model, local_id)
        if path is None:
            log_gui_data.error("unable to lookup path for local_id %s in model", local_id)
            continue
        paths.append(path)
    return paths

def dump_tv_columns(treeview):
    column_infos = []
    i = 0
    for tv_column in treeview.get_columns():
        title = tv_column.get_title()
        sort_column_id = tv_column.get_sort_column_id()
        sort_order = tv_column.get_sort_order()
        sort_indicator = tv_column.get_sort_indicator()
        
        order = {None:'None', gtk.SORT_DESCENDING:'DESCENDING', gtk.SORT_ASCENDING:'ASCENDING'}[sort_order]

        column_info = "%d:%s, order=%s indicator=%s" % (i, title, order, sort_indicator)
        column_infos.append(column_info)
        i += 1

    print '\n'.join(column_infos)
    print
    return True
    
def get_iconset_from_name(icon_name):
    icon_source = gtk.IconSource()
    icon_source.set_icon_name(icon_name)
    iconset = gtk.IconSet()
    iconset.add_source(icon_source)
    return iconset

def load_stock_icons(icon_name_list):
    factory = gtk.IconFactory()
    for icon_name in icon_name_list:
        iconset = get_iconset_from_name(icon_name)
        factory.add(icon_name, iconset)
        factory.add_default()

#------------------------------------------------------------------------------

class AlertListView(gobject.GObject):
    __gsignals__ = {
        'row-changed':
        (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)),
        'load-data':
        (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, gobject.TYPE_STRING, gobject.TYPE_INT, gobject.TYPE_STRING)),
        'properties-changed':
        (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)),
        'async-error': # callback(method, errno, strerror)
        (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_INT, gobject.TYPE_STRING)),
        }

    column_types = (gobject.TYPE_PYOBJECT,# siginfo
                    gobject.TYPE_BOOLEAN, # filter
                    gobject.TYPE_PYOBJECT,# date
                    gobject.TYPE_STRING,  # host
                    gobject.TYPE_INT,     # count
                    gobject.TYPE_STRING,  # category
                    gobject.TYPE_STRING)  # summary

    def __init__(self, username, browser):
        gobject.GObject.__init__(self)
        self.username = username
        self.browser = browser

        self.scrolled_window = gtk.ScrolledWindow()
        self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.view = gtk.TreeView()
        self.scrolled_window.add(self.view)
        
        self.tv_columns = [None] * NUM_COLUMNS

        self.selection = None

        self.sort_column_id = DATE_COLUMN
        self.sort_order = gtk.SORT_DESCENDING
        self.hide_deleted = False
        self.hide_quiet = False
        self.last_selected_row = 0

        self.base_model = None
        self.filter_model = None
        self.sort_model = None
        self.view_model = None

        self.row_inserted_signal             = SignalConnection('row-inserted')
        self.row_deleted_signal              = SignalConnection('row-deleted')
        self.row_changed_signal              = SignalConnection('row-changed')

        self.properties_changed_signal       = SignalConnection('properties-changed')
        self.load_data_signal                = SignalConnection('load-data')
        self.async_error_signal              = SignalConnection('async-error')

        self.button_press_event              = SignalConnection('button_press_event')
        self.button_press_event.connect(self.view, self.browser.on_button_press_event)

        #
        # Create each column and intitialize it's cell renderers, etc.
        #

        # --- siginfo ---
        self.tv_columns[PYOBJECT_COLUMN] = tv_column = gtk.TreeViewColumn()
        tv_column.set_visible(False)
        self.view.append_column(tv_column)

        # --- filter ---
        self.tv_columns[FILTER_TYPE_COLUMN] = tv_column = gtk.TreeViewColumn(tv_column_info[FILTER_TYPE_COLUMN]['title'])
        cell = gtk.CellRendererToggle()
        cell.set_property('activatable', True)
        cell.connect( 'toggled', self.on_filter_toggle)
        tv_column.pack_start(cell)
        tv_column.set_cell_data_func(cell, self.filter_type_cell_data, FILTER_TYPE_COLUMN)
        tv_column.set_sort_column_id(FILTER_TYPE_COLUMN)
        self.view.append_column(tv_column)

        # --- date ----
        self.tv_columns[DATE_COLUMN] = tv_column = gtk.TreeViewColumn(tv_column_info[DATE_COLUMN]['title'])
        cell = gtk.CellRendererText()
        tv_column.pack_start(cell)
        tv_column.set_cell_data_func(cell, self.date_cell_data, DATE_COLUMN)
        tv_column.set_sort_column_id(DATE_COLUMN)
        self.view.append_column(tv_column)

        # --- host ---
        self.tv_columns[HOST_COLUMN] = tv_column = gtk.TreeViewColumn(tv_column_info[HOST_COLUMN]['title'])
        cell = gtk.CellRendererText()
        tv_column.pack_start(cell)
        tv_column.set_cell_data_func(cell, self.text_cell_data, HOST_COLUMN)
        tv_column.set_sort_column_id(HOST_COLUMN)
        #tv_column.set_visible(False)
        self.view.append_column(tv_column)

        # --- count ---
        self.tv_columns[COUNT_COLUMN] = tv_column = gtk.TreeViewColumn(tv_column_info[COUNT_COLUMN]['title'])
        cell = gtk.CellRendererText()
        tv_column.pack_start(cell)
        tv_column.set_cell_data_func(cell, self.count_cell_data, COUNT_COLUMN)
        tv_column.set_sort_column_id(COUNT_COLUMN)
        self.view.append_column(tv_column)

        # --- category ---
        self.tv_columns[CATEGORY_COLUMN] = tv_column = gtk.TreeViewColumn(tv_column_info[CATEGORY_COLUMN]['title'])
        cell = gtk.CellRendererText()
        tv_column.pack_start(cell)
        tv_column.set_cell_data_func(cell, self.text_cell_data, CATEGORY_COLUMN)
        tv_column.set_sort_column_id(CATEGORY_COLUMN)
        self.view.append_column(tv_column)

        # --- summary ---
        self.tv_columns[SUMMARY_COLUMN] = tv_column = gtk.TreeViewColumn(tv_column_info[SUMMARY_COLUMN]['title'])
        cell = gtk.CellRendererText()
        tv_column.pack_start(cell)
        tv_column.set_cell_data_func(cell, self.text_cell_data, SUMMARY_COLUMN)
        tv_column.set_sort_column_id(SUMMARY_COLUMN)
        self.view.append_column(tv_column)

        #
        # Some final properties
        #

        # alternate row color for easy reading
        self.view.set_rules_hint(True)

        # Set up the selection objects
        self.selection = self.view.get_selection()
        self.selection.set_mode(gtk.SELECTION_MULTIPLE)

        self.scrolled_window.show_all()

    def on_sort_column_changed(self, treesortable, treeview):
        sort_column_id, sort_order = treesortable.get_sort_column_id()
        if debug:
            if sort_column_id is None:
                title = None
            else:
                tv_column = treeview.get_column(sort_column_id)
                title = tv_column.get_title()
            order = {None:'None', gtk.SORT_DESCENDING:'DESCENDING', gtk.SORT_ASCENDING:'ASCENDING'}[sort_order]
            log_gui.debug('on_sort_column_changed: %s %s', title, order)
        
        self.sort_column_id = sort_column_id
        self.sort_order = sort_order

    def set_model(self, base_model):
        if debug:
            log_gui.debug('set_model:')

        if base_model is None:
            self.base_model = None
            self.filter_model = None
            self.sort_model = None
            self.view_model = None
        else:
            self.base_model = base_model

            # create an intermediate model to filter rows with
            self.filter_model = self.base_model.filter_new()
            self.filter_model.set_visible_func(self.visible_row_filter)
            self.filter_model.set_modify_func(self.column_types, self.get_row_col_model_data)

            # create an intermediate model to sort the filtered rows with
            self.sort_model = gtk.TreeModelSort(self.filter_model)
            self.sort_model.connect('sort-column-changed', self.on_sort_column_changed, self.view)
            self.sort_model.set_sort_func(DATE_COLUMN, self.sort_date_column, DATE_COLUMN)

            self.view.set_model(self.sort_model)
            self.view_model = self.sort_model


        self.sort_model.set_sort_column_id(self.sort_column_id, self.sort_order)

        self.row_inserted_signal.connect(self.view_model, self.on_model_row_inserted)        
        self.row_deleted_signal.connect (self.view_model, self.on_model_row_deleted)        
        self.row_changed_signal.connect (self.view_model, self.on_model_row_changed)        

    def bind_data(self, alert_data):
        if debug:
            if alert_data is None:
                name = None
            else:
                name = alert_data.name
            log_gui.debug("view.bind_data: %s", name)

        self.alert_data = alert_data
        self.set_model(alert_data.model)

        self.properties_changed_signal.connect(self.alert_data, self.on_data_properties_changed)
        self.load_data_signal.connect(self.alert_data, self.on_load_data)

    # --- Row Handling ---

    def get_row_col_model_data(self, model, iter, column):
        value = None
        # Convert the filter iter to the corresponding child model iter.
        child_model_iter = model.convert_iter_to_child_iter(iter)
        if child_model_iter is None:
            return None
        child_model = model.get_model()
        siginfo = child_model.get_value(child_model_iter, 0)
        if siginfo is None:
            return None
        
        if column == PYOBJECT_COLUMN:
            value = siginfo
        elif column == COUNT_COLUMN:
            value = siginfo.report_count
        elif column == DATE_COLUMN:
            value = siginfo.last_seen_date
        elif column == HOST_COLUMN:
            value = siginfo.host
        elif column == CATEGORY_COLUMN:
            value = default_text(siginfo.category)
        elif column == SUMMARY_COLUMN:
            value = html_to_text(siginfo.solution.summary, 1024)

        return value


    def visible_row_filter(self, model, iter):
        visible = True

        siginfo = model.get_value(iter, PYOBJECT_COLUMN)
        if siginfo is not None:
            user_data = siginfo.get_user_data(self.username)

            if user_data.delete_flag and self.hide_deleted:
                visible = False

            if user_data.filter.filter_type == FILTER_ALWAYS and self.hide_quiet:
                visible = False

        return visible

    def on_filter_toggle(self, cell, path):
        if self.view_model is None:
            return
        model = self.view_model
        
        iter = model.get_iter_from_string(path)
        siginfo = get_siginfo_from_model_path(model, path)
        user_data = siginfo.get_user_data(self.username)

        if user_data.filter.filter_type == FILTER_NEVER:
            filter_type = FILTER_ALWAYS
        else:
            filter_type = FILTER_NEVER
        database = self.alert_data.database
        database.set_filter(siginfo.sig, self.username, filter_type, '')
        return True     # return True ==> handled, False ==> propagate

    def on_model_row_inserted(self, model, path, iter):
        if debug:
            pass
            #log_gui_data.debug("model_row_inserted: %s", path)
        self.emit('row-changed', self.view_model, 'add', iter)

    def on_model_row_changed(self, model, path, iter):
        if debug:
            pass
            #log_gui_data.debug("model_row_changed: %s", path)
        self.emit('row-changed', self.view_model, 'modify', iter)

    def on_model_row_deleted(self, model, path):
        if debug:
            pass
            #log_gui_data.debug("model_row_deleted: %s", path)
        self.emit('row-changed', self.view_model, 'delete', None)

    # --- Column Handling ---

    def sort_date_column(self, model, iter1, iter2, column_index):
        if debug:
            log_gui.debug('sort_date_column:')
        timestamp1 = model.get_value(iter1, column_index)
        timestamp2 = model.get_value(iter2, column_index)
        if timestamp1 is None or timestamp2 is None:
            return 0
        return cmp(timestamp1, timestamp2)

    def count_cell_data(self, column, cell, model, iter, column_index):
        siginfo = model.get_value(iter, PYOBJECT_COLUMN)
        text_attributes = self.get_text_attributes(siginfo)
        cell.set_property("attributes", text_attributes)

        # FIXME: this should be a global property, not set each time
        cell.set_property("xalign", 1.0)

        text = model.get_value(iter, column_index)
        cell.set_property('text', text) 
        return

    def date_cell_data(self, column, cell, model, iter, column_index):
        siginfo = model.get_value(iter, PYOBJECT_COLUMN)
        text_attributes = self.get_text_attributes(siginfo)
        cell.set_property("attributes", text_attributes)
        timestamp = model.get_value(iter, column_index)
        if timestamp is None:
            return
        cell.set_property('text', timestamp.format())
        return

    def text_cell_data(self, column, cell, model, iter, column_index):
        siginfo = model.get_value(iter, PYOBJECT_COLUMN)
        text_attributes = self.get_text_attributes(siginfo)
        cell.set_property("attributes", text_attributes)
        text = model.get_value(iter, column_index)
        cell.set_property('text', text) 
        return

    def filter_type_cell_data(self, column, cell, model, iter, column_index):
        siginfo = model.get_value(iter, PYOBJECT_COLUMN)
        user_data = siginfo.get_user_data(self.username)

        if user_data.filter.filter_type == FILTER_NEVER:
            cell.set_property('active', False)
        else:
            cell.set_property('active', True)
        return

    def restore_selection(self):
        if debug:
            log_gui.debug('restore_selection:')
        if self.selection is None or self.view_model is None:
            return
        
        self.selection.unselect_all()
        n_paths = len(self.view_model)
        if n_paths == 0:
            new_row = 0
        else:
            new_row = min(self.last_selected_row, n_paths-1)
        if debug:
            log_gui.debug('restore_selection: new_row=%s', new_row)
        self.selection.select_path((new_row,))
        self.view.scroll_to_cell((new_row,))

    def get_selected_siginfos(self):
        model, selected_paths = self.selection.get_selected_rows()

        siginfos = []
        for path in selected_paths:
            siginfo = get_siginfo_from_model_path(model, path)
            siginfos.append(siginfo)
        return siginfos

    # --- Signals ---

    def on_data_properties_changed(self, alert_data, properties):
        self.emit('properties-changed', alert_data, properties)

    def on_load_data(self, alert_data, state, errno, strerror):
        self.emit('load-data', alert_data, state, errno, strerror)

    def on_async_error(self, alert_data, method, errno, strerror):
        if debug:
            log_program.debug("%s.on_async_error(%s, %d, %s)", self.__class__.__name__, method, errno, strerror)
        self.emit('async-error', alert_data, method, errno, strerror)

    # --- Utilities ---

    def get_text_attributes(self, siginfo):
        user_data = siginfo.get_user_data(self.username)

        text_attributes = pango.AttrList()
        
        if not user_data.seen_flag:
            text_attributes.insert(pango.AttrWeight(pango.WEIGHT_HEAVY, 0, -1))

        if user_data.delete_flag:
            text_attributes.insert(pango.AttrStrikethrough(True, 0, 1000))

        return text_attributes
       
            
#------------------------------------------------------------------------------

class AlertData(gobject.GObject):

    __gsignals__ = {
        'load-data':
        (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_INT, gobject.TYPE_STRING)),
        'properties-changed':
        (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
        'async-error': # callback(method, errno, strerror)
        (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_INT, gobject.TYPE_STRING)),
        }


    def __init__(self, name, database):
        gobject.GObject.__init__(self)

        self.name = name
        self.database = database
        self.database_properties = SEDatabaseProperties()
        # create the base model, it holds a siginfo
        self.model = gtk.ListStore(gobject.TYPE_PYOBJECT)

        self.clear()
        self.get_properties()
        database.connect('signatures_updated', self.signatures_updated)
 
    def clear(self):
        if debug:
            log_gui.debug("AlertData (%s) clear: cur row count = %d", self.name, len(self.model))
        self.model.clear()

    def set_properties(self, properties):
        if debug:
            log_gui.debug("set_properties: properties=%s", properties)
        if properties is None:
            self.database_properties = SEDatabaseProperties()
        else:
            self.database_properties = properties
        self.emit('properties-changed', self.database_properties)

    def get_properties(self):
        if self.database is None: return
        async_rpc = self.database.get_properties()
        async_rpc.add_callback(self.get_properties_callback)
        async_rpc.add_errback(self.get_properties_errback)

    def get_properties_callback(self, properties):
        if debug:
            log_gui.debug("get_properties_callback: properties=%s", properties)
        self.set_properties(properties)

    def get_properties_errback(self, method, errno, strerror):
        log_rpc.error('database bind: %s', strerror)
        self.set_properties(None)

    def signatures_updated(self, database, type, item):
        if debug:
            log_gui_data.debug("signatures_updated() %s: type=%s, item=%s alert_list size=%s",
                               self.name, type, item, len(self.model))

        def new_siginfo_callback(sigs):
            if debug:
                log_gui_data.debug("new_siginfo_callback(), type=%s %s", type, str(sigs))

            for siginfo in sigs.signature_list:
                self.insert_siginfo_into_model(siginfo)

        if type == 'add' or type == 'modify':
            async_rpc = self.database.query_alerts(item)
            async_rpc.add_callback(new_siginfo_callback)
        elif type == 'delete':
            iter = self.get_iter_from_local_id(item)
            if iter is not None:
                self.model.remove(iter)
        else:
            raise ProgramError(ERR_UNKNOWN_VALUE, "signatures_updated: type = %s not recognized" % type)
            
    def get_iter_from_local_id(self, local_id, model=None):
        if model is None:
            model = self.model
        iter = model.get_iter_first()
        while iter:
            row_siginfo = model.get_value(iter, PYOBJECT_COLUMN)
            if row_siginfo.local_id == local_id:
                return iter
            iter = model.iter_next(iter)

        return None

    def insert_siginfo_into_model(self, siginfo):
        iter = self.get_iter_from_local_id(siginfo.local_id)
        if iter is None:
            if debug:
                log_gui_data.debug("insert_siginfo_into_model(): new")
            iter = self.new_model_row(siginfo)
        else:
            if debug:
                log_gui_data.debug("insert_siginfo_into_model(): replace")
            self.update_model_row(iter, siginfo)

    def update_model_row(self, iter, siginfo):
        validate_siginfo(siginfo)
        self.model.set(iter, PYOBJECT_COLUMN, siginfo)
        

    def new_model_row(self, siginfo):
        validate_siginfo(siginfo)
        iter = self.model.append((siginfo,))
        return iter

    def load_model_data_from_sigs(self, sigs):
        self.model.clear()
        for siginfo in sigs.signature_list:
            self.new_model_row(siginfo)

    def load_data(self):
        self.clear()

        if debug:
            log_gui.debug("load_data(): database=%s", self.name)

        self.get_properties()

        if self.database is not None:
            criteria = '*'
            self.emit('load-data', 'start', 0, '')
            async_rpc = self.database.query_alerts(criteria)
            async_rpc.add_callback(self.query_alerts_callback)
            async_rpc.add_errback(self.query_alerts_error)


    def query_alerts_callback(self, sigs):
        if debug:
            log_gui.debug("query_alerts_callback():")
        self.sigs = sigs
        self.load_model_data_from_sigs(sigs)
        self.emit('load-data', 'end', 0, '')


    def query_alerts_error(self, method, errno, strerror):
        log_gui.error("query_alerts: [%d] %s", errno, strerror)
        self.emit('load-data', 'end', errno, strerror)

    def get_siginfos(self, criteria=None):
        siginfos = []
        iter = self.model.get_iter_first()
        while iter:
            siginfo = self.model.get_value(iter, PYOBJECT_COLUMN)
            siginfos.append(siginfo)
            iter = self.model.iter_next(iter)
        return siginfos
            
gobject.type_register(AlertData)

#------------------------------------------------------------------------------

class AlertViewData(object):
    def __init__(self, name, username, browser):
        self.name = name
        self.username = username
        self.list_view = AlertListView(username, browser)
        self.alert_data = None
        
    def bind_data(self, alert_data):
        self.alert_data = alert_data
        self.list_view.bind_data(alert_data)

#-----------------------------------------------------

class ScanLogfileDialog(gtk.Dialog):
    def __init__(self):
        gtk.Dialog.__init__(self, title=_('Scan Log File'))
                            #buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK))

        self.analyzer = LogfileAnalyzer()
        self.analyzer.connect('progress', self.on_progress)
        self.analyzer.connect('state-changed', self.on_analyzer_state_change)
        
        icon_name = get_config('general','icon_name')
        self.set_icon_name(icon_name)
        self.set_default_size(400, 100)

        self.cancel_response_button = self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        self.cancel_response_button.connect('clicked', self.run_analysis, False)

        self.ok_response_button = self.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
        self.ok_response_button.set_sensitive(False)

        self.file_hbox = gtk.HBox()
        self.action_hbox = gtk.HBox()
        self.progress_bar = gtk.ProgressBar()
        self.vbox.pack_start(self.file_hbox, False)
        self.vbox.pack_start(self.action_hbox, False)
        self.vbox.pack_start(self.progress_bar, False)

        self.file_entry = gtk.Entry()
        self.file_entry.connect('activate', self.on_filepath_activate)
        self.file_entry.connect('changed', self.on_filepath_changed)

        self.file_chooser_button = gtk.Button(stock=gtk.STOCK_OPEN)
        self.file_chooser_button.connect('clicked', self.run_file_chooser)

        self.file_hbox.pack_start(self.file_entry, True)
        self.file_hbox.pack_start(self.file_chooser_button, False)

        self.run_analysis_button = gtk.Button(stock=gtk.STOCK_EXECUTE)
        self.run_analysis_button.connect('clicked', self.run_analysis, True)
        self.run_analysis_button.set_sensitive(False)

        self.cancel_analysis_button = gtk.Button(stock=gtk.STOCK_STOP)
        self.cancel_analysis_button.connect('clicked', self.run_analysis, False)
        self.cancel_analysis_button.set_sensitive(False)

        self.action_hbox.pack_start(self.run_analysis_button, True)
        self.action_hbox.pack_start(self.cancel_analysis_button, True)

        self.set_position(gtk.WIN_POS_CENTER)
        self.set_keep_above(True)
        self.show_all()
        self.set_widget_state('pending')

    filepath = property(lambda self: self.file_entry.get_text())

    def on_progress(self, analyzer, progress):
        self.progress_bar.set_fraction(progress)
        if progress == 1.0:
            self.cancel_analysis_button.set_sensitive(False)
        

    def on_analyzer_state_change(self, analyzer, state):
        self.set_widget_state(state)
        if state == 'stopped':
            if analyzer.strerror:
                display_error(analyzer.strerror)

    def scan_file(self, filepath):
        if debug:
            log_avc.debug('%s.scan_file(%s)', self.__class__.__name__, filepath)
        self.analyzer.cancelled = False
        self.analyzer.open(self.filepath)
        self.progress_bar.set_text(self.analyzer.friendly_name)
        self.analyzer.run()

    def set_widget_state(self, state):
        if state == 'pending':
            self.ok_response_button.set_sensitive(False)
            self.cancel_response_button.set_sensitive(True)
            self.file_chooser_button.set_sensitive(True)
            self.file_entry.set_sensitive(True)
            self.run_analysis_button.set_sensitive(True)
            self.cancel_analysis_button.set_sensitive(False)
        elif state == 'stopped':
            if not self.analyzer.errno:
                self.ok_response_button.set_sensitive(True)
            else:
                self.ok_response_button.set_sensitive(False)
            self.cancel_response_button.set_sensitive(True)
            self.file_chooser_button.set_sensitive(False)
            self.file_entry.set_sensitive(False)
            self.run_analysis_button.set_sensitive(False)
            self.cancel_analysis_button.set_sensitive(False)
        elif state == 'running':
            self.ok_response_button.set_sensitive(False)
            self.cancel_response_button.set_sensitive(True)
            self.file_chooser_button.set_sensitive(False)
            self.file_entry.set_sensitive(False)
            self.run_analysis_button.set_sensitive(False)
            self.cancel_analysis_button.set_sensitive(True)
        else:
            raise ValueError("unknown state (%s)" % state)

    def run_analysis(self, widget, start):
        if debug:
            log_avc.debug('%s.run_analysis() start=%s', self.__class__.__name__, start)
        if start:
            self.scan_file(self.filepath)
        else:
            self.analyzer.cancelled = True

    def on_filepath_changed(self, widget):
        if self.filepath:
            self.run_analysis_button.set_sensitive(True)
        else:
            self.run_analysis_button.set_sensitive(False)

    def on_filepath_activate(self, widget):
        self.run_analysis(widget, True)

    def run_file_chooser(self, widget):
	result = None
	file_open = gtk.FileChooserDialog(self.get_title(), action=gtk.FILE_CHOOSER_ACTION_OPEN,
            buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))

	if file_open.run() == gtk.RESPONSE_OK:
		self.file_entry.set_text(file_open.get_filename())
	file_open.destroy()

    def get_database(self):
        if not self.analyzer.errno:
            return self.analyzer.database
        else:
            return None


#------------------------------------------------------------------------------

class StatusMessage(object):
    LOW_PRIORITY    = 0
    MEDIUM_PRIORITY = 1
    HIGH_PRIORITY   = 2

    message_priorities = [LOW_PRIORITY, MEDIUM_PRIORITY, HIGH_PRIORITY]

    map_priority_to_string = {
        LOW_PRIORITY    : 'LOW',
        MEDIUM_PRIORITY : 'MEDIUM',
        HIGH_PRIORITY   : 'HIGH',
    }

    INFO_TYPE  = 0
    ERROR_TYPE = 1
    
    message_types = [INFO_TYPE, ERROR_TYPE]

    map_type_to_string = {
        INFO_TYPE  : 'INFO',
        ERROR_TYPE : 'ERROR',
    }

    time_fmt = '%I:%M.%S %p'

    def __init__(self, owner, type, priority, text, time_to_live=None, message_name=None):
        self.owner = owner
        self.type = type
        self.priority = priority
        self.text = text
        self.start_time = time.time()
        self.time_to_live = time_to_live
        self.message_name = message_name

    def __str__(self):
        time_str = time.strftime(self.time_fmt, time.localtime(self.start_time))
        if self.time_to_live is not None:
            expiration_time = self.start_time + self.time_to_live
            time_str += ' - %s' % (time.strftime(self.time_fmt, time.localtime(expiration_time)))

        text = "[%s,%s,%s, %s] %s" % (self.map_type_to_string[self.type],
                                       self.map_priority_to_string[self.priority],
                                       time_str, self.message_name,
                                       self.text)
        return text
                                 

    def update_text(self, text, time_to_live=None):
        self.text = text

        if time_to_live is not None:
            self.start_time = time.time()
            self.time_to_live = time_to_live
            
        self.owner.update_message_text(self)


#------------------------------------------------------------------------------

class StatusMessageManager(object):

    '''Messages are inserted into a priority list, the message
    with the highest priority is the one currently displayed.

    Messages may be either expiring (timeout) or non-expiring.

    There is at most one non-expiring message of any given
    type. The next non-expiring message written replaces the
    previous non-expiring message of the same type.

    Expiring messages persist in the message list until they
    expire or are manually removed.

    Any message may be removed at any time by calling
    remove_message(). It is not an error to remove a message not
    present in the message list.

    The currently displayed message is updated whenever the
    message list is updated which occures when a message is added,
    removed, modified, or times out.

    Setting a message does not guarantee it will be displayed, a
    higher priority message may obscure it. In such a case it
    might be displayed later if the obscuring message times out or
    is manually removed.

    '''

    def __init__(self, name, set_message_func):
        self.name = name
        self.set_message = set_message_func
        self.expire_timeout_id = None
        self.messages = []

    def _expire_timeout_callback(self):
        if debug:
            log_gui.debug("_expire_timeout_callback (%s)", self.name)
        self.expire_timeout_id = None
        self._prune_expiring_messages()
        self._display_current_message()
        return False

    def _prune_non_expiring_messages(self):
        if debug:
            self.dump_message_list("_prune_non_expiring_messages start (%s)" % (self.name))
        message_at_priority_level = {}

        # Note iterate over copy of message list (e.g. [:]) to avoid problems with modification during iteration
        for message in self.messages[:]:
            if message.time_to_live is None:
                if message_at_priority_level.get(message.priority):
                    self.messages.remove(message)
                else:
                    message_at_priority_level[message.priority] = message

        if debug:
            self.dump_message_list("_prune_non_expiring_messages end (%s)" % (self.name))

    def _prune_expiring_messages(self):
        now = time.time()

        if debug:
            self.dump_message_list("_prune_expiring_messages start (%s) now=%s" % (self.name, time.strftime(StatusMessage.time_fmt, time.localtime(now))))

        if self.expire_timeout_id is not None:
            gobject.source_remove(self.expire_timeout_id)
            self.expire_timeout_id = None

        maximum_expiration_time = 0.0
        soonest_message_to_expire = None
        # Note iterate over copy of message list (e.g. [:]) to avoid problems with modification during iteration
        for message in self.messages[:]:
            if message.time_to_live is not None:
                message_expiration_time = message.start_time + message.time_to_live
                if message_expiration_time <= now:
                    self.messages.remove(message)
                else:
                    if message_expiration_time > maximum_expiration_time:
                        maximum_expiration_time = message_expiration_time
                        soonest_message_to_expire = message
        if soonest_message_to_expire is not None:
            seconds_to_next_expire = (soonest_message_to_expire.start_time + soonest_message_to_expire.time_to_live) - now
            if debug:
                log_gui.debug("expire next (%s) in %.1f: %s", self.name, seconds_to_next_expire, soonest_message_to_expire)
            self.expire_timeout_id = gobject.timeout_add(int(seconds_to_next_expire*1000), self._expire_timeout_callback)
        else:
            if debug:
                log_gui.debug("NO expiring messages")

        if debug:
            self.dump_message_list("_prune_expiring_messages end (%s)" % (self.name))
            

    def get_message_by_name(self, message_name):
        for message in self.messages:
            if message.message_name == message_name:
                return message
        return None

    def dump_message_list(self, header=None):
        if header is not None:
            log_gui.debug(header)
        for message in self.messages:
            log_gui.debug(message)
            
    def _message_sort_func(self, msg2, msg1):
        # sort descending by swapping input parameters
        relation = cmp(msg1.priority, msg2.priority)
        if relation: return relation
        relation = cmp(msg1.start_time, msg2.start_time)
        return relation
        
    def _add_message(self, message):
        if message.message_name is not None:
            existing_named_message = self.get_message_by_name(message.message_name)
            if existing_named_message != message:
                self.remove_message(existing_named_message)

        if not message in self.messages:
            self.messages.append(message)
            self.messages.sort(self._message_sort_func)

        if debug:
            self.dump_message_list("_add_message (%s): %s" % (self.name, message))
        self._prune_non_expiring_messages()
        self._prune_expiring_messages()
        self._display_current_message()
            
    def _display_current_message(self):
        if debug:
            self.dump_message_list("_display_current_message (%s)" % (self.name))
        if len(self.messages) > 0:
            message = self.messages[0]
            self.set_message(message.text)
        else:
            self.set_message(None)

    def update_message_text(self, message):
        self._add_message(message)

    def remove_message(self, message):
        if message in self.messages:
            self.messages.remove(message)
            self._display_current_message()
        if debug:
            self.dump_message_list("remove_message (%s): %s" % (self.name, message))

    def remove_message_by_name(self, message_name):
        message = self.get_message_by_name(message_name)
        if message is not None:
                self.remove_message(message)

    def remove_all_messages(self):
        if debug:
            self.dump_message_list("remove_all_messages (%s)" % (self.name))
        # Note iterate over copy of message list (e.g. [:]) to avoid problems with modification during iteration
        for message in self.messages[:]:
            self.remove_message(message)

    def new_message(self, type, priority, text, time_to_live=None, message_name=None):
        message = StatusMessage(self, type, priority, text, time_to_live, message_name)
        self._add_message(message)
        return message

#------------------------------------------------------------------------------

class BrowserStatusBar(gtk.VBox):
    # These should sum to 1.0
    visit_msg_proportion    = 0.20
    alert_count_proportion  = 0.10
    status_msg_proportion   = 0.55
    progress_proportion     = 0.15

    def __init__(self):
        gtk.VBox.__init__(self)

        self.status_hbox = gtk.HBox()


        # Error bar
        self.error_frame = gtk.Frame()
        self.error_hbox = gtk.HBox()
        error_icon = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_MENU)
        self.error_hbox.pack_start(error_icon, expand=False)
        self.error_msg = gtk.Label("error")
        self.error_msg.set_ellipsize(pango.ELLIPSIZE_END)
        self.error_msg.set_alignment(0.0, 0.5)
        self.error_hbox.pack_start(self.error_msg, expand=True)
        self.error_frame.add(self.error_hbox)

        self.connect_icon = gtk.Image()
        self.connect_icon.set_from_stock(gtk.STOCK_DISCONNECT, gtk.ICON_SIZE_SMALL_TOOLBAR)
    
        self.connect_icon_frame = gtk.Frame()
        self.connect_icon_frame.add(self.connect_icon)
        self.status_hbox.add(self.connect_icon_frame)
	
        self.visit_msg = gtk.Label("visiting")
        self.visit_msg.set_ellipsize(pango.ELLIPSIZE_END)
        self.visit_msg.set_alignment(0.0, 0.5)
        self.visit_msg_frame = gtk.Frame()
        self.visit_msg_frame.add(self.visit_msg)
        self.status_hbox.add(self.visit_msg_frame)
	
        self.alert_count = gtk.Label("count")
        self.alert_count.set_ellipsize(pango.ELLIPSIZE_END)
        self.alert_count.set_alignment(0.0, 0.5)
        self.alert_count_frame = gtk.Frame()
        self.alert_count_frame.add(self.alert_count)
        self.status_hbox.add(self.alert_count_frame)
	
        self.status_msg = gtk.Label("status")
        self.status_msg.set_ellipsize(pango.ELLIPSIZE_END)
        self.status_msg.set_alignment(0.0, 0.5)
        self.status_msg_frame = gtk.Frame()
        self.status_msg_frame.add(self.status_msg)
        self.status_hbox.add(self.status_msg_frame)
	
        self.progress = gtk.ProgressBar()
        self.progress.set_ellipsize(pango.ELLIPSIZE_END)
        self.progress_frame = gtk.Frame()
        self.progress_frame.add(self.progress)
        self.status_hbox.add(self.progress_frame)
	
        self.pack_start(self.error_frame, expand=False)
        self.pack_start(self.status_hbox, expand=False)

        self.show_all()
        self.error_frame.hide()         # error is initially hidden
        
        self.status = StatusMessageManager('statusbar status', self.set_status_message)
        self.error  = StatusMessageManager('statusbar error',  self.set_error_message)

    def do_size_allocate(self, allocation):
        if debug:
            log_gui.debug("statusbar.do_size_allocate: (%d,%d)(%dx%d)",
                          allocation.x, allocation.y, allocation.width, allocation.height)

        max_x = allocation.x + allocation.width
        
        # If the error bar is visible, allocate space for it
        if self.error_frame.flags() & gtk.VISIBLE:
            child_rect = gtk.gdk.Rectangle(allocation.x, allocation.y, allocation.width, allocation.height/2)
            self.error_frame.size_allocate(child_rect)
            child_rect.y += child_rect.height
        else:
            child_rect = gtk.gdk.Rectangle(allocation.x, allocation.y, allocation.width, allocation.height)

        # Compute size for connection icon
        icon_width, icon_height = self.connect_icon_frame.size_request()

        # Total space available for fixed sized items
        fixed_width_total = icon_width
        fixed_width_total = min(allocation.width, fixed_width_total)
        fixed_width_remaining = fixed_width_total

        # Total space available remaining for scaled items
        scaled_width_total = allocation.width - fixed_width_total
        scaled_width_remaining = scaled_width_total

        # Move left to right laying out items:
        # [connection icon][visit msg][status msg][progress bar]

        # Connection Icon
        child_rect.width = min(fixed_width_total, icon_width)
        self.connect_icon_frame.size_allocate(child_rect)
        child_rect.x += child_rect.width
        fixed_width_remaining = fixed_width_remaining - child_rect.width

        # Visit Message
        child_rect.width = int(scaled_width_total * self.visit_msg_proportion)
        if (child_rect.x + child_rect.width) > max_x:
            child_rect.width = max_x - child_rect.x
        self.visit_msg_frame.size_allocate(child_rect)
        scaled_width_remaining = scaled_width_remaining - child_rect.width
        child_rect.x += child_rect.width

        # Alert Count
        child_rect.width = int(scaled_width_total * self.alert_count_proportion)
        if (child_rect.x + child_rect.width) > max_x:
            child_rect.width = max_x - child_rect.x
        self.alert_count_frame.size_allocate(child_rect)
        scaled_width_remaining = scaled_width_remaining - child_rect.width
        child_rect.x += child_rect.width

        # Status Message
        child_rect.width = int(scaled_width_total * self.status_msg_proportion)
        if (child_rect.x + child_rect.width) > max_x:
            child_rect.width = max_x - child_rect.x
        self.status_msg_frame.size_allocate(child_rect)
        scaled_width_remaining = scaled_width_remaining - child_rect.width
        child_rect.x += child_rect.width

        # Progress Bar
        # Note, we don't compute the proportionate space for the last scaled item
        # because of potential rounding errors, we just use whats left
        child_rect.width = scaled_width_remaining
        if child_rect.x + child_rect.width > max_x:
            child_rect.width = max(0, max_x - child_rect.x)
        self.progress_frame.size_allocate(child_rect)

    def set_status_message(self, text):
        if debug:
            log_gui.debug("set_status_message: %s", text)
        if text is None:
            self.status_msg.set_text('')
        else:
            self.status_msg.set_text(text)

    def set_error_message(self, text):
        if debug:
            log_gui.debug("set_error_message: %s", text)
        if text is None:
            self.error_frame.hide()
        else:
            text = '<span foreground="#FF0000">%s</span>' % text
            self.error_msg.set_markup(text)
            self.error_frame.show()

    def set_connected_state(self, is_connected):
        if is_connected:
            self.connect_icon.set_from_stock(gtk.STOCK_CONNECT, gtk.ICON_SIZE_SMALL_TOOLBAR)
        else:
            self.connect_icon.set_from_stock(gtk.STOCK_DISCONNECT, gtk.ICON_SIZE_SMALL_TOOLBAR)

    def set_visit_message(self, msg):
        if msg is None:
            self.visit_message = ''
        else:
            self.visit_message = msg
        self.visit_msg.set_text(self.visit_message)

gobject.type_register(BrowserStatusBar)
#------------------------------------------------------------------------------

class SignalConnection(object):
    def __init__(self, signal, emitter=None, id=None):
        self.signal = signal
        self.emitter = emitter
        self.id = id

    def connect(self, emitter, handler):
        self.disconnect()
        self.emitter = emitter
        if self.emitter is not None:
            self.id = self.emitter.connect(self.signal, handler)
        

    def disconnect(self):
        if self.emitter is not None and self.id is not None:
            self.emitter.disconnect(self.id)
        self.emitter = None
        self.id = None

#------------------------------------------------------------------------------

class BrowserApplet(object):
    VISIT_AUDIT   = 0
    VISIT_LOGFILE = 1
    map_visit_num_to_name = {VISIT_AUDIT : 'audit', VISIT_LOGFILE : 'logfile'}

    # --- Initialization ---

    def __init__(self, username=None, server=None):
        self.username = username
        self.server = server
        self.displayed_siginfo = None
        self.mark_seen_timeout_event_id = None
        self.update_alert_view_idle_event_id = None
        self.restore_selection_idle_event_id = None
        self.alert_display = None
        self.window_delete_hides = True
        self.load_in_progress = False
        self.list_view = None
        self.toolbar_visible = False

        self.view_data_collection = {}
        self.audit_data = AlertData('audit', self.server)

        self.audit_view_data = AlertViewData('audit', username, self)
        self.audit_view_data.bind_data(self.audit_data)
        self.set_visit_data('audit', self.audit_view_data)

        self.logfile_view_data = AlertViewData('logfile', username, self)
        self.set_visit_data('logfile', self.logfile_view_data)

        self.default_save_folder = get_user_home_dir()
        self.default_save_filename = 'selinux_alert.txt'
        self.clipboard = gtk.Clipboard()
        self.print_settings = None

        # How long an alert must be displayed before it's marked as having been seen
        self.seen_after_displayed_seconds = 3

        program_name = _('setroubleshoot browser')
        program_version = '1.0'
        gnome.init(program_name, program_version)
        self.window_name = _("SETroubleshoot Browser")

        self.connection_state_change_signal  = SignalConnection('connection_state_changed')
        self.connection_pending_retry_signal = SignalConnection('pending_retry')
        self.properties_changed_signal       = SignalConnection('properties-changed')
        self.load_data_signal                = SignalConnection('load-data')
        self.async_error_signal              = SignalConnection('async-error')

        self.alert_list_changed_signal       = SignalConnection('row-changed')
        self.selection_changed_signal        = SignalConnection('changed')

        self.init_widgets()

        window_state = os.getenv('SEALERT_WINDOW_STATE')
        if debug:
            log_gui.debug("read SEALERT_WINDOW_STATE from environment (%s)", window_state)
        if window_state is None:
            window_state = 'hidden'
        self.window_state = parse_window_state(window_state)
        self.update_window_state(self.window_state)

        window_geometry = os.getenv('SEALERT_WINDOW_GEOMETRY')
        if debug:
            log_gui.debug("read SEALERT_WINDOW_GEOMETRY from environment (%s)", window_geometry)
        if window_geometry is not None:
            self.set_geometry(window_geometry)

        self.update_connection_state(server, server.connection_state)

        self.connection_state_change_signal.connect(self.server, self.on_connection_state_change)
        self.connection_pending_retry_signal.connect(self.server.connection_retry, self.on_connection_pending_retry)
        self.async_error_signal.connect(self.server, self.on_async_error)

        self.do_visit('audit')
        if (self.server.connection_state.flags & ConnectionState.OPEN) and len(self.audit_data.model) == 0:
            self.audit_data.load_data()

    def create_ui(self):
        load_stock_icons(['stock_mail-send', 'connect_creating'])
        
        toggle_column_visibility = "\n".join(["                 <menuitem action='Toggle%sColumn'/>" % \
                                              tv_column_info[column]['name'] for column in range(1, NUM_COLUMNS)])


        ui_string = """<ui>
          <menubar name='Menubar'>
            <menu action='FileMenu'>
              <menuitem action='ConnectTo'/>
              <menuitem action='ScanLogfile'/>
              <separator/>
              <menuitem action='SaveAs'/>
              <menuitem action='Print'/>
              <separator/>
              <menuitem action='EditEmailList'/>
              <separator/>
              <menuitem action='Close'/>
            </menu>
            <menu action='EditMenu'>
              <menuitem action='SelectAll'/>
              <menuitem action='SelectNone'/>
              <separator/>
              <menuitem action='Copy'/>
              <menuitem action='CopyAlert'/>
              <separator/>
              <menuitem action='MarkDelete'/>
              <menuitem action='Undelete'/>
              <menuitem action='Expunge'/>
            </menu>
            <menu action='ViewMenu'>
              <menuitem action='HideDeleted'/>
              <menuitem action='HideQuiet'/>
              <menuitem action='ToggleToolbar'/>
              <separator/>
              <menuitem action='ViewAudit'/>
              <menuitem action='ViewLogfile'/>
              <separator/>
              <menu action='ColumnVisibilityMenu'>
                 %s
              </menu>
            </menu>
            <menu action='HelpMenu'>
              <menuitem action='Help'/>
              <menuitem action='About'/>
            </menu>
          </menubar>
          <popup name='PopupMenu'>
            <menuitem action='MarkDelete'/>
            <menuitem action='Undelete'/>
            <menuitem action='Expunge'/>
            <separator/>
              <menuitem action='CopyAlert'/>
            <separator/>
              <menuitem action='SaveAs'/>
              <menuitem action='Print'/>
          </popup>
          <toolbar name='Toolbar'>
            <toolitem action='Print'/>
            <toolitem action='ConnectTo'/>
            <toolitem action='ScanLogfile'/>
            <separator/>
            <toolitem action='ViewAudit'/>
            <toolitem action='ViewLogfile'/>
          </toolbar>
        </ui>""" % toggle_column_visibility

        self.action_group = gtk.ActionGroup('WindowActions')

        # File Menu
        action = gtk.Action('FileMenu', _('_File'), None, None)
        self.action_group.add_action(action)

        # View Menu
        action = gtk.Action('ViewMenu', _('_View'), None, None)
        self.action_group.add_action(action)
                            

        # Edit Menu
        action = gtk.Action('EditMenu', _('_Edit'), None, None)
        self.action_group.add_action(action)
        
        # Help Menu
        action = gtk.Action('HelpMenu', _('_Help'), None, None)
        self.action_group.add_action(action)


        # Column Visibility Menu
        action = gtk.Action('ColumnVisibilityMenu', _('_Column Visibility'), None, None)
        self.action_group.add_action(action)

        action = gtk.Action('ConnectTo', _('Connect To...'), _('Connect to setroubleshoot server, browse alert results'), 'connect_creating')
        action.connect('activate', self.on_connect_to)
        self.action_group.add_action_with_accel(action, None)

        action = gtk.Action('ScanLogfile', _('Scan Logfile...'), _('Scan a log file, browse alert results'), gtk.STOCK_OPEN)
        action.connect('activate', self.on_open_logfile)
        self.action_group.add_action_with_accel(action, None)

        action = gtk.Action('SaveAs', _('Save _As...'), _('Save selected alerts in file'), gtk.STOCK_SAVE_AS)
        action.connect('activate', self.on_save_as)
        self.action_group.add_action_with_accel(action, '<shift><control>S')

        action = gtk.Action('Print', _('Print...'), _('Print selected alerts'), gtk.STOCK_PRINT)
        action.connect('activate', self.on_print)
        self.action_group.add_action_with_accel(action, '<control>P')

        action = gtk.Action('EditEmailList', _('Edit Email Alert List...'), _('Edit list of email addresses which receive alerts'), 'stock_mail-send')
        action.connect('activate', self.on_edit_email_alert_list)
        self.action_group.add_action_with_accel(action, None)

        action = gtk.Action('Close', _('_Close'), _('Close the window'), gtk.STOCK_CLOSE)
        action.connect('activate', self.on_close)
        self.action_group.add_action_with_accel(action, '<control>W')

        action = gtk.Action('SelectAll', _('Select _All'), _('Select all alerts'), gtk.STOCK_SELECT_ALL)
        action.connect('activate', self.on_select_all)
        self.action_group.add_action_with_accel(action, '<control>A')

        action = gtk.Action('SelectNone', _('Select _None'), _('Remove all selections'), None)
        action.connect('activate', self.on_select_none)
        self.action_group.add_action_with_accel(action, None)

        action = gtk.Action('Copy', _('Copy'), _('Copy selected text in detail pane'), gtk.STOCK_COPY)
        action.connect('activate', self.on_copy)
        self.action_group.add_action_with_accel(action, '<control>C')

        action = gtk.Action('CopyAlert', _('Copy Alert'), _('Copy selected alerts in entirety to clipboard'), gtk.STOCK_COPY)
        action.connect('activate', self.on_copy_alert)
        self.action_group.add_action_with_accel(action, '<shift><control>C')

        action = gtk.Action('MarkDelete', _('Mark _Delete'), _('Mark for deletion'), gtk.STOCK_DELETE)
        action.connect('activate', self.on_delete)
        self.action_group.add_action_with_accel(action, 'Delete')

        action = gtk.Action('Undelete', _('_Undelete'), _('Clear deletion flag'), gtk.STOCK_UNDELETE)
        action.connect('activate', self.on_undelete)
        self.action_group.add_action_with_accel(action, '<shift><control>D')

        action = gtk.Action('Expunge', _('Remove Marked Deleted'), _('Permanently delete alerts marked for deletion'), gtk.STOCK_REMOVE)
        action.connect('activate', self.on_expunge)
        self.action_group.add_action_with_accel(action, '<control>E')

        action = gtk.Action('Help', _('Help'), _('Show help information'), gtk.STOCK_HELP)
        action.connect('activate', self.on_user_help)
        self.action_group.add_action_with_accel(action, 'F1')

        action = gtk.Action('About', _('About'), _('About'), gtk.STOCK_ABOUT)
        action.connect('activate', self.on_about)
        self.action_group.add_action_with_accel(action, None)


        action = gtk.RadioAction('ViewAudit', _('View Audit Alerts'), _('View alerts from audit system'), None, self.VISIT_AUDIT)
        self.visit_radio_action = action
        self.action_group.add_action_with_accel(action, None)

        action = gtk.RadioAction('ViewLogfile', _('View Logfile Scan'), _('View alerts from last log file scan'), None, self.VISIT_LOGFILE)
        action.set_group(self.visit_radio_action)
        action.set_sensitive(False)
        self.action_group.add_action_with_accel(action, None)

        self.visit_radio_action.connect('changed', self.on_visit_change)

        action = gtk.ToggleAction('HideDeleted', _('Hide deleted'), _('Toggle hide deleted alerts'), None)
        action.connect('activate', self.on_hide_deleted)
        action.set_active(False)
        self.action_group.add_action_with_accel(action, None)

        action = gtk.ToggleAction('HideQuiet', _('Hide quiet'), _('Toggle hide quiet alerts'), None)
        action.connect('activate', self.on_hide_quiet)
        action.set_active(False)
        self.action_group.add_action_with_accel(action, None)

        action = gtk.ToggleAction('ToggleToolbar', _('Show Toolbar'), _('Toggle the toolbar on/off'), None)
        action.connect('activate', self.on_toggle_toolbar)
        self.action_group.add_action_with_accel(action, None)

        # Column Visibility
        for column in range(1, NUM_COLUMNS):
            column_name = tv_column_info[column]['name']
            column_title = tv_column_info[column]['title']
            action = gtk.ToggleAction('Toggle%sColumn' % column_name, _('Show %s Column') % column_title, _('Show/Hide %s Column') % column_title, None)
            action.connect('activate', self.on_toggle_column_visibility, column)
            action.set_active(True)
            self.action_group.add_action_with_accel(action, None)

        self.ui = gtk.UIManager()
        self.ui.insert_action_group(self.action_group, 0)
        self.ui.add_ui_from_string(ui_string)
        self.browser_win.add_accel_group(self.ui.get_accel_group())

        self.menubar = self.ui.get_widget('/Menubar')
        self.toolbar = self.ui.get_widget('/Toolbar')

    def on_realize(self, widget):
        # position the divider between the top and bottom pane
        vpane_rect = self.browser_vpane.get_allocation()
        self.browser_vpane.set_position(vpane_rect.height/4)

    def get_geometry(self):
        width, height = self.browser_win.get_size()
        xoffset, yoffset = self.browser_win.get_position()
        geometry = '%dx%d+%d+%d' % (width, height, xoffset, yoffset)
        if debug:
            log_gui.debug("get_geometry() %s", geometry)
        return geometry

    def set_geometry(self, geometry):
        screen_width = gtk.gdk.screen_width()
        screen_height = gtk.gdk.screen_height()
        match = re.search("(\d+)x(\d+)", geometry)
        if match:
            width  = int(match.group(1))
            height = int(match.group(2))
            #print "width=%d height=%d" % (width, height)
            self.browser_win.resize(width, height)

        match = re.search("([+-]\d+)([+-]\d+)", geometry)
        if match:
            xoffset = int(match.group(1))
            yoffset = int(match.group(2))
            #print "xoffset=%d yoffset=%d" % (xoffset, yoffset)
            if xoffset >= 0:
                x = xoffset
            else:
                x = screen_width() + xoffset

            if yoffset >= 0:
                y = yoffset
            else:
                y = screen_height()+ yoffset
            
            x = max(x, 0)
            y = max(y, 0)
            # force window to be visible
            if x >= screen_width - 20:
                x = 0
            if y >= screen_height - 20:
                y = 0
            self.browser_win.move(x, y)

    def window_state_event_cb(self, window, event):
        if debug:
            log_gui.debug("window state event: %s", window_state_to_string(event.new_window_state))
        self.window_state = event.new_window_state

    def on_style_set(self, widget, previous_style):
        if debug:
            log_gui.debug("on_style_set:")
        self.update_alert_view()

    def configure_event_cb(self, window, event):
        if debug:
            log_gui.debug("configure event: x=%d y=%d width=%d height=%d",
                          event.x, event.y, event.width, event.height)

    def init_widgets(self):
        self.browser_win = gtk.Window()
        self.browser_win.set_position(gtk.WIN_POS_CENTER)
        self.browser_win.set_title(_("setroubleshoot browser"))
        self.browser_win.set_default_size(800, 700)
        icon_name = get_config('general','icon_name')
        self.browser_win.set_icon_name(icon_name)

        self.browser_win.connect('delete-event', self.on_close)

        self.browser_win.connect_after("realize", self.on_realize)
        self.browser_win.connect('window-state-event', self.window_state_event_cb)
        self.browser_win.connect('configure-event', self.configure_event_cb)
        self.browser_win.connect('style-set', self.on_style_set)

        self.create_ui()

        self.browser_vbox = gtk.VBox()
        self.browser_win.add(self.browser_vbox)
        self.browser_vbox.show()
        self.browser_vbox.pack_start(self.menubar, expand=False)
        self.browser_vbox.pack_start(self.toolbar, expand=False)
        if self.toolbar_visible:
            self.toolbar.show()
        else:
            self.toolbar.hide()
        self.action_group.get_action('ToggleToolbar').set_active(self.toolbar_visible)

        self.browser_vpane = gtk.VPaned()
        self.browser_vpane.show()
        self.browser_vbox.pack_start(self.browser_vpane)

        self.statusbar = BrowserStatusBar()
#        self.statusbar.show_all()
        self.browser_vbox.pack_start(self.statusbar, expand=False)

        # alert detail
        self.alert_detail_scrolled_window = gtk.ScrolledWindow()
        self.alert_detail_scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.browser_vpane.add2(self.alert_detail_scrolled_window)

        view, doc = self.create_htmlview(self.alert_detail_scrolled_window)
        doc.connect("link-clicked", self.link_clicked)
        self.detail_doc = doc
        self.detail_view = view
        self.alert_detail_scrolled_window.show_all()
        

        self.popup_menu = self.ui.get_widget('/PopupMenu')
        self.button_press_event = SignalConnection('button_press_event')
        self.button_press_event.connect(self.detail_view, self.on_button_press_event)

        self.visit_radio_action.set_current_value(self.VISIT_AUDIT)

    def on_report_bug(self, action):
        bug_report_url = get_config('help', 'bug_report_url')
        # FIXME - Should be specific to the alert
        launch_web_browser_on_url(bug_report_url)

    def link_clicked(self, doc, link):
        launch_web_browser_on_url(link)
        
    # --- Utilities ---

    def mark_delete(self, database, siginfo, value=True):
        user_data = siginfo.get_user_data(self.username)
        user_data.delete_flag = value
        database.set_user_data(siginfo.sig, self.username, 'delete_flag', value)


    # --- View Management ---

    def show(self):
        self.browser_win.present()

    def hide(self):
        self.browser_win.hide()

    def update_window_state(self, state):
        if debug:
            log_gui.debug("set_window_state() %s:%s",
                          state, window_state_to_string(state))

        if state & gtk.gdk.WINDOW_STATE_ICONIFIED:
            self.browser_win.iconify()
        else:
            self.browser_win.deiconify()

        if state & gtk.gdk.WINDOW_STATE_MAXIMIZED:
            self.browser_win.maximize()
        else:
            self.browser_win.unmaximize()

        if state & gtk.gdk.WINDOW_STATE_STICKY:
            self.browser_win.stick()

        if state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
            self.browser_win.fullscreen()
        else:
            self.browser_win.unfullscreen()

        if state & gtk.gdk.WINDOW_STATE_ABOVE:
            self.browser_win.set_keep_above(True)
        else:
            self.browser_win.set_keep_above(False)

        if state & gtk.gdk.WINDOW_STATE_BELOW:
            self.browser_win.set_keep_below(True)
        else:
            self.browser_win.set_keep_below(False)

        if state & gtk.gdk.WINDOW_STATE_WITHDRAWN:
            self.browser_win.hide()
        else:
            self.browser_win.show()

    def get_window_state(self):
        return window_state_to_string(self.window_state)
    
    def get_foreground_background_colors(self):
        style = self.browser_win.get_style()
        c = style.fg[gtk.STATE_NORMAL]
        foreground_color = "#%.2X%.2X%.2X" % (c.red / 256, c.green / 256, c.blue / 256)

        c = style.bg[gtk.STATE_NORMAL]
        background_color = "#%.2X%.2X%.2X" % (c.red / 256, c.green / 256, c.blue / 256)

        return foreground_color, background_color

    def create_htmlview(self, container):
        view = gtkhtml2.View()
        doc = gtkhtml2.Document()
        container.set_hadjustment(view.get_hadjustment())
        container.set_vadjustment(view.get_vadjustment())
        view.set_document(doc)
        container.add(view)

        return (view, doc)

    def mark_seen(self, database, siginfo):
        if self.displayed_siginfo == siginfo:
            user_data = siginfo.get_user_data(self.username)
            user_data.seen_flag = True
            database.set_user_data(siginfo.sig, self.username, 'seen_flag', True)

        self.mark_seen_timeout_event_id = None
        return False                    # False ==> do not call again, True ==> call again

    def idle_update_alert_view(self):
        if debug:
            log_gui.debug("idle_update_alert_view")
        self.update_alert_view()
        self.update_alert_view_idle_event_id = None

    def update_alert_view(self):
        if self.list_view is None: return
        n_selected_paths = self.list_view.selection.count_selected_rows()

        if n_selected_paths != 1:
            if debug:
                log_gui.debug("update_alert_view: %d selected paths, not single selection, clearing alert view",
                              n_selected_paths)
            self.clear_alert_view()
            return

        siginfo = self.list_view.get_selected_siginfos()[0]

        if debug:
            log_gui.debug("update_alert_view: siginfo=%s", siginfo.local_id)

        self.detail_doc.clear()
        self.detail_doc.open_stream("text/html")
        foreground_color, background_color = self.get_foreground_background_colors()
        html_body = siginfo.format_html(foreground_color, background_color)
        html_doc = html_document(html_body)

        if debug:
            #log_gui.debug(html_doc)
            pass

        self.detail_doc.write_stream(html_doc)
        self.detail_doc.close_stream()

        if self.displayed_siginfo != siginfo:
            # Displaying different alert than previously.

            # If we had a timeout pending from the previous alert then cancel it.
            if self.mark_seen_timeout_event_id is not None:
                gobject.source_remove(self.mark_seen_timeout_event_id)
                self.mark_seen_timeout_event_id = None

            # If this alert has not been seen start a timer to mark it seen
            user_data = siginfo.get_user_data(self.username)
            if not user_data.seen_flag:
                self.mark_seen_timeout_event_id = \
                    gobject.timeout_add(1000*self.seen_after_displayed_seconds,
                                        self.mark_seen, self.list_view.alert_data.database, siginfo)

        self.displayed_siginfo = siginfo

    # --- Data/View Management ---

    def set_visit_data(self, name, view_data):
        self.view_data_collection[name] = view_data
        
    def get_visit_data(self, name):
        return self.view_data_collection.get(name)

    def clear_alert_view(self):
        self.detail_doc.clear()
        self.alert_detail_scrolled_window.queue_draw()

    def do_visit(self, name):
        if debug:
            log_gui.debug("do_visit: %s", name)
        visit_data = self.get_visit_data(name)
        self.set_view_data(visit_data)

    def set_view_data(self, view_data):
        if debug:
            log_gui.debug("set_view_data: %s", view_data.name)

        self.selection_changed_signal.disconnect()

        previous = self.browser_vpane.get_child1()
        if previous is not None:
            self.browser_vpane.remove(previous)
        self.list_view = view_data.list_view
        self.browser_vpane.add1(self.list_view.scrolled_window)

        self.properties_changed_signal.connect(self.list_view, self.on_view_data_properties_changed)
        self.load_data_signal.connect         (self.list_view, self.on_load_data)

        self.alert_list_changed_signal.connect(self.list_view, self.on_alert_list_changed)

        self.action_group.get_action('HideDeleted').set_active(self.list_view.hide_deleted)
        self.action_group.get_action('HideQuiet').set_active(self.list_view.hide_quiet)

        # Column Visibility
        for column in range(1, NUM_COLUMNS):
            column_name = tv_column_info[column]['name']
            tv_column = self.list_view.tv_columns[column]
            self.action_group.get_action('Toggle%sColumn' % column_name).set_active(tv_column.get_visible())

        self.selection_changed_signal.connect(self.list_view.selection, self.on_selection_changed)
        self.update_alert_view()
        self.update_alert_count()
        self.update_visit_message(view_data.alert_data.database_properties.friendly_name)

    def update_visit_message(self, name):
        if name is None:
            self.statusbar.set_visit_message(_('None'))
        else:
            self.statusbar.set_visit_message(name)

    def update_alert_count(self):
        if self.list_view is None or self.list_view.view_model is None:
            text = '--/--'
        else:
            text = '%d/%d' % (len(self.list_view.base_model), len(self.list_view.view_model))
        self.statusbar.alert_count.set_text(text)

    # --- Selections ---

    def idle_restore_selection(self):
        if self.list_view is None: return
        self.list_view.restore_selection()
        self.restore_selection_idle_event_id = None

    def on_selection_changed(self, selection):
        if self.list_view is None: return
        model, selected_paths = selection.get_selected_rows()
        if debug:
            log_gui.debug("on_selection_changed(): paths=%s", ','.join([str(row) for row in selected_paths]))

        if len(selected_paths) > 0:
            self.list_view.last_selected_row = selected_paths[0]
        else:
            self.list_view.last_selected_row = 0

        if self.update_alert_view_idle_event_id is None:
            self.update_alert_view_idle_event_id = gobject.idle_add(self.idle_update_alert_view)

    # --- Data Handling ---

    def on_alert_list_changed(self, alert_list, model, type, iter):
        if debug:
            log_gui_data.debug("on_alert_list_changed: %s", type)
        if type == 'add':
            self.update_alert_count()
        elif type == 'delete':
            self.update_alert_count()
        elif type == 'modify':
            # If the row which changed is what is currently being displayed
            # in the alert detail window then update the alert view.

            siginfo = model.get_value(iter, PYOBJECT_COLUMN)
            if self.displayed_siginfo is None or self.displayed_siginfo.local_id == siginfo.local_id:
                self.update_alert_view()
        else:
            raise ProgramError(ERR_UNKNOWN_VALUE, "on_alert_list_changed: type = %s not recognized" % type)
            

    def on_view_data_properties_changed(self, list_view, alert_data, properties):
        if debug:
            log_gui.debug("on_view_data_properties_changed: %s %s", alert_data.name, properties)
        self.update_visit_message(properties.friendly_name)
        

    def progress_pulse(self):
        if self.load_in_progress:
            self.statusbar.progress.pulse()
            return True                 # call again
        else:
            return False                # do not call again

    def on_load_data(self, list_view, alert_data, state, errno, strerror):
        if debug:
            log_gui.debug('on_load_data: alert_data=%s state=%s errno=%d strerror=%s',
                          alert_data.name, state, errno, strerror)
        if state == 'start':
            self.statusbar.status.new_message(StatusMessage.INFO_TYPE, StatusMessage.MEDIUM_PRIORITY, _('loading data ...'), message_name='load_data')
            self.load_in_progress = True
            self.statusbar.progress.pulse()
            gobject.timeout_add(100, self.progress_pulse)
            self.statusbar.progress.set_text(_("Load %s" % alert_data.name))
        elif state == 'end':
            self.load_in_progress = False
            if self.list_view is not None: self.list_view.restore_selection()
            self.statusbar.progress.set_text("")
            self.statusbar.progress.set_fraction(0)
            if errno == 0:
                self.statusbar.status.new_message(StatusMessage.INFO_TYPE, StatusMessage.MEDIUM_PRIORITY, _('loading data done'), 15, message_name='load_data')
            else:
                self.statusbar.status.remove_message_by_name('load_data')
                self.statusbar.error.new_message(StatusMessage.ERROR_TYPE, StatusMessage.MEDIUM_PRIORITY, _("load data failed: %s") % (strerror), 15, message_name='load_data')

        self.update_alert_count()

    def save_siginfos(self, file, siginfos):
        text = ''
        try:
            fd=open(file, "w")
        except IOError, e:
            raise ProgramError(ERR_FILE_OPEN, detail="%s, %s" % (filepath, e.strerror))

        i = 0
        for siginfo in siginfos:
            text += siginfo.format_text()
            i += 1
            if i < len(siginfos):
                text += "\f\n"      # break between multiple siginfo's

        fd.write(text)
        fd.close()
        
    # --- Event Handlers ---

    def on_button_press_event(self, treeview, event):
        if debug:
            log_gui.debug("on_button_press_event(): button=%s", event.button)
        if event.button == 3:
            self.popup_menu.popup( None, None, None, event.button, event.time)
            return True

    def on_copy(self, action):
        text = gtkhtml2.html_selection_get_text(self.detail_view)

        if debug:
            log_gui.debug("on_copy(): text=%s", text)

        if text is not None:
            self.clipboard.set_text(text)
        return True     # return True ==> handled, False ==> propagate

    def on_copy_alert(self, action):
        if self.list_view is None: return
        text = ''
        siginfos = self.list_view.get_selected_siginfos()
        i = 0
        for siginfo in siginfos:
            text += siginfo.format_text()
            i += 1
            if i < len(siginfos):
                text += "\n"            # break between multiple siginfo's
        self.clipboard.set_text(text)
        return True     # return True ==> handled, False ==> propagate
        

    def on_close(self, *args):
        if self.window_delete_hides:
            self.hide()
            return True
        else:
            gtk.main_quit()
        return True     # return True ==> handled, False ==> propagate
            
    def on_connection_pending_retry(self, retry, seconds_pending, alert_client):
        if seconds_pending <= 0.005:
            self.statusbar.set_connected_state(False)
            self.statusbar.status.new_message(StatusMessage.INFO_TYPE, StatusMessage.MEDIUM_PRIORITY, _('retrying connection'), message_name='pending_retry')
        else:
            minutes = int(seconds_pending) / 60
            seconds = seconds_pending % 60
            if minutes:
                pending = _("%d minutes %.1f seconds") % (minutes, seconds)
            else:
                pending = _("%.0f seconds") % (seconds)
            self.statusbar.set_connected_state(False)
            self.statusbar.status.new_message(StatusMessage.INFO_TYPE, StatusMessage.MEDIUM_PRIORITY, _('retrying connection in %s' % pending), message_name='pending_retry')
            

    def on_user_help(self, action):
        help_url = get_config('help','help_url')
        launch_web_browser_on_url(help_url)

    def on_about(self, action):
        pkg_name    = get_config('general', 'pkg_name')
        pkg_version = get_config('general', 'pkg_version')
        project_url = get_config('general', 'project_url')

        def email_clicked(dialog, addr, user_data=None):
            launch_web_browser_on_url('mailto:' + addr)

        def link_clicked(dialog, url, user_data=None):
            launch_web_browser_on_url(url)

        gtk.about_dialog_set_email_hook(email_clicked)
        gtk.about_dialog_set_url_hook(link_clicked)

        dlg = gtk.AboutDialog()
        dlg.set_name(pkg_name)
        dlg.set_version(pkg_version)
        dlg.set_copyright(_("Copyright 2006-2007 Red Hat, Inc."))
        dlg.set_comments(_("A user friendly tool to diagnose and monitor SELinux AVC denials"))
        dlg.set_license("GPL v2")
        dlg.set_website(project_url)
        dlg.set_authors(["John Dennis <jdennis@redhat.com>",
                         "Daniel Walsh <dwalsh@redhat.com>",
                         "Karl MacMillon <kmacmil@redhat.com>"])

        icon_name = get_config('general','icon_name')
        dlg.set_logo_icon_name(icon_name)

        
        dlg.run()
        dlg.destroy()

    def on_save_as(self, action):
        dialog = gtk.FileChooserDialog(_("Save.."),
                                       None,
                                       gtk.FILE_CHOOSER_ACTION_SAVE,
                                       (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                        gtk.STOCK_SAVE, gtk.RESPONSE_OK))
        dialog.set_default_response(gtk.RESPONSE_OK)

        if self.default_save_folder is not None:
            dialog.set_current_folder(self.default_save_folder)
        if self.default_save_filename is not None:
            dialog.set_current_name(self.default_save_filename)

        filter = gtk.FileFilter()
        filter.set_name("All files")
        filter.add_pattern("*")
        dialog.add_filter(filter)

        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            self.default_save_folder = dialog.get_current_folder()
            self.default_save_filename = os.path.basename(dialog.get_filename())
            try:
                if self.list_view is not None:
                    self.save_siginfos(dialog.get_filename(),
                                       self.list_view.get_selected_siginfos())
            except ProgramError, e:
                self.display_error(e.strerror)

        dialog.destroy()

    def on_connect_to(self, action):
        connection = None
        dlg = OpenConnectionDialog()
        response = dlg.run()
        if response == gtk.RESPONSE_OK:
            connection = dlg.get_connection()
            dlg.destroy()
            if debug:
                log_communication.debug("on_connect_to: %s", connection)

            # FIXME: we need a more centralized way to handle retry's

            self.statusbar.status.remove_message_by_name('pending_retry')
            self.server.retry_connection_if_closed = False
            if self.server.connection_state.flags & ConnectionState.RETRY:
                self.server.connection_retry.stop()
            self.server.connection_state.update(0, ConnectionState.RETRY)
            self.server.close_connection()
            if connection is None:
                self.statusbar.error.remove_all_messages()
            else:
                self.server.retry_connection_if_closed = False
                self.server.open(connection)
        else:
            dlg.destroy()
        

    def on_open_logfile(self, action):
        dlg = ScanLogfileDialog()
        response = dlg.run()
        if response == gtk.RESPONSE_OK:
            database = dlg.get_database()
            database_rpc = SETroubleshootDatabaseLocal(database)
            alert_data = AlertData('logfile', database_rpc)

            # FIXME: Is there a better way to populate the model?
            for siginfo in database.sigs.signature_list:
                alert_data.signatures_updated(database_rpc, 'add', siginfo.local_id)

            self.logfile_view_data.bind_data(alert_data)

            self.action_group.get_action('ViewLogfile').set_sensitive(True)
            self.visit_radio_action.set_current_value(self.VISIT_LOGFILE)
            if self.restore_selection_idle_event_id is None:
                self.restore_selection_idle_event_id = gobject.idle_add(self.idle_restore_selection)

        dlg.destroy()

    def on_edit_email_alert_list(self, action):
        def query_email_recipients_callback(recipients):
            if debug:
                log_gui.debug("query_email_recipients_callback: %s", recipients)

            dlg = EmailDialog(recipients, parent=self.browser_win)
            new_recipients = dlg.run()
            if debug:
                log_gui.debug("EmailDialog returned: %s", str(new_recipients))

            if new_recipients is not None:
                self.server.set_email_recipients(new_recipients)

        async_rpc = self.server.query_email_recipients()
        async_rpc.add_callback(query_email_recipients_callback)

    def on_print(self, action):

        def draw_page(operation, context, page_number, siginfos):
            try:
                siginfo = siginfos[page_number]
            except IndexError:
                return
            text = siginfo.format_text()

            cr = context.get_cairo_context()
            layout = context.create_pango_layout()
            layout.set_font_description(pango.FontDescription('monospace 10'))
            layout.set_text(text)
            cr.show_layout(layout)

        if self.list_view is not None:
            siginfos = self.list_view.get_selected_siginfos()
        else:
            siginfos = []

        dialog = gtk.PrintOperation()
        if self.print_settings != None:
            dialog.set_print_settings(self.print_settings)
        dialog.connect("draw_page", draw_page, siginfos)
        dialog.set_n_pages(len(siginfos))

        res = dialog.run(gtk.PRINT_OPERATION_ACTION_PRINT_DIALOG)
        if res == gtk.PRINT_OPERATION_RESULT_ERROR:
            error_dialog = gtk.MessageDialog(self.browser_win, gtk.DIALOG_DESTROY_WITH_PARENT,
                                             gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
                                             "Error printing file:\n")
            error_dialog.connect("response", lambda w,id: w.destroy())
            error_dialog.show()
        elif res == gtk.PRINT_OPERATION_RESULT_APPLY:
            self.print_settings = dialog.get_print_settings()
        return True     # return True ==> handled, False ==> propagate
      
    def on_hide_deleted(self, action):
        if self.list_view is None: return
        self.list_view.hide_deleted = action.get_active()
        if debug:
            log_gui.debug("on_hide_deleted() hide_deleted=%s", self.list_view.hide_deleted)
        if self.list_view.filter_model is not None:
            self.list_view.filter_model.refilter()

    def on_hide_quiet(self, action):
        if self.list_view is None: return
        self.list_view.hide_quiet = action.get_active()
        if debug:
            log_gui.debug("on_hide_quiet() hide_quiet=%s", self.list_view.hide_quiet)
        if self.list_view.filter_model is not None:
            self.list_view.filter_model.refilter()

    def on_visit_change(self, action, current):
        visit_name = self.map_visit_num_to_name[action.get_current_value()]
        if debug:
            log_gui.debug("on_visit_change: %s", visit_name)
        self.do_visit(visit_name)

    def on_delete(self, action):
        if self.list_view is None: return
        database = self.list_view.alert_data.database
        for siginfo in self.list_view.get_selected_siginfos():
            self.mark_delete(database, siginfo, True)
        return True     # return True ==> handled, False ==> propagate

    def on_expunge(self, action):
        if self.list_view is None: return
        if self.list_view.alert_data is None: return

        database = self.list_view.alert_data.database
        for siginfo in self.list_view.alert_data.get_siginfos():
            user_data = siginfo.get_user_data(self.username)
            if user_data.delete_flag:
                database.delete_signature(siginfo.sig)

    def on_undelete(self, action):
        if self.list_view is None: return
        database = self.list_view.alert_data.database
        for siginfo in self.list_view.get_selected_siginfos():
            self.mark_delete(database, siginfo, False)
        return True     # return True ==> handled, False ==> propagate

    def on_select_all(self, action):
        if self.list_view is None: return
        self.list_view.selection.select_all()
        return True     # return True ==> handled, False ==> propagate
        
    def on_select_none(self, action):
        if self.list_view is None: return
        self.list_view.selection.unselect_all()
        return True     # return True ==> handled, False ==> propagate
        
    def on_toggle_toolbar(self, action):
        self.toolbar_visible = action.get_active()
        if debug:
            log_gui.debug("toggle_toolbar: %s", ('hide', 'show')[self.toolbar_visible])
        if self.toolbar_visible:
            self.toolbar.show()
        else:
            self.toolbar.hide()

    def on_toggle_column_visibility(self, action, column):
        if self.list_view is None: return
        tv_column = self.list_view.tv_columns[column]
        is_active = action.get_active()
        is_visible = tv_column.get_visible()
        if debug:
            log_gui.debug("toggle_column_visibility(%s): should be active=%s, currently is visible=%s",
                          tv_column.get_title(), is_active, is_visible)
        tv_column.set_visible(is_active)

    # --- Interaction Dialogs ---

    def display_error(self, message):
        self.statusbar.error.new_message(StatusMessage.ERROR_TYPE, StatusMessage.MEDIUM_PRIORITY, message, 15)
        return display_error(message)
    
    # --- Status Notification ---

    def on_async_error(self, alert_data, method, errno, strerror):
        message_name = None
        time_to_live = 15
        if errno in [ERR_USER_PROHIBITED, ERR_NOT_AUTHENTICATED]:
            message_name ='not_authenticated'
            time_to_live = None
        self.statusbar.error.new_message(StatusMessage.ERROR_TYPE, StatusMessage.MEDIUM_PRIORITY, strerror, time_to_live, message_name)


    def update_connection_state(self, connection, connection_state):
        if debug:
            log_gui.debug("update_connection_state: state=%s", connection_state)

        if connection_state.flags & ConnectionState.OPEN:
            self.statusbar.set_connected_state(True)
            self.statusbar.status.new_message(StatusMessage.INFO_TYPE, StatusMessage.MEDIUM_PRIORITY, _('connected'), 15, message_name='connection_state')
            self.statusbar.error.remove_all_messages()

            if not (connection_state.flags & ConnectionState.AUTHENTICATED):
                self.statusbar.status.new_message(StatusMessage.INFO_TYPE, StatusMessage.MEDIUM_PRIORITY, _('open but not logged on'), 15, message_name='connection_state')
        else:
            self.statusbar.set_connected_state(False)
            self.statusbar.status.new_message(StatusMessage.INFO_TYPE, StatusMessage.MEDIUM_PRIORITY, _('connection closed'), 15, message_name='connection_state')

        if connection_state.flags & ConnectionState.ERROR:
            self.statusbar.set_connected_state(False)
            errno, strerror = connection_state.get_result()
            self.statusbar.error.new_message(StatusMessage.ERROR_TYPE, StatusMessage.HIGH_PRIORITY, _('connection failed at %s, %s') % (connection.socket_address.get_friendly_name(), strerror), message_name='connection_state')

        if connection_state.flags & ConnectionState.HUP:
            self.statusbar.set_connected_state(False)
            self.statusbar.error.new_message(StatusMessage.ERROR_TYPE, StatusMessage.HIGH_PRIORITY, _('connection lost to %s') % (connection.socket_address.get_friendly_name()), message_name='connection_state')

        if connection_state.flags & ConnectionState.TIMEOUT:
            self.statusbar.set_connected_state(False)
            self.statusbar.status.new_message(StatusMessage.INFO_TYPE, StatusMessage.MEDIUM_PRIORITY, _('connection timed out'), 15, message_name='connection_state')

        if not (connection_state.flags & ConnectionState.RETRY):
            self.statusbar.status.remove_message_by_name('pending_retry')

    def on_connection_state_change(self, connection, connection_state, flags, flags_added, flags_removed):
        if debug:
            log_program.debug("%s.on_connection_state_change: connection_state=%s flags_added=%s flags_removed=%s address=%s",
                              self.__class__.__name__, connection_state,
                              connection_state.flags_to_string(flags_added), connection_state.flags_to_string(flags_removed),
                              connection.socket_address)

        if flags_removed & ConnectionState.OPEN:
            self.audit_data.clear()
            self.audit_data.set_properties(None)
            self.statusbar.set_connected_state(False)
            self.statusbar.status.new_message(StatusMessage.INFO_TYPE, StatusMessage.MEDIUM_PRIORITY, _('connection closed'), 15, message_name='connection_state')

        if flags_added & ConnectionState.OPEN:
            self.statusbar.set_connected_state(True)
            self.statusbar.status.new_message(StatusMessage.INFO_TYPE, StatusMessage.MEDIUM_PRIORITY, _('connected'), 15, message_name='connection_state')
            self.statusbar.error.remove_all_messages()

        if flags_added & ConnectionState.AUTHENTICATED:
            self.audit_data.clear()
            self.audit_data.load_data()
            self.statusbar.set_connected_state(True)
            self.statusbar.status.new_message(StatusMessage.INFO_TYPE, StatusMessage.MEDIUM_PRIORITY, _('authenticated'), 15, message_name='connection_state')

        if flags_added & ConnectionState.TIMEOUT:
            self.statusbar.set_connected_state(True)
            self.statusbar.status.new_message(StatusMessage.INFO_TYPE, StatusMessage.MEDIUM_PRIORITY, _('connection timed out'), 15, message_name='connection_state')

        if flags_added & ConnectionState.HUP:
            self.statusbar.set_connected_state(False)
            self.statusbar.error.new_message(StatusMessage.ERROR_TYPE, StatusMessage.HIGH_PRIORITY, _('connection lost to %s)') % (connection.socket_address.get_friendly_name()), message_name='connection_state')

        if flags_added & (ConnectionState.ERROR):
            errno, strerror = connection_state.get_result()
            self.statusbar.set_connected_state(False)
            self.statusbar.error.new_message(StatusMessage.ERROR_TYPE, StatusMessage.HIGH_PRIORITY, _('connection failed at %s, %s') % (connection.socket_address.get_friendly_name(), strerror), message_name='connection_state')


        if flags_removed & ConnectionState.RETRY:
            self.statusbar.status.remove_message_by_name('pending_retry')

#------------------------------------------------------------------------------

class OpenConnectionDialog(gtk.Dialog):
    def __init__(self):
        gtk.Dialog.__init__(self, title=_('Open Connection'),
                            buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                     gtk.STOCK_OK, gtk.RESPONSE_OK))

        self.connection_choice = None

        table = gtk.Table(3, 2)
        
        self.vbox.pack_start(table)

        self.disconnect_radiobutton = gtk.RadioButton(None, _('No Connection'))
        self.disconnect_radiobutton.connect('toggled', self.on_connection_choice_changed, 'disconnect')
        table.attach(self.disconnect_radiobutton, left_attach=0, right_attach=1, top_attach=0, bottom_attach=1)
        radiobutton_group = self.disconnect_radiobutton

        self.local_radiobutton = gtk.RadioButton(radiobutton_group, _('Local Server'))
        self.local_radiobutton.connect('toggled', self.on_connection_choice_changed, 'local')
        table.attach(self.local_radiobutton, left_attach=0, right_attach=1, top_attach=1, bottom_attach=2)

        self.remote_radiobutton = gtk.RadioButton(radiobutton_group, _('Remote Server'))
        self.remote_radiobutton.connect('toggled', self.on_connection_choice_changed, 'remote')
        table.attach(self.remote_radiobutton, left_attach=0, right_attach=1, top_attach=2, bottom_attach=3)

        self.remote_addr_entry = gtk.Entry()
        self.remote_addr_entry.connect('activate', self.on_remote_addr_activate)
        self.remote_addr_entry.set_sensitive(False)
        table.attach(self.remote_addr_entry, left_attach=1, right_attach=2, top_attach=2, bottom_attach=3)

        self.on_connection_choice_changed(radiobutton_group, 'local')

        self.vbox.show_all()
        self.local_radiobutton.set_active(True)

    def on_connection_choice_changed(self, radiobutton, choice):
        if radiobutton.get_active():
            self.connection_choice = choice
            log_gui.debug("on_connection_choice_changed: %s", self.connection_choice)

            if self.connection_choice == 'remote':
                self.remote_addr_entry.set_sensitive(True)
            else:
                self.remote_addr_entry.set_sensitive(False)


    def on_remote_addr_activate(self, widget):
        self.emit('response', gtk.RESPONSE_OK)

    def get_connection_choice(self):
        group = self.local_radiobutton.get_group()
        for radiobutton in group:
            if radiobutton.get_active():
                return radiobutton
        return None

    def get_connection(self):
        server_address = None
        cfg_section = 'client_connect_to'

        if self.connection_choice == 'local':
            server_address = get_local_server_socket_address()
        elif self.connection_choice == 'remote':
            remote_addr = self.remote_addr_entry.get_text().strip()
            server_address = SocketAddress('inet', remote_addr)
        elif self.connection_choice == 'disconnect':
            return None

        return server_address


#-----------------------------------------------------------------------------

# -- Main --

if __name__ == "__main__":
    import getopt

    def usage():
        print '''
        -h help
'''

    try:
        opts, args = getopt.getopt(sys.argv[1:], "h", ["help"])
    except getopt.GetoptError:
        # print help information and exit:
        usage()
        sys.exit(2)

    for o, a in opts:
        if o in ("-h", "--help"):
            usage()
            sys.exit()

    browser_applet = BrowserApplet()
    browser_applet.show()

    gtk.main()

Anon7 - 2021