uiutils/windows.py
author Pierre-Yves David <pierre-yves.david@logilab.fr>
Fri, 13 Jun 2008 15:37:48 +0200
changeset 5 ee38ef554863
parent 0 7710b138d4eb
permissions -rw-r--r--
remove apycot.ini

# -*- coding: ISO-8859-1 -*-
# Copyright (c) 2004 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# 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.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""some user interface utilities"""

__revision__ = '$Id: windows.py,v 1.10 2004-12-13 17:22:12 syt Exp $'

import commands
import os.path as osp

import gtk, gobject
import gtk.glade

from pigg.utils import update_gui, confirm
from logilab.common import logservice

from oobrother import get_path, OOBLogger
from oobrother.uiutils.basemixins import DelegatedWindowMixIn, TreeViewMixIn, \
     TextWindowMixIn, MenuControlledWindowMixIn, PluggableFrameMixIn, \
     WindowMixIn
from oobrother.uiutils.trees import init_treeview_columns
from oobrother.config_tools import PluggableConfig, build_config_model, \
     configuration_models, GlobalConfigurationModel


__metaclass__ = type


class ConfigurableListWindow(DelegatedWindowMixIn, TreeViewMixIn):
    """a user configurable list window

    Handling of a max items number is triggered by the presence of a
    "default_max_items" attribute, giving a positive int a default
    maximum number of items in the list.
    
    FIXME:
    * afficher le nombre de lignes dans une barre d'etat
    """
    options = ()
    
    def __init__(self, config_file, store_desc, column_titles, **kwargs):
        self.options += (
            ('columns', {'type': 'multiple_choice', 'group': 'display',
                         'choices': column_titles, 'default': column_titles}),
            )
        if hasattr(self, 'default_max_items'):
            self.options += (
                ('max-items', {'type': 'int', 'group': 'display',
                               'default': self.default_max_items}),
                )
        self.cfg = PluggableConfig(self.name, config_file, self.options)
        self.cfg.model.register_commit_notification(self.cb_config_changed)
        self.treeview = gtk.TreeView()
        super(ConfigurableListWindow, self).__init__(**kwargs)
        self.column_titles = column_titles
        self.__sort_info = None
        self.__kwargs = None
        treestore = gtk.ListStore(*store_desc)
        scr = gtk.ScrolledWindow()
        scr.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.__vbox = vbox = gtk.VBox(False)
        vbox.pack_end(scr)
        self.win.add(vbox)
        self.treeview.set_model(treestore)
        scr.add(self.treeview)
        self.win.connect('delete-event', self.hide)

    def subconfiguration(self):
        """return a list of configuration nodes for subconfiguration
        (i.e. nothing here)
        """
        return []

    def init_columns(self, displayed, sort_info=None, **kwargs):
        """init the displayed tree view column"""
        if self.__kwargs is None:
            # remember first call arguments
            self.__sort_info = sort_info
            self.__kwargs = kwargs
        sort_info = sort_info or self.__sort_info
        init_treeview_columns(self.treeview, self.column_titles,
                              displayed, self.__sort_info, **self.__kwargs)

    # FIXME: should maybe go in DelegatedWindowMixIn
    def create_toolbar(self):
        """insert and return a default toolbar with a Clear button"""
        toolbar = gtk.Toolbar()
        toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
        toolbar.set_style(gtk.TOOLBAR_BOTH)
        clear_button = gtk.ToolButton(gtk.STOCK_CLEAR)
        clear_button.connect('clicked', self.clear)
        tip =  gtk.Tooltips()
        tip.set_tip(clear_button, _("clear all messages in the window"), 'Private')
        clear_button.set_tooltip(tip)
        toolbar.insert(clear_button, 0)
##         toolbar.append_widget(clear_button, "clear all messages in the window",
##                               'Private')
        self.__vbox.pack_start(toolbar, False, False)
        return toolbar
        
    def append_line(self, line, scroll_to_line=False, update=True):
        """append a line to the store"""
        # be sure not to exceed the message queue size
        store = self.treeview.get_model()
        max_item = self.cfg.get('max-items')
        if max_item and len(store) == max_item:
            first_line_iter = store.get_iter((0,))
            store.remove(first_line_iter)
        treeiter = store.append(line)
        if scroll_to_line:
            path = store.get_path(treeiter)
            self.treeview.scroll_to_cell(path)
        if update:
            update_gui()

    def cb_config_changed(self):
        """the configuration has changed, the list columns may have changed"""
        self.init_columns(self.cfg['columns'])


