#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007  Donald N. Allingham
#
# 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
#

# $Id: DisplayState.py 10103 2008-02-24 13:55:55Z acraphae $

#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
from cStringIO import StringIO
from gettext import gettext as _

#-------------------------------------------------------------------------
#
# set up logging
#
#-------------------------------------------------------------------------
import logging
_LOG = logging.getLogger(".DisplayState")

#-------------------------------------------------------------------------
#
# GNOME python modules
#
#-------------------------------------------------------------------------
import gtk
from gtk import glade
import gobject

#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
import gen.utils
import Config
from BasicUtils import name_displayer
import const
import ManagedWindow
from PluginUtils import _PluginMgr

DISABLED = -1

#-------------------------------------------------------------------------
#
# History manager
#
#-------------------------------------------------------------------------
class History(gen.utils.Callback):
    """ History manages the objects of a certain type that have been viewed, 
        with ability to go back, or forward. 
        When accessing an object, it should be pushed on the History.
    """

    __signals__ = {
        'changed'      : (list, ), 
        'menu-changed' : (list, ), 
        }

    def __init__(self):
        gen.utils.Callback.__init__(self)
        self.clear()

    def clear(self):
        """
        Cleares the history, resetting the values back to their defaults
        """
        self.history = []
        self.mhistory = []
        self.index = -1
        self.lock = False

    def remove(self, handle, old_id=None):
        """
        Remove a handle from the history list
        """
        if old_id:
            del_id = old_id
        else:
            del_id = handle

        history_count = self.history.count(del_id)
        for c in range(history_count):
            self.history.remove(del_id)
            self.index -= 1
        
        mhc = self.mhistory.count(del_id)
        for c in range(mhc):
            self.mhistory.remove(del_id)
        self.emit('changed', (self.history, ))
        self.emit('menu-changed', (self.mhistory, ))

    def push(self, handle):
        """
        Pushes the handle on the history stack
        """
        self.prune()
        if len(self.history) == 0 or handle != self.history[-1]:
            self.history.append(handle)
            if handle in self.mhistory:
                self.mhistory.remove(handle)
            self.mhistory.append(handle)
            self.index += 1
        self.emit('menu-changed', (self.mhistory, ))
        self.emit('changed', (self.history, ))

    def forward(self, step=1):
        """
        Moves forward in the history list
        """
        self.index += step
        handle = self.history[self.index]
        if handle not in self.mhistory:
            self.mhistory.append(handle)
            self.emit('menu-changed', (self.mhistory, ))
        return str(self.history[self.index])

    def back(self, step=1):
        """
        Moves backward in the history list
        """
        self.index -= step
        try:
            handle = self.history[self.index]
            if handle not in self.mhistory:
                self.mhistory.append(handle)
                self.emit('menu-changed', (self.mhistory, ))
            return str(self.history[self.index])
        except IndexError:
            return u""
        
    def present(self):
        """
        return the person handle that is now active in the history
        """
        try :
            if self.history :
                return self.history[self.index]
            else:
                return u""
        except IndexError:
            return u""
        
    def at_end(self):
        """
        returns True if we are at the end of the history list
        """
        return self.index+1 == len(self.history)

    def at_front(self):
        """
        returns True if we are at the front of the history list
        """
        return self.index <= 0

    def prune(self):
        """
        Truncates the history list at the current object.
        """
        if not self.at_end():
            self.history = self.history[0:self.index+1]


#-------------------------------------------------------------------------
#
# Recent Docs Menu
#
#-------------------------------------------------------------------------

_RCT_TOP = '<ui><menubar name="MenuBar"><menu action="FileMenu"><menu action="OpenRecent">'
_RCT_BTM = '</menu></menu></menubar></ui>'

import RecentFiles
import os