class ConfigurationWindow(WindowMixIn, MenuControlledWindowMixIn, PluggableFrameMixIn):
    """the configuration window is used to edit the application and
    plugins configurations
    """
    
    def __init__(self, configurables):
        super(ConfigurationWindow, self).__init__()
        self.widgets = gtk.glade.XML(get_path('glade'), 'config_window', 'oobrother')
        self.win = self.widgets.get_widget('config_window')
        self.win.connect('response', self.cb_response)
        self.win.set_default_size(600, 500)
        self.set_title('OOBrother - Configuration')
        self.treeview = self.widgets.get_widget('config_treeview')
        treeselection = self.treeview.get_selection()
        treeselection.set_select_function(self.selection_changed)
        self.plugin_frame = self.widgets.get_widget('config_frame')
        # init columns / set model
        init_treeview_columns(self.treeview, ['Section'])
        treestore = build_config_model(configurables)
        self.treeview.set_model(treestore)
        # main callbacks
        # handlers = {'on_config_treeview_button_press_event' :
        #             self.treeview_button_pressed,
        #             #'response', self.ctrl.cb_response,
        #             }
        # self.widgets.signal_autoconnect(handlers)
        self._model = GlobalConfigurationModel(
            configuration_models(configurables))
        
    def selection_changed(self, path):#selection, model, path, is_selected):
        """selection-changed callback, sets the plugin widget"""
        model = self.treeview.get_model()
        configurable = model[path][1]
        if configurable.cfg and configurable.cfg.wdg:
            # FIXME
            configurable.cfg.model.notify_all()
        self.set_plugin_widget(configurable.cfg and configurable.cfg.wdg)
        return gtk.TRUE

    def cb_response(self, widget, response):
        """handle 'response' events"""
        if response ==  gtk.RESPONSE_OK:
            self._model.commit()
            widget.hide()
        elif response ==  gtk.RESPONSE_APPLY:
            self._model.commit()
        elif response ==  gtk.RESPONSE_HELP:
            pass
        elif response ==  gtk.RESPONSE_CANCEL:
            return self.close_window(widget)
        elif response ==  gtk.RESPONSE_DELETE_EVENT:
            return self.close_window(widget)
        else:
            assert False, 'UNKOWN RESPONSE %s' % response

    def close_window(self, widget):
        """close the window if there is no pending modifications, else
        ask for confirmation
        """
        if self._model.get_modifications():
            if confirm('There are some unsaved modifications. Close anyway ?'):
                # FIXME: restore original values ?
                self._model.rollback()
                widget.hide()
        else:
            widget.hide()
        return gtk.TRUE


class LogServiceWindow(MenuControlledWindowMixIn, ConfigurableListWindow):
    """a window to display logged messages"""
    name = 'messages'
    options = (('log-threshold', {'type': 'int', 'default': LOG_DEBUG}),
               )
    default_max_items = 100
    
    def __init__(self):
        super(LogServiceWindow, self).__init__(
            config_file='messages_display.ini',
            store_desc=(gobject.TYPE_STRING, # time
                        gobject.TYPE_STRING, # message type
                        gobject.TYPE_STRING, # message
                        gobject.TYPE_STRING, # color
                        ),
            column_titles=('Time', 'Type', 'Message'))
        self.set_title('OOBrother - Log messages')
        self.win.set_default_size(600, 200)
        self.create_toolbar()
        self.init_columns(self.cfg['columns'], foreground=3)
        logservice.init_log(self.cfg['log-threshold'], logger=OOBLogger(self))



class SystemCommandWindow(TextWindowMixIn, MenuControlledWindowMixIn):
    """a mixin extending the TextWindowMixIn to add a `execute` method
    that may by use to execute an arbitrary command and get its output
    in the text view.
    """

    def __init_(self, **kwargs):
        super(SystemWindow, self).__init__(**kwargs)
        self.set_title('OOBrother - Simple Terminal')
        self.win.set_size_request(600, 400)

    def execute(self, command):
        """execute a python file"""
        self.write('>>> %s\n' % command)
        status, output = commands.getstatusoutput(command)
        for line in output.splitlines():
            self.write(line + '\n')
        return status
    
    def flush(self):
        """from the file interface"""
        update_gui()

try:
    import vte

    class SystemWindow(DelegatedWindowMixIn, MenuControlledWindowMixIn): # ConfigurableListWindow, MenuControlledWindowMixIn):
        """a terminal emulator"""
        name = 'ooterm'
        options = (('font', {'type' : 'font', 'default' : "fixed 10"}),
                   ('background', {'type' : 'file', 'default' : None}),
                   ('foreground', {'type' : 'color', 'default': '#000000'}),
                   )
        def __init__(self, **kwargs):
            super(SystemWindow, self).__init__(**kwargs)
            self.cfg = PluggableConfig(self.name, 'ooterm_display.ini',
                                       self.options)
            self.cfg.model.register_commit_notification(self.cb_config_changed)
            self.set_title('OOBrother - Terminal')
            self._term = vte.Terminal()
            self._term.set_emulation('xterm')
            self._term.fork_command()
            self._term.set_size_request(200, 200)
            self.win.set_default_size(600, 400)
            self.win.add(self._term)
            self.win.connect('delete-event', self.hide)
            # Update term prefs with initial config
            self.cb_config_changed()

        def subconfiguration(self):
            """return a list of configuration nodes for subconfiguration
            (i.e. nothing here)
            """
            return []

        def execute(self, command):
            """execute command"""
            self.show()
            self.win.grab_focus()
            self._term.feed_child(command + '\n')

        def _set_font(self, font_name):
            """uses <font_name> to set terminal's font"""
            import pango
            font_desc = pango.FontDescription(font_name)
            # FIXME: stretch is not taken in account !
            font_desc.set_stretch(pango.STRETCH_ULTRA_EXPANDED)
            self._term.set_font_full(font_desc, True)

        def cb_config_changed(self):
            """config has changed, update terminal preferences"""
            pix_file = self.cfg['background']
            if pix_file is not None:
                self._term.set_background_image_file(pix_file)
                self._term.set_background_saturation(0.2)
            font_name = self.cfg['font']
            self._set_font(font_name)
            gdk_color = gtk.gdk.color_parse(self.cfg['foreground'])
            self._term.set_color_foreground(gdk_color)
            
except ImportError:
    log(LOG_WARN, 'python-vte not found, full shell won\'t be available')
    SystemWindow = SystemCommandWindow