class RecentDocsMenu:
    def __init__(self, uistate, state, fileopen):
        self.action_group = gtk.ActionGroup('RecentFiles')
        self.active = DISABLED
        self.uistate = uistate
        self.uimanager = uistate.uimanager
        self.fileopen = fileopen
        self.state = state

    def load(self, item):
        filename = item.get_path()
        self.fileopen(filename)

    def build(self):
        buf = StringIO()
        buf.write(_RCT_TOP)
        gramps_rf = RecentFiles.RecentFiles()

        count = 0
        
        if self.active != DISABLED:
            self.uimanager.remove_ui(self.active)
            self.uimanager.remove_action_group(self.action_group)
            self.action_group = gtk.ActionGroup('RecentFiles')
            self.active = DISABLED
            
        actions = []
        rfiles = gramps_rf.gramps_recent_files
        rfiles.sort(by_time)

        new_menu = gtk.Menu()

        for item in rfiles:
            try:
                title = item.get_name().replace('_', '__')
                filename = os.path.basename(item.get_path())
                action_id = "RecentMenu%d" % count
                buf.write('<menuitem action="%s"/>' % action_id)
                actions.append((action_id, None, title, None, None, 
                                make_callback(item, self.load)))
                mitem = gtk.MenuItem(title)
                mitem.connect('activate', make_callback(item, self.load))
                mitem.show()
                new_menu.append(mitem)
            except RuntimeError:
                pass    # ignore no longer existing files
            
            count += 1
        buf.write(_RCT_BTM)
        self.action_group.add_actions(actions)
        self.uimanager.insert_action_group(self.action_group, 1)
        self.active = self.uimanager.add_ui_from_string(buf.getvalue())
        self.uimanager.ensure_update()
        buf.close()

        if len(rfiles) > 0:
            new_menu.show()
            self.uistate.set_open_recent_menu(new_menu)

def make_callback(val, func):
    return lambda x: func(val)

def by_time(first, second):
    return cmp(second.get_time(), first.get_time())


from GrampsLogger import RotateHandler

class WarnHandler(RotateHandler):

    def __init__(self, capacity, button):
        RotateHandler.__init__(self, capacity)
        self.setLevel(logging.WARN)
        self.button = button
        button.on_clicked(self.display)
        self.timer = None

    def emit(self, record):
        if self.timer:
            gobject.source_remove(self.timer)
        gobject.timeout_add(180*1000, self._clear)
        RotateHandler.emit(self, record)
        self.button.show()

    def _clear(self):
        self.button.hide()
        self.set_capacity(self._capacity)
        self.timer = None
        return False

    def display(self, obj):
        obj.hide()
        xml = glade.XML(const.GLADE_FILE, 'scrollmsg')
        top = xml.get_widget('scrollmsg')
        msg = xml.get_widget('msg')
        buf = msg.get_buffer()
        for i in self.get_formatted_log():
            buf.insert_at_cursor(i + '\n')
        self.set_capacity(self._capacity)
        top.run()
        top.destroy()

class DisplayState(gen.utils.Callback):

    __signals__ = {
        'filters-changed' : (str, ), 
        'filter-name-changed' : (str, unicode, unicode), 
        'nameformat-changed' : None, 
        'plugins-reloaded' : (list, list), 
        }

    def __init__(self, window, status, progress, warnbtn, uimanager, 
                 progress_monitor, viewmanager=None):

        self.busy = False
        self.viewmanager = viewmanager
        self.uimanager = uimanager
        self.progress_monitor = progress_monitor
        self.window = window
        gen.utils.Callback.__init__(self)
        self.status = status
        self.status_id = status.get_context_id('GRAMPS')
        self.progress = progress
        self.phistory = History()
        self.gwm = ManagedWindow.GrampsWindowManager(uimanager)
        self.widget = None
        self.disprel_old = ''
        self.disprel_defpers = None
        self.disprel_active = None
        self.warnbtn = warnbtn
        self.last_bar = self.status.insert(min_width=15, ralign=True)
        self.set_relationship_class()

        formatter = logging.Formatter('%(levelname)s %(name)s: %(message)s')
        self.rhandler = WarnHandler(capacity=400, button=warnbtn)
        self.rhandler.setFormatter(formatter)
        self.rhandler.setLevel(logging.WARNING)
        self.log = logging.getLogger()
        self.log.addHandler(self.rhandler)
        # This call has been moved one level up, 
        # but this connection is still made!
        # self.dbstate.connect('database-changed', self.db_changed)

    def set_sensitive(self, state):
        self.window.set_sensitive(state)
        
    def db_changed(self, db):
        db.connect('long-op-start', self.progress_monitor.add_op)

    def set_relationship_class(self):
        """method that rebinds the relationship to the current rel calc
           Should be called after load or reload of plugins
        """
        self.relationship = _PluginMgr.relationship_class()

    def set_gendepth(self, value):
        """ Set the generations we search back for showing relationships
            on GRAMPS interface. Value must be integer > 0
            This method will be used by the preference editor when user changes
            the generations. 
        """
        self.relationship.set_depth(value)
        
    def display_relationship(self, dbstate):
        """ Construct the relationship in order to show it in the statusbar
            This can be a time intensive calculation, so we only want to do
            it if persons are different than before.
            Eg: select a person, then double click, will result in calling
                three times to construct build the statusbar. We only want
                to obtain relationship once!
            This means the relationship part of statusbar only changes on
            change of row.
        """
        self.relationship.connect_db_signals(dbstate)
        default_person = dbstate.db.get_default_person()
        active = dbstate.get_active_person()
        if default_person == None or active == None:
            return u''
        if default_person.handle == self.disprel_defpers and \
                active.handle == self.disprel_active :
            return self.disprel_old
        
        name = self.relationship.get_one_relationship(
                                            dbstate.db, default_person, active)
        #store present call data
        self.disprel_old = name
        self.disprel_defpers = default_person.handle
        self.disprel_active = active.handle
        if name:
            return name
        else:
            return u""

    def clear_history(self, handle=None):
        """Clear the history. If handle is given, then the history is 
            immediately initialized with a first entry 
            (you'd eg want active person you view there as History contains the 
             present object too!)
        """
        self.phistory.clear()
        if handle :
            self.phistory.push(handle)

    def set_busy_cursor(self, value):
        if value == self.busy:
            return
        else:
            self.busy = value
        if value:
            self.window.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
        else:
            self.window.window.set_cursor(None)
        while gtk.events_pending():
            gtk.main_iteration()

    def set_open_widget(self, widget):
        self.widget = widget

    def set_open_recent_menu(self, menu):
        self.widget.set_menu(menu)

    def push_message(self, dbstate, text):
        self.status_text(text)
        gobject.timeout_add(5000, self.modify_statusbar, dbstate)

    def show_filter_results(self, dbstate, matched, total):
        text = "%d/%d" % (matched, total)
        self.status.pop(1, self.last_bar)
        self.status.push(1, text, self.last_bar)

    def clear_filter_results(self):
        self.status.pop(1, self.last_bar)
        self.status.push(1, '', self.last_bar)

    def modify_statusbar(self, dbstate, active=None):
        self.status.pop(self.status_id)
        if dbstate.active == None:
            self.status.push(self.status_id, "")
        else:
            person = dbstate.get_active_person()
            if person:
                pname = name_displayer.display(person)
                name = "[%s] %s" % (person.get_gramps_id(), pname)
                if Config.get(Config.STATUSBAR) > 1:
                    if person.handle != dbstate.db.get_default_handle():
                        msg = self.display_relationship(dbstate)
                        if msg:
                            name = "%s (%s)" % (name, msg.strip())
            else:
                name = _("No active person")
            self.status.push(self.status_id, name)

        while gtk.events_pending():
            gtk.main_iteration()

    def pulse_progressbar(self, value):
        self.progress.set_fraction(min(value/100.0, 1.0))
        self.progress.set_text("%d%%" % value)
        while gtk.events_pending():
            gtk.main_iteration()

    def status_text(self, text):
        self.status.pop(self.status_id)
        self.status.push(self.status_id, text)
        while gtk.events_pending():
            gtk.main_iteration()
